@cratestack/refine 0.7.14 → 0.7.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,44 +1,78 @@
1
1
  # @cratestack/refine
2
2
 
3
- A [refine.dev](https://refine.dev) `DataProvider` over CrateStack's generated TypeScript **REST**
4
- client. Wires refine's fixed `getList`/`getOne`/`getMany`/`create`/`update`/`deleteOne` surface to
5
- a generated model class (`client.widgets`, `client.ledgers`, …), mapping refine's filter operators,
6
- pagination, primary keys, and `@version` optimistic-locking conflicts onto the server's real
7
- contract — the same one [cratestack-studio](https://cratestack.dev) deliberately bypasses.
8
- `cratestack-studio` talks to `[target.db]` directly and skips `@@allow`; a refine app built on this
9
- package goes through the generated API and inherits policy, validation, `@version` concurrency, and
10
- audit. That's the reason this package exists (cratestack#571): refine is the safe end-user admin
11
- surface, Studio is the sysadmin one.
12
-
13
- This package covers REST-transport schemas only (`generate-typescript`'s default transport, no
14
- `transport rpc`) — see [Scope](#scope) below.
15
-
16
- ## Why a runtime package, not a generator
17
-
18
- The open question cratestack#571 asked to settle first: how much of this genuinely has to be
19
- generated per schema? Answer: none of it, given a small hand-written manifest.
20
-
21
- A `DataProvider` needs three schema facts per resource — its primary-key field name, whether its
22
- model declares `@@paged`, and whether/where it declares `@version` — plus the resource's own
23
- generated API object. None of those four things has a runtime discovery path: the generated client
24
- carries no `$meta` object, no introspection endpoint, nothing beyond what its TypeScript *types*
25
- already encode at compile time (confirmed by reading the real generated `client.ts`/`models.ts`
26
- output this package's own tests drive — see [tests/](./tests)). Whether or not `@cratestack/refine`
27
- ships a code generator, an app still needs to write down those four things per resource somewhere.
28
- A generator that emits a five-line object literal isn't materially cheaper to maintain than writing
29
- that object literal directly against the schema the developer already wrote — it's a second
30
- artifact to keep in sync instead of one. So this package is exactly what `createCratestackDataProvider`
31
- below is: a hand-written runtime function over a hand-written manifest, no `cratestack generate-*`
32
- subcommand, no build step beyond `tsc`.
33
-
34
- ## Usage
3
+ A [refine.dev](https://refine.dev) `DataProvider` over CrateStack's generated TypeScript client —
4
+ **REST** (`createCratestackDataProvider`) or **RPC** (`createCratestackRpcDataProvider`, for
5
+ `transport rpc` schemas). Wires refine's fixed `getList`/`getOne`/`getMany`/`create`/`update`/
6
+ `deleteOne` surface to a generated model class (`client.widgets`, `client.ledgers`, …), mapping
7
+ refine's filter operators, pagination, primary keys, and `@version` optimistic-locking conflicts
8
+ onto the server's real contract — the same one [cratestack-studio](https://cratestack.dev)
9
+ deliberately bypasses. `cratestack-studio` talks to `[target.db]` directly and skips `@@allow`; a
10
+ refine app built on this package goes through the generated API and inherits policy, validation,
11
+ `@version` concurrency, and audit. That's the reason this package exists (cratestack#571): refine
12
+ is the safe end-user admin surface, Studio is the sysadmin one.
13
+
14
+ Both transports share this same README except where a section says otherwise — the two providers'
15
+ `getList`/`getOne`/`getMany`/`create`/`update`/`deleteOne`/`createMany`/`updateMany`/`deleteMany`/
16
+ `custom` behavior, filter-operator table, pagination semantics, primary-key handling, and `@version`
17
+ optimistic-locking guarantee are identical; only the wire calls underneath differ. See
18
+ [RPC transport](#rpc-transport) below for what's RPC-specific, and [Scope](#scope) for what neither
19
+ provider does.
20
+
21
+ ## A runtime package with a generated manifest
22
+
23
+ A `DataProvider` needs four things per resource — the generated API object itself, the model's
24
+ primary-key field name, whether it declares `@@paged`, and whether/where it declares `@version`.
25
+ None of them has a *runtime* discovery path: the generated client carries no `$meta` object and no
26
+ introspection endpoint, nothing beyond what its TypeScript **types** already encode at compile time
27
+ (confirmed against the real generated `client.ts`/`models.ts` this package's own tests drive — see
28
+ [tests/](./tests)). So the provider itself has to be a hand-written runtime function; there is
29
+ nothing schema-shaped in its logic to generate.
30
+
31
+ The **manifest** is a different question, and cratestack#571 settled it the other way: those four
32
+ facts all live in the `.cstack` schema, so the generator emits them. Pass `--refine` to
33
+ `cratestack generate-typescript` and it writes an extra `src/refine.ts` next to the client:
34
+
35
+ ```bash
36
+ cratestack generate-typescript \
37
+ --schema schema.cstack \
38
+ --out ./generated \
39
+ --refine
40
+ ```
41
+
42
+ ```ts
43
+ // generated/src/refine.ts — generated, do not edit
44
+ export function cratestackRefineResources(client: ExampleApiClientClient): ResourceMap { … }
45
+ ```
46
+
47
+ Writing that manifest by hand is still perfectly supported (it is a plain object literal, and every
48
+ example below shows one), but the generated one cannot drift: a model that gains `@version`, or one
49
+ whose `@id` is not called `id`, updates itself on the next `generate-typescript` run instead of
50
+ failing quietly at runtime.
51
+
52
+ `--refine` works for **both** REST and RPC schemas. The emitted function is the same shape either
53
+ way — `cratestackRefineResources(client)` — typed `ResourceMap` for REST and `RpcResourceMap` for
54
+ RPC, so consumer code is identical across transports. Only `transport grpc` is rejected
55
+ (`TypeScriptGeneratorError::RefineRequiresRestOrRpc`): its client speaks typed protobuf with no
56
+ URL-query shaping, so there is nothing for this provider to drive. It also composes freely with
57
+ `--swr` (the file-per-model + hooks layout, `<package-name>/swr`): the manifest binds to the
58
+ always-emitted default layout's client class regardless of that flag. See [Scope](#scope).
59
+
60
+ ## Usage (REST)
61
+
62
+ With the generated manifest:
35
63
 
36
64
  ```ts
37
65
  import { createCratestackDataProvider } from "@cratestack/refine";
38
- import { ExampleApiClientClient } from "./generated/client"; // your project's generated REST client
66
+ import { ExampleApiClientClient } from "./generated/src/client.js";
67
+ import { cratestackRefineResources } from "./generated/src/refine.js";
39
68
 
40
69
  const client = new ExampleApiClientClient("https://api.example.com", { basePath: "/api" });
70
+ const dataProvider = createCratestackDataProvider(cratestackRefineResources(client));
71
+ ```
41
72
 
73
+ Or writing the manifest yourself — identical shape, and what the generated file contains:
74
+
75
+ ```ts
42
76
  const dataProvider = createCratestackDataProvider({
43
77
  widgets: { api: client.widgets, primaryKey: "id", paged: false },
44
78
  ledgers: { api: client.ledgers, primaryKey: "id", paged: true, versionField: "version" },
@@ -48,10 +82,59 @@ const dataProvider = createCratestackDataProvider({
48
82
 
49
83
  Pass this straight to refine's `<Refine dataProvider={dataProvider} .../>`.
50
84
 
85
+ ## RPC transport
86
+
87
+ For a `transport rpc` schema, use `createCratestackRpcDataProvider` and an `RpcResourceMap` instead
88
+ — same four facts per resource (`api`, `primaryKey`, `paged`, optional `versionField`), same
89
+ `getList`/`getOne`/.../`custom` behavior, same filter-operator table and `@version` guarantee as
90
+ REST. There is no generated manifest for RPC yet (`--refine` rejects RPC schemas — see above), so
91
+ write it by hand:
92
+
93
+ ```ts
94
+ import { createCratestackRpcDataProvider } from "@cratestack/refine";
95
+ import { ExampleApiClientClient } from "./generated/src/client.js";
96
+
97
+ const client = new ExampleApiClientClient("https://api.example.com", { basePath: "/api" });
98
+ const dataProvider = createCratestackRpcDataProvider({
99
+ widgets: { api: client.widgets, primaryKey: "id", paged: false },
100
+ ledgers: { api: client.ledgers, primaryKey: "id", paged: true, versionField: "version" },
101
+ products: { api: client.products, primaryKey: "sku", paged: false },
102
+ });
103
+ ```
104
+
105
+ What's different from REST, mechanically:
106
+
107
+ - The generated RPC model class's `list` takes its query positionally
108
+ (`list(query, options)`) instead of nested in an options object the way REST's `list({ query,
109
+ headers, signal })` does — `createCratestackRpcDataProvider` builds an `RpcListQuery` (RPC's typed
110
+ list-input shape) instead of REST's `CratestackFetchQuery`. `get`/`create`/`update`/`delete` are
111
+ positionally identical between the two transports; only their options type differs
112
+ (`CratestackRpcCallOptions` vs `CratestackRequestConfig`).
113
+ - Filters compile to `RpcListPredicate[]` (`{ key, value }` pairs) instead of REST's
114
+ `Record<string,string>` — same `field[__operator]` key convention either way (see
115
+ [Filters](#filters) below), just a different container shape on the wire.
116
+ - Sort compiles to a single comma-joined string (`"-createdAt,id"`) instead of REST's `string[]`
117
+ the runtime joins client-side — same `field`/`-field` DSL.
118
+ - **Every unary call carries `If-Match` exactly like REST does.** The RPC dispatch arms
119
+ (`crates/cratestack-macros/src/transport/rpc.rs`) pass the real HTTP `HeaderMap` straight through
120
+ to the identical `handle_update_*_dispatch`/`handle_delete_*_dispatch` fns REST uses
121
+ (`crates/cratestack-macros/src/axum/model/handlers_update.rs`), which read `If-Match` via
122
+ `parse_if_match_version` — so a stale or missing `If-Match` against a `@version` model returns the
123
+ same `412 Precondition Failed` on both transports, verified against a real generated RPC client in
124
+ this package's own test suite (`tests/rpc-optimistic-locking.test.ts`).
125
+ - **`createCratestackRpcDataProvider` never calls `POST /rpc/batch`.** A batch request is one HTTP
126
+ request carrying N frames, so a per-frame `If-Match` header isn't expressible there —
127
+ `createMany`/`updateMany`/`deleteMany` are N real unary round trips instead, same non-atomic
128
+ strategy REST uses (see [createMany / updateMany / deleteMany](#createmany--updatemany--deletemany)
129
+ below). A batched RPC data provider is possible in principle but out of scope here — see
130
+ [Scope](#scope).
131
+
51
132
  ## Filters
52
133
 
53
- refine's `CrudFilters` (`{ field, operator, value }`) map onto the generated list route's
54
- `field__operator=value` query convention — the same operator set the generated client's shared
134
+ Applies to both providers. refine's `CrudFilters` (`{ field, operator, value }`) map onto the same
135
+ `field[__operator]` key convention on both transports — REST's `field__operator=value` query params
136
+ (`toQueryFilters`) and RPC's `RpcListPredicate[]` (`toRpcQueryFilters`) carry an identical `key`,
137
+ just in different container shapes on the wire. Same operator set the generated client's shared
55
138
  filter types (`EqualityFilter`/`ComparableFilter`/`StringFilter`) expose:
56
139
 
57
140
  | refine operator | cratestack query key |
@@ -75,7 +158,8 @@ package can work around.
75
158
 
76
159
  ## Pagination
77
160
 
78
- refine's `{ current, pageSize }` maps to `limit`/`offset` directly. **`totalCount` is only ever
161
+ Applies to both providers. refine's `{ current, pageSize }` maps to `limit`/`offset` directly.
162
+ **`totalCount` is only ever
79
163
  emitted for a `@@paged` model's list route.** Set `paged: false` in a resource's config honestly —
80
164
  a non-`@@paged` resource's `getList` still returns real data, but `total` degrades to
81
165
  "how many rows this one response returned" rather than a true count. Either add `@@paged` to the
@@ -84,7 +168,7 @@ one (capped) page.
84
168
 
85
169
  ## Primary keys
86
170
 
87
- refine assumes every record has `id: BaseKey`. cratestack's `@id` can be on any field
171
+ Applies to both providers. refine assumes every record has `id: BaseKey`. cratestack's `@id` can be on any field
88
172
  (`primaryKey` in the resource config above). Every returned record gets a synthetic `id` field
89
173
  alongside its real primary key so refine's row-selection machinery has something to key off;
90
174
  writes (`get`/`update`/`deleteOne`) receive that same value back and pass it straight through as
@@ -94,12 +178,15 @@ this bites: a `<Create>` form's fields must use the schema's real primary-key fi
94
178
 
95
179
  ## Optimistic locking (`@version` / `If-Match`)
96
180
 
97
- **The single most important correctness point in this package.** A `@version` model requires
98
- `If-Match` on both update *and* delete (cratestack#493/#519/#538) — missing or stale `If-Match`
99
- returns `412 Precondition Failed` and leaves the row untouched. refine's `update`/`deleteOne` hooks
100
- fetch the record before editing it, so the version is known by the time a mutation fires; this
101
- package remembers it (keyed per `createCratestackDataProvider` call, not module-global) from every
102
- read/write that returns a fresh record, and sends it automatically. A `412` is surfaced as a
181
+ **The single most important correctness point in this package, and it holds on both transports.**
182
+ A `@version` model requires `If-Match` on both update *and* delete (cratestack#493/#519/#538) —
183
+ missing or stale `If-Match` returns `412 Precondition Failed` and leaves the row untouched, whether
184
+ the request went out as an RPC unary call or a REST `PATCH`/`DELETE` (see
185
+ [RPC transport](#rpc-transport) for why the RPC dispatch path enforces this identically to REST).
186
+ refine's `update`/`deleteOne` hooks fetch the record before editing it, so the version is known by
187
+ the time a mutation fires; each provider remembers it (keyed per `createCratestackDataProvider`/
188
+ `createCratestackRpcDataProvider` call, not module-global) from every read/write that returns a
189
+ fresh record, and sends it automatically. A `412` is surfaced as a
103
190
  distinguishable conflict (`statusCode: 412`, a human-readable message) rather than a generic
104
191
  failure — check `error.statusCode === 412` in an `onError` handler to show a "someone else changed
105
192
  this" message instead of a generic one.
@@ -111,16 +198,20 @@ fail on the wire the way a *stale* value does, so this package refuses to make t
111
198
 
112
199
  ## `createMany` / `updateMany` / `deleteMany`
113
200
 
114
- Implemented, not declined — but as N sequential single-record round trips (`Promise.all` over
115
- `create`/`update`/`deleteOne`), not a real batch. The generated REST client exposes no
116
- `updateMany`/`deleteMany` wrapper around the server's actual `update_many`/`delete_many`, and
117
- `/rpc/batch` is an RPC-transport-only endpoint. So these three methods work, but without atomicity
118
- and at the cost of N requests instead of one.
201
+ Implemented, not declined, on both providers — but as N sequential single-record round trips
202
+ (`Promise.all` over `create`/`update`/`deleteOne`), not a real batch. The generated REST client
203
+ exposes no `updateMany`/`deleteMany` wrapper around the server's actual `update_many`/
204
+ `delete_many`. The RPC transport does have a real batch endpoint (`POST /rpc/batch`), but
205
+ `createCratestackRpcDataProvider` deliberately doesn't use it here either — see
206
+ [RPC transport](#rpc-transport) for why (per-frame `If-Match` isn't expressible in a batch request).
207
+ So these three methods work on both transports, but without atomicity and at the cost of N requests
208
+ instead of one.
119
209
 
120
210
  ## Procedures (`custom`)
121
211
 
122
212
  A cratestack `procedure` has no other home in a `DataProvider`. Pass a `procedures` map to
123
- `createCratestackDataProvider`'s second argument and call it through refine's `custom`:
213
+ `createCratestackDataProvider`'s (or `createCratestackRpcDataProvider`'s) second argument and call
214
+ it through refine's `custom` — identical shape on both providers:
124
215
 
125
216
  ```ts
126
217
  const dataProvider = createCratestackDataProvider(resources, {
@@ -141,10 +232,17 @@ await dataProvider.custom!({
141
232
 
142
233
  - **`liveProvider`** — the generated TypeScript client has no SSE consumer today (no `EventSource`
143
234
  anywhere in `crates/cratestack-client-typescript/templates/`), even though the server has a real
144
- subscribe endpoint. Needs a TS SSE client first.
145
- - **`authProvider`** — a separate, orthogonal concern from data access.
146
- - **RPC-transport schemas** (`transport rpc`) — this package's filter/pagination query-string
147
- convention is REST-specific. An RPC dataProvider needs its own mapping layer.
235
+ subscribe endpoint. Needs a TS SSE client first. Applies to both transports.
236
+ - **`authProvider`** — a separate, orthogonal concern from data access. Applies to both transports.
237
+ - **Batched RPC writes** — `createCratestackRpcDataProvider` never calls `POST /rpc/batch`, even
238
+ though the RPC transport has one. A per-frame `If-Match` header isn't expressible in a single
239
+ batch request, and this package's `@version` guarantee is not something it will silently weaken
240
+ to get atomicity — see [RPC transport](#rpc-transport).
241
+ - **RPC's `where`/`or` filter-expression DSL, and `fields`/`include`/`includeFields` projection** —
242
+ `CratestackRpcListQuery` supports all of these on the wire, but refine's `CrudFilters`/`Sort`
243
+ shapes have nothing that maps onto them (refine has no "raw server filter expression" or
244
+ "relation projection" concept), so this provider never writes to them. Available to a caller who
245
+ wants to reach past refine's abstraction: call `config.api.list({ where: "..." })` directly.
148
246
 
149
247
  **A known, pre-existing gap this package inherits rather than causes:** route suppression
150
248
  (cratestack#514) isn't implemented, so a policy-denied `create`/`update`/`delete` still generates a
@@ -0,0 +1,21 @@
1
+ import type { HttpError } from "@refinedev/core";
2
+ import type { CratestackHttpErrorLike } from "./types.js";
3
+ /** Structural check for the generated client's `CratestackHttpError`
4
+ * (`crates/cratestack-client-typescript/templates/src/rest-runtime.ts.j2`)
5
+ * — `instanceof` doesn't work here because that class is regenerated
6
+ * per consumer into their own package, so this package has no shared
7
+ * class reference to check against. `status` is set unconditionally by
8
+ * the runtime's `!response.ok` branch regardless of codec, which makes
9
+ * it the more robust field to key on than pattern-matching the error
10
+ * envelope's `payload.code`. */
11
+ export declare function isCratestackHttpError(error: unknown): error is CratestackHttpErrorLike;
12
+ /** Converts a thrown error from a generated model API call into refine's
13
+ * `HttpError`. A `412 Precondition Failed` — the response an
14
+ * `If-Match`-guarded update/delete returns on a stale version — is
15
+ * surfaced as a distinguishable conflict (`statusCode: 412` with a
16
+ * human-readable message) rather than folded into a generic failure;
17
+ * this is the one piece of information a refine UI needs to tell "your
18
+ * edit was rejected because someone else changed this record first"
19
+ * apart from every other kind of failure. */
20
+ export declare function toRefineError(error: unknown): HttpError;
21
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;iCAOiC;AACjC,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,uBAAuB,CAOtF;AAED;;;;;;;8CAO8C;AAC9C,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAevD"}
package/dist/errors.js ADDED
@@ -0,0 +1,39 @@
1
+ /** Structural check for the generated client's `CratestackHttpError`
2
+ * (`crates/cratestack-client-typescript/templates/src/rest-runtime.ts.j2`)
3
+ * — `instanceof` doesn't work here because that class is regenerated
4
+ * per consumer into their own package, so this package has no shared
5
+ * class reference to check against. `status` is set unconditionally by
6
+ * the runtime's `!response.ok` branch regardless of codec, which makes
7
+ * it the more robust field to key on than pattern-matching the error
8
+ * envelope's `payload.code`. */
9
+ export function isCratestackHttpError(error) {
10
+ return (typeof error === "object" &&
11
+ error !== null &&
12
+ "status" in error &&
13
+ typeof error.status === "number");
14
+ }
15
+ /** Converts a thrown error from a generated model API call into refine's
16
+ * `HttpError`. A `412 Precondition Failed` — the response an
17
+ * `If-Match`-guarded update/delete returns on a stale version — is
18
+ * surfaced as a distinguishable conflict (`statusCode: 412` with a
19
+ * human-readable message) rather than folded into a generic failure;
20
+ * this is the one piece of information a refine UI needs to tell "your
21
+ * edit was rejected because someone else changed this record first"
22
+ * apart from every other kind of failure. */
23
+ export function toRefineError(error) {
24
+ if (isCratestackHttpError(error)) {
25
+ if (error.status === 412) {
26
+ return {
27
+ message: "This record changed since it was loaded. Reload it and try again.",
28
+ statusCode: 412,
29
+ };
30
+ }
31
+ const payload = error.payload;
32
+ return {
33
+ message: payload?.message ?? error.message ?? "Request failed",
34
+ statusCode: error.status,
35
+ };
36
+ }
37
+ return { message: error instanceof Error ? error.message : "Unknown error", statusCode: 500 };
38
+ }
39
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;;;;iCAOiC;AACjC,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,QAAQ,IAAI,KAAK;QACjB,OAAQ,KAA6B,CAAC,MAAM,KAAK,QAAQ,CAC1D,CAAC;AACJ,CAAC;AAED;;;;;;;8CAO8C;AAC9C,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,OAAO;gBACL,OAAO,EAAE,mEAAmE;gBAC5E,UAAU,EAAE,GAAG;aAChB,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAA2C,CAAC;QAClE,OAAO;YACL,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,gBAAgB;YAC9D,UAAU,EAAE,KAAK,CAAC,MAAM;SACzB,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AAChG,CAAC"}
@@ -0,0 +1,12 @@
1
+ /** Returns the `field__<suffix>` query key's suffix (`null` for bare
2
+ * equality), or **throws** for a refine operator with no cratestack
3
+ * equivalent (`endswith`, `between`, `nin`, `containss`, …). Throwing
4
+ * rather than silently dropping the filter matters: a dropped filter
5
+ * would show the caller an unfiltered result set as if it were
6
+ * filtered, which is worse than an error because nothing signals the
7
+ * data is wrong. */
8
+ export declare function requireOperatorSuffix(field: string, operator: string): string | null;
9
+ /** Renders a refine filter's `value` into the string every cratestack
10
+ * query key expects on the wire, for either transport. */
11
+ export declare function filterValue(operator: string, value: unknown): string;
12
+ //# sourceMappingURL=filter-operators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-operators.d.ts","sourceRoot":"","sources":["../src/filter-operators.ts"],"names":[],"mappings":"AAiCA;;;;;;qBAMqB;AACrB,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASpF;AAED;2DAC2D;AAC3D,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAMpE"}
@@ -0,0 +1,60 @@
1
+ /** refine `CrudFilters` operator -> the generated list route's
2
+ * `field__<operator>=value` query-string suffix (`null` means "no
3
+ * suffix", i.e. plain equality). Shared by both transports' list-query
4
+ * builders — `filters.ts` (REST, `Record<string,string>`) and
5
+ * `rpc-filters.ts` (RPC, `RpcListPredicate[]`) — because the underlying
6
+ * `field__op` key convention is identical on the wire: both are
7
+ * generated from the same per-field arm table
8
+ * (`crates/cratestack-macros/src/axum/filter_arms.rs::generate_query_filter_arm`),
9
+ * confirmed by reading `crates/cratestack-axum/src/rpc/synthesize.rs`,
10
+ * which turns an RPC `filters: [{key, value}]` predicate into the exact
11
+ * same `key=value` query pair the REST list handler parses. Keeping this
12
+ * table in ONE place is what makes "REST and RPC agree on filter
13
+ * operators" a fact rather than something that can drift between two
14
+ * copies. Also keep this in lockstep with `guides/refine-integration.md`
15
+ * in cratestack-docs, which documents the identical mapping for
16
+ * hand-wired consumers. */
17
+ const SUPPORTED_OPERATOR_SUFFIX = {
18
+ eq: null,
19
+ ne: "ne",
20
+ in: "in",
21
+ lt: "lt",
22
+ lte: "lte",
23
+ gt: "gt",
24
+ gte: "gte",
25
+ contains: "contains",
26
+ startswith: "startsWith",
27
+ null: "isNull",
28
+ nnull: "isNull",
29
+ };
30
+ /** Every operator this map understands, for the error message below. */
31
+ const SUPPORTED_OPERATORS = Object.keys(SUPPORTED_OPERATOR_SUFFIX);
32
+ /** Returns the `field__<suffix>` query key's suffix (`null` for bare
33
+ * equality), or **throws** for a refine operator with no cratestack
34
+ * equivalent (`endswith`, `between`, `nin`, `containss`, …). Throwing
35
+ * rather than silently dropping the filter matters: a dropped filter
36
+ * would show the caller an unfiltered result set as if it were
37
+ * filtered, which is worse than an error because nothing signals the
38
+ * data is wrong. */
39
+ export function requireOperatorSuffix(field, operator) {
40
+ if (!(operator in SUPPORTED_OPERATOR_SUFFIX)) {
41
+ throw new Error(`refine operator "${operator}" on "${field}" has no cratestack equivalent ` +
42
+ `(supported: ${SUPPORTED_OPERATORS.join(", ")}) — failing loudly instead of silently ` +
43
+ "returning unfiltered data");
44
+ }
45
+ return SUPPORTED_OPERATOR_SUFFIX[operator] ?? null;
46
+ }
47
+ /** Renders a refine filter's `value` into the string every cratestack
48
+ * query key expects on the wire, for either transport. */
49
+ export function filterValue(operator, value) {
50
+ if (operator === "null")
51
+ return "true";
52
+ if (operator === "nnull")
53
+ return "false";
54
+ if (Array.isArray(value))
55
+ return value.map(String).join(",");
56
+ if (value instanceof Date)
57
+ return value.toISOString();
58
+ return String(value);
59
+ }
60
+ //# sourceMappingURL=filter-operators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-operators.js","sourceRoot":"","sources":["../src/filter-operators.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;4BAe4B;AAC5B,MAAM,yBAAyB,GAAkC;IAC/D,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,KAAK;IACV,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,KAAK;IACV,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;IACxB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;CAChB,CAAC;AAEF,wEAAwE;AACxE,MAAM,mBAAmB,GAAG,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;AAEnE;;;;;;qBAMqB;AACrB,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,QAAgB;IACnE,IAAI,CAAC,CAAC,QAAQ,IAAI,yBAAyB,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CACb,oBAAoB,QAAQ,SAAS,KAAK,iCAAiC;YACzE,eAAe,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,yCAAyC;YACtF,2BAA2B,CAC9B,CAAC;IACJ,CAAC;IACD,OAAO,yBAAyB,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;AACrD,CAAC;AAED;2DAC2D;AAC3D,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAc;IAC1D,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACvC,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IACtD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { CrudFilters, CrudSorting } from "@refinedev/core";
2
+ /** Converts refine's `CrudFilters` into the generated REST list route's
3
+ * flat `field__operator=value` query-param convention (`RpcResourceMap`'s
4
+ * sibling `toRpcQueryFilters` in `rpc-filters.ts` does the same mapping
5
+ * for the RPC transport's `RpcListPredicate[]` shape — see
6
+ * `filter-operators.ts` for the shared operator table both draw from).
7
+ * refine operators with no cratestack equivalent — `endswith`,
8
+ * `between`, `nin`, `containss`, and every refine conditional-filter
9
+ * group (`or`/`and`) — **throw** rather than silently dropping the
10
+ * filter: a dropped filter would show the caller an unfiltered result
11
+ * set as if it were filtered, which is worse than an error because
12
+ * nothing signals the data is wrong. */
13
+ export declare function toQueryFilters(filters?: CrudFilters): Record<string, string>;
14
+ /** refine `CrudSorting` -> the generated list route's `sort` query
15
+ * values (`field` ascending, `-field` descending). */
16
+ export declare function toSortQuery(sorters?: CrudSorting): string[] | undefined;
17
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGhE;;;;;;;;;;yCAUyC;AACzC,wBAAgB,cAAc,CAAC,OAAO,GAAE,WAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAchF;AAED;uDACuD;AACvD,wBAAgB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,MAAM,EAAE,GAAG,SAAS,CAG3E"}
@@ -0,0 +1,33 @@
1
+ import { filterValue, requireOperatorSuffix } from "./filter-operators.js";
2
+ /** Converts refine's `CrudFilters` into the generated REST list route's
3
+ * flat `field__operator=value` query-param convention (`RpcResourceMap`'s
4
+ * sibling `toRpcQueryFilters` in `rpc-filters.ts` does the same mapping
5
+ * for the RPC transport's `RpcListPredicate[]` shape — see
6
+ * `filter-operators.ts` for the shared operator table both draw from).
7
+ * refine operators with no cratestack equivalent — `endswith`,
8
+ * `between`, `nin`, `containss`, and every refine conditional-filter
9
+ * group (`or`/`and`) — **throw** rather than silently dropping the
10
+ * filter: a dropped filter would show the caller an unfiltered result
11
+ * set as if it were filtered, which is worse than an error because
12
+ * nothing signals the data is wrong. */
13
+ export function toQueryFilters(filters = []) {
14
+ const output = {};
15
+ for (const filter of filters) {
16
+ if (!("field" in filter)) {
17
+ throw new Error(`refine "${filter.operator}" filter groups have no cratestack equivalent — ` +
18
+ "this dataProvider only supports a flat AND of per-field predicates");
19
+ }
20
+ const suffix = requireOperatorSuffix(filter.field, filter.operator);
21
+ const key = suffix ? `${filter.field}__${suffix}` : filter.field;
22
+ output[key] = filterValue(filter.operator, filter.value);
23
+ }
24
+ return output;
25
+ }
26
+ /** refine `CrudSorting` -> the generated list route's `sort` query
27
+ * values (`field` ascending, `-field` descending). */
28
+ export function toSortQuery(sorters = []) {
29
+ if (sorters.length === 0)
30
+ return undefined;
31
+ return sorters.map((s) => (s.order === "desc" ? `-${s.field}` : s.field));
32
+ }
33
+ //# sourceMappingURL=filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE3E;;;;;;;;;;yCAUyC;AACzC,MAAM,UAAU,cAAc,CAAC,OAAO,GAAgB,EAAE;IACtD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,WAAW,MAAM,CAAC,QAAQ,kDAAkD;gBAC1E,oEAAoE,CACvE,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QACjE,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;uDACuD;AACvD,MAAM,UAAU,WAAW,CAAC,OAAO,GAAgB,EAAE;IACnD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { DataProvider } from "@refinedev/core";
2
+ import type { ResourceMap } from "./types.js";
3
+ export { isCratestackHttpError, toRefineError } from "./errors.js";
4
+ export { toQueryFilters, toSortQuery } from "./filters.js";
5
+ export { toRpcQueryFilters, toRpcSortQuery } from "./rpc-filters.js";
6
+ export type { CreateCratestackRpcDataProviderOptions } from "./rpc-provider.js";
7
+ export { createCratestackRpcDataProvider } from "./rpc-provider.js";
8
+ export type { CratestackRpcCallOptions, CratestackRpcListQuery, CratestackRpcModelApi, RpcListPredicate, RpcResourceConfig, RpcResourceMap, } from "./rpc-types.js";
9
+ export type { CratestackFetchQuery, CratestackHttpErrorLike, CratestackModelApi, CratestackPage, CratestackRequestConfig, ResourceConfig, ResourceMap, } from "./types.js";
10
+ export interface CreateCratestackDataProviderOptions {
11
+ /** Backs `getApiUrl()`. Defaults to `() => ""` — refine rarely calls
12
+ * this in practice, but a real generated client's own origin/basePath
13
+ * is available at `client.runtime.origin` / `client.runtime.basePath`
14
+ * if a consumer needs an accurate value. */
15
+ getApiUrl?: () => string;
16
+ /** Backs `custom()`, refine's escape hatch for a cratestack `procedure`
17
+ * call — there's nowhere else in a `DataProvider` for one to live.
18
+ * Keyed by procedure name, called as `client.procedures.<name>`
19
+ * would be; `custom({ meta: { procedure: "<name>" }, payload })`
20
+ * dispatches to `procedures[name](payload)`. Omit if the schema has
21
+ * no procedures a refine app needs to call. */
22
+ procedures?: Record<string, (args: unknown) => Promise<unknown>>;
23
+ }
24
+ /** Builds a refine `DataProvider` over one or more cratestack generated
25
+ * REST model classes (`client.widgets`, `client.ledgers`, …), given a
26
+ * manifest describing each resource's primary-key field, `@@paged`
27
+ * status, and `@version` field.
28
+ *
29
+ * Write that manifest by hand, or generate it: `cratestack
30
+ * generate-typescript --refine` emits a `src/refine.ts` next to the
31
+ * client whose `cratestackRefineResources(client)` returns exactly this
32
+ * {@link ResourceMap}. The provider itself is not generated and has no
33
+ * CLI subcommand — see the package README's "A runtime package with a
34
+ * generated manifest" section for the split.
35
+ *
36
+ * RPC-transport schemas aren't wired here — see the README's "REST
37
+ * only" note. */
38
+ export declare function createCratestackDataProvider(resources: ResourceMap, options?: CreateCratestackDataProviderOptions): DataProvider;
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EASV,YAAY,EAeb,MAAM,iBAAiB,CAAC;AAGzB,OAAO,KAAK,EAAwD,WAAW,EAAE,MAAM,YAAY,CAAC;AAGpG,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACrE,YAAY,EAAE,sCAAsC,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AACpE,YAAY,EACV,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,GACf,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAC;AAwBpB,MAAM,WAAW,mCAAmC;IAClD;;;iDAG6C;IAC7C,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;IACzB;;;;;oDAKgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;CAClE;AAED;;;;;;;;;;;;;kBAakB;AAClB,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,WAAW,EACtB,OAAO,GAAE,mCAAwC,GAChD,YAAY,CAoNd"}