@marktiderman/genesis-core 1.8.0 → 1.10.0

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.
Files changed (68) hide show
  1. package/README.md +236 -0
  2. package/dist/chunk-52T2EYXH.js +52 -0
  3. package/dist/chunk-52T2EYXH.js.map +1 -0
  4. package/dist/chunk-5ORRAXIV.js +30 -0
  5. package/dist/chunk-5ORRAXIV.js.map +1 -0
  6. package/dist/{chunk-ZKMAU6NN.js → chunk-BGTXN2E6.js} +5 -4
  7. package/dist/chunk-BGTXN2E6.js.map +1 -0
  8. package/dist/chunk-DF2UWEEB.js +38 -0
  9. package/dist/chunk-DF2UWEEB.js.map +1 -0
  10. package/dist/{chunk-XKLYDAT5.js → chunk-EF5BKT4B.js} +98 -21
  11. package/dist/chunk-EF5BKT4B.js.map +1 -0
  12. package/dist/chunk-HBAPYA5K.js +257 -0
  13. package/dist/chunk-HBAPYA5K.js.map +1 -0
  14. package/dist/{chunk-QFR6YJ3R.js → chunk-NMFABW4Y.js} +6 -5
  15. package/dist/chunk-NMFABW4Y.js.map +1 -0
  16. package/dist/{chunk-ADD2RIGA.js → chunk-NZPJI7FC.js} +4 -3
  17. package/dist/chunk-NZPJI7FC.js.map +1 -0
  18. package/dist/{chunk-SQR4GFRM.js → chunk-OSPLIKSH.js} +7 -7
  19. package/dist/{chunk-SQR4GFRM.js.map → chunk-OSPLIKSH.js.map} +1 -1
  20. package/dist/chunk-RCAP3Q4T.js +75 -0
  21. package/dist/chunk-RCAP3Q4T.js.map +1 -0
  22. package/dist/{chunk-OBUBP4BY.js → chunk-S2ZXUNXB.js} +30 -6
  23. package/dist/chunk-S2ZXUNXB.js.map +1 -0
  24. package/dist/dates.d.ts +84 -0
  25. package/dist/dates.js +117 -0
  26. package/dist/dates.js.map +1 -0
  27. package/dist/hooks/draft-controller.d.ts +158 -0
  28. package/dist/hooks/draft-controller.js +4 -0
  29. package/dist/hooks/draft-controller.js.map +1 -0
  30. package/dist/hooks/index.d.ts +45 -3
  31. package/dist/hooks/index.js +15 -11
  32. package/dist/hooks/mutation-mode.d.ts +14 -2
  33. package/dist/hooks/mutation-mode.js +2 -1
  34. package/dist/hooks/related-keys.d.ts +1 -1
  35. package/dist/hooks/use-draft.d.ts +33 -0
  36. package/dist/hooks/use-draft.js +5 -0
  37. package/dist/hooks/use-draft.js.map +1 -0
  38. package/dist/hooks/use-infinite-resource.d.ts +1 -1
  39. package/dist/hooks/use-infinite-resource.js +2 -1
  40. package/dist/hooks/use-live.d.ts +1 -1
  41. package/dist/hooks/use-one.d.ts +1 -1
  42. package/dist/hooks/use-one.js +2 -1
  43. package/dist/hooks/use-resource-form.d.ts +1 -1
  44. package/dist/hooks/use-resource-form.js +8 -7
  45. package/dist/hooks/use-resource.d.ts +1 -1
  46. package/dist/hooks/use-resource.js +5 -4
  47. package/dist/hooks/use-write.d.ts +45 -0
  48. package/dist/hooks/use-write.js +4 -0
  49. package/dist/hooks/use-write.js.map +1 -0
  50. package/dist/index.d.ts +78 -5
  51. package/dist/index.js +17 -13
  52. package/dist/provider/index.d.ts +85 -5
  53. package/dist/provider/index.js +3 -3
  54. package/dist/read-outcome-BnboMYOV.d.ts +30 -0
  55. package/dist/schema/index.d.ts +3 -2
  56. package/dist/testing/index.d.ts +102 -0
  57. package/dist/testing/index.js +210 -0
  58. package/dist/testing/index.js.map +1 -0
  59. package/dist/{types-CS9UpxQS.d.ts → types-B9DDfvWa.d.ts} +1 -1
  60. package/dist/{read-outcome-B6nACXZ7.d.ts → write-outcome-B2fE29yZ.d.ts} +1 -30
  61. package/package.json +14 -1
  62. package/dist/chunk-ADD2RIGA.js.map +0 -1
  63. package/dist/chunk-OBUBP4BY.js.map +0 -1
  64. package/dist/chunk-QFR6YJ3R.js.map +0 -1
  65. package/dist/chunk-V3GDAELU.js +0 -3
  66. package/dist/chunk-V3GDAELU.js.map +0 -1
  67. package/dist/chunk-XKLYDAT5.js.map +0 -1
  68. package/dist/chunk-ZKMAU6NN.js.map +0 -1
