@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.
@@ -0,0 +1,95 @@
1
+ import type { CratestackPage } from "./types.js";
2
+ /** Mirrors the generated RPC client's own `RpcListPredicate`
3
+ * (`crates/cratestack-client-typescript/templates/src/rpc-queries.ts.j2`)
4
+ * exactly — a single arbitrary `(key, value)` predicate, where `key`
5
+ * already carries the `field[__operator]` suffix (`toRpcQueryFilters` in
6
+ * `rpc-filters.ts` builds these). */
7
+ export interface RpcListPredicate {
8
+ key: string;
9
+ value: string;
10
+ }
11
+ /** Mirrors the generated RPC client's own `CratestackRpcListQuery`
12
+ * structurally — only the subset this package writes to (`filters`,
13
+ * `sort`, `limit`, `offset`). Unlike REST's `CratestackFetchQuery`,
14
+ * `sort` is a single already-joined string and `filters` is an array of
15
+ * {@link RpcListPredicate} rather than a `Record<string,string>` — see
16
+ * `rpc-filters.ts`'s doc comments for why each differs from its REST
17
+ * counterpart. `where`/`or`/`fields`/`include`/`includeFields` exist on
18
+ * the wire but this package never writes to them. */
19
+ export interface CratestackRpcListQuery {
20
+ limit?: number;
21
+ offset?: number;
22
+ fields?: string[];
23
+ include?: string[];
24
+ includeFields?: Record<string, string[]>;
25
+ sort?: string;
26
+ where?: string;
27
+ or?: string;
28
+ filters?: RpcListPredicate[];
29
+ }
30
+ /** Mirrors the generated RPC client's own `CratestackRpcCallOptions`
31
+ * (`rpc-runtime.ts.j2`) structurally — REST's `CratestackRequestConfig`
32
+ * sibling, plus `idempotencyKey` (RPC-only; propagated as the
33
+ * `Idempotency-Key` header). `headers` is where `If-Match` travels for
34
+ * `update`/`deleteOne` on a `@version` model — the RPC dispatch arms
35
+ * (`crates/cratestack-macros/src/transport/rpc.rs`) pass the real HTTP
36
+ * `HeaderMap` straight through to the same `handle_update_*_dispatch`/
37
+ * `handle_delete_*_dispatch` fns REST uses
38
+ * (`crates/cratestack-macros/src/axum/model/handlers_update.rs`), which
39
+ * read it via `parse_if_match_version` — so `If-Match` is enforced
40
+ * identically on both transports. */
41
+ export interface CratestackRpcCallOptions {
42
+ headers?: HeadersInit;
43
+ signal?: AbortSignal;
44
+ idempotencyKey?: string;
45
+ }
46
+ /** The structural surface a generated RPC model class (`client.widgets`,
47
+ * `client.ledgers`, …) needs for this package to drive it — matched
48
+ * against the real shape emitted by `cratestack generate-typescript`
49
+ * onto a `transport rpc` schema
50
+ * (`crates/cratestack-client-typescript/templates/src/rpc-client.ts.j2`).
51
+ * `get`/`create`/`update`/`delete` are positionally identical to REST's
52
+ * `CratestackModelApi` — only `list` differs, taking the query
53
+ * positionally (`list(query, options)`) instead of nested in an options
54
+ * object (REST's `list({ query, headers, signal })`). */
55
+ export interface CratestackRpcModelApi<TModel = unknown, TCreateInput = unknown, TUpdateInput = unknown> {
56
+ list(query?: CratestackRpcListQuery, options?: CratestackRpcCallOptions): Promise<TModel[] | CratestackPage<TModel>>;
57
+ get(id: unknown, options?: CratestackRpcCallOptions): Promise<TModel>;
58
+ /** Absent on the generated class entirely when the model declares no
59
+ * `@@allow("create", ...)` policy at all — same caveat as REST's
60
+ * `CratestackModelApi.create`, see the package README's
61
+ * "Policy-denied operations" note. */
62
+ create?(input: TCreateInput, options?: CratestackRpcCallOptions): Promise<TModel>;
63
+ update(id: unknown, patch: TUpdateInput, options?: CratestackRpcCallOptions): Promise<TModel>;
64
+ delete(id: unknown, options?: CratestackRpcCallOptions): Promise<void>;
65
+ }
66
+ /** One RPC resource's binding to a generated model class — the RPC
67
+ * sibling of REST's `ResourceConfig` in `types.ts`, same four facts
68
+ * (`api`, `primaryKey`, `paged`, optional `versionField`), same
69
+ * reasoning for why they can't be discovered at runtime and have to be
70
+ * supplied. Hand-write it (as every example in the README does), or
71
+ * generate it: `cratestack generate-typescript --refine` has emitted a
72
+ * real `refine.ts` typed to `RpcResourceMap` for `transport rpc` schemas
73
+ * since issue #571's RPC follow-up (#586 —
74
+ * `TypeScriptGeneratorError::RefineRequiresRest` was widened to
75
+ * `RefineRequiresRestOrRpc`). This package's own tests check both paths:
76
+ * `tests/typecheck/rpc-manifest.ts` builds one by hand against a real
77
+ * generated RPC client, and `tests/typecheck/generated-manifest-rpc.ts`
78
+ * checks the generator's own output round-trips into this type. */
79
+ export interface RpcResourceConfig<TModel = unknown, TCreateInput = unknown, TUpdateInput = unknown> {
80
+ api: CratestackRpcModelApi<TModel, TCreateInput, TUpdateInput>;
81
+ /** The schema's `@id` field name. refine assumes `id`; cratestack's
82
+ * `@id` may be on any field. */
83
+ primaryKey: string;
84
+ /** Mirrors whether the model declares `@@paged` — gates whether
85
+ * `.list()` returns `Page<TModel>` (with a real `totalCount`) or a
86
+ * bare `TModel[]`. */
87
+ paged: boolean;
88
+ /** Mirrors whether the model declares `@version`, and which field it's
89
+ * on. Omit for a model with no `@version` field — `update`/
90
+ * `deleteOne` then send no `If-Match` at all, matching the server's
91
+ * own behavior of not requiring one. */
92
+ versionField?: string;
93
+ }
94
+ export type RpcResourceMap = Record<string, RpcResourceConfig<any, any, any>>;
95
+ //# sourceMappingURL=rpc-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-types.d.ts","sourceRoot":"","sources":["../src/rpc-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;sCAIsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;sDAOsD;AACtD,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED;;;;;;;;;;sCAUsC;AACtC,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;0DAQ0D;AAC1D,MAAM,WAAW,qBAAqB,CACpC,MAAM,GAAG,OAAO,EAChB,YAAY,GAAG,OAAO,EACtB,YAAY,GAAG,OAAO;IAEtB,IAAI,CACF,KAAK,CAAC,EAAE,sBAAsB,EAC9B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtE;;;2CAGuC;IACvC,MAAM,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClF,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9F,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED;;;;;;;;;;;;oEAYoE;AACpE,MAAM,WAAW,iBAAiB,CAChC,MAAM,GAAG,OAAO,EAChB,YAAY,GAAG,OAAO,EACtB,YAAY,GAAG,OAAO;IAEtB,GAAG,EAAE,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IAC/D;qCACiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB;;2BAEuB;IACvB,KAAK,EAAE,OAAO,CAAC;IACf;;;6CAGyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rpc-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-types.js","sourceRoot":"","sources":["../src/rpc-types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ /** The structural surface a generated model class (`client.widgets`,
2
+ * `client.ledgers`, …) needs for this package to drive it — matched
3
+ * against the real shape emitted by `cratestack generate-typescript`
4
+ * (`crates/cratestack-client-typescript/templates/src/client.ts.j2`),
5
+ * not a hand-invented interface. Each generated model class is its own
6
+ * concrete TypeScript class with no shared base type the generator
7
+ * exports, so this is duck-typed rather than an `implements` target —
8
+ * every real generated class satisfies it structurally. */
9
+ export interface CratestackModelApi<TModel = unknown, TCreateInput = unknown, TUpdateInput = unknown> {
10
+ list(options?: {
11
+ query?: CratestackFetchQuery;
12
+ headers?: HeadersInit;
13
+ signal?: AbortSignal;
14
+ }): Promise<TModel[] | CratestackPage<TModel>>;
15
+ get(id: unknown, options?: {
16
+ query?: CratestackFetchQuery;
17
+ headers?: HeadersInit;
18
+ signal?: AbortSignal;
19
+ }): Promise<TModel>;
20
+ /** Absent on the generated class entirely when the model declares no
21
+ * `@@allow("create", ...)` policy at all — see the package README's
22
+ * "Policy-denied operations" note for why that's a different, weaker
23
+ * guarantee than "the current caller can create one". */
24
+ create?(input: TCreateInput, options?: CratestackRequestConfig): Promise<TModel>;
25
+ update(id: unknown, input: TUpdateInput, options?: CratestackRequestConfig): Promise<TModel>;
26
+ delete(id: unknown, options?: CratestackRequestConfig): Promise<void>;
27
+ }
28
+ /** Mirrors the generated client's own `CratestackFetchQuery`
29
+ * (`queries.ts.j2`) structurally — only the subset this package writes
30
+ * to (`filters`, `sort`, `limit`, `offset`). */
31
+ export interface CratestackFetchQuery {
32
+ filters?: Record<string, string>;
33
+ sort?: string[];
34
+ limit?: number;
35
+ offset?: number;
36
+ }
37
+ export interface CratestackRequestConfig {
38
+ signal?: AbortSignal;
39
+ headers?: HeadersInit;
40
+ }
41
+ /** Mirrors `cratestack_core::page::Page<T>` (`models.ts.j2`) — only the
42
+ * `items`/`totalCount` fields this package reads. */
43
+ export interface CratestackPage<T> {
44
+ items: T[];
45
+ totalCount: number | null;
46
+ }
47
+ /** Structural match for the generated client's `CratestackHttpError`
48
+ * (`rest-runtime.ts.j2`) — checked with {@link isCratestackHttpError}
49
+ * rather than `instanceof`, since that class has no shared import path
50
+ * this package can reference (it's regenerated per consumer, into
51
+ * their own package). */
52
+ export interface CratestackHttpErrorLike {
53
+ status: number;
54
+ response?: unknown;
55
+ payload?: unknown;
56
+ message?: string;
57
+ }
58
+ /** One resource's binding to a generated model class. Every field here
59
+ * is a fact about the `.cstack` model that the generated client's own
60
+ * TypeScript types encode at compile time but expose nowhere at
61
+ * runtime — there is no `client.widgets.$meta` object to introspect,
62
+ * confirmed by reading the generated `client.ts`/`models.ts` output.
63
+ *
64
+ * Hand-write it, or let `cratestack generate-typescript --refine` emit
65
+ * it from the schema those facts came from — see the README's "A
66
+ * runtime package with a generated manifest". */
67
+ export interface ResourceConfig<TModel = unknown, TCreateInput = unknown, TUpdateInput = unknown> {
68
+ api: CratestackModelApi<TModel, TCreateInput, TUpdateInput>;
69
+ /** The schema's `@id` field name. refine assumes `id`; cratestack's
70
+ * `@id` may be on any field (`crates/cratestack-client-typescript/src/types.rs::primary_key_field`). */
71
+ primaryKey: string;
72
+ /** Mirrors whether the model declares `@@paged` — gates whether
73
+ * `.list()` returns `Page<TModel>` (with a real `totalCount`) or a
74
+ * bare `TModel[]`. A non-`@@paged` resource cannot report a true
75
+ * `total` beyond what one page returned — see the package README's
76
+ * "Pagination" section for why, and set `false` here honestly rather
77
+ * than treating a non-paged model as paged. */
78
+ paged: boolean;
79
+ /** Mirrors whether the model declares `@version`, and which field
80
+ * it's on. Omit for a model with no `@version` field — `update`/
81
+ * `deleteOne` then send no `If-Match` at all, matching the server's
82
+ * own behavior of not requiring one. */
83
+ versionField?: string;
84
+ }
85
+ export type ResourceMap = Record<string, ResourceConfig<any, any, any>>;
86
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;4DAO4D;AAC5D,MAAM,WAAW,kBAAkB,CACjC,MAAM,GAAG,OAAO,EAChB,YAAY,GAAG,OAAO,EACtB,YAAY,GAAG,OAAO;IAEtB,IAAI,CAAC,OAAO,CAAC,EAAE;QACb,KAAK,CAAC,EAAE,oBAAoB,CAAC;QAC7B,OAAO,CAAC,EAAE,WAAW,CAAC;QACtB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,GAAG,CACD,EAAE,EAAE,OAAO,EACX,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,oBAAoB,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACtF,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB;;;8DAG0D;IAC1D,MAAM,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjF,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7F,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED;;iDAEiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;sDACsD;AACtD,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;0BAI0B;AAC1B,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;kDAQkD;AAClD,MAAM,WAAW,cAAc,CAAC,MAAM,GAAG,OAAO,EAAE,YAAY,GAAG,OAAO,EAAE,YAAY,GAAG,OAAO;IAC9F,GAAG,EAAE,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IAC5D;6GACyG;IACzG,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;oDAKgD;IAChD,KAAK,EAAE,OAAO,CAAC;IACf;;;6CAGyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAQD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ /** The one fact this module needs out of either transport's resource
2
+ * config (`ResourceConfig` in `types.ts`, `RpcResourceConfig` in
3
+ * `rpc-types.ts`) — deliberately narrower than either so this module
4
+ * stays shared between both `createCratestackDataProvider` and
5
+ * `createCratestackRpcDataProvider` rather than picking one transport's
6
+ * type and asking the other to import it. Both real config types satisfy
7
+ * this structurally already; nothing about them changes. */
8
+ export interface VersionedResourceConfig {
9
+ versionField?: string;
10
+ }
11
+ /** `If-Match` is required on both `PATCH` (update) and `DELETE` for a
12
+ * `@version` model (cratestack#493/#519, closed by #538 — the
13
+ * delete-side enforcement deliberately mirrors the update path
14
+ * token-for-token, `crates/cratestack-macros/src/axum/model/prep/etag.rs`).
15
+ * refine's `update`/`deleteOne` hooks fetch the record before editing it
16
+ * (`useOne`/`useShow` populate the edit form; a list/detail view is
17
+ * usually on screen before a delete button is clicked), so the version
18
+ * is available by the time a mutation fires — it just isn't part of
19
+ * refine's `UpdateParams`/`DeleteOneParams` by default. This cache,
20
+ * populated by every read and write that returns a fresh record, is how
21
+ * the provider threads it through. One cache per `createCratestackDataProvider`
22
+ * call, not module-global, so two providers (e.g. two tenants in the
23
+ * same process) never cross-pollinate versions. */
24
+ export declare function createVersionCache(): Map<string, number>;
25
+ export declare function rememberVersion(cache: Map<string, number>, resource: string, id: unknown, record: Record<string, unknown>, config: VersionedResourceConfig): void;
26
+ export declare function forgetVersion(cache: Map<string, number>, resource: string, id: unknown): void;
27
+ /** Builds the `If-Match` header for a `@version` model's update/delete,
28
+ * or `{}` for a model with no `versionField` configured (matching the
29
+ * server's own behavior of not requiring one). Throws rather than
30
+ * silently omitting `If-Match` when a version is required but unknown
31
+ * — sending an update/delete with no `If-Match` against a `@version`
32
+ * model doesn't fail loudly on the wire (cratestack#519/#538 gate on
33
+ * the header's *presence*, so a caller can accidentally opt out of the
34
+ * safety net exactly when it matters most), so this package refuses to
35
+ * make that mistake on the caller's behalf. */
36
+ export declare function ifMatchHeaders(cache: Map<string, number>, resource: string, id: unknown, config: VersionedResourceConfig, override?: number): HeadersInit;
37
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;6DAM6D;AAC7D,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;oDAYoD;AACpD,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAExD;AAMD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,uBAAuB,GAC9B,IAAI,CAMN;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAE7F;AAED;;;;;;;;gDAQgD;AAChD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,CAAC,EAAE,MAAM,GAChB,WAAW,CAUb"}
@@ -0,0 +1,50 @@
1
+ /** `If-Match` is required on both `PATCH` (update) and `DELETE` for a
2
+ * `@version` model (cratestack#493/#519, closed by #538 — the
3
+ * delete-side enforcement deliberately mirrors the update path
4
+ * token-for-token, `crates/cratestack-macros/src/axum/model/prep/etag.rs`).
5
+ * refine's `update`/`deleteOne` hooks fetch the record before editing it
6
+ * (`useOne`/`useShow` populate the edit form; a list/detail view is
7
+ * usually on screen before a delete button is clicked), so the version
8
+ * is available by the time a mutation fires — it just isn't part of
9
+ * refine's `UpdateParams`/`DeleteOneParams` by default. This cache,
10
+ * populated by every read and write that returns a fresh record, is how
11
+ * the provider threads it through. One cache per `createCratestackDataProvider`
12
+ * call, not module-global, so two providers (e.g. two tenants in the
13
+ * same process) never cross-pollinate versions. */
14
+ export function createVersionCache() {
15
+ return new Map();
16
+ }
17
+ function versionKey(resource, id) {
18
+ return `${resource}:${String(id)}`;
19
+ }
20
+ export function rememberVersion(cache, resource, id, record, config) {
21
+ if (!config.versionField)
22
+ return;
23
+ const version = record[config.versionField];
24
+ if (typeof version === "number") {
25
+ cache.set(versionKey(resource, id), version);
26
+ }
27
+ }
28
+ export function forgetVersion(cache, resource, id) {
29
+ cache.delete(versionKey(resource, id));
30
+ }
31
+ /** Builds the `If-Match` header for a `@version` model's update/delete,
32
+ * or `{}` for a model with no `versionField` configured (matching the
33
+ * server's own behavior of not requiring one). Throws rather than
34
+ * silently omitting `If-Match` when a version is required but unknown
35
+ * — sending an update/delete with no `If-Match` against a `@version`
36
+ * model doesn't fail loudly on the wire (cratestack#519/#538 gate on
37
+ * the header's *presence*, so a caller can accidentally opt out of the
38
+ * safety net exactly when it matters most), so this package refuses to
39
+ * make that mistake on the caller's behalf. */
40
+ export function ifMatchHeaders(cache, resource, id, config, override) {
41
+ if (!config.versionField)
42
+ return {};
43
+ const version = override ?? cache.get(versionKey(resource, id));
44
+ if (version === undefined) {
45
+ throw new Error(`no known version for ${resource}/${String(id)} — call getOne (or getList) before ` +
46
+ "update/delete on a @version model, or pass meta: { ifMatch: <version> } explicitly");
47
+ }
48
+ return { "If-Match": `"${version}"` };
49
+ }
50
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;oDAYoD;AACpD,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,GAAG,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,UAAU,CAAC,QAAgB,EAAE,EAAW;IAC/C,OAAO,GAAG,QAAQ,IAAI,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAA0B,EAC1B,QAAgB,EAChB,EAAW,EACX,MAA+B,EAC/B,MAA+B;IAE/B,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAO;IACjC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA0B,EAAE,QAAgB,EAAE,EAAW;IACrF,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;gDAQgD;AAChD,MAAM,UAAU,cAAc,CAC5B,KAA0B,EAC1B,QAAgB,EAChB,EAAW,EACX,MAA+B,EAC/B,QAAiB;IAEjB,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;IAChE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,wBAAwB,QAAQ,IAAI,MAAM,CAAC,EAAE,CAAC,qCAAqC;YACjF,oFAAoF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,IAAI,OAAO,GAAG,EAAE,CAAC;AACxC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cratestack/refine",
3
- "version": "0.7.14",
4
- "description": "A refine.dev DataProvider over CrateStack's generated TypeScript REST client — the safe end-user admin-UI surface that goes through the generated API (policy, validation, @version concurrency, audit), unlike cratestack-studio's direct-to-database sysadmin access.",
3
+ "version": "0.7.17",
4
+ "description": "A refine.dev DataProvider over CrateStack's generated TypeScript client (REST or RPC) — the safe end-user admin-UI surface that goes through the generated API (policy, validation, @version concurrency, audit), unlike cratestack-studio's direct-to-database sysadmin access.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "build": "tsc -p tsconfig.json",
38
- "test": "vitest run",
38
+ "test": "tsc --noEmit -p tsconfig.typecheck.json && vitest run",
39
39
  "lint": "biome check ."
40
40
  },
41
41
  "peerDependencies": {