diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 69d9056e7..33dd30733 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -8,6 +8,7 @@ on:
env:
TELEMETRY_TRACKING_TOKEN: ${{ secrets.TELEMETRY_TRACKING_TOKEN }}
+ VSCODE_TELEMETRY_TRACKING_TOKEN: ${{ secrets.VSCODE_TELEMETRY_TRACKING_TOKEN }}
DO_NOT_TRACK: '1'
permissions:
diff --git a/README.md b/README.md
index 859364b29..871dc8a7a 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ ZenStack is a TypeScript database toolkit for developing full-stack or backend N
- 🧩 Designed for extensibility and flexibility
- ⚙️ Automatic CRUD web APIs with adapters for popular frameworks
- 🏖️ Automatic [TanStack Query](https://github.com/TanStack/query) hooks for easy CRUD from the frontend
+- 💎 [Zod](https://zod.dev) schema generation
# What's New in V3
@@ -111,3 +112,23 @@ Thank you for your generous support!
# Community
Join our [discord server](https://discord.gg/Ykhr738dUe) for chat and updates!
+
+# Contributors
+
+Thanks to all the contributors who have helped make ZenStack better!
+
+### Source
+
+
+
+
+
+### Docs
+
+
+
+
+
+## License
+
+[MIT](LICENSE)
diff --git a/package.json b/package.json
index 419fa65b4..dad6b22a1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "zenstack-v3",
- "version": "3.4.6",
+ "version": "3.5.0",
"description": "ZenStack",
"packageManager": "pnpm@10.23.0",
"type": "module",
diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json
index 56e06ce41..b95ba687c 100644
--- a/packages/auth-adapters/better-auth/package.json
+++ b/packages/auth-adapters/better-auth/package.json
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/better-auth",
- "version": "3.4.6",
+ "version": "3.5.0",
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
"type": "module",
"scripts": {
diff --git a/packages/cli/README.md b/packages/cli/README.md
new file mode 100644
index 000000000..7adc0ae1a
--- /dev/null
+++ b/packages/cli/README.md
@@ -0,0 +1,24 @@
+# @zenstackhq/cli
+
+The command-line interface for ZenStack. Provides commands for initializing projects, generating TypeScript code from ZModel schemas, managing database migrations, and etc.
+
+## Key Commands
+
+- `zenstack init` — Initialize ZenStack in an existing project
+- `zenstack generate` — Compile ZModel schema to TypeScript
+- `zenstack db push` — Sync schema to the database
+- `zenstack db pull` — Pull database schema changes into ZModel
+- `zenstack migrate dev` — Create and apply database migrations
+- `zenstack migrate deploy` — Deploy migrations to production
+- `zenstack format` — Format ZModel schema files
+- `zenstack proxy|studio` — Start a database proxy server for using studio
+
+## Installation
+
+```bash
+npm install -D @zenstackhq/cli
+```
+
+## Learn More
+
+- [ZenStack Documentation](https://zenstack.dev/docs)
diff --git a/packages/cli/package.json b/packages/cli/package.json
index b1bdb6b05..02ebb3c6c 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack CLI",
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
- "version": "3.4.6",
+ "version": "3.5.0",
"type": "module",
"author": {
"name": "ZenStack Team"
diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts
index aed343193..5f18f9be4 100644
--- a/packages/cli/src/actions/action-utils.ts
+++ b/packages/cli/src/actions/action-utils.ts
@@ -291,6 +291,14 @@ export async function loadPluginModule(provider: string, basePath: string) {
}
}
+ // try jiti import for bare package specifiers (handles workspace packages)
+ try {
+ const result = (await jiti.import(moduleSpec, { default: true })) as CliPlugin;
+ return result;
+ } catch {
+ // fall through to last resort
+ }
+
// last resort, try to import as esm directly
try {
const mod = await import(moduleSpec);
diff --git a/packages/cli/src/actions/check.ts b/packages/cli/src/actions/check.ts
index a7f765f36..61f12c57f 100644
--- a/packages/cli/src/actions/check.ts
+++ b/packages/cli/src/actions/check.ts
@@ -29,7 +29,9 @@ async function checkPluginResolution(schemaFile: string, model: Model) {
for (const plugin of plugins) {
const provider = getPluginProvider(plugin);
if (!provider.startsWith('@core/')) {
- await loadPluginModule(provider, path.dirname(schemaFile));
+ const pluginSourcePath =
+ plugin.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile;
+ await loadPluginModule(provider, path.dirname(pluginSourcePath));
}
}
}
diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts
index ce499ef11..e65747405 100644
--- a/packages/cli/src/actions/generate.ts
+++ b/packages/cli/src/actions/generate.ts
@@ -186,7 +186,11 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
throw new CliError(`Unknown core plugin: ${provider}`);
}
} else {
- cliPlugin = await loadPluginModule(provider, path.dirname(schemaFile));
+ // resolve relative plugin paths against the schema file where the plugin is declared,
+ // not the entry schema file
+ const pluginSourcePath =
+ plugin.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile;
+ cliPlugin = await loadPluginModule(provider, path.dirname(pluginSourcePath));
}
if (cliPlugin) {
@@ -252,7 +256,7 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
spinner?.succeed();
} catch (err) {
spinner?.fail();
- console.error(err);
+ throw err;
}
}
}
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index bdeca9b5d..f187ad93c 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -1,7 +1,7 @@
-import 'dotenv/config';
import { ZModelLanguageMetaData } from '@zenstackhq/language';
import colors from 'colors';
import { Command, CommanderError, Option } from 'commander';
+import 'dotenv/config';
import * as actions from './actions';
import { CliError } from './cli-error';
import { telemetry } from './telemetry';
diff --git a/packages/cli/test/generate.test.ts b/packages/cli/test/generate.test.ts
index 291f0e734..9cc31ba96 100644
--- a/packages/cli/test/generate.test.ts
+++ b/packages/cli/test/generate.test.ts
@@ -1,7 +1,8 @@
+import { formatDocument } from '@zenstackhq/language';
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
-import { createProject, runCli } from './utils';
+import { createProject, getDefaultPrelude, runCli } from './utils';
const model = `
model User {
@@ -272,6 +273,84 @@ model User {
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
});
+ it('should load plugin from a bare package specifier via jiti', async () => {
+ const modelWithBarePlugin = `
+plugin foo {
+ provider = 'my-test-plugin'
+}
+
+model User {
+ id String @id @default(cuid())
+}
+`;
+ const { workDir } = await createProject(modelWithBarePlugin);
+ // Create a fake node_modules package with a TS entry point
+ // This can only be resolved by jiti, not by native import() or fs.existsSync checks
+ const pkgDir = path.join(workDir, 'node_modules/my-test-plugin');
+ fs.mkdirSync(pkgDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(pkgDir, 'package.json'),
+ JSON.stringify({ name: 'my-test-plugin', main: './index.ts' }),
+ );
+ fs.writeFileSync(
+ path.join(pkgDir, 'index.ts'),
+ `
+const plugin = {
+ name: 'test-bare-plugin',
+ statusText: 'Testing bare plugin',
+ async generate() {},
+};
+export default plugin;
+`,
+ );
+ runCli('generate', workDir);
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
+ });
+
+ it('should resolve plugin paths relative to the schema file where the plugin is declared', async () => {
+ // Entry schema imports a sub-schema that declares a plugin with a relative path.
+ // The plugin path should resolve relative to the sub-schema, not the entry schema.
+ const { workDir } = await createProject(
+ `import './core/core'
+
+${getDefaultPrelude()}
+
+model User {
+ id String @id @default(cuid())
+}
+`,
+ { customPrelude: true },
+ );
+
+ // Create core/ subdirectory with its own schema and plugin
+ const coreDir = path.join(workDir, 'zenstack/core');
+ fs.mkdirSync(coreDir, { recursive: true });
+
+ const coreSchema = await formatDocument(`
+plugin foo {
+ provider = './my-core-plugin.ts'
+}
+`);
+ fs.writeFileSync(path.join(coreDir, 'core.zmodel'), coreSchema);
+
+ // Plugin lives next to the core schema, NOT next to the entry schema
+ fs.writeFileSync(
+ path.join(coreDir, 'my-core-plugin.ts'),
+ `
+const plugin = {
+ name: 'core-plugin',
+ statusText: 'Testing core plugin',
+ async generate() {},
+};
+export default plugin;
+`,
+ );
+
+ // This would fail if the plugin path was resolved relative to the entry schema
+ runCli('generate', workDir);
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
+ });
+
it('should prefer CLI options over @core/typescript plugin settings for generateModels and generateInput', async () => {
const modelWithPlugin = `
plugin typescript {
diff --git a/packages/clients/client-helpers/package.json b/packages/clients/client-helpers/package.json
index 384a8915e..0b35d8bab 100644
--- a/packages/clients/client-helpers/package.json
+++ b/packages/clients/client-helpers/package.json
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/client-helpers",
- "version": "3.4.6",
+ "version": "3.5.0",
"description": "Helpers for implementing clients that consume ZenStack's CRUD service",
"type": "module",
"scripts": {
diff --git a/packages/clients/client-helpers/src/types.ts b/packages/clients/client-helpers/src/types.ts
index da66f9483..5f30ac613 100644
--- a/packages/clients/client-helpers/src/types.ts
+++ b/packages/clients/client-helpers/src/types.ts
@@ -1,8 +1,28 @@
+import type { ClientContract, QueryOptions } from '@zenstackhq/orm';
+import type { SchemaDef } from '@zenstackhq/schema';
+
/**
* A type that represents either a value of type T or a Promise that resolves to type T.
*/
export type MaybePromise = T | Promise | PromiseLike;
+/**
+ * Infers the schema definition from a client contract type, or passes through a raw SchemaDef.
+ */
+export type InferSchema = T extends { $schema: infer S extends SchemaDef } ? S : T extends SchemaDef ? T : never;
+
+/**
+ * Extracts the ExtResult type from a client contract, or defaults to `{}`.
+ */
+export type InferExtResult = T extends ClientContract ? E : {};
+
+/**
+ * Infers query options from a client contract type, or defaults to `QueryOptions`.
+ */
+export type InferOptions = T extends { $options: infer O extends QueryOptions }
+ ? O
+ : QueryOptions;
+
/**
* List of ORM write actions.
*/
diff --git a/packages/clients/tanstack-query/package.json b/packages/clients/tanstack-query/package.json
index f3fa224ac..21533de8e 100644
--- a/packages/clients/tanstack-query/package.json
+++ b/packages/clients/tanstack-query/package.json
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/tanstack-query",
- "version": "3.4.6",
+ "version": "3.5.0",
"description": "TanStack Query Client for consuming ZenStack v3's CRUD service",
"type": "module",
"scripts": {
diff --git a/packages/clients/tanstack-query/src/common/types.ts b/packages/clients/tanstack-query/src/common/types.ts
index f745ceeb4..a967869ed 100644
--- a/packages/clients/tanstack-query/src/common/types.ts
+++ b/packages/clients/tanstack-query/src/common/types.ts
@@ -3,11 +3,12 @@ import type { FetchFn } from '@zenstackhq/client-helpers/fetch';
import type {
GetProcedureNames,
GetSlicedOperations,
- OperationsIneligibleForDelegateModels,
+ ModelAllowsCreate,
+ OperationsRequiringCreate,
ProcedureFunc,
QueryOptions,
} from '@zenstackhq/orm';
-import type { GetModels, IsDelegateModel, SchemaDef } from '@zenstackhq/schema';
+import type { GetModels, SchemaDef } from '@zenstackhq/schema';
/**
* Context type for configuring the hooks.
@@ -59,8 +60,8 @@ export type ExtraMutationOptions = {
optimisticDataProvider?: OptimisticDataProvider;
} & QueryContext;
-type HooksOperationsIneligibleForDelegateModels = OperationsIneligibleForDelegateModels extends any
- ? `use${Capitalize}`
+type HooksOperationsRequiringCreate = OperationsRequiringCreate extends any
+ ? `use${Capitalize}`
: never;
type Modifiers = '' | 'Suspense' | 'Infinite' | 'SuspenseInfinite';
@@ -76,12 +77,12 @@ export type TrimSlicedOperations<
> = {
// trim operations based on slicing options
[Key in keyof T as Key extends `use${Modifiers}${Capitalize>}`
- ? IsDelegateModel extends true
- ? // trim operations ineligible for delegate models
- Key extends HooksOperationsIneligibleForDelegateModels
- ? never
- : Key
- : Key
+ ? ModelAllowsCreate extends true
+ ? Key
+ : // trim create operations for models that don't allow create
+ Key extends HooksOperationsRequiringCreate
+ ? never
+ : Key
: never]: T[Key];
};
diff --git a/packages/clients/tanstack-query/src/react.ts b/packages/clients/tanstack-query/src/react.ts
index 5129750bf..e84aa7e09 100644
--- a/packages/clients/tanstack-query/src/react.ts
+++ b/packages/clients/tanstack-query/src/react.ts
@@ -19,13 +19,14 @@ import {
type UseSuspenseQueryOptions,
type UseSuspenseQueryResult,
} from '@tanstack/react-query';
-import { createInvalidator, createOptimisticUpdater, DEFAULT_QUERY_ENDPOINT } from '@zenstackhq/client-helpers';
+import { createInvalidator, createOptimisticUpdater, DEFAULT_QUERY_ENDPOINT, type InferExtResult, type InferOptions, type InferSchema } from '@zenstackhq/client-helpers';
import { fetcher, makeUrl, marshal } from '@zenstackhq/client-helpers/fetch';
import { lowerCaseFirst } from '@zenstackhq/common-helpers';
import type {
AggregateArgs,
AggregateResult,
BatchResult,
+ ClientContract,
CountArgs,
CountResult,
CreateArgs,
@@ -34,6 +35,7 @@ import type {
DeleteArgs,
DeleteManyArgs,
ExistsArgs,
+ ExtResultBase,
FindFirstArgs,
FindManyArgs,
FindUniqueArgs,
@@ -68,6 +70,8 @@ import type {
WithOptimistic,
} from './common/types.js';
export type { FetchFn } from '@zenstackhq/client-helpers/fetch';
+export type { InferExtResult, InferOptions, InferSchema } from '@zenstackhq/client-helpers';
+export type { SchemaDef } from '@zenstackhq/schema';
type ProcedureHookFn<
Schema extends SchemaDef,
@@ -142,15 +146,20 @@ export type ModelMutationModelResult<
TArgs,
Array extends boolean = false,
Options extends QueryOptions = QueryOptions,
-> = Omit, TArgs>, 'mutateAsync'> & {
+ ExtResult extends ExtResultBase = {},
+> = Omit, TArgs>, 'mutateAsync'> & {
mutateAsync(
args: T,
- options?: ModelMutationOptions, T>,
- ): Promise>;
+ options?: ModelMutationOptions, T>,
+ ): Promise>;
};
-export type ClientHooks = QueryOptions> = {
- [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks;
+export type ClientHooks<
+ Schema extends SchemaDef,
+ Options extends QueryOptions = QueryOptions,
+ ExtResult extends ExtResultBase = {},
+> = {
+ [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks;
} & ProcedureHooks;
type ProcedureHookGroup> = {
@@ -213,87 +222,88 @@ export type ModelQueryHooks<
Schema extends SchemaDef,
Model extends GetModels,
Options extends QueryOptions = QueryOptions,
+ ExtResult extends ExtResultBase = {},
> = TrimSlicedOperations<
Schema,
Model,
Options,
{
- useFindUnique>(
- args: SelectSubset>,
- options?: ModelQueryOptions | null>,
- ): ModelQueryResult | null>;
-
- useSuspenseFindUnique>(
- args: SelectSubset>,
- options?: ModelSuspenseQueryOptions | null>,
- ): ModelSuspenseQueryResult | null>;
-
- useFindFirst>(
- args?: SelectSubset>,
- options?: ModelQueryOptions | null>,
- ): ModelQueryResult | null>;
-
- useSuspenseFindFirst>(
- args?: SelectSubset>,
- options?: ModelSuspenseQueryOptions | null>,
- ): ModelSuspenseQueryResult | null>;
+ useFindUnique>(
+ args: SelectSubset>,
+ options?: ModelQueryOptions | null>,
+ ): ModelQueryResult | null>;
+
+ useSuspenseFindUnique>(
+ args: SelectSubset>,
+ options?: ModelSuspenseQueryOptions | null>,
+ ): ModelSuspenseQueryResult | null>;
+
+ useFindFirst>(
+ args?: SelectSubset>,
+ options?: ModelQueryOptions | null>,
+ ): ModelQueryResult | null>;
+
+ useSuspenseFindFirst>(
+ args?: SelectSubset>,
+ options?: ModelSuspenseQueryOptions | null>,
+ ): ModelSuspenseQueryResult | null>;
useExists>(
args?: Subset>,
options?: ModelQueryOptions,
): ModelQueryResult;
- useFindMany>(
- args?: SelectSubset>,
- options?: ModelQueryOptions[]>,
- ): ModelQueryResult[]>;
+ useFindMany>(
+ args?: SelectSubset>,
+ options?: ModelQueryOptions[]>,
+ ): ModelQueryResult[]>;
- useSuspenseFindMany>(
- args?: SelectSubset>,
- options?: ModelSuspenseQueryOptions[]>,
- ): ModelSuspenseQueryResult[]>;
+ useSuspenseFindMany>(
+ args?: SelectSubset>,
+ options?: ModelSuspenseQueryOptions[]>,
+ ): ModelSuspenseQueryResult[]>;
- useInfiniteFindMany>(
- args?: SelectSubset>,
- options?: ModelInfiniteQueryOptions[]>,
- ): ModelInfiniteQueryResult[]>>;
+ useInfiniteFindMany>(
+ args?: SelectSubset>,
+ options?: ModelInfiniteQueryOptions[]>,
+ ): ModelInfiniteQueryResult[]>>;
- useSuspenseInfiniteFindMany>(
- args?: SelectSubset>,
- options?: ModelSuspenseInfiniteQueryOptions[]>,
- ): ModelSuspenseInfiniteQueryResult[]>>;
+ useSuspenseInfiniteFindMany>(
+ args?: SelectSubset>,
+ options?: ModelSuspenseInfiniteQueryOptions[]>,
+ ): ModelSuspenseInfiniteQueryResult[]>>;
- useCreate>(
- options?: ModelMutationOptions, T>,
- ): ModelMutationModelResult;
+ useCreate>(
+ options?: ModelMutationOptions, T>,
+ ): ModelMutationModelResult;
useCreateMany>(
options?: ModelMutationOptions,
): ModelMutationResult;
- useCreateManyAndReturn>(
- options?: ModelMutationOptions[], T>,
- ): ModelMutationModelResult;
+ useCreateManyAndReturn>(
+ options?: ModelMutationOptions[], T>,
+ ): ModelMutationModelResult;
- useUpdate>(
- options?: ModelMutationOptions, T>,
- ): ModelMutationModelResult;
+ useUpdate>(
+ options?: ModelMutationOptions, T>,
+ ): ModelMutationModelResult;
useUpdateMany>(
options?: ModelMutationOptions,
): ModelMutationResult;
- useUpdateManyAndReturn>(
- options?: ModelMutationOptions[], T>,
- ): ModelMutationModelResult;
+ useUpdateManyAndReturn>(
+ options?: ModelMutationOptions[], T>,
+ ): ModelMutationModelResult;
- useUpsert>(
- options?: ModelMutationOptions, T>,
- ): ModelMutationModelResult;
+ useUpsert>(
+ options?: ModelMutationOptions, T>,
+ ): ModelMutationModelResult;
- useDelete>(
- options?: ModelMutationOptions, T>,
- ): ModelMutationModelResult;
+ useDelete>(
+ options?: ModelMutationOptions, T>,
+ ): ModelMutationModelResult;
useDeleteMany>(
options?: ModelMutationOptions,
@@ -334,23 +344,38 @@ export type ModelQueryHooks<
/**
* Gets data query hooks for all models in the schema.
*
+ * Accepts either a raw `SchemaDef` or a `ClientContract` type (e.g. `typeof db`) as the generic parameter.
+ * When a `ClientContract` type is provided, computed fields from plugins are reflected in the result types.
+ *
+ * @example
+ * ```typescript
+ * // Basic usage with schema
+ * const client = useClientQueries(schema)
+ *
+ * // With server client type for computed field support
+ * import type { DbType } from '~/server/db'
+ * const client = useClientQueries(schema)
+ * ```
+ *
* @param schema The schema.
* @param options Options for all queries originated from this hook.
*/
-export function useClientQueries = QueryOptions>(
- schema: Schema,
+export function useClientQueries<
+ SchemaOrClient extends SchemaDef | ClientContract,
+>(
+ schema: InferSchema,
options?: QueryContext,
-): ClientHooks {
+): ClientHooks, InferOptions>, InferExtResult extends ExtResultBase> ? InferExtResult : {}> {
const result = Object.keys(schema.models).reduce(
(acc, model) => {
- (acc as any)[lowerCaseFirst(model)] = useModelQueries, Options>(
- schema,
- model as GetModels,
+ (acc as any)[lowerCaseFirst(model)] = useModelQueries(
+ schema as any,
+ model as any,
options,
);
return acc;
},
- {} as ClientHooks,
+ {} as any,
);
const procedures = (schema as any).procedures as Record | undefined;
@@ -407,7 +432,8 @@ export function useModelQueries<
Schema extends SchemaDef,
Model extends GetModels,
Options extends QueryOptions,
->(schema: Schema, model: Model, rootOptions?: QueryContext): ModelQueryHooks {
+ ExtResult extends ExtResultBase = {},
+>(schema: Schema, model: Model, rootOptions?: QueryContext): ModelQueryHooks {
const modelDef = Object.values(schema.models).find((m) => m.name.toLowerCase() === model.toLowerCase());
if (!modelDef) {
throw new Error(`Model "${model}" not found in schema`);
@@ -517,7 +543,7 @@ export function useModelQueries<
useSuspenseGroupBy: (args: any, options?: any) => {
return useInternalSuspenseQuery(schema, modelName, 'groupBy', args, { ...rootOptions, ...options });
},
- } as ModelQueryHooks;
+ } as ModelQueryHooks;
}
export function useInternalQuery(
diff --git a/packages/clients/tanstack-query/src/svelte/index.svelte.ts b/packages/clients/tanstack-query/src/svelte/index.svelte.ts
index 45af47cb3..9ddfe40e2 100644
--- a/packages/clients/tanstack-query/src/svelte/index.svelte.ts
+++ b/packages/clients/tanstack-query/src/svelte/index.svelte.ts
@@ -19,6 +19,9 @@ import {
createInvalidator,
createOptimisticUpdater,
DEFAULT_QUERY_ENDPOINT,
+ type InferExtResult,
+ type InferOptions,
+ type InferSchema,
type InvalidationPredicate,
} from '@zenstackhq/client-helpers';
import { fetcher, makeUrl, marshal } from '@zenstackhq/client-helpers/fetch';
@@ -27,6 +30,7 @@ import type {
AggregateArgs,
AggregateResult,
BatchResult,
+ ClientContract,
CountArgs,
CountResult,
CreateArgs,
@@ -35,6 +39,7 @@ import type {
DeleteArgs,
DeleteManyArgs,
ExistsArgs,
+ ExtResultBase,
FindFirstArgs,
FindManyArgs,
FindUniqueArgs,
@@ -69,6 +74,8 @@ import type {
WithOptimistic,
} from '../common/types.js';
export type { FetchFn } from '@zenstackhq/client-helpers/fetch';
+export type { InferExtResult, InferOptions, InferSchema } from '@zenstackhq/client-helpers';
+export type { SchemaDef } from '@zenstackhq/schema';
type ProcedureHookFn<
Schema extends SchemaDef,
@@ -139,15 +146,20 @@ export type ModelMutationModelResult<
TArgs,
Array extends boolean = false,
Options extends QueryOptions = QueryOptions,
-> = Omit, TArgs>, 'mutateAsync'> & {
+ ExtResult extends ExtResultBase = {},
+> = Omit, TArgs>, 'mutateAsync'> & {
mutateAsync(
args: T,
- options?: ModelMutationOptions, T>,
- ): Promise>;
+ options?: ModelMutationOptions, T>,
+ ): Promise>;
};
-export type ClientHooks = QueryOptions> = {
- [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks;
+export type ClientHooks<
+ Schema extends SchemaDef,
+ Options extends QueryOptions = QueryOptions,
+ ExtResult extends ExtResultBase = {},
+> = {
+ [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks;
} & ProcedureHooks;
type ProcedureHookGroup> = {
@@ -200,65 +212,66 @@ export type ModelQueryHooks<
Schema extends SchemaDef,
Model extends GetModels,
Options extends QueryOptions = QueryOptions,
+ ExtResult extends ExtResultBase = {},
> = TrimSlicedOperations<
Schema,
Model,
Options,
{
- useFindUnique>(
- args: Accessor>>,
- options?: Accessor | null>>,
- ): ModelQueryResult | null>;
+ useFindUnique>(
+ args: Accessor>>,
+ options?: Accessor | null>>,
+ ): ModelQueryResult | null>;
- useFindFirst>(
- args?: Accessor>>,
- options?: Accessor | null>>,
- ): ModelQueryResult | null>;
+ useFindFirst>(
+ args?: Accessor>>,
+ options?: Accessor | null>>,
+ ): ModelQueryResult | null>;
useExists>(
args?: Accessor>>,
options?: Accessor>,
): ModelQueryResult;
- useFindMany>(
- args?: Accessor>>,
- options?: Accessor[]>>,
- ): ModelQueryResult[]>;
+ useFindMany>(
+ args?: Accessor>>,
+ options?: Accessor[]>>,
+ ): ModelQueryResult[]>;
- useInfiniteFindMany>(
- args?: Accessor>>,
- options?: Accessor[]>>,
- ): ModelInfiniteQueryResult[]>>;
+ useInfiniteFindMany>(
+ args?: Accessor>>,
+ options?: Accessor[]>>,
+ ): ModelInfiniteQueryResult[]>>;
- useCreate>(
- options?: Accessor, T>>,
- ): ModelMutationModelResult;
+ useCreate>(
+ options?: Accessor, T>>,
+ ): ModelMutationModelResult;
useCreateMany>(
options?: Accessor>,
): ModelMutationResult;
- useCreateManyAndReturn>(
- options?: Accessor[], T>>,
- ): ModelMutationModelResult;
+ useCreateManyAndReturn>(
+ options?: Accessor[], T>>,
+ ): ModelMutationModelResult