@@ -0,0 +1,102 @@
1
+ import { D as DataProvider, a as Identifier } from '../types-B9DDfvWa.js';
2
+ import { D as DataWriteError } from '../write-outcome-B2fE29yZ.js';
3
+ import '../types-C7FJtFXV.js';
4
+ import '../form-types.js';
5
+
6
+ /** The shared fixture logins, the same ids `genesis_testing.user_id()` uses. */
7
+ declare const FIXTURE_USERS: {
8
+ readonly owner: "00000000-0000-4000-8000-000000000001";
9
+ readonly staff: "00000000-0000-4000-8000-000000000002";
10
+ readonly client: "00000000-0000-4000-8000-000000000003";
11
+ readonly stranger: "00000000-0000-4000-8000-000000000004";
12
+ };
13
+ type FixtureUser = keyof typeof FIXTURE_USERS;
14
+ /** A broken contract: the message says what happened instead. */
15
+ declare class ContractError extends Error {
16
+ constructor(message: string);
17
+ }
18
+ /**
19
+ * A signed access token for a fixture person (or any user id), for a test
20
+ * database's legacy JWT secret. Hand it to the client as
21
+ * `Authorization: Bearer <token>`; `auth.uid()` then reads that user. Test
22
+ * databases only: never give it a production secret.
23
+ */
24
+ declare function fixtureAccessToken(user: FixtureUser | string, options: {
25
+ secret: string;
26
+ expiresInSeconds?: number;
27
+ role?: string;
28
+ }): Promise<string>;
29
+ /** The error's message is plain words: no raw database text or error codes. */
30
+ declare function expectPlainMessage(error: unknown): void;
31
+ /**
32
+ * Whether a failed write is a refusal: a refusal code (42501, P0001), or the
33
+ * provider's own `forbidden` (the row is still visible but unchanged) or
34
+ * `unauthenticated`. `gone` is not: it also means the id matched nothing.
35
+ */
36
+ declare function isRefusal(error: unknown): error is DataWriteError;
37
+ interface ExpectRefusedOptions {
38
+ /** With a write function: who tries it (and must be refused). */
39
+ as?: DataProvider;
40
+ /**
41
+ * Proof a write that reached no row (`gone`) was refused, not mistyped.
42
+ * With a write function: a provider for someone who may make it, which runs
43
+ * the same function. With a promise: a function making the same write as
44
+ * that someone. Either way it must resolve with the row it changed. Its
45
+ * write is real: run the check on test data.
46
+ */
47
+ control?: DataProvider | (() => Promise<unknown>);
48
+ }
49
+ /**
50
+ * The write is refused, in plain words: a refusal code (42501, P0001) or the
51
+ * provider's `forbidden` / `unauthenticated`. A write that reaches no row
52
+ * (`gone`) counts only with a `control` that makes the same write and changes
53
+ * a row, so a typo'd id or resource can't pass. Pass the write as a function
54
+ * of a provider (with `as` and a control provider) to run exactly the same
55
+ * write both ways. Returns the refusal for more checks.
56
+ *
57
+ * ```ts
58
+ * await expectRefused((p) => p.update("tasks", { id, data: { title: "x" } }), { as: stranger, control: owner });
59
+ * ```
60
+ */
61
+ declare function expectRefused(write: Promise<unknown> | ((provider: DataProvider) => Promise<unknown>), options?: ExpectRefusedOptions): Promise<DataWriteError>;
62
+ /** The write succeeds. Returns what it resolved to. */
63
+ declare function expectAllowed<T>(write: Promise<T>): Promise<T>;
64
+ /**
65
+ * A client can't change a column only the server sets. First `system` (the
66
+ * service role, or whoever the server writes as) sets `field` to `value` and
67
+ * must change the stored value, then puts it back: that proves the row exists
68
+ * and the value can change at all. Then `as` tries the same update, and the
69
+ * stored value must stay as it was, whether the update was refused, reached
70
+ * no row, or a trigger put the value back. Any other error fails.
71
+ */
72
+ declare function expectServerOwned(args: {
73
+ as: DataProvider;
74
+ system: DataProvider;
75
+ resource: string;
76
+ id: Identifier;
77
+ field: string;
78
+ value: unknown;
79
+ }): Promise<void>;
80
+ /**
81
+ * `marker` (a value only a private row holds) appears nowhere in what the read
82
+ * returns: use a list or report read that returns the rows around the private
83
+ * one. A refused read (42501, or `forbidden` / `unauthenticated`) shows
84
+ * nothing and passes; any other failure fails, `gone` included, so a wrong id
85
+ * or a broken query can't pass.
86
+ */
87
+ declare function expectNeverShows(read: Promise<unknown>, marker: string): Promise<void>;
88
+ /** The read returns exactly `expected` rows (a list result, a getMany result or an array). */
89
+ declare function expectSees(read: Promise<unknown>, expected: number): Promise<void>;
90
+ /**
91
+ * A delete on a recycle-bin table moves the row to the bin: `as` deletes it,
92
+ * can no longer read it (gone or forbidden), and `inBin` (a check with the
93
+ * service role or a bin query) confirms the row is still stored.
94
+ */
95
+ declare function expectBinned(args: {
96
+ as: DataProvider;
97
+ resource: string;
98
+ id: Identifier;
99
+ inBin: () => Promise<boolean>;
100
+ }): Promise<void>;
101
+
102
+ export { ContractError, type ExpectRefusedOptions, FIXTURE_USERS, type FixtureUser, expectAllowed, expectBinned, expectNeverShows, expectPlainMessage, expectRefused, expectSees, expectServerOwned, fixtureAccessToken, isRefusal };
@@ -0,0 +1,210 @@
1
+ import { isDataReadError } from '../chunk-HUNGCTRA.js';
2
+ import { isDataWriteError } from '../chunk-LG6TUBVM.js';
3
+
4
+ // src/testing/index.ts
5
+ var FIXTURE_USERS = {
6
+ owner: "00000000-0000-4000-8000-000000000001",
7
+ staff: "00000000-0000-4000-8000-000000000002",
8
+ client: "00000000-0000-4000-8000-000000000003",
9
+ stranger: "00000000-0000-4000-8000-000000000004"
10
+ };
11
+ var ContractError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "ContractError";
15
+ }
16
+ };
17
+ async function fixtureAccessToken(user, options) {
18
+ const sub = Object.hasOwn(FIXTURE_USERS, user) ? FIXTURE_USERS[user] : user;
19
+ const now = Math.floor(Date.now() / 1e3);
20
+ const role = options.role ?? "authenticated";
21
+ const claims = { sub, role, aud: role, iat: now, exp: now + (options.expiresInSeconds ?? 3600) };
22
+ const encode = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
23
+ const text = (value) => encode(new TextEncoder().encode(JSON.stringify(value)));
24
+ const body = `${text({ alg: "HS256", typ: "JWT" })}.${text(claims)}`;
25
+ const key = await crypto.subtle.importKey(
26
+ "raw",
27
+ new TextEncoder().encode(options.secret),
28
+ { name: "HMAC", hash: "SHA-256" },
29
+ false,
30
+ ["sign"]
31
+ );
32
+ const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(body));
33
+ return `${body}.${encode(new Uint8Array(signature))}`;
34
+ }
35
+ var RAW_TEXT = /violates|constraint|duplicate key|row-level security|permission denied for|relation "|syntax error|null value in column|\b(?:42501|42P01|42703|23505|23503|23502|23514|22P02|P0001|PGRST[0-9]{3})\b/i;
36
+ function expectPlainMessage(error) {
37
+ const message = error instanceof Error ? error.message : error && typeof error === "object" && typeof error.message === "string" ? error.message : typeof error === "string" ? error : "";
38
+ if (!message.trim()) throw new ContractError("the error has no message to show");
39
+ if (RAW_TEXT.test(message)) throw new ContractError(`the message shows raw database text: ${message}`);
40
+ }
41
+ var REFUSAL_CODES = /* @__PURE__ */ new Set(["42501", "P0001"]);
42
+ function isRefusal(error) {
43
+ if (!isDataWriteError(error)) return false;
44
+ if (error.code !== void 0 && REFUSAL_CODES.has(error.code)) return true;
45
+ return error.outcome === "forbidden" || error.outcome === "unauthenticated";
46
+ }
47
+ var describeError = (error) => isDataWriteError(error) || isDataReadError(error) ? `${error.outcome}${error.code ? ` (${error.code})` : ""}: ${error.message}` : String(error);
48
+ function changedARow(result) {
49
+ if (!result || typeof result !== "object") return false;
50
+ const r = result;
51
+ if (r.error !== void 0 && r.error !== null) return false;
52
+ if (Array.isArray(r.data)) return r.data.length > 0;
53
+ return r.data !== null && typeof r.data === "object";
54
+ }
55
+ async function expectRefused(write, options = {}) {
56
+ if (typeof write !== "function") void write.catch(() => void 0);
57
+ let attempt;
58
+ let control;
59
+ if (typeof write === "function") {
60
+ if (!options.as) throw new ContractError("pass `as`: the provider that should be refused");
61
+ const by = options.control;
62
+ if (by !== void 0 && typeof by === "function") {
63
+ throw new ContractError("with a write function, `control` is a provider that runs the same write");
64
+ }
65
+ attempt = write(options.as);
66
+ control = by ? () => write(by) : void 0;
67
+ } else {
68
+ attempt = write;
69
+ if (options.control !== void 0 && typeof options.control !== "function") {
70
+ throw new ContractError("with a write promise, `control` is a function making the same write");
71
+ }
72
+ control = options.control;
73
+ }
74
+ let result;
75
+ try {
76
+ result = await attempt;
77
+ } catch (error) {
78
+ if (isRefusal(error)) {
79
+ expectPlainMessage(error);
80
+ return error;
81
+ }
82
+ if (isDataWriteError(error) && error.outcome === "gone") {
83
+ if (!control) {
84
+ throw new ContractError(
85
+ `it reached no row (${error.message}); pass a control that can make the same write, or it could be a wrong id`
86
+ );
87
+ }
88
+ let controlResult;
89
+ try {
90
+ controlResult = await control();
91
+ } catch (controlError) {
92
+ throw new ContractError(`the control failed too (${describeError(controlError)}): nothing was refused`);
93
+ }
94
+ if (!changedARow(controlResult)) {
95
+ throw new ContractError(
96
+ `the control changed no row (${JSON.stringify(controlResult)?.slice(0, 200)}): nothing was refused`
97
+ );
98
+ }
99
+ expectPlainMessage(error);
100
+ return error;
101
+ }
102
+ throw new ContractError(`it failed, but not as a refusal: ${describeError(error)}`);
103
+ }
104
+ throw new ContractError(`it was allowed: ${JSON.stringify(result)?.slice(0, 300)}`);
105
+ }
106
+ async function expectAllowed(write) {
107
+ try {
108
+ return await write;
109
+ } catch (error) {
110
+ throw new ContractError(`it was refused: ${describeError(error)}`);
111
+ }
112
+ }
113
+ var stable = (value) => JSON.stringify(
114
+ value,
115
+ (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a < b ? -1 : 1)) : v
116
+ );
117
+ async function expectServerOwned(args) {
118
+ const { as, system, resource, id, field, value } = args;
119
+ const read = async () => {
120
+ const row = (await system.getOne(resource, { id })).data;
121
+ if (!row || !(field in row)) throw new ContractError(`${resource} ${String(id)} has no field ${field}`);
122
+ return row[field];
123
+ };
124
+ const before = await read();
125
+ if (stable(before) === stable(value)) {
126
+ throw new ContractError(`${field} is already ${stable(value)}: try a different value`);
127
+ }
128
+ const set = async (to, what) => {
129
+ try {
130
+ await system.update(resource, { id, data: { [field]: to } });
131
+ } catch (error) {
132
+ throw new ContractError(`the system couldn't ${what} ${field} (${describeError(error)}): nothing was tested`);
133
+ }
134
+ };
135
+ await set(value, "set");
136
+ const changed = await read();
137
+ await set(before, "put back");
138
+ if (stable(changed) === stable(before)) {
139
+ throw new ContractError(`the system couldn't change ${field} either: nothing was tested`);
140
+ }
141
+ if (stable(await read()) !== stable(before)) {
142
+ throw new ContractError(`the system couldn't put ${field} back to ${stable(before)}`);
143
+ }
144
+ try {
145
+ await as.update(resource, { id, data: { [field]: value } });
146
+ } catch (error) {
147
+ if (!isRefusal(error) && !(isDataWriteError(error) && error.outcome === "gone")) {
148
+ throw new ContractError(`setting ${field} failed, but not as a refusal: ${describeError(error)}`);
149
+ }
150
+ }
151
+ const after = await read();
152
+ if (stable(after) !== stable(before)) {
153
+ await system.update(resource, { id, data: { [field]: before } }).catch(() => void 0);
154
+ throw new ContractError(`${resource}.${field} changed from ${stable(before)} to ${stable(after)}`);
155
+ }
156
+ }
157
+ async function expectNeverShows(read, marker) {
158
+ if (!marker) throw new ContractError("give a marker to look for");
159
+ let result;
160
+ try {
161
+ result = await read;
162
+ } catch (error) {
163
+ const refused = (isDataReadError(error) || isDataWriteError(error)) && (error.code !== void 0 && REFUSAL_CODES.has(error.code) || error.outcome === "forbidden" || error.outcome === "unauthenticated");
164
+ if (refused) return;
165
+ throw new ContractError(`the read failed: ${describeError(error)}`);
166
+ }
167
+ if (result === void 0) throw new ContractError("the read returned nothing to look through");
168
+ if (result && typeof result === "object" && "error" in result && result.error != null) {
169
+ throw new ContractError(`the read failed: ${JSON.stringify(result.error)?.slice(0, 200)}`);
170
+ }
171
+ const text = JSON.stringify(result) ?? "";
172
+ const escaped = JSON.stringify(marker).slice(1, -1);
173
+ if (text.includes(marker) || text.includes(escaped)) {
174
+ throw new ContractError(`"${marker}" appears in: ${text.slice(0, 300)}`);
175
+ }
176
+ }
177
+ async function expectSees(read, expected) {
178
+ let result;
179
+ try {
180
+ result = await read;
181
+ } catch (error) {
182
+ throw new ContractError(`the read failed: ${error instanceof Error ? error.message : String(error)}`);
183
+ }
184
+ const rows = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.data) ? result.data : void 0;
185
+ if (!rows) throw new ContractError(`the read returned no rows to count: ${JSON.stringify(result)?.slice(0, 300)}`);
186
+ if (rows.length !== expected) throw new ContractError(`saw ${rows.length} row(s), expected ${expected}`);
187
+ }
188
+ async function expectBinned(args) {
189
+ const { as, resource, id, inBin } = args;
190
+ try {
191
+ await as.getOne(resource, { id });
192
+ } catch (error) {
193
+ throw new ContractError(`${resource} ${String(id)} isn't visible before the delete: ${describeError(error)}`);
194
+ }
195
+ await expectAllowed(as.deleteOne(resource, { id }));
196
+ try {
197
+ await as.getOne(resource, { id });
198
+ } catch (error) {
199
+ if (!isDataReadError(error) || error.outcome !== "gone" && error.outcome !== "forbidden") {
200
+ throw new ContractError(`reading it back failed: ${describeError(error)}`);
201
+ }
202
+ if (!await inBin()) throw new ContractError(`${resource} ${String(id)} was deleted for good, not binned`);
203
+ return;
204
+ }
205
+ throw new ContractError(`${resource} ${String(id)} is still visible after the delete`);
206
+ }
207
+
208
+ export { ContractError, FIXTURE_USERS, expectAllowed, expectBinned, expectNeverShows, expectPlainMessage, expectRefused, expectSees, expectServerOwned, fixtureAccessToken, isRefusal };
209
+ //# sourceMappingURL=index.js.map
210
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Contract test kit (README \"Test your data contract\")\n//\n// Checks an app's own provider keeps the Genesis data rules, run against the\n// app's real schema (a local Supabase in CI): a write the user may not make is\n// refused in plain words, a column only the server sets stays as it was, a\n// private value never shows, a binned delete keeps the row. The pgTAP half is\n// genesis-supabase's `genesis_testing`; both use the same fixture people.\n//\n// Every check passes only on evidence and throws a ContractError otherwise, so\n// it works in vitest, jest or node:test alike. A wrong resource, a missing row\n// or a typo fails the check instead of passing it.\n// ---------------------------------------------------------------------------\n\nimport type { BaseRecord, DataProvider, Identifier } from \"../provider/types\";\nimport { isDataReadError } from \"../schema/read-outcome\";\nimport { isDataWriteError, type DataWriteError } from \"../schema/write-outcome\";\n\n/** The shared fixture logins, the same ids `genesis_testing.user_id()` uses. */\nexport const FIXTURE_USERS = {\n owner: \"00000000-0000-4000-8000-000000000001\",\n staff: \"00000000-0000-4000-8000-000000000002\",\n client: \"00000000-0000-4000-8000-000000000003\",\n stranger: \"00000000-0000-4000-8000-000000000004\",\n} as const;\n\nexport type FixtureUser = keyof typeof FIXTURE_USERS;\n\n/** A broken contract: the message says what happened instead. */\nexport class ContractError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ContractError\";\n }\n}\n\n/**\n * A signed access token for a fixture person (or any user id), for a test\n * database's legacy JWT secret. Hand it to the client as\n * `Authorization: Bearer <token>`; `auth.uid()` then reads that user. Test\n * databases only: never give it a production secret.\n */\nexport async function fixtureAccessToken(\n user: FixtureUser | string,\n options: { secret: string; expiresInSeconds?: number; role?: string },\n): Promise<string> {\n const sub = Object.hasOwn(FIXTURE_USERS, user) ? FIXTURE_USERS[user as FixtureUser] : user;\n const now = Math.floor(Date.now() / 1000);\n const role = options.role ?? \"authenticated\";\n const claims = { sub, role, aud: role, iat: now, exp: now + (options.expiresInSeconds ?? 3600) };\n const encode = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes)).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n const text = (value: unknown) => encode(new TextEncoder().encode(JSON.stringify(value)));\n const body = `${text({ alg: \"HS256\", typ: \"JWT\" })}.${text(claims)}`;\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(options.secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\"HMAC\", key, new TextEncoder().encode(body));\n return `${body}.${encode(new Uint8Array(signature))}`;\n}\n\n/** Raw database text that must never reach a person. */\nconst RAW_TEXT =\n /violates|constraint|duplicate key|row-level security|permission denied for|relation \"|syntax error|null value in column|\\b(?:42501|42P01|42703|23505|23503|23502|23514|22P02|P0001|PGRST[0-9]{3})\\b/i;\n\n/** The error's message is plain words: no raw database text or error codes. */\nexport function expectPlainMessage(error: unknown): void {\n const message =\n error instanceof Error\n ? error.message\n : error && typeof error === \"object\" && typeof (error as { message?: unknown }).message === \"string\"\n ? (error as { message: string }).message\n : typeof error === \"string\"\n ? error\n : \"\";\n if (!message.trim()) throw new ContractError(\"the error has no message to show\");\n if (RAW_TEXT.test(message)) throw new ContractError(`the message shows raw database text: ${message}`);\n}\n\n/** Codes that are refusals: insufficient privilege (RLS, grants) and a guard's `raise exception`. */\nconst REFUSAL_CODES = new Set([\"42501\", \"P0001\"]);\n\n/**\n * Whether a failed write is a refusal: a refusal code (42501, P0001), or the\n * provider's own `forbidden` (the row is still visible but unchanged) or\n * `unauthenticated`. `gone` is not: it also means the id matched nothing.\n */\nexport function isRefusal(error: unknown): error is DataWriteError {\n if (!isDataWriteError(error)) return false;\n if (error.code !== undefined && REFUSAL_CODES.has(error.code)) return true;\n return error.outcome === \"forbidden\" || error.outcome === \"unauthenticated\";\n}\n\nconst describeError = (error: unknown) =>\n isDataWriteError(error) || isDataReadError(error)\n ? `${error.outcome}${error.code ? ` (${error.code})` : \"\"}: ${error.message}`\n : String(error);\n\n/** Whether a write's result shows a row it changed: `{ data: row }` or a non-empty `data` list, with no `error`. */\nfunction changedARow(result: unknown): boolean {\n if (!result || typeof result !== \"object\") return false;\n const r = result as { data?: unknown; error?: unknown };\n if (r.error !== undefined && r.error !== null) return false;\n if (Array.isArray(r.data)) return r.data.length > 0;\n return r.data !== null && typeof r.data === \"object\";\n}\n\nexport interface ExpectRefusedOptions {\n /** With a write function: who tries it (and must be refused). */\n as?: DataProvider;\n /**\n * Proof a write that reached no row (`gone`) was refused, not mistyped.\n * With a write function: a provider for someone who may make it, which runs\n * the same function. With a promise: a function making the same write as\n * that someone. Either way it must resolve with the row it changed. Its\n * write is real: run the check on test data.\n */\n control?: DataProvider | (() => Promise<unknown>);\n}\n\n/**\n * The write is refused, in plain words: a refusal code (42501, P0001) or the\n * provider's `forbidden` / `unauthenticated`. A write that reaches no row\n * (`gone`) counts only with a `control` that makes the same write and changes\n * a row, so a typo'd id or resource can't pass. Pass the write as a function\n * of a provider (with `as` and a control provider) to run exactly the same\n * write both ways. Returns the refusal for more checks.\n *\n * ```ts\n * await expectRefused((p) => p.update(\"tasks\", { id, data: { title: \"x\" } }), { as: stranger, control: owner });\n * ```\n */\nexport async function expectRefused(\n write: Promise<unknown> | ((provider: DataProvider) => Promise<unknown>),\n options: ExpectRefusedOptions = {},\n): Promise<DataWriteError> {\n // A write promise may reject before an argument check below throws: mark it handled now\n // (awaiting it later still sees the rejection).\n if (typeof write !== \"function\") void write.catch(() => undefined);\n let attempt: Promise<unknown>;\n let control: (() => Promise<unknown>) | undefined;\n if (typeof write === \"function\") {\n if (!options.as) throw new ContractError(\"pass `as`: the provider that should be refused\");\n const by = options.control;\n if (by !== undefined && typeof by === \"function\") {\n throw new ContractError(\"with a write function, `control` is a provider that runs the same write\");\n }\n attempt = write(options.as);\n control = by ? () => write(by) : undefined;\n } else {\n attempt = write;\n if (options.control !== undefined && typeof options.control !== \"function\") {\n throw new ContractError(\"with a write promise, `control` is a function making the same write\");\n }\n control = options.control;\n }\n let result: unknown;\n try {\n result = await attempt;\n } catch (error) {\n if (isRefusal(error)) {\n expectPlainMessage(error);\n return error;\n }\n if (isDataWriteError(error) && error.outcome === \"gone\") {\n if (!control) {\n throw new ContractError(\n `it reached no row (${error.message}); pass a control that can make the same write, or it could be a wrong id`,\n );\n }\n let controlResult: unknown;\n try {\n controlResult = await control();\n } catch (controlError) {\n throw new ContractError(`the control failed too (${describeError(controlError)}): nothing was refused`);\n }\n if (!changedARow(controlResult)) {\n throw new ContractError(\n `the control changed no row (${JSON.stringify(controlResult)?.slice(0, 200)}): nothing was refused`,\n );\n }\n expectPlainMessage(error);\n return error;\n }\n throw new ContractError(`it failed, but not as a refusal: ${describeError(error)}`);\n }\n throw new ContractError(`it was allowed: ${JSON.stringify(result)?.slice(0, 300)}`);\n}\n\n/** The write succeeds. Returns what it resolved to. */\nexport async function expectAllowed<T>(write: Promise<T>): Promise<T> {\n try {\n return await write;\n } catch (error) {\n throw new ContractError(`it was refused: ${describeError(error)}`);\n }\n}\n\nconst stable = (value: unknown) =>\n JSON.stringify(value, (_key, v: unknown) =>\n v && typeof v === \"object\" && !Array.isArray(v)\n ? Object.fromEntries(Object.entries(v as Record<string, unknown>).sort(([a], [b]) => (a < b ? -1 : 1)))\n : v,\n );\n\n/**\n * A client can't change a column only the server sets. First `system` (the\n * service role, or whoever the server writes as) sets `field` to `value` and\n * must change the stored value, then puts it back: that proves the row exists\n * and the value can change at all. Then `as` tries the same update, and the\n * stored value must stay as it was, whether the update was refused, reached\n * no row, or a trigger put the value back. Any other error fails.\n */\nexport async function expectServerOwned(args: {\n as: DataProvider;\n system: DataProvider;\n resource: string;\n id: Identifier;\n field: string;\n value: unknown;\n}): Promise<void> {\n const { as, system, resource, id, field, value } = args;\n const read = async () => {\n const row = (await system.getOne<BaseRecord>(resource, { id })).data;\n if (!row || !(field in row)) throw new ContractError(`${resource} ${String(id)} has no field ${field}`);\n return row[field];\n };\n const before = await read();\n if (stable(before) === stable(value)) {\n throw new ContractError(`${field} is already ${stable(value)}: try a different value`);\n }\n const set = async (to: unknown, what: string) => {\n try {\n await system.update(resource, { id, data: { [field]: to } });\n } catch (error) {\n throw new ContractError(`the system couldn't ${what} ${field} (${describeError(error)}): nothing was tested`);\n }\n };\n await set(value, \"set\");\n const changed = await read();\n await set(before, \"put back\");\n if (stable(changed) === stable(before)) {\n throw new ContractError(`the system couldn't change ${field} either: nothing was tested`);\n }\n if (stable(await read()) !== stable(before)) {\n throw new ContractError(`the system couldn't put ${field} back to ${stable(before)}`);\n }\n try {\n await as.update(resource, { id, data: { [field]: value } });\n } catch (error) {\n if (!isRefusal(error) && !(isDataWriteError(error) && error.outcome === \"gone\")) {\n throw new ContractError(`setting ${field} failed, but not as a refusal: ${describeError(error)}`);\n }\n }\n const after = await read();\n if (stable(after) !== stable(before)) {\n // Put the value back, so a failed check doesn't change what the next one sees.\n await system.update(resource, { id, data: { [field]: before } }).catch(() => undefined);\n throw new ContractError(`${resource}.${field} changed from ${stable(before)} to ${stable(after)}`);\n }\n}\n\n/**\n * `marker` (a value only a private row holds) appears nowhere in what the read\n * returns: use a list or report read that returns the rows around the private\n * one. A refused read (42501, or `forbidden` / `unauthenticated`) shows\n * nothing and passes; any other failure fails, `gone` included, so a wrong id\n * or a broken query can't pass.\n */\nexport async function expectNeverShows(read: Promise<unknown>, marker: string): Promise<void> {\n if (!marker) throw new ContractError(\"give a marker to look for\");\n let result: unknown;\n try {\n result = await read;\n } catch (error) {\n const refused =\n (isDataReadError(error) || isDataWriteError(error)) &&\n ((error.code !== undefined && REFUSAL_CODES.has(error.code)) ||\n error.outcome === \"forbidden\" ||\n error.outcome === \"unauthenticated\");\n if (refused) return;\n throw new ContractError(`the read failed: ${describeError(error)}`);\n }\n if (result === undefined) throw new ContractError(\"the read returned nothing to look through\");\n if (result && typeof result === \"object\" && \"error\" in result && (result as { error: unknown }).error != null) {\n throw new ContractError(`the read failed: ${JSON.stringify((result as { error: unknown }).error)?.slice(0, 200)}`);\n }\n const text = JSON.stringify(result) ?? \"\";\n const escaped = JSON.stringify(marker).slice(1, -1);\n if (text.includes(marker) || text.includes(escaped)) {\n throw new ContractError(`\"${marker}\" appears in: ${text.slice(0, 300)}`);\n }\n}\n\n/** The read returns exactly `expected` rows (a list result, a getMany result or an array). */\nexport async function expectSees(read: Promise<unknown>, expected: number): Promise<void> {\n let result: unknown;\n try {\n result = await read;\n } catch (error) {\n throw new ContractError(`the read failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n const rows = Array.isArray(result)\n ? result\n : result && typeof result === \"object\" && Array.isArray((result as { data?: unknown }).data)\n ? (result as { data: unknown[] }).data\n : undefined;\n if (!rows) throw new ContractError(`the read returned no rows to count: ${JSON.stringify(result)?.slice(0, 300)}`);\n if (rows.length !== expected) throw new ContractError(`saw ${rows.length} row(s), expected ${expected}`);\n}\n\n/**\n * A delete on a recycle-bin table moves the row to the bin: `as` deletes it,\n * can no longer read it (gone or forbidden), and `inBin` (a check with the\n * service role or a bin query) confirms the row is still stored.\n */\nexport async function expectBinned(args: {\n as: DataProvider;\n resource: string;\n id: Identifier;\n inBin: () => Promise<boolean>;\n}): Promise<void> {\n const { as, resource, id, inBin } = args;\n // Visible first, so \"can't read it back\" is evidence of the delete.\n try {\n await as.getOne(resource, { id });\n } catch (error) {\n throw new ContractError(`${resource} ${String(id)} isn't visible before the delete: ${describeError(error)}`);\n }\n await expectAllowed(as.deleteOne(resource, { id }));\n try {\n await as.getOne(resource, { id });\n } catch (error) {\n if (!isDataReadError(error) || (error.outcome !== \"gone\" && error.outcome !== \"forbidden\")) {\n throw new ContractError(`reading it back failed: ${describeError(error)}`);\n }\n if (!(await inBin())) throw new ContractError(`${resource} ${String(id)} was deleted for good, not binned`);\n return;\n }\n throw new ContractError(`${resource} ${String(id)} is still visible after the delete`);\n}\n"]}
@@ -347,4 +347,4 @@ interface DataProvider {
347
347
  invalidates?(event: WriteEvent): InvalidationKey[];
348
348
  }
