Skip to content

Commit 2c6aa23

Browse files
ymc9claude
andcommitted
feat(fetch-client): add @zenstackhq/fetch-client package
A simple fetch-based, non-reactive client library that consumes the RPC-style auto CRUD API. - Per-model CRUD operations (`findUnique`, `findMany`, `create`, etc.) derived from the ORM's `AllModelOperations`, with slicing applied via a key-filter mapped type that preserves per-call generic narrowing. - `findUniqueOrThrow` / `findFirstOrThrow` raise a typed `CrudError` with `code: CrudErrorCode.NotFound` and the offending model name. - Sequential transactions via `client.\$transaction([...])` accepting an array of typed `{ model, op, args }` operations; result tuple is typed per-position via `TransactionResults`. - Custom procedures exposed under `client.\$procs` with `query` / `mutate` based on the procedure shape. - Endpoint is required and validated as a fully qualified URL. - Threads `ExtQueryArgs` and `ExtResult` through model ops and transactions so plugin-extended query args / result fields are reflected in user-facing types. Refactors: - Move `TransactionOperation` / `TransactionResults` types and the `CUSTOM_PROC_ROUTE_NAME` / `TRANSACTION_ROUTE_PREFIX` constants into `@zenstackhq/client-helpers` so both clients can share them. - Move the `DEFAULT_QUERY_ENDPOINT` constant to tanstack-query (where it belongs). Tests: 47 runtime tests + 13 type-level tests covering CRUD ops, HTTP methods, SuperJSON serialization, error handling, transactions, slicing, ExtQueryArgs, and ExtResult propagation. Test schemas use real ZModel files generated via the existing `scripts/test-generate.ts`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2ef5a99 commit 2c6aa23

22 files changed

Lines changed: 1507 additions & 82 deletions
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
/**
2-
* The default query endpoint.
2+
* Route segment used for custom procedures.
33
*/
4-
export const DEFAULT_QUERY_ENDPOINT = '/api/model';
4+
export const CUSTOM_PROC_ROUTE_NAME = '$procs';
5+
6+
/**
7+
* Route prefix used for transactions.
8+
*/
9+
export const TRANSACTION_ROUTE_PREFIX = '$transaction';

packages/clients/client-helpers/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export * from './nested-read-visitor';
77
export * from './nested-write-visitor';
88
export * from './optimistic';
99
export * from './query-analysis';
10+
export * from './transaction';
1011
export * from './types';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type {
2+
CoreCrudOperations,
3+
CrudArgsMap,
4+
CrudReturnMap,
5+
ExtQueryArgsBase,
6+
ExtResultBase,
7+
GetSlicedOperations,
8+
ModelAllowsCreate,
9+
OperationsRequiringCreate,
10+
QueryOptions,
11+
} from '@zenstackhq/orm';
12+
import type { GetModels, SchemaDef } from '@zenstackhq/schema';
13+
14+
/**
15+
* Operations available in a sequential transaction.
16+
*/
17+
type AllowedTransactionOps<
18+
Schema extends SchemaDef,
19+
Model extends GetModels<Schema>,
20+
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
21+
> =
22+
ModelAllowsCreate<Schema, Model> extends true
23+
? GetSlicedOperations<Schema, Model, Options> & CoreCrudOperations
24+
: Exclude<GetSlicedOperations<Schema, Model, Options> & CoreCrudOperations, OperationsRequiringCreate>;
25+
26+
/**
27+
* Represents a single operation to execute within a sequential transaction.
28+
*
29+
* The `model`, `op`, and `args` fields are correlated: `op` is constrained to
30+
* the CRUD operations available on `model` (respecting `Options['slicing']`), and
31+
* `args` is typed accordingly.
32+
*/
33+
export type TransactionOperation<
34+
Schema extends SchemaDef,
35+
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
36+
ExtQueryArgs extends ExtQueryArgsBase = {},
37+
ExtResult extends ExtResultBase<Schema> = {},
38+
> = {
39+
[Model in GetModels<Schema>]: {
40+
[Op in AllowedTransactionOps<Schema, Model, Options>]: {} extends CrudArgsMap<
41+
Schema,
42+
Model,
43+
Options,
44+
ExtQueryArgs,
45+
ExtResult
46+
>[Op]
47+
? { model: Model; op: Op; args?: CrudArgsMap<Schema, Model, Options, ExtQueryArgs, ExtResult>[Op] }
48+
: { model: Model; op: Op; args: CrudArgsMap<Schema, Model, Options, ExtQueryArgs, ExtResult>[Op] };
49+
}[AllowedTransactionOps<Schema, Model, Options>];
50+
}[GetModels<Schema>];
51+
52+
/**
53+
* Maps each operation in a transaction tuple to its precise result type, preserving
54+
* per-position typing.
55+
*/
56+
export type TransactionResults<
57+
Schema extends SchemaDef,
58+
Ops extends readonly TransactionOperation<Schema, any, any, any>[],
59+
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
60+
ExtResult extends ExtResultBase<Schema> = {},
61+
> = {
62+
[K in keyof Ops]: Ops[K] extends { model: infer M; op: infer O; args?: infer A }
63+
? M extends GetModels<Schema>
64+
? O extends keyof CrudReturnMap<Schema, M, A, Options, ExtResult>
65+
? CrudReturnMap<Schema, M, A, Options, ExtResult>[O]
66+
: never
67+
: never
68+
: never;
69+
};
70+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "@zenstackhq/fetch-client",
3+
"displayName": "ZenStack Fetch Client",
4+
"description": "Simple fetch-based client for consuming ZenStack's RPC-style CRUD API",
5+
"version": "3.6.4",
6+
"type": "module",
7+
"author": {
8+
"name": "ZenStack Team",
9+
"email": "contact@zenstack.dev"
10+
},
11+
"homepage": "https://zenstack.dev",
12+
"repository": {
13+
"type": "git",
14+
"url": "https://github.com/zenstackhq/zenstack"
15+
},
16+
"license": "MIT",
17+
"scripts": {
18+
"build": "tsc --noEmit && tsdown && pnpm test:generate && pnpm test:typecheck",
19+
"watch": "tsdown --watch",
20+
"lint": "eslint src --ext ts",
21+
"test": "vitest run",
22+
"test:generate": "tsx ../../../scripts/test-generate.ts test --lite-only",
23+
"test:typecheck": "tsc --noEmit --project tsconfig.test.json",
24+
"pack": "pnpm pack"
25+
},
26+
"files": [
27+
"dist"
28+
],
29+
"exports": {
30+
".": {
31+
"types": "./dist/index.d.mts",
32+
"default": "./dist/index.mjs"
33+
}
34+
},
35+
"dependencies": {
36+
"@zenstackhq/client-helpers": "workspace:*",
37+
"@zenstackhq/common-helpers": "workspace:*",
38+
"@zenstackhq/orm": "workspace:*",
39+
"@zenstackhq/schema": "workspace:*"
40+
},
41+
"devDependencies": {
42+
"@types/node": "catalog:",
43+
"@zenstackhq/cli": "workspace:*",
44+
"@zenstackhq/eslint-config": "workspace:*",
45+
"@zenstackhq/tsdown-config": "workspace:*",
46+
"@zenstackhq/typescript-config": "workspace:*",
47+
"@zenstackhq/vitest-config": "workspace:*",
48+
"decimal.js": "catalog:"
49+
},
50+
"funding": "https://github.com/sponsors/zenstackhq"
51+
}

0 commit comments

Comments
 (0)