349
349
 
350
- export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FilterParam as F, GetManyParams as G, HistoryParams as H, Identifier as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, WriteEvent as W, BulkChange as a, BulkResult as b, CreateResult as c, DeleteParams as d, DeleteResult as e, GetManyResult as f, GetOneParams as g, GetOneResult as h, ImportParams as i, InvalidationKey as j, ListResult as k, ResourceChangeEvent as l, RowFailure as m, SortParam as n, SubscribeParams as o, UpdateResult as p };
350
+ export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FilterParam as F, GetManyParams as G, HistoryParams as H, InvalidationKey as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, WriteEvent as W, Identifier as a, BulkChange as b, BulkResult as c, CreateResult as d, DeleteParams as e, DeleteResult as f, GetManyResult as g, GetOneParams as h, GetOneResult as i, ImportParams as j, ListResult as k, ResourceChangeEvent as l, RowFailure as m, SortParam as n, SubscribeParams as o, UpdateResult as p };
@@ -59,33 +59,4 @@ declare class DataWriteError extends Error {
59
59
  */
60
60
  declare function isDataWriteError(error: unknown): error is DataWriteError;
61
61
 
62
- /**
63
- * - `unauthenticated`: no signed-in user, or the session expired.
64
- * - `forbidden`: this user may not read these records.
65
- * - `gone`: the one record asked for doesn't exist, or isn't visible.
66
- * - `invalid`: the request itself was wrong (an unknown column, a bad value).
67
- * - `error`: anything else.
68
- */
69
- type ReadOutcome = "unauthenticated" | "forbidden" | "gone" | "invalid" | "error";
70
- interface DataReadErrorOptions {
71
- /** The store's own error code (a Postgres SQLSTATE or PostgREST code). */
72
- code?: string;
73
- /** The underlying error, with its raw text, kept for logging. */
74
- cause?: unknown;
75
- }
76
- /** Thrown by a provider when a read failed. Its `message` is safe to show. */
77
- declare class DataReadError extends Error {
78
- readonly outcome: ReadOutcome;
79
- /** The store's own error code, when it gave one. */
80
- readonly code?: string;
81
- constructor(outcome: ReadOutcome, message: string, options?: DataReadErrorOptions);
82
- }
83
- /**
84
- * `instanceof` fails when two copies of this package are installed, so check
85
- * the shape instead.
86
- */
87
- declare function isDataReadError(error: unknown): error is DataReadError;
88
- /** The plain sentence for a failed read of `noun` records (one, or a list). */
89
- declare function readFailureMessage(outcome: ReadOutcome, noun: string, one: boolean, plural?: string): string;
90
-
91
- export { DataReadError as D, type ReadOutcome as R, type WriteOutcome as W, type DataReadErrorOptions as a, DataWriteError as b, type DataWriteErrorOptions as c, isDataWriteError as d, isDataReadError as i, readFailureMessage as r };
62
+ export { DataWriteError as D, type WriteOutcome as W, type DataWriteErrorOptions as a, isDataWriteError as i };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marktiderman/genesis-core",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -50,6 +50,14 @@
50
50
  "./navigation": {
51
51
  "types": "./dist/navigation.d.ts",
52
52
  "import": "./dist/navigation.js"
53
+ },
54
+ "./testing": {
55
+ "types": "./dist/testing/index.d.ts",
56
+ "import": "./dist/testing/index.js"
57
+ },
58
+ "./dates": {
59
+ "types": "./dist/dates.d.ts",
60
+ "import": "./dist/dates.js"
53
61
  }
54
62
  },
55
63
  "files": [
@@ -66,7 +74,12 @@
66
74
  "devDependencies": {
67
75
  "@hookform/resolvers": "^5.0.1",
68
76
  "@tanstack/react-query": "^5.75.0",
77
+ "@testing-library/react": "^16.3.2",
69
78
  "@types/react": "^19.2.14",
79
+ "@types/react-dom": "^19.2.3",
80
+ "happy-dom": "^20.9.0",
81
+ "react": "^19.0.0",
82
+ "react-dom": "^19.0.0",
70
83
  "react-hook-form": "^7.56.4",
71
84
  "tsup": "^8.3.0",
72
85
  "typescript": "~6.0.3",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/use-infinite-resource.ts"],"names":["total"],"mappings":"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","file":"chunk-ADD2RIGA.js","sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\nimport {\n useInfiniteQuery,\n type InfiniteData,\n type UseInfiniteQueryResult,\n} from \"@tanstack/react-query\";\nimport { useDataProvider } from \"../provider/context\";\nimport type {\n BaseRecord,\n FilterParam,\n ListParams,\n ListResult,\n SortParam,\n} from \"../provider/types\";\nimport { useLiveInvalidation, type LiveOptions } from \"./use-live\";\n\n// ---------------------------------------------------------------------------\n// useInfiniteResource — a list that grows with \"Load more\", by cursor.\n//\n// Asks for the first page with `cursor: null`. A provider that serves cursor\n// paging answers with `nextCursor` and is paged by keyset from then on; one\n// that doesn't answers without it and is paged by `page` (offset) until\n// `total` rows are loaded. Pages live under `[resource, \"infinite\", params]`,\n// so writes on the resource (which invalidate `[resource]`) refetch them,\n// and pending optimistic and undoable writes are patched into every page.\n// ---------------------------------------------------------------------------\n\nexport interface UseInfiniteResourceOptions {\n defaultSort?: SortParam;\n /** Re-synced when its value changes, like `useResource`'s. */\n defaultFilters?: FilterParam[];\n /** Rows per page. Default 25. */\n perPage?: number;\n /** The search to start with. Default \"\". */\n defaultSearch?: string;\n /** Supabase select/join syntax. Must include the sort and key columns. */\n select?: string;\n /** Fields to search across when `setSearch` is called. */\n searchFields?: string[];\n /** Disable fetching. */\n enabled?: boolean;\n /** Refetch the loaded pages when the provider reports a change. See `UseResourceOptions.live`. */\n live?: boolean | LiveOptions;\n}\n\n/** Where one page starts: its offset page, and its cursor (`null` for the first). */\nexport interface InfinitePageParam {\n page: number;\n cursor: string | null;\n}\n\nexport interface UseInfiniteResourceReturn<T> {\n /** The TanStack infinite query. */\n query: UseInfiniteQueryResult<InfiniteData<ListResult<T>, InfinitePageParam>>;\n /** Every loaded row, in order. */\n rows: T[];\n /** Rows matching the filters and search, from the first page's count. */\n total: number | undefined;\n /** Loads the next page. Does nothing while one is loading or none is left. */\n fetchNextPage: () => void;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** The params every page is asked with (without its page and cursor). */\n params: Omit<ListParams, \"pagination\"> & { perPage: number };\n setSort: (sort: SortParam | undefined) => void;\n setFilters: (filters: FilterParam[]) => void;\n setSearch: (query: string) => void;\n}\n\nfunction filterKey(filters: FilterParam[] | undefined): string {\n return JSON.stringify(\n (filters ?? []).map((f) =>\n Object.fromEntries(Object.entries(f).sort(([a], [b]) => a.localeCompare(b))),\n ),\n );\n}\n\nexport function useInfiniteResource<T extends BaseRecord = BaseRecord>(\n resource: string,\n options: UseInfiniteResourceOptions = {},\n): UseInfiniteResourceReturn<T> {\n const {\n defaultSort,\n defaultFilters,\n perPage = 25,\n defaultSearch = \"\",\n select,\n searchFields,\n enabled = true,\n live,\n } = options;\n const provider = useDataProvider();\n\n const [sort, setSort] = useState<SortParam | undefined>(defaultSort);\n const [filters, setFiltersRaw] = useState<FilterParam[]>(defaultFilters ?? []);\n const [search, setSearch] = useState(defaultSearch);\n\n // Follow `defaultFilters` by value, during render (see useResource).\n const defaultKey = filterKey(defaultFilters);\n const [syncedKey, setSyncedKey] = useState(defaultKey);\n if (defaultKey !== syncedKey) {\n setSyncedKey(defaultKey);\n setFiltersRaw(defaultFilters ?? []);\n }\n\n const setFilters = useCallback((next: FilterParam[]) => setFiltersRaw(next), []);\n\n const params = useMemo(() => {\n const p: Omit<ListParams, \"pagination\"> & { perPage: number } = {\n perPage,\n sort,\n filters: filters.length > 0 ? filters : undefined,\n select,\n };\n if (search && searchFields && searchFields.length > 0) {\n p.search = { query: search, fields: searchFields };\n }\n return p;\n }, [perPage, sort, filters, select, search, searchFields]);\n\n const query = useInfiniteQuery<\n ListResult<T>,\n Error,\n InfiniteData<ListResult<T>, InfinitePageParam>,\n readonly unknown[],\n InfinitePageParam\n >({\n queryKey: [resource, \"infinite\", params],\n initialPageParam: { page: 1, cursor: null },\n queryFn: ({ pageParam }) => {\n const { perPage: size, ...rest } = params;\n return provider.getList<T>(resource, {\n ...rest,\n pagination: { page: pageParam.page, perPage: size, cursor: pageParam.cursor },\n });\n },\n getNextPageParam: (last, pages, lastParam) => {\n if (last.nextCursor !== undefined) {\n // Cursor mode: the provider says whether there is more.\n return last.nextCursor ? { page: lastParam.page + 1, cursor: last.nextCursor } : undefined;\n }\n // Offset mode: more while fewer than `total` rows are loaded and the\n // last page was full.\n const loaded = pages.reduce((n, p) => n + p.data.length, 0);\n const total = pages[0]?.total ?? 0;\n if (last.data.length < params.perPage || loaded >= total) return undefined;\n return { page: lastParam.page + 1, cursor: null };\n },\n enabled,\n });\n\n useLiveInvalidation(resource, live, enabled, { filters: params.filters }, [resource]);\n\n // A row can move between pages while the user scrolls (its sort value\n // changed, or rows before it were added): show it once, where it first appears.\n const rows = useMemo(() => {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const page of query.data?.pages ?? []) {\n for (const row of page.data) {\n const id = row?.id;\n if (id != null) {\n const key = String(id);\n if (seen.has(key)) continue;\n seen.add(key);\n }\n out.push(row);\n }\n }\n return out;\n }, [query.data]);\n const firstTotal = query.data?.pages[0]?.total;\n const total = firstTotal === undefined || firstTotal < 0 ? undefined : firstTotal;\n\n const { fetchNextPage: fetchNext, hasNextPage, isFetchingNextPage } = query;\n const fetchNextPage = useCallback(() => {\n if (!hasNextPage || isFetchingNextPage) return;\n void fetchNext();\n }, [fetchNext, hasNextPage, isFetchingNextPage]);\n\n return {\n query,\n rows,\n total,\n fetchNextPage,\n hasNextPage: !!hasNextPage,\n isFetchingNextPage,\n params,\n setSort,\n setFilters,\n setSearch,\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/mutation-mode.ts"],"names":["rows"],"mappings":"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","file":"chunk-OBUBP4BY.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Mutation modes — when a write reaches the cache, and when the provider.\n//\n// - `pessimistic` (default): wait for the provider, then refetch. Today's\n// behaviour.\n// - `optimistic`: patch the TanStack Query cache at once, send the write, and\n// put the cache back (and report the error) if it fails.\n// - `undoable`: patch the cache at once and offer an undo (`onUndoable`);\n// the write is sent after a delay unless undone. A provider that serves\n// `undo` (capabilities `undo: true`) gets the write at once instead, and\n// the undo asks the provider to reverse it.\n//\n// Core has no UI: the undo toast is the caller's (`onUndoable`). genesis-ui's\n// ResourceForm and ResourcePage show a sonner toast.\n//\n// Pending writes belong to the QueryClient, not to the hook that started\n// them: a form that closes on success can still be undone from its toast.\n// ---------------------------------------------------------------------------\n\nimport type { QueryClient, QueryKey } from \"@tanstack/react-query\";\nimport type { BaseRecord, DataProvider, Identifier, ListResult } from \"../provider/types\";\nimport type { AuditEntry, ProviderCapabilities } from \"../schema/types\";\nimport { capabilitiesQueryKey } from \"./use-capabilities\";\n\nexport type MutationMode = \"pessimistic\" | \"optimistic\" | \"undoable\";\n\n/** How long an `undoable` write waits for an undo before it is sent. */\nexport const DEFAULT_UNDO_DELAY_MS = 5000;\n\n/** An undoable write, as handed to `onUndoable` so a UI can offer the undo. */\nexport interface UndoableWrite {\n resource: string;\n kind: \"create\" | \"update\" | \"delete\";\n /** The record's id; absent for a create (it has none until it is sent). */\n id?: Identifier;\n /**\n * - `delay`: the write has not been sent. `undo()` discards it; `commit()`\n * sends it now instead of waiting.\n * - `provider`: the write was sent at once, because the provider serves\n * `undo`. `undo()` asks the provider to reverse it; `commit()` does\n * nothing.\n */\n strategy: \"delay\" | \"provider\";\n /** How long the undo is offered, in ms (the toast's duration). */\n delayMs: number;\n /**\n * Take the write back. Resolves true when it was undone (or will never be\n * sent), false when it could not be: already sent under `delay` (the\n * window closed, it was committed, a later write on the same record sent\n * it first, or the page unloaded), already undone, or the provider's undo\n * failed (reported through `onError`). Works after the hook that made the\n * write has unmounted. Safe to call more than once.\n */\n undo: () => Promise<boolean>;\n /** Send a `delay` write now. */\n commit: () => void;\n}\n\nexport interface MutationModeOptions {\n /** Default `\"pessimistic\"`. */\n mutationMode?: MutationMode;\n /** How long an `undoable` write waits, in ms. Default 5000. */\n undoDelayMs?: number;\n /** Called with every `undoable` write, to show an undo control. */\n onUndoable?: (write: UndoableWrite) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Small helpers\n// ---------------------------------------------------------------------------\n\nfunction sameId(a: unknown, b: unknown): boolean {\n return a != null && b != null && String(a) === String(b);\n}\n\nfunction isListResult(value: unknown): value is ListResult<BaseRecord> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { data?: unknown }).data)\n );\n}\n\n/** TanStack infinite-query data whose pages are list results. */\ninterface InfiniteLists {\n pages: unknown[];\n pageParams: unknown[];\n}\n\nfunction isInfiniteLists(value: unknown): value is InfiniteLists {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { pages?: unknown }).pages) &&\n Array.isArray((value as { pageParams?: unknown }).pageParams)\n );\n}\n\n/**\n * Runs `fn` on every list a cache entry holds: a `[resource, \"list\", …]`\n * entry is one list (keyed by its query hash); a `[resource, \"infinite\",\n * …]` entry (useInfiniteResource) is one list per loaded page (keyed\n * `hash#page`). Returns the entry rebuilt, or the same object when `fn`\n * changed nothing. Rows added or removed change every page's `total`,\n * since each page reports the whole list's count.\n */\nfunction mapLists(\n key: QueryKey,\n hash: string,\n value: unknown,\n fn: (list: ListResult<BaseRecord>, hash: string) => ListResult<BaseRecord>,\n): unknown {\n if (key[1] === \"list\") return isListResult(value) ? fn(value, hash) : value;\n if (key[1] !== \"infinite\" || !isInfiniteLists(value)) return value;\n let changed = false;\n // Rows added or removed across pages, applied to every page's count: a\n // page after the first may report -1 (not counted), so its own total\n // can't carry the change.\n let rowDelta = 0;\n const pages = value.pages.map((page, i) => {\n if (!isListResult(page)) return page;\n const next = fn(page, `${hash}#${i}`);\n if (next !== page) {\n changed = true;\n rowDelta += next.data.length - page.data.length;\n }\n return next;\n });\n if (!changed) return value;\n const withTotals = value.pages.map((orig, i) => {\n const page = pages[i];\n if (!isListResult(page) || !isListResult(orig) || typeof orig.total !== \"number\") return page;\n return { ...page, total: orig.total < 0 ? orig.total : Math.max(0, orig.total + rowDelta) };\n });\n return { ...value, pages: withTotals };\n}\n\n/** Every list result a cache entry holds (see `mapLists`). */\nfunction listsIn(key: QueryKey, value: unknown): ListResult<BaseRecord>[] {\n if (key[1] === \"list\") return isListResult(value) ? [value] : [];\n if (key[1] === \"infinite\" && isInfiniteLists(value)) return value.pages.filter(isListResult);\n return [];\n}\n\nfunction isDetailKeyFor(key: QueryKey, resource: string, id: Identifier): boolean {\n return key[0] === resource && key[1] === \"detail\" && sameId(key[2], id);\n}\n\n/** Structural equality for row values (JSON-like data, Dates). */\nexport function sameValue(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a)) {\n const other = b as unknown[];\n return a.length === other.length && a.every((item, i) => sameValue(item, other[i]));\n }\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(\n (k) => Object.prototype.hasOwnProperty.call(b, k) &&\n sameValue((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]),\n );\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\n/** The record as the cache has it now, from a detail entry or any list. */\nexport function cachedRecord(\n queryClient: QueryClient,\n resource: string,\n id: Identifier,\n): BaseRecord | undefined {\n for (const [key, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource, \"detail\"] })) {\n if (isDetailKeyFor(key, resource, id) && value && typeof value === \"object\") return value as BaseRecord;\n }\n for (const [key, value] of queryClient.getQueriesData<unknown>({ queryKey: [resource] })) {\n for (const list of listsIn(key, value)) {\n const found = list.data.find((row) => sameId(row.id, id));\n if (found) return found;\n }\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// The registry — one per QueryClient\n//\n// Holds every optimistic/undoable write not yet settled, and the cache\n// patches they made. Patches are re-applied to any `[resource, \"list\" |\n// \"infinite\" | \"detail\", …]` data that arrives while they are pending (a refetch, a new\n// page, filter or search), so a deleted row stays gone and an edit stays\n// shown until the write settles. Because of that, refetching is always safe\n// and nothing waits for pending writes before invalidating.\n// ---------------------------------------------------------------------------\n\n/** One record's change, kept applied to the cache while its write is pending. */\ninterface Patch {\n resource: string;\n id: Identifier;\n kind: \"update\" | \"delete\";\n /** An update's values; the same object its write sends, so a rebase changes both. */\n data?: Record<string, unknown>;\n /** Per cache entry (query hash): the row just before this patch, and its list index. */\n before: Map<string, { row: BaseRecord; index: number }>;\n}\n\n/** One optimistic or undoable write that has not settled. */\ninterface Tracked {\n seq: number;\n queryClient: QueryClient;\n provider: DataProvider;\n resource: string;\n kind: \"create\" | \"update\" | \"delete\";\n /** `resource` + id: writes on the same record are sent in order. */\n rowKey?: string;\n patch?: Patch;\n /** Row-shaped values it will send (update); undefined when not row-shaped. */\n data?: Record<string, unknown>;\n /** The cached row just before this write's patch. */\n baseline?: BaseRecord;\n /**\n * - waiting: an undoable write in its delay\n * - queued: to be sent, behind earlier writes on the same record\n * - sending / done\n */\n state: \"waiting\" | \"queued\" | \"sending\" | \"done\";\n /** Sent with `keepalive` (because the page was unloading). */\n keepalive: boolean;\n /** Set when it was dropped because an earlier write it built on was taken back. */\n dropped: boolean;\n /** Send now: a waiting write skips its delay; `unloading` also skips the row queue. */\n flush: (unloading?: boolean) => void;\n /** Drop a waiting or queued write (see `dropped`). */\n drop: () => void;\n}\n\ninterface Registry {\n pending: Map<string, number>;\n patches: Patch[];\n writes: Tracked[];\n rowTails: Map<string, Promise<void>>;\n /** Cache data this module wrote, so the cache listener does not re-patch it. */\n produced: WeakSet<object>;\n /** True while this module is writing to the cache. */\n writing: boolean;\n unsubscribe?: () => void;\n}\n\nconst registries = new WeakMap<QueryClient, Registry>();\nlet nextSeq = 0;\n\nfunction registryFor(queryClient: QueryClient): Registry {\n let reg = registries.get(queryClient);\n if (!reg) {\n reg = {\n pending: new Map(),\n patches: [],\n writes: [],\n rowTails: new Map(),\n produced: new WeakSet(),\n writing: false,\n };\n registries.set(queryClient, reg);\n }\n return reg;\n}\n\n/** Optimistic or undoable writes on `resource` not yet settled. */\nexport function pendingWriteCount(queryClient: QueryClient, resource: string): number {\n return registries.get(queryClient)?.pending.get(resource) ?? 0;\n}\n\nfunction beginPending(reg: Registry, resource: string): void {\n reg.pending.set(resource, (reg.pending.get(resource) ?? 0) + 1);\n}\n\nfunction endPending(reg: Registry, resource: string): void {\n const left = Math.max(0, (reg.pending.get(resource) ?? 0) - 1);\n if (left === 0) reg.pending.delete(resource);\n else reg.pending.set(resource, left);\n}\n\n/**\n * Invalidate `[resource]`. Safe while optimistic/undoable writes on it are\n * pending: their patches are re-applied to whatever the refetch brings.\n */\nexport function invalidateResource(queryClient: QueryClient, resource: string): Promise<void> {\n return queryClient.invalidateQueries({ queryKey: [resource] });\n}\n\nfunction setProduced(reg: Registry, queryClient: QueryClient, key: QueryKey, value: unknown): void {\n const wasWriting = reg.writing;\n reg.writing = true;\n try {\n queryClient.setQueryData(key, value);\n } finally {\n reg.writing = wasWriting;\n }\n // Structural sharing may store a different object than `value`.\n const stored: unknown = queryClient.getQueryData(key);\n if (stored && typeof stored === \"object\") reg.produced.add(stored);\n}\n\nfunction applyUpdate(row: BaseRecord, data: Record<string, unknown>): BaseRecord {\n return { ...row, ...data, id: row.id };\n}\n\n/** `row` with `data`'s fields put back to what they were in `before`. */\nfunction revertFields(row: BaseRecord, data: Record<string, unknown>, before: BaseRecord): BaseRecord {\n const out: Record<string, unknown> = { ...row };\n for (const k of Object.keys(data)) {\n if (k === \"id\") continue;\n if (k in before) out[k] = before[k];\n else delete out[k];\n }\n return out as BaseRecord;\n}\n\n/**\n * Applies `patches`, in order, to one cache entry's data, recording each\n * patch's before-row for that entry. Returns the same object when nothing\n * applied.\n */\nfunction applyPatches(key: QueryKey, hash: string, value: unknown, patches: Patch[]): unknown {\n if (key[1] === \"list\" || key[1] === \"infinite\") {\n return mapLists(key, hash, value, (list, listHash) => {\n let rows = list.data;\n let total = list.total;\n let changed = false;\n for (const p of patches) {\n const index = rows.findIndex((row) => sameId(row.id, p.id));\n if (index < 0) continue;\n p.before.set(listHash, { row: rows[index]!, index });\n if (p.kind === \"update\") {\n rows = rows.slice();\n rows[index] = applyUpdate(rows[index]!, p.data ?? {});\n } else {\n rows = rows.filter((_, i) => i !== index);\n if (typeof total === \"number\" && total >= 0) total = Math.max(0, total - 1);\n }\n changed = true;\n }\n if (!changed) return list;\n return typeof total === \"number\" ? { ...list, data: rows, total } : { ...list, data: rows };\n });\n }\n if (key[1] === \"detail\" && value && typeof value === \"object\") {\n let row = value as BaseRecord;\n let changed = false;\n for (const p of patches) {\n if (p.kind !== \"update\" || !sameId(key[2], p.id)) continue;\n p.before.set(hash, { row, index: -1 });\n row = applyUpdate(row, p.data ?? {});\n changed = true;\n }\n return changed ? row : value;\n }\n return value;\n}\n\n/** Re-applies pending patches to data that just landed in the cache. */\nfunction ensureListener(reg: Registry, queryClient: QueryClient): void {\n if (reg.unsubscribe) return;\n reg.unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n if (reg.writing || event.type !== \"updated\" || event.action.type !== \"success\") return;\n const { queryKey: key, queryHash: hash } = event.query;\n const resource = key[0];\n if (typeof resource !== \"string\") return;\n const data: unknown = event.query.state.data;\n if (!data || typeof data !== \"object\" || reg.produced.has(data)) return;\n const patches = reg.patches.filter((p) => p.resource === resource);\n if (patches.length === 0) return;\n const next = applyPatches(key, hash, data, patches);\n if (next !== data) setProduced(reg, queryClient, key, next);\n });\n}\n\nfunction addPatch(reg: Registry, queryClient: QueryClient, patch: Patch): void {\n reg.patches.push(patch);\n ensureListener(reg, queryClient);\n for (const query of queryClient.getQueryCache().findAll({ queryKey: [patch.resource] })) {\n const value: unknown = query.state.data;\n const next = applyPatches(query.queryKey, query.queryHash, value, [patch]);\n if (next !== value) setProduced(reg, queryClient, query.queryKey, next);\n }\n}\n\nfunction dropPatchRecord(reg: Registry, patch: Patch): number {\n const index = reg.patches.indexOf(patch);\n if (index >= 0) reg.patches.splice(index, 1);\n if (reg.patches.length === 0 && reg.unsubscribe) {\n reg.unsubscribe();\n reg.unsubscribe = undefined;\n }\n return index;\n}\n\nfunction firstBefore(patch: Patch): { row: BaseRecord; index: number } | undefined {\n for (const value of patch.before.values()) return value;\n return undefined;\n}\n\n/**\n * Takes a patch back out of the cache, keeping every other pending patch on\n * the same record applied (field by field, not by restoring a whole saved\n * row).\n */\nfunction retractPatch(reg: Registry, queryClient: QueryClient, patch: Patch): void {\n const index = dropPatchRecord(reg, patch);\n if (index < 0) return;\n const onRow = (p: Patch) => p.resource === patch.resource && sameId(p.id, patch.id);\n const remaining = reg.patches.filter(onRow);\n\n // Later patches on the record recorded their before-rows on top of this\n // one: take its values out of them too.\n if (patch.kind === \"update\" && patch.data) {\n for (const later of reg.patches.slice(index).filter(onRow)) {\n for (const [hash, b] of later.before) {\n const src = patch.before.get(hash) ?? firstBefore(patch);\n if (src) later.before.set(hash, { ...b, row: revertFields(b.row, patch.data, src.row) });\n }\n }\n }\n\n const reapply = (row: BaseRecord) =>\n remaining.reduce((r, p) => (p.kind === \"update\" ? applyUpdate(r, p.data ?? {}) : r), row);\n\n for (const query of queryClient.getQueryCache().findAll({ queryKey: [patch.resource] })) {\n const key = query.queryKey;\n const value: unknown = query.state.data;\n let next: unknown = value;\n if (key[1] === \"list\" || key[1] === \"infinite\") {\n next = mapLists(key, query.queryHash, value, (list, listHash) => {\n const b = patch.before.get(listHash);\n const at = list.data.findIndex((row) => sameId(row.id, patch.id));\n if (patch.kind === \"update\") {\n const src = b ?? firstBefore(patch);\n if (at < 0 || !src || !patch.data) return list;\n const rows = list.data.slice();\n rows[at] = reapply(revertFields(rows[at]!, patch.data, src.row));\n return { ...list, data: rows };\n }\n // A delete puts the row back where it was, unless a later delete of\n // the same record still hides it.\n if (at >= 0 || !b || remaining.some((p) => p.kind === \"delete\")) return list;\n const rows = list.data.slice();\n rows.splice(Math.min(b.index, rows.length), 0, reapply(b.row));\n return {\n ...list,\n data: rows,\n ...(typeof list.total === \"number\" && list.total >= 0 ? { total: list.total + 1 } : {}),\n };\n });\n } else if (\n key[1] === \"detail\" &&\n patch.kind === \"update\" &&\n patch.data &&\n sameId(key[2], patch.id) &&\n value &&\n typeof value === \"object\"\n ) {\n const src = patch.before.get(query.queryHash) ?? firstBefore(patch);\n if (!src) continue;\n next = reapply(revertFields(value as BaseRecord, patch.data, src.row));\n }\n if (next !== value) setProduced(reg, queryClient, key, next);\n }\n}\n\n/**\n * A write was taken back (undone, or it failed): retract its patch, and fix\n * the later writes on the same record that have not been sent. A later\n * update that kept this write's values unchanged (its form opened from the\n * patched cache) gets them put back to what they were before; one whose\n * values are not row-shaped cannot be fixed, so it is dropped.\n */\nfunction takeBack(reg: Registry, write: Tracked): void {\n if (write.patch) retractPatch(reg, write.queryClient, write.patch);\n if (!write.rowKey || write.kind !== \"update\" || !write.patch?.data) return;\n const undone = write.patch.data;\n const before = write.baseline ?? firstBefore(write.patch)?.row ?? ({} as BaseRecord);\n const later = reg.writes.filter(\n (w) =>\n w.seq > write.seq &&\n w.rowKey === write.rowKey &&\n w.kind === \"update\" &&\n (w.state === \"waiting\" || w.state === \"queued\"),\n );\n for (const w of later) {\n if (!w.data) {\n w.drop();\n continue;\n }\n const rebased: Record<string, unknown> = {};\n const removed: string[] = [];\n for (const k of Object.keys(undone)) {\n if (k === \"id\" || !(k in w.data)) continue;\n const kept = sameValue(w.data[k], undone[k]) && w.baseline != null && sameValue(w.baseline[k], undone[k]);\n if (!kept) continue;\n if (k in before) rebased[k] = before[k];\n else removed.push(k);\n }\n if (Object.keys(rebased).length === 0 && removed.length === 0) continue;\n // w.data is also w.patch.data: rebase it in place, then show it.\n Object.assign(w.data, rebased);\n for (const k of removed) delete w.data[k];\n if (w.baseline) w.baseline = revertFields(w.baseline, undone, before);\n if (w.patch) refreshFields(reg, w.queryClient, w.patch, [...Object.keys(rebased), ...removed]);\n }\n}\n\n/**\n * After a patch's values changed in place: shows its new values for `keys`\n * in every cached copy of the record, except where a later patch on the\n * record sets the same field.\n */\nfunction refreshFields(reg: Registry, queryClient: QueryClient, patch: Patch, keys: string[]): void {\n const index = reg.patches.indexOf(patch);\n if (index < 0 || !patch.data) return;\n const data = patch.data;\n const overridden = new Set<string>();\n for (const p of reg.patches.slice(index + 1)) {\n if (p.resource === patch.resource && sameId(p.id, patch.id) && p.data) {\n for (const k of Object.keys(p.data)) overridden.add(k);\n }\n }\n const fields = keys.filter((k) => !overridden.has(k));\n if (fields.length === 0) return;\n const fix = (row: BaseRecord, hash: string): BaseRecord => {\n const out: Record<string, unknown> = { ...row };\n const before = (patch.before.get(hash) ?? firstBefore(patch))?.row;\n for (const k of fields) {\n if (k in data) out[k] = data[k];\n else if (before && k in before) out[k] = before[k];\n else delete out[k];\n }\n return out as BaseRecord;\n };\n for (const query of queryClient.getQueryCache().findAll({ queryKey: [patch.resource] })) {\n const key = query.queryKey;\n const value: unknown = query.state.data;\n let next: unknown = value;\n if (key[1] === \"list\" || key[1] === \"infinite\") {\n next = mapLists(key, query.queryHash, value, (list, listHash) => {\n const at = list.data.findIndex((row) => sameId(row.id, patch.id));\n if (at < 0) return list;\n const rows = list.data.slice();\n rows[at] = fix(rows[at]!, listHash);\n return { ...list, data: rows };\n });\n } else if (key[1] === \"detail\" && sameId(key[2], patch.id) && value && typeof value === \"object\") {\n next = fix(value as BaseRecord, query.queryHash);\n }\n if (next !== value) setProduced(reg, queryClient, key, next);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Page unload\n//\n// Waiting writes are sent when the page goes away (`beforeunload`, and\n// `pagehide` for mobile tab kills and bfcache), with `keepalive: true` in\n// their params so a fetch-based provider can let them outlive the page. The\n// \"Leave site?\" prompt shows only while a write is being sent without that\n// guarantee: its provider does not report `keepaliveWrites`, or it was\n// already on its way without keepalive.\n// ---------------------------------------------------------------------------\n\nconst allWrites = new Set<Tracked>();\n\nfunction servesKeepalive(write: Tracked): boolean {\n const caps = write.queryClient.getQueryData<ProviderCapabilities>(\n capabilitiesQueryKey(write.provider, write.resource),\n );\n return caps?.keepaliveWrites === true;\n}\n\nfunction flushForUnload(): void {\n const writes = [...allWrites].sort((a, b) => a.seq - b.seq);\n for (const write of writes) {\n if (write.state === \"waiting\" || write.state === \"queued\") write.flush(true);\n }\n}\n\nfunction onBeforeUnload(event: BeforeUnloadEvent): void {\n flushForUnload();\n const atRisk = [...allWrites].some((w) => w.state === \"sending\" && !(w.keepalive && servesKeepalive(w)));\n if (!atRisk) return;\n event.preventDefault();\n // Older browsers need returnValue set to show the prompt.\n event.returnValue = \"\";\n}\n\nfunction onPageHide(): void {\n flushForUnload();\n}\n\nfunction track(write: Tracked): void {\n allWrites.add(write);\n if (allWrites.size === 1 && typeof window !== \"undefined\") {\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n window.addEventListener(\"pagehide\", onPageHide);\n }\n}\n\nfunction untrack(write: Tracked): void {\n if (!allWrites.delete(write)) return;\n if (allWrites.size === 0 && typeof window !== \"undefined\") {\n window.removeEventListener(\"beforeunload\", onBeforeUnload);\n window.removeEventListener(\"pagehide\", onPageHide);\n }\n}\n\n// ---------------------------------------------------------------------------\n// The engine\n// ---------------------------------------------------------------------------\n\n/** What `send` is given when the write goes out. */\nexport interface SendContext {\n /**\n * The row-shaped values to send, when `data` was given. They can differ\n * from `data`: an earlier write on the record that this one built on was\n * taken back, and its values were put back. Send these.\n */\n data?: Record<string, unknown>;\n /** The page is unloading: pass `keepalive: true` to the provider. */\n keepalive: boolean;\n}\n\nexport interface ExecuteWriteArgs<R extends BaseRecord | void> {\n queryClient: QueryClient;\n provider: DataProvider;\n resource: string;\n kind: \"create\" | \"update\" | \"delete\";\n id?: Identifier;\n /**\n * The values to merge into the cached row (update). Leave out to patch\n * nothing, e.g. when the values are form-shaped and the row is not.\n */\n data?: Record<string, unknown>;\n mode: MutationMode;\n undoDelayMs?: number;\n onUndoable?: (write: UndoableWrite) => void;\n /** Sends the write. */\n send: (context: SendContext) => Promise<{ record: R; audit?: AuditEntry }>;\n /** After the write committed. Invalidates and calls success callbacks. */\n committed: (record: R) => Promise<void> | void;\n /** A write that failed (or was dropped) after the caller was told it went through. */\n failedLater: (error: Error) => void;\n}\n\nexport type ExecuteWriteResult<R> =\n | { status: \"committed\"; record: R }\n | { status: \"applied\" };\n\nconst CAPABILITIES_QUERY = (provider: DataProvider, resource: string) => ({\n // The same key as useCapabilities, so the answer is shared.\n queryKey: capabilitiesQueryKey(provider, resource),\n queryFn: () => provider.capabilities!(resource),\n staleTime: Infinity,\n // A write may wait on this answer; an app's default retries would hold\n // it for seconds.\n retry: false as const,\n});\n\n/**\n * Whether `provider` serves `undo` for `resource`, from its capabilities\n * (useCapabilities' key, so the answer is shared, and scoped to this\n * provider: a swapped-out provider's capabilities never offer undo).\n * @internal Exported for tests.\n */\nexport async function providerServesUndo(\n queryClient: QueryClient,\n provider: DataProvider,\n resource: string,\n): Promise<boolean> {\n if (!provider.undo || !provider.capabilities) return false;\n try {\n const caps = await queryClient.fetchQuery<ProviderCapabilities>(CAPABILITIES_QUERY(provider, resource));\n return caps?.undo === true;\n } catch {\n return false;\n }\n}\n\n/** A later write that built on one that was taken back. */\nexport const DROPPED_WRITE_MESSAGE =\n \"This change was not saved: it was made on top of an earlier change that was undone or failed. Please make it again.\";\n\n/**\n * Runs one write in the given mode.\n *\n * - `pessimistic`: sends it and waits; a failure rejects.\n * - `optimistic` / `undoable`: resolves `{ status: \"applied\" }` as soon as\n * the cache is patched. A later failure rolls the patch back and goes to\n * `failedLater`, never to the returned promise.\n *\n * Writes on the same record are sent one after another, in the order they\n * were made; sending one first sends the earlier ones still waiting.\n */\nexport async function executeWrite<R extends BaseRecord | void>(\n args: ExecuteWriteArgs<R>,\n): Promise<ExecuteWriteResult<R>> {\n const { queryClient, provider, resource, kind, id, mode, send, committed, failedLater } = args;\n\n if (mode === \"pessimistic\") {\n const { record } = await send({ data: args.data, keepalive: false });\n await committed(record);\n return { status: \"committed\", record };\n }\n\n const viaProviderUndo = mode === \"undoable\" && (await providerServesUndo(queryClient, provider, resource));\n // Warm the capabilities answer, so the unload handler can tell (without\n // waiting) whether this provider keeps writes alive.\n if (provider.capabilities && !queryClient.getQueryData(capabilitiesQueryKey(provider, resource))) {\n void queryClient.prefetchQuery(CAPABILITIES_QUERY(provider, resource));\n }\n\n const reg = registryFor(queryClient);\n const hasRow = kind !== \"create\" && id != null;\n const data = args.data ? { ...args.data } : undefined;\n let patch: Patch | undefined;\n if (hasRow && kind === \"update\" && data) patch = { resource, id: id!, kind: \"update\", data, before: new Map() };\n else if (hasRow && kind === \"delete\") patch = { resource, id: id!, kind: \"delete\", before: new Map() };\n // A create has no cache entry yet: its id and its place in each sorted,\n // filtered list are the server's to say.\n\n const delayMs = Math.max(0, args.undoDelayMs ?? DEFAULT_UNDO_DELAY_MS);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let resolveSent: (result: { record: R; audit?: AuditEntry } | undefined) => void = () => {};\n const sent = new Promise<{ record: R; audit?: AuditEntry } | undefined>((resolve) => {\n resolveSent = resolve;\n });\n\n const write: Tracked = {\n seq: ++nextSeq,\n queryClient,\n provider,\n resource,\n kind,\n rowKey: hasRow ? `${resource}\\u0000${String(id)}` : undefined,\n patch,\n data: kind === \"update\" ? data : undefined,\n baseline: hasRow ? cachedRecord(queryClient, resource, id!) : undefined,\n state: \"queued\",\n keepalive: false,\n dropped: false,\n flush: () => {},\n drop: () => {},\n };\n\n const settle = () => {\n write.state = \"done\";\n clearTimeout(timer);\n const at = reg.writes.indexOf(write);\n if (at >= 0) reg.writes.splice(at, 1);\n untrack(write);\n endPending(reg, resource);\n };\n\n const sendNow = async (keepalive: boolean): Promise<void> => {\n if (write.state !== \"queued\") return;\n write.state = \"sending\";\n write.keepalive = keepalive;\n let result: { record: R; audit?: AuditEntry };\n try {\n result = await send({ data: write.data, keepalive });\n } catch (error) {\n settle();\n takeBack(reg, write);\n void invalidateResource(queryClient, resource);\n failedLater(asError(error));\n resolveSent(undefined);\n return;\n }\n // The server has it now; the refetch `committed` starts replaces the patch.\n if (patch) dropPatchRecord(reg, patch);\n settle();\n try {\n await committed(result.record);\n } catch (error) {\n // The write did commit; only a callback failed.\n console.error(`[genesis] after-write callback failed for \"${resource}\":`, error);\n }\n resolveSent(result);\n };\n\n const enqueue = () => {\n if (!write.rowKey) {\n void sendNow(false);\n return;\n }\n const key = write.rowKey;\n const previous = reg.rowTails.get(key);\n const run = () => sendNow(false);\n const next = previous ? previous.then(run, run) : run();\n reg.rowTails.set(key, next);\n void next.finally(() => {\n if (reg.rowTails.get(key) === next) reg.rowTails.delete(key);\n });\n };\n\n write.flush = (unloading = false) => {\n if (write.state === \"waiting\") {\n clearTimeout(timer);\n // Earlier writes on the record go first, so the server sees them in order.\n if (!unloading) {\n for (const earlier of reg.writes) {\n if (earlier.seq < write.seq && earlier.rowKey === write.rowKey && earlier.rowKey && earlier.state === \"waiting\") {\n earlier.flush();\n }\n }\n }\n write.state = \"queued\";\n if (unloading) void sendNow(true);\n else enqueue();\n } else if (write.state === \"queued\" && unloading) {\n // Behind an earlier write on the record; the page will not wait for it.\n void sendNow(true);\n }\n };\n\n write.drop = () => {\n if (write.state !== \"waiting\" && write.state !== \"queued\") return;\n write.dropped = true;\n settle();\n takeBack(reg, write);\n void invalidateResource(queryClient, resource);\n failedLater(new Error(DROPPED_WRITE_MESSAGE));\n resolveSent(undefined);\n };\n\n beginPending(reg, resource);\n reg.writes.push(write);\n track(write);\n if (patch) addPatch(reg, queryClient, patch);\n\n if (mode === \"optimistic\") {\n enqueue();\n return { status: \"applied\" };\n }\n\n if (viaProviderUndo) {\n enqueue();\n void sent.then((result) => {\n const auditId = result?.audit?.id;\n if (!result || !args.onUndoable) return;\n if (!auditId) {\n console.warn(\n `[genesis] \"${resource}\" serves undo, but its ${kind} returned no audit entry; no undo offered.`,\n );\n return;\n }\n let undone = false;\n args.onUndoable({\n resource,\n kind,\n id: id ?? (result.record as BaseRecord | undefined)?.id,\n strategy: \"provider\",\n delayMs,\n commit: () => {},\n undo: async () => {\n if (undone) return false;\n undone = true;\n let ok = true;\n try {\n await provider.undo!(resource, { auditId });\n } catch (error) {\n ok = false;\n failedLater(asError(error));\n }\n await invalidateResource(queryClient, resource);\n return ok;\n },\n });\n });\n return { status: \"applied\" };\n }\n\n // Delayed: wait, then send unless undone or flushed first.\n write.state = \"waiting\";\n timer = setTimeout(() => write.flush(), delayMs);\n\n args.onUndoable?.({\n resource,\n kind,\n id,\n strategy: \"delay\",\n delayMs,\n commit: () => write.flush(),\n undo: async () => {\n if (write.dropped) return true;\n if (write.state !== \"waiting\") return false;\n settle();\n takeBack(reg, write);\n void invalidateResource(queryClient, resource);\n resolveSent(undefined);\n return true;\n },\n });\n\n return { status: \"applied\" };\n}\n"]}