@marktiderman/genesis-core 1.3.0 → 1.5.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 (65) hide show
  1. package/README.md +223 -0
  2. package/dist/chunk-5NHR6ZXS.js +31 -0
  3. package/dist/chunk-5NHR6ZXS.js.map +1 -0
  4. package/dist/chunk-7NUL32W7.js +14 -0
  5. package/dist/chunk-7NUL32W7.js.map +1 -0
  6. package/dist/chunk-BQOJX67X.js +31 -0
  7. package/dist/chunk-BQOJX67X.js.map +1 -0
  8. package/dist/{chunk-QFTMHOEH.js → chunk-DVW5GXWZ.js} +84 -31
  9. package/dist/chunk-DVW5GXWZ.js.map +1 -0
  10. package/dist/{chunk-6V2TIN3G.js → chunk-GGSXSI6Z.js} +6 -5
  11. package/dist/chunk-GGSXSI6Z.js.map +1 -0
  12. package/dist/chunk-HT43MPKW.js +3 -0
  13. package/dist/{chunk-EL7YGOTY.js.map → chunk-HT43MPKW.js.map} +1 -1
  14. package/dist/chunk-HUNGCTRA.js +33 -0
  15. package/dist/chunk-HUNGCTRA.js.map +1 -0
  16. package/dist/{chunk-5FTUB2M6.js → chunk-IZT2O57C.js} +160 -40
  17. package/dist/chunk-IZT2O57C.js.map +1 -0
  18. package/dist/chunk-QH4P5MI2.js +278 -0
  19. package/dist/chunk-QH4P5MI2.js.map +1 -0
  20. package/dist/chunk-RLY7TZUY.js +444 -0
  21. package/dist/chunk-RLY7TZUY.js.map +1 -0
  22. package/dist/chunk-XQO4OH2N.js +32 -0
  23. package/dist/chunk-XQO4OH2N.js.map +1 -0
  24. package/dist/{chunk-3P4PHPMJ.js → chunk-ZK5C3OOT.js} +3 -3
  25. package/dist/{chunk-3P4PHPMJ.js.map → chunk-ZK5C3OOT.js.map} +1 -1
  26. package/dist/form-types.d.ts +6 -0
  27. package/dist/hooks/index.d.ts +5 -1
  28. package/dist/hooks/index.js +11 -6
  29. package/dist/hooks/mutation-mode.d.ts +109 -0
  30. package/dist/hooks/mutation-mode.js +3 -0
  31. package/dist/hooks/mutation-mode.js.map +1 -0
  32. package/dist/hooks/related-keys.d.ts +12 -0
  33. package/dist/hooks/related-keys.js +3 -0
  34. package/dist/hooks/related-keys.js.map +1 -0
  35. package/dist/hooks/use-capabilities.d.ts +21 -0
  36. package/dist/hooks/use-capabilities.js +5 -0
  37. package/dist/hooks/use-capabilities.js.map +1 -0
  38. package/dist/hooks/use-one.d.ts +2 -1
  39. package/dist/hooks/use-one.js +2 -2
  40. package/dist/hooks/use-resource-form.d.ts +53 -2
  41. package/dist/hooks/use-resource-form.js +7 -3
  42. package/dist/hooks/use-resource.d.ts +42 -5
  43. package/dist/hooks/use-resource.js +4 -2
  44. package/dist/hooks/use-unsaved-changes-guard.d.ts +33 -0
  45. package/dist/hooks/use-unsaved-changes-guard.js +3 -0
  46. package/dist/hooks/use-unsaved-changes-guard.js.map +1 -0
  47. package/dist/index.d.ts +7 -3
  48. package/dist/index.js +14 -9
  49. package/dist/provider/index.d.ts +212 -6
  50. package/dist/provider/index.js +5 -3
  51. package/dist/read-outcome-BQkYcV4t.d.ts +88 -0
  52. package/dist/schema/index.d.ts +4 -3
  53. package/dist/schema/index.js +2 -1
  54. package/dist/{types-DUbRb8qV.d.ts → types-C5bjsbxz.d.ts} +13 -255
  55. package/dist/types-Dsqqr9s7.d.ts +281 -0
  56. package/package.json +2 -2
  57. package/dist/chunk-4W4W7F4I.js +0 -20
  58. package/dist/chunk-4W4W7F4I.js.map +0 -1
  59. package/dist/chunk-5FTUB2M6.js.map +0 -1
  60. package/dist/chunk-6AA4ISUA.js +0 -125
  61. package/dist/chunk-6AA4ISUA.js.map +0 -1
  62. package/dist/chunk-6V2TIN3G.js.map +0 -1
  63. package/dist/chunk-EL7YGOTY.js +0 -3
  64. package/dist/chunk-QFTMHOEH.js.map +0 -1
  65. package/dist/write-outcome-aYToY3dK.d.ts +0 -38
@@ -0,0 +1,444 @@
1
+ import { DataReadError, readFailureMessage } from './chunk-HUNGCTRA.js';
2
+ import { isDataWriteError, DataWriteError } from './chunk-5NHR6ZXS.js';
3
+ import { titleCase } from './chunk-RYEXWQFD.js';
4
+
5
+ // src/provider/postgrest-errors.ts
6
+ function readOutcomeFromCode(code) {
7
+ if (!code) return "error";
8
+ if (code === "42703" || code === "42P01" || /^PGRST1\d\d$/.test(code) && code !== "PGRST116") {
9
+ return "invalid";
10
+ }
11
+ const write = writeOutcomeFromCode(code);
12
+ return write === "forbidden" || write === "gone" || write === "unauthenticated" || write === "invalid" ? write : "error";
13
+ }
14
+ function writeOutcomeFromCode(code) {
15
+ if (!code) return "error";
16
+ if (code === "42501") return "forbidden";
17
+ if (code === "PGRST116") return "gone";
18
+ if (code === "PGRST301" || code === "PGRST302" || code === "PGRST303") return "unauthenticated";
19
+ if (code === "28000" || code === "28P01") return "unauthenticated";
20
+ if (/^2[23][0-9A-Z]{3}$/.test(code)) return "invalid";
21
+ return "error";
22
+ }
23
+ var COLUMN = /^[A-Za-z_][A-Za-z0-9_]*$/;
24
+ function keyColumns(details, tail) {
25
+ const match = /^Key \(([^()]*)\)=\(/.exec(details);
26
+ if (!match || !tail.test(details)) return [];
27
+ const columns = match[1].split(",").map((c) => c.trim());
28
+ return columns.length > 0 && columns.every((c) => COLUMN.test(c)) ? columns : [];
29
+ }
30
+ function listLabels(columns) {
31
+ const labels = columns.map((c) => titleCase(c));
32
+ if (labels.length === 1) return labels[0];
33
+ return `${labels.slice(0, -1).join(", ")} and ${labels[labels.length - 1]}`;
34
+ }
35
+ function fieldErrorsFromPostgrest(error) {
36
+ const code = typeof error.code === "string" ? error.code : void 0;
37
+ const details = typeof error.details === "string" ? error.details.trim() : "";
38
+ const message = typeof error.message === "string" ? error.message : "";
39
+ if (code === "23505") {
40
+ const columns = keyColumns(details, /\) already exists\.?$/);
41
+ if (columns.length === 0) return void 0;
42
+ const each = columns.length === 1 ? "This value is already in use." : "This combination is already in use.";
43
+ return {
44
+ fields: Object.fromEntries(columns.map((c) => [c, each])),
45
+ message: columns.length === 1 ? `${listLabels(columns)} is already in use.` : `This combination of ${listLabels(columns)} is already in use.`
46
+ };
47
+ }
48
+ if (code === "23502") {
49
+ const column = /^null value in column "([A-Za-z_][A-Za-z0-9_]*)"(?: |$)/.exec(message)?.[1];
50
+ if (!column || !COLUMN.test(column)) return void 0;
51
+ return {
52
+ fields: { [column]: "This field is required." },
53
+ message: `${titleCase(column)} is required.`
54
+ };
55
+ }
56
+ if (code === "23503") {
57
+ const columns = keyColumns(details, /\) is not present in table "[^"]*"\.?$/);
58
+ if (columns.length === 0) return void 0;
59
+ return {
60
+ fields: Object.fromEntries(columns.map((c) => [c, "The selected item doesn't exist."])),
61
+ message: `${listLabels(columns)}: the selected item doesn't exist.`
62
+ };
63
+ }
64
+ return void 0;
65
+ }
66
+ function toDataWriteError(error) {
67
+ if (isDataWriteError(error)) return error;
68
+ const shape = typeof error === "object" && error !== null ? error : {};
69
+ const code = typeof shape.code === "string" ? shape.code : void 0;
70
+ const mapped = fieldErrorsFromPostgrest(shape);
71
+ if (mapped) {
72
+ return new DataWriteError(writeOutcomeFromCode(code), mapped.message, {
73
+ code,
74
+ fields: mapped.fields,
75
+ cause: error
76
+ });
77
+ }
78
+ const message = typeof shape.message === "string" && shape.message ? shape.message : error instanceof Error ? error.message : String(error);
79
+ return new DataWriteError(writeOutcomeFromCode(code), message, { code, cause: error });
80
+ }
81
+
82
+ // src/provider/supabase-provider.ts
83
+ function isMissingColumn(error, column) {
84
+ if (!error || typeof error !== "object") return false;
85
+ const { code, message } = error;
86
+ if (code !== "42703" || typeof message !== "string") return false;
87
+ return new RegExp(`(^|[^A-Za-z0-9_])${column}([^A-Za-z0-9_]|$)`).test(message);
88
+ }
89
+ var SERVED_OPERATORS = [
90
+ "eq",
91
+ "neq",
92
+ "gt",
93
+ "lt",
94
+ "gte",
95
+ "lte",
96
+ "contains",
97
+ "in",
98
+ "between",
99
+ "is_null",
100
+ "is_not_null"
101
+ ];
102
+ function escapeIlike(value) {
103
+ return value.replace(/[%_\\]/g, (char) => `\\${char}`);
104
+ }
105
+ function isIdentifier(value) {
106
+ return typeof value === "string" || typeof value === "number";
107
+ }
108
+ function createSupabaseProvider(client, options = {}) {
109
+ function applyFilters(query, filters) {
110
+ if (!filters) return query;
111
+ for (const f of filters) {
112
+ switch (f.operator) {
113
+ case "eq":
114
+ query = query.eq(f.field, f.value);
115
+ break;
116
+ case "neq":
117
+ query = query.neq(f.field, f.value);
118
+ break;
119
+ case "gt":
120
+ query = query.gt(f.field, f.value);
121
+ break;
122
+ case "lt":
123
+ query = query.lt(f.field, f.value);
124
+ break;
125
+ case "gte":
126
+ query = query.gte(f.field, f.value);
127
+ break;
128
+ case "lte":
129
+ query = query.lte(f.field, f.value);
130
+ break;
131
+ case "contains":
132
+ query = query.ilike(f.field, `%${escapeIlike(String(f.value))}%`);
133
+ break;
134
+ case "in":
135
+ query = query.in(f.field, f.value);
136
+ break;
137
+ case "is_null":
138
+ query = query.is(f.field, null);
139
+ break;
140
+ case "is_not_null":
141
+ query = query.not(f.field, "is", null);
142
+ break;
143
+ case "between": {
144
+ const [lo, hi] = f.value;
145
+ query = query.gte(f.field, lo).lte(f.field, hi);
146
+ break;
147
+ }
148
+ default:
149
+ throw new Error(`[SupabaseProvider] Unknown filter operator "${f.operator}"`);
150
+ }
151
+ }
152
+ return query;
153
+ }
154
+ const maxPerPage = options.maxPerPage ?? 1e3;
155
+ const keylessResources = /* @__PURE__ */ new Set();
156
+ async function reportReadError(error, resource, operation) {
157
+ try {
158
+ await Promise.resolve(options.onReadError?.(error, { resource, operation }));
159
+ } catch {
160
+ }
161
+ throw error;
162
+ }
163
+ async function throwIfReadError(result, resource, operation) {
164
+ if (!result.error) return;
165
+ const raw = result.error;
166
+ const code = typeof raw.code === "string" ? raw.code : void 0;
167
+ const outcome = readOutcomeFromCode(code);
168
+ const error = new DataReadError(
169
+ outcome,
170
+ readFailureMessage(outcome, nounFor(resource), operation === "getOne", configFor(resource).nounPlural),
171
+ { code, cause: result.error }
172
+ );
173
+ await reportReadError(error, resource, operation);
174
+ }
175
+ const from = (resource) => client.from(resource);
176
+ const configFor = (resource) => options.resources?.[resource] ?? {};
177
+ const nounFor = (resource) => configFor(resource).noun ?? "record";
178
+ function isBinTable(resource) {
179
+ const mode = configFor(resource).deleteMode;
180
+ if (mode) return mode === "bin";
181
+ const bin = options.binTables;
182
+ if (!bin) return false;
183
+ return typeof bin === "function" ? bin(resource) : bin.includes(resource);
184
+ }
185
+ async function run(query) {
186
+ let result;
187
+ try {
188
+ result = await query;
189
+ } catch (error) {
190
+ throw toDataWriteError(error);
191
+ }
192
+ if (result.error) throw toDataWriteError(result.error);
193
+ if (Array.isArray(result.data)) return result.data;
194
+ return result.data == null ? [] : [result.data];
195
+ }
196
+ async function isVisible(resource, id) {
197
+ return (await run(from(resource).select("id").eq("id", id))).length > 0;
198
+ }
199
+ async function readBack(resource, id) {
200
+ return (await run(from(resource).select("*").eq("id", id)))[0];
201
+ }
202
+ function goneError(resource) {
203
+ return new DataWriteError(
204
+ "gone",
205
+ `This ${nounFor(resource)} no longer exists, or you can no longer see it.`
206
+ );
207
+ }
208
+ async function noRowsError(resource, id, verb) {
209
+ let visible;
210
+ try {
211
+ visible = await isVisible(resource, id);
212
+ } catch (error) {
213
+ const cause = toDataWriteError(error);
214
+ return new DataWriteError(
215
+ cause.outcome === "unauthenticated" ? "unauthenticated" : "error",
216
+ `This ${nounFor(resource)} was not ${verb === "change" ? "changed" : "deleted"}, and reading it back failed (${cause.message}).`,
217
+ { code: cause.code, cause: error }
218
+ );
219
+ }
220
+ if (visible) {
221
+ return new DataWriteError("forbidden", `You can't ${verb} this ${nounFor(resource)}.`);
222
+ }
223
+ return goneError(resource);
224
+ }
225
+ async function prepare(resource, values, context) {
226
+ const rule = configFor(resource).prepare;
227
+ if (!rule) return values;
228
+ try {
229
+ return await rule({ ...values }, context);
230
+ } catch (error) {
231
+ throw toDataWriteError(error);
232
+ }
233
+ }
234
+ async function committed(event) {
235
+ if (!options.onWrite) return;
236
+ try {
237
+ await options.onWrite(event);
238
+ } catch (error) {
239
+ console.error(`[SupabaseProvider] onWrite failed for "${event.resource}":`, error);
240
+ }
241
+ }
242
+ const provider = {
243
+ async getList(resource, params) {
244
+ if (params.pagination && params.pagination.perPage > maxPerPage) {
245
+ const { perPage } = params.pagination;
246
+ await reportReadError(
247
+ new DataReadError(
248
+ "invalid",
249
+ readFailureMessage("invalid", nounFor(resource), false, configFor(resource).nounPlural),
250
+ {
251
+ cause: new Error(
252
+ `[SupabaseProvider] "${resource}" asked for ${perPage} rows per page; the most is ${maxPerPage} (maxPerPage). Ask for at most ${maxPerPage} and page through the rest.`
253
+ )
254
+ }
255
+ ),
256
+ resource,
257
+ "getList"
258
+ );
259
+ }
260
+ const configuredKey = configFor(resource).orderKey;
261
+ const run2 = (key) => {
262
+ let query = client.from(resource).select(
263
+ params.select ?? "*",
264
+ { count: "exact" }
265
+ );
266
+ query = applyFilters(query, params.filters);
267
+ const searchFields = configFor(resource).searchFields ?? params.search?.fields ?? [];
268
+ if (params.search && params.search.query && searchFields.length > 0) {
269
+ const escaped = escapeIlike(params.search.query);
270
+ const safeFieldPattern = /^[a-zA-Z_][a-zA-Z0-9_.]*$/;
271
+ const clauses = searchFields.filter((f) => safeFieldPattern.test(f)).map((f) => `${f}.ilike.%${escaped}%`).join(",");
272
+ if (clauses) {
273
+ query = query.or(clauses);
274
+ }
275
+ }
276
+ if (params.sort) {
277
+ query = query.order(params.sort.field, {
278
+ ascending: params.sort.order === "asc"
279
+ });
280
+ if (key && key !== params.sort.field) query = query.order(key, { ascending: true });
281
+ } else if (params.pagination && key) {
282
+ query = query.order(key, { ascending: true });
283
+ }
284
+ if (params.pagination) {
285
+ const { page, perPage } = params.pagination;
286
+ const from2 = (page - 1) * perPage;
287
+ const to = from2 + perPage - 1;
288
+ query = query.range(from2, to);
289
+ }
290
+ return query;
291
+ };
292
+ const guessKey = configuredKey === void 0 && !keylessResources.has(resource);
293
+ let result = await run2(configuredKey ?? (keylessResources.has(resource) ? false : "id"));
294
+ if (guessKey && isMissingColumn(result.error, "id")) {
295
+ keylessResources.add(resource);
296
+ if (process.env.NODE_ENV !== "production") {
297
+ console.warn(
298
+ `[SupabaseProvider] "${resource}" has no "id" column, so its pages have no stable order. Set resources.${resource}.orderKey to a unique column (or false to silence this).`
299
+ );
300
+ }
301
+ result = await run2(false);
302
+ }
303
+ await throwIfReadError(result, resource, "getList");
304
+ return { data: result.data ?? [], total: result.count ?? 0 };
305
+ },
306
+ async getOne(resource, params) {
307
+ const result = await client.from(resource).select(params.select ?? "*").eq("id", params.id).single();
308
+ await throwIfReadError(result, resource, "getOne");
309
+ return { data: result.data };
310
+ },
311
+ async getMany(resource, params) {
312
+ if (params.ids.length === 0) return { data: [] };
313
+ const result = await client.from(resource).select("*").in("id", params.ids);
314
+ await throwIfReadError(result, resource, "getMany");
315
+ return { data: result.data ?? [] };
316
+ },
317
+ async capabilities(resource) {
318
+ return {
319
+ search: resource ? configFor(resource).searchFields?.length !== 0 : true,
320
+ sort: true,
321
+ filterOperators: SERVED_OPERATORS,
322
+ bulkWrites: false,
323
+ versionedWrites: false,
324
+ compositeKeys: false,
325
+ multiSort: false,
326
+ create: true,
327
+ delete: true,
328
+ maxPerPage,
329
+ softDelete: resource ? isBinTable(resource) : false
330
+ };
331
+ },
332
+ async create(resource, params) {
333
+ const row = await prepare(resource, params.data, {
334
+ resource,
335
+ action: "create"
336
+ });
337
+ const override = configFor(resource).create;
338
+ let record;
339
+ if (override) {
340
+ let returned;
341
+ try {
342
+ returned = await override(row);
343
+ } catch (error) {
344
+ throw toDataWriteError(error);
345
+ }
346
+ if (isIdentifier(returned)) {
347
+ record = await readBack(resource, returned).catch(() => void 0) ?? { id: returned };
348
+ } else if (returned && typeof returned === "object" && isIdentifier(returned.id)) {
349
+ record = returned;
350
+ } else {
351
+ throw new DataWriteError(
352
+ "error",
353
+ `[SupabaseProvider] The create rule for "${resource}" returned neither the new row nor its id.`
354
+ );
355
+ }
356
+ } else {
357
+ const rows = await run(from(resource).insert(row).select("*").single());
358
+ record = rows[0];
359
+ }
360
+ await committed({ resource, kind: "create", id: record.id, record });
361
+ return { data: record };
362
+ },
363
+ async update(resource, params) {
364
+ const { id } = params;
365
+ const row = await prepare(resource, params.data, {
366
+ resource,
367
+ action: "update",
368
+ id
369
+ });
370
+ const override = configFor(resource).update;
371
+ let record;
372
+ if (override) {
373
+ let returned;
374
+ try {
375
+ returned = await override(id, row);
376
+ } catch (error) {
377
+ throw toDataWriteError(error);
378
+ }
379
+ const result = returned;
380
+ if (result == null || result === false) throw await noRowsError(resource, id, "change");
381
+ if (isIdentifier(result) || result === true) {
382
+ const readId = result === true ? id : result;
383
+ record = await readBack(resource, readId).catch(() => void 0) ?? { ...row, id: readId };
384
+ } else if (typeof result === "object") {
385
+ record = { id, ...result };
386
+ } else {
387
+ throw new DataWriteError(
388
+ "error",
389
+ `[SupabaseProvider] The update rule for "${resource}" returned neither the row, its id, nor nothing.`
390
+ );
391
+ }
392
+ } else {
393
+ const rows = await run(from(resource).update(row).eq("id", id).select("*"));
394
+ if (rows.length === 0) throw await noRowsError(resource, id, "change");
395
+ record = rows[0];
396
+ }
397
+ await committed({ resource, kind: "update", id, record });
398
+ return { data: record };
399
+ },
400
+ async deleteOne(resource, params) {
401
+ const { id } = params;
402
+ let deletedId = id;
403
+ if (isBinTable(resource)) {
404
+ if (!await isVisible(resource, id)) throw goneError(resource);
405
+ await run(from(resource).delete().eq("id", id));
406
+ let stillVisible;
407
+ try {
408
+ stillVisible = await isVisible(resource, id);
409
+ } catch (error) {
410
+ const cause = toDataWriteError(error);
411
+ throw new DataWriteError(
412
+ "error",
413
+ `The ${nounFor(resource)} may not have been deleted: checking it failed (${cause.message}).`,
414
+ { code: cause.code, cause: error }
415
+ );
416
+ }
417
+ if (stillVisible) {
418
+ throw new DataWriteError("forbidden", `You can't delete this ${nounFor(resource)}.`);
419
+ }
420
+ } else {
421
+ const rows = await run(from(resource).delete().eq("id", id).select("id"));
422
+ if (rows.length === 0) throw await noRowsError(resource, id, "delete");
423
+ const returnedId = rows[0].id;
424
+ if (isIdentifier(returnedId)) deletedId = returnedId;
425
+ }
426
+ await committed({ resource, kind: "delete", id: deletedId });
427
+ return { data: { id: deletedId } };
428
+ },
429
+ toFormValues(resource, record) {
430
+ const map = configFor(resource).toFormValues;
431
+ return map ? map(record) : record;
432
+ },
433
+ invalidates(event) {
434
+ const keys = configFor(event.resource).invalidates;
435
+ if (!keys) return [];
436
+ return typeof keys === "function" ? keys(event) : keys;
437
+ }
438
+ };
439
+ return provider;
440
+ }
441
+
442
+ export { createSupabaseProvider, fieldErrorsFromPostgrest, readOutcomeFromCode, toDataWriteError, writeOutcomeFromCode };
443
+ //# sourceMappingURL=chunk-RLY7TZUY.js.map
444
+ //# sourceMappingURL=chunk-RLY7TZUY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider/postgrest-errors.ts","../src/provider/supabase-provider.ts"],"names":["run","from"],"mappings":";;;;;AAiBO,SAAS,oBAAoB,IAAA,EAA8C;AAChF,EAAA,IAAI,CAAC,MAAM,OAAO,OAAA;AAClB,EAAA,IAAI,IAAA,KAAS,WAAW,IAAA,KAAS,OAAA,IAAY,eAAe,IAAA,CAAK,IAAI,CAAA,IAAK,IAAA,KAAS,UAAA,EAAa;AAC9F,IAAA,OAAO,SAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GAAQ,qBAAqB,IAAI,CAAA;AACvC,EAAA,OAAO,KAAA,KAAU,eAAe,KAAA,KAAU,MAAA,IAAU,UAAU,iBAAA,IAAqB,KAAA,KAAU,YACzF,KAAA,GACA,OAAA;AACN;AAgBO,SAAS,qBAAqB,IAAA,EAA+C;AAClF,EAAA,IAAI,CAAC,MAAM,OAAO,OAAA;AAClB,EAAA,IAAI,IAAA,KAAS,SAAS,OAAO,WAAA;AAC7B,EAAA,IAAI,IAAA,KAAS,YAAY,OAAO,MAAA;AAChC,EAAA,IAAI,SAAS,UAAA,IAAc,IAAA,KAAS,UAAA,IAAc,IAAA,KAAS,YAAY,OAAO,iBAAA;AAC9E,EAAA,IAAI,IAAA,KAAS,OAAA,IAAW,IAAA,KAAS,OAAA,EAAS,OAAO,iBAAA;AACjD,EAAA,IAAI,oBAAA,CAAqB,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,SAAA;AAC5C,EAAA,OAAO,OAAA;AACT;AAGA,IAAM,MAAA,GAAS,0BAAA;AAGf,SAAS,UAAA,CAAW,SAAiB,IAAA,EAAwB;AAC3D,EAAA,MAAM,KAAA,GAAQ,sBAAA,CAAuB,IAAA,CAAK,OAAO,CAAA;AACjD,EAAA,IAAI,CAAC,SAAS,CAAC,IAAA,CAAK,KAAK,OAAO,CAAA,SAAU,EAAC;AAC3C,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,CAAC,CAAA,CAAG,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,EAAM,CAAA;AACxD,EAAA,OAAO,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,MAAA,CAAO,IAAA,CAAK,CAAC,CAAC,CAAA,GAAI,UAAU,EAAC;AACjF;AAEA,SAAS,WAAW,OAAA,EAA2B;AAC7C,EAAA,MAAM,SAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,SAAA,CAAU,CAAC,CAAC,CAAA;AAC9C,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,OAAO,CAAC,CAAA;AACxC,EAAA,OAAO,CAAA,EAAG,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,KAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,MAAA,GAAS,CAAC,CAAC,CAAA,CAAA;AAC3E;AAwBO,SAAS,yBAAyB,KAAA,EAIJ;AACnC,EAAA,MAAM,OAAO,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,GAAW,MAAM,IAAA,GAAO,MAAA;AAC3D,EAAA,MAAM,OAAA,GAAU,OAAO,KAAA,CAAM,OAAA,KAAY,WAAW,KAAA,CAAM,OAAA,CAAQ,MAAK,GAAI,EAAA;AAC3E,EAAA,MAAM,UAAU,OAAO,KAAA,CAAM,OAAA,KAAY,QAAA,GAAW,MAAM,OAAA,GAAU,EAAA;AACpE,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAS,uBAAuB,CAAA;AAC3D,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AACjC,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,MAAA,KAAW,CAAA,GAAI,+BAAA,GAAkC,qCAAA;AACtE,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,MAAA,CAAO,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,EAAG,IAAI,CAAC,CAAC,CAAA;AAAA,MACxD,OAAA,EACE,OAAA,CAAQ,MAAA,KAAW,CAAA,GACf,CAAA,EAAG,UAAA,CAAW,OAAO,CAAC,CAAA,mBAAA,CAAA,GACtB,CAAA,oBAAA,EAAuB,UAAA,CAAW,OAAO,CAAC,CAAA,mBAAA;AAAA,KAClD;AAAA,EACF;AACA,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,MAAM,MAAA,GAAS,yDAAA,CAA0D,IAAA,CAAK,OAAO,IAAI,CAAC,CAAA;AAC1F,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAO,IAAA,CAAK,MAAM,GAAG,OAAO,MAAA;AAC5C,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,EAAE,CAAC,MAAM,GAAG,yBAAA,EAA0B;AAAA,MAC9C,OAAA,EAAS,CAAA,EAAG,SAAA,CAAU,MAAM,CAAC,CAAA,aAAA;AAAA,KAC/B;AAAA,EACF;AACA,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAS,wCAAwC,CAAA;AAC5E,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AACjC,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,MAAA,CAAO,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,EAAG,kCAAkC,CAAC,CAAC,CAAA;AAAA,MACtF,OAAA,EAAS,CAAA,EAAG,UAAA,CAAW,OAAO,CAAC,CAAA,kCAAA;AAAA,KACjC;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAaO,SAAS,iBAAiB,KAAA,EAAgC;AAC/D,EAAA,IAAI,gBAAA,CAAiB,KAAK,CAAA,EAAG,OAAO,KAAA;AACpC,EAAA,MAAM,QAAS,OAAO,KAAA,KAAU,YAAY,KAAA,KAAU,IAAA,GAAO,QAAQ,EAAC;AAKtE,EAAA,MAAM,OAAO,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,GAAW,MAAM,IAAA,GAAO,MAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,yBAAyB,KAAK,CAAA;AAC7C,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,OAAO,IAAI,cAAA,CAAe,oBAAA,CAAqB,IAAI,CAAA,EAAG,OAAO,OAAA,EAAS;AAAA,MACpE,IAAA;AAAA,MACA,QAAQ,MAAA,CAAO,MAAA;AAAA,MACf,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,MAAM,OAAA,GACJ,OAAO,KAAA,CAAM,OAAA,KAAY,YAAY,KAAA,CAAM,OAAA,GACvC,KAAA,CAAM,OAAA,GACN,KAAA,YAAiB,KAAA,GACf,KAAA,CAAM,OAAA,GACN,OAAO,KAAK,CAAA;AACpB,EAAA,OAAO,IAAI,cAAA,CAAe,oBAAA,CAAqB,IAAI,CAAA,EAAG,SAAS,EAAE,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,CAAA;AACvF;;;ACyBA,SAAS,eAAA,CAAgB,OAAgB,MAAA,EAAyB;AAChE,EAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AAChD,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAQ,GAAI,KAAA;AAC1B,EAAA,IAAI,IAAA,KAAS,OAAA,IAAW,OAAO,OAAA,KAAY,UAAU,OAAO,KAAA;AAC5D,EAAA,OAAO,IAAI,MAAA,CAAO,CAAA,iBAAA,EAAoB,MAAM,CAAA,iBAAA,CAAmB,CAAA,CAAE,KAAK,OAAO,CAAA;AAC/E;AAEA,IAAM,gBAAA,GAA8C;AAAA,EAClD,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,UAAA;AAAA,EAAY,IAAA;AAAA,EAAM,SAAA;AAAA,EAAW,SAAA;AAAA,EAAW;AACjF,CAAA;AAEA,SAAS,YAAY,KAAA,EAAuB;AAC1C,EAAA,OAAO,MAAM,OAAA,CAAQ,SAAA,EAAW,CAAC,IAAA,KAAS,CAAA,EAAA,EAAK,IAAI,CAAA,CAAE,CAAA;AACvD;AAEA,SAAS,aAAa,KAAA,EAAqC;AACzD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,KAAA,KAAU,QAAA;AACvD;AAmBO,SAAS,sBAAA,CACd,MAAA,EACA,OAAA,GAAmC,EAAC,EACtB;AAKd,EAAA,SAAS,YAAA,CACP,OACA,OAAA,EACsB;AACtB,IAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AACrB,IAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,MAAA,QAAQ,EAAE,QAAA;AAAU,QAClB,KAAK,IAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,EAAA,CAAG,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AACjC,UAAA;AAAA,QACF,KAAK,KAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AAClC,UAAA;AAAA,QACF,KAAK,IAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,EAAA,CAAG,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AACjC,UAAA;AAAA,QACF,KAAK,IAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,EAAA,CAAG,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AACjC,UAAA;AAAA,QACF,KAAK,KAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AAClC,UAAA;AAAA,QACF,KAAK,KAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AAClC,UAAA;AAAA,QACF,KAAK,UAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,CAAE,KAAA,EAAO,CAAA,CAAA,EAAI,WAAA,CAAY,MAAA,CAAO,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA;AAChE,UAAA;AAAA,QACF,KAAK,IAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,EAAA,CAAG,CAAA,CAAE,KAAA,EAAO,EAAE,KAAK,CAAA;AACjC,UAAA;AAAA,QACF,KAAK,SAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,EAAA,CAAG,CAAA,CAAE,KAAA,EAAO,IAAI,CAAA;AAC9B,UAAA;AAAA,QACF,KAAK,aAAA;AACH,UAAA,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,MAAM,IAAI,CAAA;AACrC,UAAA;AAAA,QACF,KAAK,SAAA,EAAW;AACd,UAAA,MAAM,CAAC,EAAA,EAAI,EAAE,CAAA,GAAI,CAAA,CAAE,KAAA;AACnB,UAAA,KAAA,GAAQ,KAAA,CAAM,IAAI,CAAA,CAAE,KAAA,EAAO,EAAE,CAAA,CAAE,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,EAAE,CAAA;AAC9C,UAAA;AAAA,QACF;AAAA,QACA;AACE,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4CAAA,EAAgD,CAAA,CAAkB,QAAQ,CAAA,CAAA,CAAG,CAAA;AAC7F;AACJ,IACF;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,GAAA;AAEzC,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAY;AAGzC,EAAA,eAAe,eAAA,CACb,KAAA,EACA,QAAA,EACA,SAAA,EACgB;AAChB,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,CAAQ,QAAQ,OAAA,CAAQ,WAAA,GAAc,OAAO,EAAE,QAAA,EAAU,SAAA,EAAW,CAAC,CAAA;AAAA,IAC7E,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,MAAM,KAAA;AAAA,EACR;AAGA,EAAA,eAAe,gBAAA,CACb,MAAA,EACA,QAAA,EACA,SAAA,EACe;AACf,IAAA,IAAI,CAAC,OAAO,KAAA,EAAO;AACnB,IAAA,MAAM,MAAM,MAAA,CAAO,KAAA;AACnB,IAAA,MAAM,OAAO,OAAO,GAAA,CAAI,IAAA,KAAS,QAAA,GAAW,IAAI,IAAA,GAAO,MAAA;AACvD,IAAA,MAAM,OAAA,GAAU,oBAAoB,IAAI,CAAA;AACxC,IAAA,MAAM,QAAQ,IAAI,aAAA;AAAA,MAChB,OAAA;AAAA,MACA,kBAAA,CAAmB,OAAA,EAAS,OAAA,CAAQ,QAAQ,CAAA,EAAG,cAAc,QAAA,EAAU,SAAA,CAAU,QAAQ,CAAA,CAAE,UAAU,CAAA;AAAA,MACrG,EAAE,IAAA,EAAM,KAAA,EAAO,MAAA,CAAO,KAAA;AAAM,KAC9B;AACA,IAAA,MAAM,eAAA,CAAgB,KAAA,EAAO,QAAA,EAAU,SAAS,CAAA;AAAA,EAClD;AAMA,EAAA,MAAM,IAAA,GAAO,CAAC,QAAA,KAAqB,MAAA,CAAO,KAAK,QAAQ,CAAA;AACvD,EAAA,MAAM,YAAY,CAAC,QAAA,KACjB,QAAQ,SAAA,GAAY,QAAQ,KAAK,EAAC;AACpC,EAAA,MAAM,UAAU,CAAC,QAAA,KAAqB,SAAA,CAAU,QAAQ,EAAE,IAAA,IAAQ,QAAA;AAElE,EAAA,SAAS,WAAW,QAAA,EAA2B;AAC7C,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,QAAQ,CAAA,CAAE,UAAA;AACjC,IAAA,IAAI,IAAA,SAAa,IAAA,KAAS,KAAA;AAC1B,IAAA,MAAM,MAAM,OAAA,CAAQ,SAAA;AACpB,IAAA,IAAI,CAAC,KAAK,OAAO,KAAA;AACjB,IAAA,OAAO,OAAO,QAAQ,UAAA,GAAa,GAAA,CAAI,QAAQ,CAAA,GAAI,GAAA,CAAI,SAAS,QAAQ,CAAA;AAAA,EAC1E;AAGA,EAAA,eAAe,IAAI,KAAA,EAAiD;AAClE,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAA,GAAU,MAAM,KAAA;AAAA,IAClB,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,iBAAiB,KAAK,CAAA;AAAA,IAC9B;AACA,IAAA,IAAI,MAAA,CAAO,KAAA,EAAO,MAAM,gBAAA,CAAiB,OAAO,KAAK,CAAA;AACrD,IAAA,IAAI,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAA,SAAU,MAAA,CAAO,IAAA;AAC9C,IAAA,OAAO,OAAO,IAAA,IAAQ,IAAA,GAAO,EAAC,GAAI,CAAC,OAAO,IAAI,CAAA;AAAA,EAChD;AAEA,EAAA,eAAe,SAAA,CAAU,UAAkB,EAAA,EAAkC;AAC3E,IAAA,OAAA,CAAQ,MAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,CAAO,IAAI,CAAA,CAAE,EAAA,CAAG,IAAA,EAAM,EAAE,CAAC,GAAG,MAAA,GAAS,CAAA;AAAA,EACxE;AAEA,EAAA,eAAe,QAAA,CAAS,UAAkB,EAAA,EAAiD;AACzF,IAAA,OAAA,CAAQ,MAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,EAAA,CAAG,IAAA,EAAM,EAAE,CAAC,GAAG,CAAC,CAAA;AAAA,EAC/D;AAEA,EAAA,SAAS,UAAU,QAAA,EAAkC;AACnD,IAAA,OAAO,IAAI,cAAA;AAAA,MACT,MAAA;AAAA,MACA,CAAA,KAAA,EAAQ,OAAA,CAAQ,QAAQ,CAAC,CAAA,+CAAA;AAAA,KAC3B;AAAA,EACF;AAOA,EAAA,eAAe,WAAA,CACb,QAAA,EACA,EAAA,EACA,IAAA,EACyB;AACzB,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,MAAM,SAAA,CAAU,QAAA,EAAU,EAAE,CAAA;AAAA,IACxC,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,KAAA,GAAQ,iBAAiB,KAAK,CAAA;AACpC,MAAA,OAAO,IAAI,cAAA;AAAA,QACT,KAAA,CAAM,OAAA,KAAY,iBAAA,GAAoB,iBAAA,GAAoB,OAAA;AAAA,QAC1D,CAAA,KAAA,EAAQ,OAAA,CAAQ,QAAQ,CAAC,CAAA,SAAA,EAAY,IAAA,KAAS,QAAA,GAAW,SAAA,GAAY,SAAS,CAAA,8BAAA,EAAiC,KAAA,CAAM,OAAO,CAAA,EAAA,CAAA;AAAA,QAC5H,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,OAAO,KAAA;AAAM,OACnC;AAAA,IACF;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAO,IAAI,eAAe,WAAA,EAAa,CAAA,UAAA,EAAa,IAAI,CAAA,MAAA,EAAS,OAAA,CAAQ,QAAQ,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,IACvF;AACA,IAAA,OAAO,UAAU,QAAQ,CAAA;AAAA,EAC3B;AAEA,EAAA,eAAe,OAAA,CACb,QAAA,EACA,MAAA,EACA,OAAA,EACkC;AAClC,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,QAAQ,CAAA,CAAE,OAAA;AACjC,IAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,IAAA,CAAK,EAAE,GAAG,MAAA,IAAU,OAAO,CAAA;AAAA,IAC1C,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,iBAAiB,KAAK,CAAA;AAAA,IAC9B;AAAA,EACF;AAGA,EAAA,eAAe,UAAU,KAAA,EAAkC;AACzD,IAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACtB,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,CAAQ,QAAQ,KAAK,CAAA;AAAA,IAC7B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,uCAAA,EAA0C,KAAA,CAAM,QAAQ,MAAM,KAAK,CAAA;AAAA,IACnF;AAAA,EACF;AAMA,EAAA,MAAM,QAAA,GAAyB;AAAA,IAC7B,MAAM,OAAA,CAAQ,QAAA,EAAU,MAAA,EAAQ;AAG9B,MAAA,IAAI,MAAA,CAAO,UAAA,IAAc,MAAA,CAAO,UAAA,CAAW,UAAU,UAAA,EAAY;AAC/D,QAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAA,CAAO,UAAA;AAC3B,QAAA,MAAM,eAAA;AAAA,UACJ,IAAI,aAAA;AAAA,YACF,SAAA;AAAA,YACA,kBAAA,CAAmB,WAAW,OAAA,CAAQ,QAAQ,GAAG,KAAA,EAAO,SAAA,CAAU,QAAQ,CAAA,CAAE,UAAU,CAAA;AAAA,YACtF;AAAA,cACE,OAAO,IAAI,KAAA;AAAA,gBACT,uBAAuB,QAAQ,CAAA,YAAA,EAAe,OAAO,CAAA,4BAAA,EAA+B,UAAU,kCAAkC,UAAU,CAAA,2BAAA;AAAA;AAC5I;AACF,WACF;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAEA,MAAA,MAAM,aAAA,GAAgB,SAAA,CAAU,QAAQ,CAAA,CAAE,QAAA;AAC1C,MAAA,MAAMA,IAAAA,GAAM,CAAC,GAAA,KAAwB;AACnC,QAAA,IAAI,KAAA,GAAS,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,CAA2B,MAAA;AAAA,UAC1D,OAAO,MAAA,IAAU,GAAA;AAAA,UACjB,EAAE,OAAO,OAAA;AAAQ,SACnB;AAEA,QAAA,KAAA,GAAQ,YAAA,CAAa,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA;AAI1C,QAAA,MAAM,YAAA,GAAe,UAAU,QAAQ,CAAA,CAAE,gBAAgB,MAAA,CAAO,MAAA,EAAQ,UAAU,EAAC;AACnF,QAAA,IAAI,OAAO,MAAA,IAAU,MAAA,CAAO,OAAO,KAAA,IAAS,YAAA,CAAa,SAAS,CAAA,EAAG;AACnE,UAAA,MAAM,OAAA,GAAU,WAAA,CAAY,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA;AAC/C,UAAA,MAAM,gBAAA,GAAmB,2BAAA;AACzB,UAAA,MAAM,OAAA,GAAU,aACb,MAAA,CAAO,CAAC,MAAM,gBAAA,CAAiB,IAAA,CAAK,CAAC,CAAC,CAAA,CACtC,IAAI,CAAC,CAAA,KAAM,GAAG,CAAC,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,CAAG,CAAA,CACpC,KAAK,GAAG,CAAA;AACX,UAAA,IAAI,OAAA,EAAS;AACX,YAAA,KAAA,GAAQ,KAAA,CAAM,GAAG,OAAO,CAAA;AAAA,UAC1B;AAAA,QACF;AAMA,QAAA,IAAI,OAAO,IAAA,EAAM;AACf,UAAA,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,KAAA,EAAO;AAAA,YACrC,SAAA,EAAW,MAAA,CAAO,IAAA,CAAK,KAAA,KAAU;AAAA,WAClC,CAAA;AACD,UAAA,IAAI,GAAA,IAAO,GAAA,KAAQ,MAAA,CAAO,IAAA,CAAK,KAAA,EAAO,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAA,EAAK,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAAA,QACpF,CAAA,MAAA,IAAW,MAAA,CAAO,UAAA,IAAc,GAAA,EAAK;AACnC,UAAA,KAAA,GAAQ,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,QAC9C;AAEA,QAAA,IAAI,OAAO,UAAA,EAAY;AACrB,UAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAQ,GAAI,MAAA,CAAO,UAAA;AACjC,UAAA,MAAMC,KAAAA,GAAAA,CAAQ,OAAO,CAAA,IAAK,OAAA;AAC1B,UAAA,MAAM,EAAA,GAAKA,QAAO,OAAA,GAAU,CAAA;AAC5B,UAAA,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAMA,KAAAA,EAAM,EAAE,CAAA;AAAA,QAC9B;AAEA,QAAA,OAAO,KAAA;AAAA,MAKT,CAAA;AAIA,MAAA,MAAM,WAAW,aAAA,KAAkB,MAAA,IAAa,CAAC,gBAAA,CAAiB,IAAI,QAAQ,CAAA;AAC9E,MAAA,IAAI,MAAA,GAAS,MAAMD,IAAAA,CAAI,aAAA,KAAkB,iBAAiB,GAAA,CAAI,QAAQ,CAAA,GAAI,KAAA,GAAQ,IAAA,CAAK,CAAA;AACvF,MAAA,IAAI,QAAA,IAAY,eAAA,CAAgB,MAAA,CAAO,KAAA,EAAO,IAAI,CAAA,EAAG;AACnD,QAAA,gBAAA,CAAiB,IAAI,QAAQ,CAAA;AAC7B,QAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA,EAAc;AACzC,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,CAAA,oBAAA,EAAuB,QAAQ,CAAA,uEAAA,EAA0E,QAAQ,CAAA,wDAAA;AAAA,WACnH;AAAA,QACF;AACA,QAAA,MAAA,GAAS,MAAMA,KAAI,KAAK,CAAA;AAAA,MAC1B;AACA,MAAA,MAAM,gBAAA,CAAiB,MAAA,EAAQ,QAAA,EAAU,SAAS,CAAA;AAElD,MAAA,OAAO,EAAE,MAAM,MAAA,CAAO,IAAA,IAAQ,EAAC,EAAG,KAAA,EAAO,MAAA,CAAO,KAAA,IAAS,CAAA,EAAE;AAAA,IAC7D,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,QAAA,EAAU,MAAA,EAAQ;AAC7B,MAAA,MAAM,SAAU,MAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,EACxC,MAAA,CAAO,MAAA,CAAO,MAAA,IAAU,GAAG,EAC3B,EAAA,CAAG,IAAA,EAAM,MAAA,CAAO,EAAE,EAClB,MAAA,EAAO;AACV,MAAA,MAAM,gBAAA,CAAiB,MAAA,EAAQ,QAAA,EAAU,QAAQ,CAAA;AACjD,MAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAK;AAAA,IAC7B,CAAA;AAAA,IAEA,MAAM,OAAA,CAAQ,QAAA,EAAU,MAAA,EAAQ;AAC9B,MAAA,IAAI,MAAA,CAAO,IAAI,MAAA,KAAW,CAAA,SAAU,EAAE,IAAA,EAAM,EAAC,EAAE;AAC/C,MAAA,MAAM,MAAA,GAAU,MAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,CACxC,MAAA,CAAO,GAAG,CAAA,CACV,EAAA,CAAG,IAAA,EAAM,MAAA,CAAO,GAAG,CAAA;AACtB,MAAA,MAAM,gBAAA,CAAiB,MAAA,EAAQ,QAAA,EAAU,SAAS,CAAA;AAClD,MAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,IAAQ,EAAC,EAAE;AAAA,IACnC,CAAA;AAAA,IAEA,MAAM,aAAa,QAAA,EAAkD;AACnE,MAAA,OAAO;AAAA,QACL,QAAQ,QAAA,GAAW,SAAA,CAAU,QAAQ,CAAA,CAAE,YAAA,EAAc,WAAW,CAAA,GAAI,IAAA;AAAA,QACpE,IAAA,EAAM,IAAA;AAAA,QACN,eAAA,EAAiB,gBAAA;AAAA,QACjB,UAAA,EAAY,KAAA;AAAA,QACZ,eAAA,EAAiB,KAAA;AAAA,QACjB,aAAA,EAAe,KAAA;AAAA,QACf,SAAA,EAAW,KAAA;AAAA,QACX,MAAA,EAAQ,IAAA;AAAA,QACR,MAAA,EAAQ,IAAA;AAAA,QACR,UAAA;AAAA,QACA,UAAA,EAAY,QAAA,GAAW,UAAA,CAAW,QAAQ,CAAA,GAAI;AAAA,OAChD;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,QAAA,EAAU,MAAA,EAAQ;AAC7B,MAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,QAAA,EAAU,OAAO,IAAA,EAAiC;AAAA,QAC1E,QAAA;AAAA,QACA,MAAA,EAAQ;AAAA,OACT,CAAA;AACD,MAAA,MAAM,QAAA,GAAW,SAAA,CAAU,QAAQ,CAAA,CAAE,MAAA;AACrC,MAAA,IAAI,MAAA;AACJ,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,IAAI,QAAA;AACJ,QAAA,IAAI;AACF,UAAA,QAAA,GAAW,MAAM,SAAS,GAAG,CAAA;AAAA,QAC/B,SAAS,KAAA,EAAO;AACd,UAAA,MAAM,iBAAiB,KAAK,CAAA;AAAA,QAC9B;AACA,QAAA,IAAI,YAAA,CAAa,QAAQ,CAAA,EAAG;AAG1B,UAAA,MAAA,GAAU,MAAM,QAAA,CAAS,QAAA,EAAU,QAAQ,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA,IAAM,EAAE,EAAA,EAAI,QAAA,EAAS;AAAA,QACzF,CAAA,MAAA,IAAW,YAAY,OAAO,QAAA,KAAa,YAAY,YAAA,CAAa,QAAA,CAAS,EAAE,CAAA,EAAG;AAChF,UAAA,MAAA,GAAS,QAAA;AAAA,QACX,CAAA,MAAO;AACL,UAAA,MAAM,IAAI,cAAA;AAAA,YACR,OAAA;AAAA,YACA,2CAA2C,QAAQ,CAAA,0CAAA;AAAA,WACrD;AAAA,QACF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,QAAQ,CAAA;AACtE,QAAA,MAAA,GAAS,KAAK,CAAC,CAAA;AAAA,MACjB;AACA,MAAA,MAAM,SAAA,CAAU,EAAE,QAAA,EAAU,IAAA,EAAM,UAAU,EAAA,EAAI,MAAA,CAAO,EAAA,EAAI,MAAA,EAAQ,CAAA;AACnE,MAAA,OAAO,EAAE,MAAM,MAAA,EAAO;AAAA,IACxB,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,QAAA,EAAU,MAAA,EAAQ;AAC7B,MAAA,MAAM,EAAE,IAAG,GAAI,MAAA;AACf,MAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,QAAA,EAAU,OAAO,IAAA,EAAiC;AAAA,QAC1E,QAAA;AAAA,QACA,MAAA,EAAQ,QAAA;AAAA,QACR;AAAA,OACD,CAAA;AACD,MAAA,MAAM,QAAA,GAAW,SAAA,CAAU,QAAQ,CAAA,CAAE,MAAA;AACrC,MAAA,IAAI,MAAA;AACJ,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,IAAI,QAAA;AACJ,QAAA,IAAI;AACF,UAAA,QAAA,GAAW,MAAM,QAAA,CAAS,EAAA,EAAI,GAAG,CAAA;AAAA,QACnC,SAAS,KAAA,EAAO;AACd,UAAA,MAAM,iBAAiB,KAAK,CAAA;AAAA,QAC9B;AACA,QAAA,MAAM,MAAA,GAAkB,QAAA;AACxB,QAAA,IAAI,MAAA,IAAU,QAAQ,MAAA,KAAW,KAAA,QAAa,MAAM,WAAA,CAAY,QAAA,EAAU,EAAA,EAAI,QAAQ,CAAA;AACtF,QAAA,IAAI,YAAA,CAAa,MAAM,CAAA,IAAK,MAAA,KAAW,IAAA,EAAM;AAE3C,UAAA,MAAM,MAAA,GAAS,MAAA,KAAW,IAAA,GAAO,EAAA,GAAK,MAAA;AACtC,UAAA,MAAA,GAAU,MAAM,QAAA,CAAS,QAAA,EAAU,MAAM,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA,IAAM,EAAE,GAAG,GAAA,EAAK,IAAI,MAAA,EAAO;AAAA,QAC7F,CAAA,MAAA,IAAW,OAAO,MAAA,KAAW,QAAA,EAAU;AACrC,UAAA,MAAA,GAAS,EAAE,EAAA,EAAI,GAAI,MAAA,EAAmC;AAAA,QACxD,CAAA,MAAO;AACL,UAAA,MAAM,IAAI,cAAA;AAAA,YACR,OAAA;AAAA,YACA,2CAA2C,QAAQ,CAAA,gDAAA;AAAA,WACrD;AAAA,QACF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,GAAG,IAAA,EAAM,EAAE,CAAA,CAAE,MAAA,CAAO,GAAG,CAAC,CAAA;AAC1E,QAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG,MAAM,MAAM,WAAA,CAAY,QAAA,EAAU,IAAI,QAAQ,CAAA;AACrE,QAAA,MAAA,GAAS,KAAK,CAAC,CAAA;AAAA,MACjB;AACA,MAAA,MAAM,UAAU,EAAE,QAAA,EAAU,MAAM,QAAA,EAAU,EAAA,EAAI,QAAQ,CAAA;AACxD,MAAA,OAAO,EAAE,MAAM,MAAA,EAAO;AAAA,IACxB,CAAA;AAAA,IAEA,MAAM,SAAA,CAAU,QAAA,EAAU,MAAA,EAAQ;AAChC,MAAA,MAAM,EAAE,IAAG,GAAI,MAAA;AACf,MAAA,IAAI,SAAA,GAAwB,EAAA;AAC5B,MAAA,IAAI,UAAA,CAAW,QAAQ,CAAA,EAAG;AAGxB,QAAA,IAAI,CAAE,MAAM,SAAA,CAAU,QAAA,EAAU,EAAE,CAAA,EAAI,MAAM,UAAU,QAAQ,CAAA;AAC9D,QAAA,MAAM,GAAA,CAAI,KAAK,QAAQ,CAAA,CAAE,QAAO,CAAE,EAAA,CAAG,IAAA,EAAM,EAAE,CAAC,CAAA;AAC9C,QAAA,IAAI,YAAA;AACJ,QAAA,IAAI;AACF,UAAA,YAAA,GAAe,MAAM,SAAA,CAAU,QAAA,EAAU,EAAE,CAAA;AAAA,QAC7C,SAAS,KAAA,EAAO;AACd,UAAA,MAAM,KAAA,GAAQ,iBAAiB,KAAK,CAAA;AACpC,UAAA,MAAM,IAAI,cAAA;AAAA,YACR,OAAA;AAAA,YACA,OAAO,OAAA,CAAQ,QAAQ,CAAC,CAAA,gDAAA,EAAmD,MAAM,OAAO,CAAA,EAAA,CAAA;AAAA,YACxF,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,OAAO,KAAA;AAAM,WACnC;AAAA,QACF;AACA,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,MAAM,IAAI,cAAA,CAAe,WAAA,EAAa,yBAAyB,OAAA,CAAQ,QAAQ,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,QACrF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,EAAA,CAAG,IAAA,EAAM,EAAE,CAAA,CAAE,MAAA,CAAO,IAAI,CAAC,CAAA;AACxE,QAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG,MAAM,MAAM,WAAA,CAAY,QAAA,EAAU,IAAI,QAAQ,CAAA;AACrE,QAAA,MAAM,UAAA,GAAc,IAAA,CAAK,CAAC,CAAA,CAAuB,EAAA;AACjD,QAAA,IAAI,YAAA,CAAa,UAAU,CAAA,EAAG,SAAA,GAAY,UAAA;AAAA,MAC5C;AACA,MAAA,MAAM,UAAU,EAAE,QAAA,EAAU,MAAM,QAAA,EAAU,EAAA,EAAI,WAAW,CAAA;AAC3D,MAAA,OAAO,EAAE,IAAA,EAAM,EAAE,EAAA,EAAI,WAAU,EAAE;AAAA,IACnC,CAAA;AAAA,IAEA,YAAA,CAAa,UAAU,MAAA,EAAQ;AAC7B,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,QAAQ,CAAA,CAAE,YAAA;AAChC,MAAA,OAAO,GAAA,GAAM,GAAA,CAAI,MAAM,CAAA,GAAI,MAAA;AAAA,IAC7B,CAAA;AAAA,IAEA,YAAY,KAAA,EAAO;AACjB,MAAA,MAAM,IAAA,GAAO,SAAA,CAAU,KAAA,CAAM,QAAQ,CAAA,CAAE,WAAA;AACvC,MAAA,IAAI,CAAC,IAAA,EAAM,OAAO,EAAC;AACnB,MAAA,OAAO,OAAO,IAAA,KAAS,UAAA,GAAa,IAAA,CAAK,KAAK,CAAA,GAAI,IAAA;AAAA,IACpD;AAAA,GACF;AAEA,EAAA,OAAO,QAAA;AACT","file":"chunk-RLY7TZUY.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Postgres / PostgREST error codes → Genesis write outcomes\n// ---------------------------------------------------------------------------\n\nimport {\n DataWriteError,\n isDataWriteError,\n type WriteOutcome,\n} from \"../schema/write-outcome\";\nimport type { ReadOutcome } from \"../schema/read-outcome\";\nimport { titleCase } from \"../utils\";\n\n/**\n * Maps a store error code to the read outcome it means: the write mapping,\n * with `22***` and PostgREST request errors (`PGRST1**` other than PGRST116,\n * `42703` unknown column, `42P01` unknown table) read as `invalid`.\n */\nexport function readOutcomeFromCode(code: string | null | undefined): ReadOutcome {\n if (!code) return \"error\";\n if (code === \"42703\" || code === \"42P01\" || (/^PGRST1\\d\\d$/.test(code) && code !== \"PGRST116\")) {\n return \"invalid\";\n }\n const write = writeOutcomeFromCode(code);\n return write === \"forbidden\" || write === \"gone\" || write === \"unauthenticated\" || write === \"invalid\"\n ? write\n : \"error\";\n}\n\n/**\n * Maps a Postgres SQLSTATE or PostgREST error code to the write outcome it\n * means. Anything not listed is `error`: a code is only read as a refusal or\n * a bad value when it says so.\n *\n * - `42501` (insufficient privilege, including \"new row violates row-level\n * security policy\") → `forbidden`\n * - `22***` (data exception: bad type, out of range) and `23***` (integrity\n * constraint: `23505` unique, `23503` foreign key, `23502` not null,\n * `23514` check) → `invalid`\n * - `PGRST301`–`PGRST303` (JWT missing, expired or rejected), `28000`,\n * `28P01` → `unauthenticated`\n * - `PGRST116` (one row was asked for and none came back) → `gone`\n */\nexport function writeOutcomeFromCode(code: string | null | undefined): WriteOutcome {\n if (!code) return \"error\";\n if (code === \"42501\") return \"forbidden\";\n if (code === \"PGRST116\") return \"gone\";\n if (code === \"PGRST301\" || code === \"PGRST302\" || code === \"PGRST303\") return \"unauthenticated\";\n if (code === \"28000\" || code === \"28P01\") return \"unauthenticated\";\n if (/^2[23][0-9A-Z]{3}$/.test(code)) return \"invalid\";\n return \"error\";\n}\n\n/** A column name as Postgres reports it unquoted; anything else is not mapped. */\nconst COLUMN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** The columns of a `Key (a, b)=(...)` detail line, or none when any is not a plain column. */\nfunction keyColumns(details: string, tail: RegExp): string[] {\n const match = /^Key \\(([^()]*)\\)=\\(/.exec(details);\n if (!match || !tail.test(details)) return [];\n const columns = match[1]!.split(\",\").map((c) => c.trim());\n return columns.length > 0 && columns.every((c) => COLUMN.test(c)) ? columns : [];\n}\n\nfunction listLabels(columns: string[]): string {\n const labels = columns.map((c) => titleCase(c));\n if (labels.length === 1) return labels[0]!;\n return `${labels.slice(0, -1).join(\", \")} and ${labels[labels.length - 1]}`;\n}\n\n/** Field errors read from a store error, with the safe sentence that sums them up. */\nexport interface PostgrestFieldErrors {\n /** Column name → safe message for that input. */\n fields: Record<string, string>;\n /** One safe sentence for the whole error, to replace the store's raw text. */\n message: string;\n}\n\n/**\n * Reads which columns a Postgres constraint error is about, from PostgREST's\n * `message` / `details`, and gives safe messages for them. Only plain column\n * names are taken from the store's text; the values it echoes are never\n * used. Returns `undefined` when nothing can be mapped safely.\n *\n * - `23505` unique violation, `Key (email)=(…) already exists.` → each\n * column: \"This value is already in use.\" (several columns: \"This\n * combination is already in use.\")\n * - `23502` not-null, `null value in column \"title\" …` → \"This field is\n * required.\"\n * - `23503` foreign key on insert/update, `Key (owner_id)=(…) is not present\n * in table …` → \"The selected item doesn't exist.\"\n */\nexport function fieldErrorsFromPostgrest(error: {\n code?: unknown;\n message?: unknown;\n details?: unknown;\n}): PostgrestFieldErrors | undefined {\n const code = typeof error.code === \"string\" ? error.code : undefined;\n const details = typeof error.details === \"string\" ? error.details.trim() : \"\";\n const message = typeof error.message === \"string\" ? error.message : \"\";\n if (code === \"23505\") {\n const columns = keyColumns(details, /\\) already exists\\.?$/);\n if (columns.length === 0) return undefined;\n const each = columns.length === 1 ? \"This value is already in use.\" : \"This combination is already in use.\";\n return {\n fields: Object.fromEntries(columns.map((c) => [c, each])),\n message:\n columns.length === 1\n ? `${listLabels(columns)} is already in use.`\n : `This combination of ${listLabels(columns)} is already in use.`,\n };\n }\n if (code === \"23502\") {\n const column = /^null value in column \"([A-Za-z_][A-Za-z0-9_]*)\"(?: |$)/.exec(message)?.[1];\n if (!column || !COLUMN.test(column)) return undefined;\n return {\n fields: { [column]: \"This field is required.\" },\n message: `${titleCase(column)} is required.`,\n };\n }\n if (code === \"23503\") {\n const columns = keyColumns(details, /\\) is not present in table \"[^\"]*\"\\.?$/);\n if (columns.length === 0) return undefined;\n return {\n fields: Object.fromEntries(columns.map((c) => [c, \"The selected item doesn't exist.\"])),\n message: `${listLabels(columns)}: the selected item doesn't exist.`,\n };\n }\n return undefined;\n}\n\n/**\n * Turns anything a write threw, or the `{ message, code }` error a Supabase\n * client returned, into a `DataWriteError`. A `DataWriteError` passes through\n * unchanged; anything else keeps its message and `code`, and is kept as the\n * `cause`.\n *\n * When the error names the columns it is about (`fieldErrorsFromPostgrest`:\n * unique, not-null and foreign-key violations), the result carries `fields`\n * and its message is replaced with a safe sentence; the raw text stays on\n * the `cause`.\n */\nexport function toDataWriteError(error: unknown): DataWriteError {\n if (isDataWriteError(error)) return error;\n const shape = (typeof error === \"object\" && error !== null ? error : {}) as {\n message?: unknown;\n code?: unknown;\n details?: unknown;\n };\n const code = typeof shape.code === \"string\" ? shape.code : undefined;\n const mapped = fieldErrorsFromPostgrest(shape);\n if (mapped) {\n return new DataWriteError(writeOutcomeFromCode(code), mapped.message, {\n code,\n fields: mapped.fields,\n cause: error,\n });\n }\n const message =\n typeof shape.message === \"string\" && shape.message\n ? shape.message\n : error instanceof Error\n ? error.message\n : String(error);\n return new DataWriteError(writeOutcomeFromCode(code), message, { code, cause: error });\n}\n","import type {\n BaseRecord,\n DataProvider,\n FilterParam,\n Identifier,\n InvalidationKey,\n WriteEvent,\n} from \"./types\";\nimport { DataWriteError } from \"../schema/write-outcome\";\nimport { DataReadError, readFailureMessage } from \"../schema/read-outcome\";\nimport type { FilterOperator, ProviderCapabilities } from \"../schema/types\";\nimport { readOutcomeFromCode, toDataWriteError } from \"./postgrest-errors\";\n\n// ---------------------------------------------------------------------------\n// Minimal Supabase client shape — no hard dependency on @supabase/supabase-js\n// ---------------------------------------------------------------------------\n\n/** A chainable query builder returned by `client.from(table)`. */\ninterface SupabaseQueryBuilder {\n select(\n columns?: string,\n options?: { count?: \"exact\" | \"planned\" | \"estimated\" },\n ): SupabaseQueryBuilder;\n insert(data: Record<string, unknown> | Record<string, unknown>[]): SupabaseQueryBuilder;\n update(data: Record<string, unknown>): SupabaseQueryBuilder;\n delete(): SupabaseQueryBuilder;\n eq(column: string, value: unknown): SupabaseQueryBuilder;\n neq(column: string, value: unknown): SupabaseQueryBuilder;\n gt(column: string, value: unknown): SupabaseQueryBuilder;\n lt(column: string, value: unknown): SupabaseQueryBuilder;\n gte(column: string, value: unknown): SupabaseQueryBuilder;\n lte(column: string, value: unknown): SupabaseQueryBuilder;\n ilike(column: string, value: string): SupabaseQueryBuilder;\n in(column: string, values: unknown[]): SupabaseQueryBuilder;\n is(column: string, value: null): SupabaseQueryBuilder;\n not(column: string, operator: string, value: unknown): SupabaseQueryBuilder;\n or(filters: string): SupabaseQueryBuilder;\n order(column: string, options?: { ascending?: boolean }): SupabaseQueryBuilder;\n range(from: number, to: number): SupabaseQueryBuilder;\n single(): SupabaseQueryBuilder;\n then: Promise<{ data: unknown; error: unknown; count?: number | null }>[\"then\"];\n}\n\n/**\n * Minimal Supabase client interface — the public shape this package has\n * always exposed for a caller that wants to type its own client or a\n * wrapper around one. Unchanged: `from()` still returns `SupabaseQueryBuilder`\n * so existing consumers that annotate a variable with this type and chain\n * off it keep type-checking exactly as before.\n */\nexport interface SupabaseClient {\n from(table: string): SupabaseQueryBuilder;\n}\n\n/**\n * What `createSupabaseProvider` actually requires from its `client` argument\n * — deliberately looser than the public `SupabaseClient` above, and used\n * ONLY for that one parameter. A real `@supabase/supabase-js` client's\n * `from()` returns a generic, table-typed `PostgrestQueryBuilder` whose chain\n * methods don't structurally match `SupabaseQueryBuilder`, which used to\n * force callers to pass their client through `as never`. Since `unknown` is\n * a supertype of everything, any real client's `from(table: string)`\n * satisfies THIS shape without a cast; `createSupabaseProvider` narrows to\n * `SupabaseQueryBuilder` internally, once, right after each `from()` call.\n * Loosening only the input here — rather than `SupabaseClient` itself —\n * means a caller relying on `SupabaseClient`'s stricter return type for\n * their own chaining is unaffected.\n */\nexport interface SupabaseClientInput {\n from(table: string): unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Provider factory\n// ---------------------------------------------------------------------------\n\n/** What a write hook is told about the write it is part of. */\nexport interface SupabaseWriteContext {\n resource: string;\n action: \"create\" | \"update\";\n /** The row's id, for an update. */\n id?: Identifier;\n}\n\n/** Per-resource rules for `createSupabaseProvider`. Every field is optional. */\nexport interface SupabaseResourceConfig {\n /** Singular noun for failure messages, e.g. \"project\". Defaults to \"record\". */\n noun?: string;\n /** Plural for list messages, when it isn't `noun` + \"s\" (\"categories\"). */\n nounPlural?: string;\n /**\n * The columns a search covers. `[]` means the resource can't be searched,\n * and `capabilities(resource).search` reports false so a UI hides the box.\n * Set, they replace the caller's `search.fields`; omitted, those are used\n * as given.\n */\n searchFields?: readonly string[];\n /**\n * The column a sorted list orders by last, so rows with equal sort values\n * keep one order from page to page (none repeats, none is skipped).\n * Default `\"id\"`; `false` for a table or view without one. With the\n * default, a view that turns out to have no `id` column is read as before\n * (without the key order) and a development warning suggests setting this.\n */\n orderKey?: string | false;\n /**\n * `\"bin\"` for a table whose delete trigger moves the row to a recycle bin\n * instead of removing it (see `SupabaseProviderOptions.binTables`).\n * `\"hard\"` forces a plain delete even when `binTables` lists the table.\n */\n deleteMode?: \"hard\" | \"bin\";\n /**\n * Turns submitted values into the row to write: trim, drop read-only keys,\n * validate. Runs before every create and update, including the `create`\n * and `update` overrides. Throw (ideally `DataWriteError(\"invalid\", ...)`)\n * to refuse the write; nothing is sent.\n */\n prepare?: (\n values: Record<string, unknown>,\n context: SupabaseWriteContext,\n ) => Record<string, unknown> | Promise<Record<string, unknown>>;\n /**\n * Creates the row some other way than an insert, e.g. a security-definer\n * RPC. Return the new row, or its id and the provider reads the row back\n * (if the read-back fails, the create is still reported as saved, with\n * just the id, so the user does not retry and make a second one). Throw to\n * fail; a Supabase `{ message, code }` error is mapped like any other.\n */\n create?: (row: Record<string, unknown>) => Promise<BaseRecord | Identifier>;\n /**\n * Updates the row some other way than an update, e.g. an RPC. Return the\n * updated row, or its id (or `true`) and the provider reads it back.\n * Returning nothing (`undefined`/`null`/`false`) means no row changed: the provider then reads the\n * row to say why (`forbidden` or `gone`), exactly as for a plain update,\n * so a function that silently skips a row never reports a save.\n */\n update?: (\n id: Identifier,\n row: Record<string, unknown>,\n ) => Promise<BaseRecord | Identifier | boolean | null | undefined | void>;\n /** Maps a stored row to edit-form values; see `DataProvider.toFormValues`. */\n toFormValues?: (record: Record<string, unknown>) => Record<string, unknown>;\n /**\n * Other cache keys a committed write to this resource makes stale, e.g.\n * `[[\"staff\"], [\"me\"]]`. Served through `DataProvider.invalidates`, which\n * `useResource` and `useResourceForm` invalidate after their writes.\n */\n invalidates?: InvalidationKey[] | ((event: WriteEvent) => InvalidationKey[]);\n}\n\n/** Options for `createSupabaseProvider`. All optional; none changes reads. */\nexport interface SupabaseProviderOptions {\n /**\n * Recycle-bin tables: their delete trigger turns a delete into a \"binned\"\n * mark and the binned row drops out of the caller's view (RLS or a view\n * filter). The database reports such a delete as zero rows, so for these\n * the provider checks the row is visible, deletes it, and reads it back:\n * gone from view is success, still visible is `forbidden`. A row that was\n * not visible beforehand is `gone`.\n */\n binTables?: readonly string[] | ((resource: string) => boolean);\n /** Per-resource rules, keyed by resource (table) name. */\n resources?: Record<string, SupabaseResourceConfig>;\n /**\n * Called after every committed create, update and delete, e.g. to\n * invalidate caches the Genesis hooks don't know about. Awaited, but a\n * failure is logged and never turns a committed write into a failed one.\n */\n onWrite?: (event: WriteEvent) => void | Promise<void>;\n /**\n * The most rows one list page may ask for (PostgREST's `max-rows` caps\n * reads anyway; default 1000). A larger request is refused with an\n * `invalid` `DataReadError` (its `cause` says why) rather than cut short,\n * so page boundaries never disagree with the caller's `perPage`.\n * `capabilities().maxPerPage` reports it.\n */\n maxPerPage?: number;\n /**\n * Called with every failed read, before it is thrown. The thrown\n * `DataReadError` carries a plain message for users; the store's own\n * error (its raw text and code) is its `cause`, for logging here.\n */\n onReadError?: (\n error: DataReadError,\n context: { resource: string; operation: \"getList\" | \"getOne\" | \"getMany\" },\n ) => void | Promise<void>;\n}\n\n/** A PostgREST \"undefined column\" error (42703) naming `column`. */\nfunction isMissingColumn(error: unknown, column: string): boolean {\n if (!error || typeof error !== \"object\") return false;\n const { code, message } = error as { code?: unknown; message?: unknown };\n if (code !== \"42703\" || typeof message !== \"string\") return false;\n return new RegExp(`(^|[^A-Za-z0-9_])${column}([^A-Za-z0-9_]|$)`).test(message);\n}\n\nconst SERVED_OPERATORS: readonly FilterOperator[] = [\n \"eq\", \"neq\", \"gt\", \"lt\", \"gte\", \"lte\", \"contains\", \"in\", \"between\", \"is_null\", \"is_not_null\",\n];\n\nfunction escapeIlike(value: string): string {\n return value.replace(/[%_\\\\]/g, (char) => `\\\\${char}`);\n}\n\nfunction isIdentifier(value: unknown): value is Identifier {\n return typeof value === \"string\" || typeof value === \"number\";\n}\n\n/**\n * The Supabase data provider. Reads and writes go through the client you\n * pass, under that client's session and RLS.\n *\n * Writes never report a save that did not happen:\n * - `update` and `deleteOne` ask for the affected rows. Zero rows is a\n * `DataWriteError`: the row is read back, and still visible means\n * `forbidden` (RLS or a trigger refused the change), no longer visible\n * means `gone`.\n * - Database errors on writes are thrown as `DataWriteError` with the\n * outcome their code means (`writeOutcomeFromCode`) and the code kept on\n * `error.code`.\n * - Recycle-bin tables (`binTables`) succeed when the row leaves the\n * caller's view.\n *\n * Reads are unchanged: errors are thrown as an `Error` carrying `code`.\n */\nexport function createSupabaseProvider(\n client: SupabaseClientInput,\n options: SupabaseProviderOptions = {},\n): DataProvider {\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function applyFilters(\n query: SupabaseQueryBuilder,\n filters: FilterParam[] | undefined,\n ): SupabaseQueryBuilder {\n if (!filters) return query;\n for (const f of filters) {\n switch (f.operator) {\n case \"eq\":\n query = query.eq(f.field, f.value);\n break;\n case \"neq\":\n query = query.neq(f.field, f.value);\n break;\n case \"gt\":\n query = query.gt(f.field, f.value);\n break;\n case \"lt\":\n query = query.lt(f.field, f.value);\n break;\n case \"gte\":\n query = query.gte(f.field, f.value);\n break;\n case \"lte\":\n query = query.lte(f.field, f.value);\n break;\n case \"contains\":\n query = query.ilike(f.field, `%${escapeIlike(String(f.value))}%`);\n break;\n case \"in\":\n query = query.in(f.field, f.value);\n break;\n case \"is_null\":\n query = query.is(f.field, null);\n break;\n case \"is_not_null\":\n query = query.not(f.field, \"is\", null);\n break;\n case \"between\": {\n const [lo, hi] = f.value;\n query = query.gte(f.field, lo).lte(f.field, hi);\n break;\n }\n default:\n throw new Error(`[SupabaseProvider] Unknown filter operator \"${(f as FilterParam).operator}\"`);\n break;\n }\n }\n return query;\n }\n\n const maxPerPage = options.maxPerPage ?? 1000;\n // Resources found to have no \"id\" column for the default orderKey.\n const keylessResources = new Set<string>();\n\n /** Tell `onReadError` (sync or async; its failure is swallowed), then throw. */\n async function reportReadError(\n error: DataReadError,\n resource: string,\n operation: \"getList\" | \"getOne\" | \"getMany\",\n ): Promise<never> {\n try {\n await Promise.resolve(options.onReadError?.(error, { resource, operation }));\n } catch {\n // A logging hook never changes what the caller is told.\n }\n throw error;\n }\n\n /** A failed read, as a `DataReadError` with a message users can be shown. */\n async function throwIfReadError(\n result: { error: unknown },\n resource: string,\n operation: \"getList\" | \"getOne\" | \"getMany\",\n ): Promise<void> {\n if (!result.error) return;\n const raw = result.error as { message?: string; code?: string };\n const code = typeof raw.code === \"string\" ? raw.code : undefined;\n const outcome = readOutcomeFromCode(code);\n const error = new DataReadError(\n outcome,\n readFailureMessage(outcome, nounFor(resource), operation === \"getOne\", configFor(resource).nounPlural),\n { code, cause: result.error },\n );\n await reportReadError(error, resource, operation);\n }\n\n // ---------------------------------------------------------------------------\n // Write helpers\n // ---------------------------------------------------------------------------\n\n const from = (resource: string) => client.from(resource) as SupabaseQueryBuilder;\n const configFor = (resource: string): SupabaseResourceConfig =>\n options.resources?.[resource] ?? {};\n const nounFor = (resource: string) => configFor(resource).noun ?? \"record\";\n\n function isBinTable(resource: string): boolean {\n const mode = configFor(resource).deleteMode;\n if (mode) return mode === \"bin\";\n const bin = options.binTables;\n if (!bin) return false;\n return typeof bin === \"function\" ? bin(resource) : bin.includes(resource);\n }\n\n /** Awaits a write-path query: its rows, or a `DataWriteError`. */\n async function run(query: SupabaseQueryBuilder): Promise<unknown[]> {\n let result: { data: unknown; error: unknown };\n try {\n result = (await query) as { data: unknown; error: unknown };\n } catch (error) {\n throw toDataWriteError(error);\n }\n if (result.error) throw toDataWriteError(result.error);\n if (Array.isArray(result.data)) return result.data;\n return result.data == null ? [] : [result.data];\n }\n\n async function isVisible(resource: string, id: Identifier): Promise<boolean> {\n return (await run(from(resource).select(\"id\").eq(\"id\", id))).length > 0;\n }\n\n async function readBack(resource: string, id: Identifier): Promise<BaseRecord | undefined> {\n return (await run(from(resource).select(\"*\").eq(\"id\", id)))[0] as BaseRecord | undefined;\n }\n\n function goneError(resource: string): DataWriteError {\n return new DataWriteError(\n \"gone\",\n `This ${nounFor(resource)} no longer exists, or you can no longer see it.`,\n );\n }\n\n /**\n * A write matched no rows: read the row back to say why. Still visible\n * means the change was refused; not visible means it is gone (deleted, or\n * hidden from this user — the two can't be told apart under RLS).\n */\n async function noRowsError(\n resource: string,\n id: Identifier,\n verb: \"change\" | \"delete\",\n ): Promise<DataWriteError> {\n let visible: boolean;\n try {\n visible = await isVisible(resource, id);\n } catch (error) {\n const cause = toDataWriteError(error);\n return new DataWriteError(\n cause.outcome === \"unauthenticated\" ? \"unauthenticated\" : \"error\",\n `This ${nounFor(resource)} was not ${verb === \"change\" ? \"changed\" : \"deleted\"}, and reading it back failed (${cause.message}).`,\n { code: cause.code, cause: error },\n );\n }\n if (visible) {\n return new DataWriteError(\"forbidden\", `You can't ${verb} this ${nounFor(resource)}.`);\n }\n return goneError(resource);\n }\n\n async function prepare(\n resource: string,\n values: Record<string, unknown>,\n context: SupabaseWriteContext,\n ): Promise<Record<string, unknown>> {\n const rule = configFor(resource).prepare;\n if (!rule) return values;\n try {\n return await rule({ ...values }, context);\n } catch (error) {\n throw toDataWriteError(error);\n }\n }\n\n /** After a committed write. Never turns it into a failure. */\n async function committed(event: WriteEvent): Promise<void> {\n if (!options.onWrite) return;\n try {\n await options.onWrite(event);\n } catch (error) {\n console.error(`[SupabaseProvider] onWrite failed for \"${event.resource}\":`, error);\n }\n }\n\n // ---------------------------------------------------------------------------\n // DataProvider implementation\n // ---------------------------------------------------------------------------\n\n const provider: DataProvider = {\n async getList(resource, params) {\n // Refused, not cut short: a shorter page than the caller asked for\n // would shift every later page's boundary and skip or repeat rows.\n if (params.pagination && params.pagination.perPage > maxPerPage) {\n const { perPage } = params.pagination;\n await reportReadError(\n new DataReadError(\n \"invalid\",\n readFailureMessage(\"invalid\", nounFor(resource), false, configFor(resource).nounPlural),\n {\n cause: new Error(\n `[SupabaseProvider] \"${resource}\" asked for ${perPage} rows per page; the most is ${maxPerPage} (maxPerPage). Ask for at most ${maxPerPage} and page through the rest.`,\n ),\n },\n ),\n resource,\n \"getList\",\n );\n }\n\n const configuredKey = configFor(resource).orderKey;\n const run = (key: string | false) => {\n let query = (client.from(resource) as SupabaseQueryBuilder).select(\n params.select ?? \"*\",\n { count: \"exact\" },\n );\n\n query = applyFilters(query, params.filters);\n\n // Search — convert to OR of ilike. A resource's configured\n // `searchFields` win over the caller's.\n const searchFields = configFor(resource).searchFields ?? params.search?.fields ?? [];\n if (params.search && params.search.query && searchFields.length > 0) {\n const escaped = escapeIlike(params.search.query);\n const safeFieldPattern = /^[a-zA-Z_][a-zA-Z0-9_.]*$/;\n const clauses = searchFields\n .filter((f) => safeFieldPattern.test(f))\n .map((f) => `${f}.ilike.%${escaped}%`)\n .join(\",\");\n if (clauses) {\n query = query.or(clauses);\n }\n }\n\n // Sort, then the key column, so equal sort values keep one order\n // across pages (otherwise a row can repeat on page 2 or be skipped).\n // An unsorted page is ordered by the key alone: without an ORDER BY\n // the database may return pages in any order.\n if (params.sort) {\n query = query.order(params.sort.field, {\n ascending: params.sort.order === \"asc\",\n });\n if (key && key !== params.sort.field) query = query.order(key, { ascending: true });\n } else if (params.pagination && key) {\n query = query.order(key, { ascending: true });\n }\n\n if (params.pagination) {\n const { page, perPage } = params.pagination;\n const from = (page - 1) * perPage;\n const to = from + perPage - 1;\n query = query.range(from, to);\n }\n\n return query as unknown as Promise<{\n data: unknown[];\n error: unknown;\n count?: number | null;\n }>;\n };\n\n // The default key (\"id\") is a guess: a view without that column reads\n // as before, without the key order, and is not ordered by it again.\n const guessKey = configuredKey === undefined && !keylessResources.has(resource);\n let result = await run(configuredKey ?? (keylessResources.has(resource) ? false : \"id\"));\n if (guessKey && isMissingColumn(result.error, \"id\")) {\n keylessResources.add(resource);\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n `[SupabaseProvider] \"${resource}\" has no \"id\" column, so its pages have no stable order. Set resources.${resource}.orderKey to a unique column (or false to silence this).`,\n );\n }\n result = await run(false);\n }\n await throwIfReadError(result, resource, \"getList\");\n\n return { data: result.data ?? [], total: result.count ?? 0 } as never;\n },\n\n async getOne(resource, params) {\n const result = (await (client.from(resource) as SupabaseQueryBuilder)\n .select(params.select ?? \"*\")\n .eq(\"id\", params.id)\n .single()) as { data: unknown; error: unknown };\n await throwIfReadError(result, resource, \"getOne\");\n return { data: result.data } as never;\n },\n\n async getMany(resource, params) {\n if (params.ids.length === 0) return { data: [] } as never;\n const result = (await (client.from(resource) as SupabaseQueryBuilder)\n .select(\"*\")\n .in(\"id\", params.ids)) as { data: unknown[]; error: unknown };\n await throwIfReadError(result, resource, \"getMany\");\n return { data: result.data ?? [] } as never;\n },\n\n async capabilities(resource?: string): Promise<ProviderCapabilities> {\n return {\n search: resource ? configFor(resource).searchFields?.length !== 0 : true,\n sort: true,\n filterOperators: SERVED_OPERATORS,\n bulkWrites: false,\n versionedWrites: false,\n compositeKeys: false,\n multiSort: false,\n create: true,\n delete: true,\n maxPerPage,\n softDelete: resource ? isBinTable(resource) : false,\n };\n },\n\n async create(resource, params) {\n const row = await prepare(resource, params.data as Record<string, unknown>, {\n resource,\n action: \"create\",\n });\n const override = configFor(resource).create;\n let record: BaseRecord;\n if (override) {\n let returned: BaseRecord | Identifier;\n try {\n returned = await override(row);\n } catch (error) {\n throw toDataWriteError(error);\n }\n if (isIdentifier(returned)) {\n // The row exists from here on. A failed read-back must not report\n // the create as failed: the user would retry and make a second one.\n record = (await readBack(resource, returned).catch(() => undefined)) ?? { id: returned };\n } else if (returned && typeof returned === \"object\" && isIdentifier(returned.id)) {\n record = returned;\n } else {\n throw new DataWriteError(\n \"error\",\n `[SupabaseProvider] The create rule for \"${resource}\" returned neither the new row nor its id.`,\n );\n }\n } else {\n const rows = await run(from(resource).insert(row).select(\"*\").single());\n record = rows[0] as BaseRecord;\n }\n await committed({ resource, kind: \"create\", id: record.id, record });\n return { data: record } as never;\n },\n\n async update(resource, params) {\n const { id } = params;\n const row = await prepare(resource, params.data as Record<string, unknown>, {\n resource,\n action: \"update\",\n id,\n });\n const override = configFor(resource).update;\n let record: BaseRecord;\n if (override) {\n let returned: BaseRecord | Identifier | boolean | null | undefined | void;\n try {\n returned = await override(id, row);\n } catch (error) {\n throw toDataWriteError(error);\n }\n const result: unknown = returned;\n if (result == null || result === false) throw await noRowsError(resource, id, \"change\");\n if (isIdentifier(result) || result === true) {\n // The change committed; a failed read-back still reports it saved.\n const readId = result === true ? id : result;\n record = (await readBack(resource, readId).catch(() => undefined)) ?? { ...row, id: readId };\n } else if (typeof result === \"object\") {\n record = { id, ...(result as Record<string, unknown>) } as BaseRecord;\n } else {\n throw new DataWriteError(\n \"error\",\n `[SupabaseProvider] The update rule for \"${resource}\" returned neither the row, its id, nor nothing.`,\n );\n }\n } else {\n const rows = await run(from(resource).update(row).eq(\"id\", id).select(\"*\"));\n if (rows.length === 0) throw await noRowsError(resource, id, \"change\");\n record = rows[0] as BaseRecord;\n }\n await committed({ resource, kind: \"update\", id, record });\n return { data: record } as never;\n },\n\n async deleteOne(resource, params) {\n const { id } = params;\n let deletedId: Identifier = id;\n if (isBinTable(resource)) {\n // A binned delete reports zero rows whether or not it worked, so the\n // answer comes from visibility before and after.\n if (!(await isVisible(resource, id))) throw goneError(resource);\n await run(from(resource).delete().eq(\"id\", id));\n let stillVisible: boolean;\n try {\n stillVisible = await isVisible(resource, id);\n } catch (error) {\n const cause = toDataWriteError(error);\n throw new DataWriteError(\n \"error\",\n `The ${nounFor(resource)} may not have been deleted: checking it failed (${cause.message}).`,\n { code: cause.code, cause: error },\n );\n }\n if (stillVisible) {\n throw new DataWriteError(\"forbidden\", `You can't delete this ${nounFor(resource)}.`);\n }\n } else {\n const rows = await run(from(resource).delete().eq(\"id\", id).select(\"id\"));\n if (rows.length === 0) throw await noRowsError(resource, id, \"delete\");\n const returnedId = (rows[0] as { id?: unknown }).id;\n if (isIdentifier(returnedId)) deletedId = returnedId;\n }\n await committed({ resource, kind: \"delete\", id: deletedId });\n return { data: { id: deletedId } };\n },\n\n toFormValues(resource, record) {\n const map = configFor(resource).toFormValues;\n return map ? map(record) : record;\n },\n\n invalidates(event) {\n const keys = configFor(event.resource).invalidates;\n if (!keys) return [];\n return typeof keys === \"function\" ? keys(event) : keys;\n },\n };\n\n return provider;\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { useRef, useEffect, useCallback } from 'react';
2
+
3
+ // src/hooks/use-unsaved-changes-guard.ts
4
+ var DEFAULT_UNSAVED_CHANGES_MESSAGE = "You have unsaved changes. Leave without saving?";
5
+ function useUnsavedChangesGuard(options) {
6
+ const { when, message = DEFAULT_UNSAVED_CHANGES_MESSAGE, confirm } = options;
7
+ const whenRef = useRef(when);
8
+ whenRef.current = when;
9
+ const messageRef = useRef(message);
10
+ messageRef.current = message;
11
+ const confirmRef = useRef(confirm);
12
+ confirmRef.current = confirm;
13
+ useEffect(() => {
14
+ if (!when || typeof window === "undefined") return;
15
+ const onBeforeUnload = (event) => {
16
+ event.preventDefault();
17
+ event.returnValue = "";
18
+ };
19
+ window.addEventListener("beforeunload", onBeforeUnload);
20
+ return () => window.removeEventListener("beforeunload", onBeforeUnload);
21
+ }, [when]);
22
+ const confirmLeave = useCallback(() => {
23
+ if (!whenRef.current) return true;
24
+ const ask = confirmRef.current ?? (typeof window !== "undefined" && typeof window.confirm === "function" ? (text) => window.confirm(text) : () => true);
25
+ return ask(messageRef.current);
26
+ }, []);
27
+ return { isBlocking: when, confirmLeave };
28
+ }
29
+
30
+ export { DEFAULT_UNSAVED_CHANGES_MESSAGE, useUnsavedChangesGuard };
31
+ //# sourceMappingURL=chunk-XQO4OH2N.js.map
32
+ //# sourceMappingURL=chunk-XQO4OH2N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-unsaved-changes-guard.ts"],"names":[],"mappings":";;;AAEO,IAAM,+BAAA,GACX;AAiCK,SAAS,uBACd,OAAA,EAC8B;AAC9B,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,GAAU,+BAAA,EAAiC,SAAQ,GAAI,OAAA;AACrE,EAAA,MAAM,OAAA,GAAU,OAAO,IAAI,CAAA;AAC3B,EAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AACrB,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,MAAA,KAAW,WAAA,EAAa;AAC5C,IAAA,MAAM,cAAA,GAAiB,CAAC,KAAA,KAA6B;AACnD,MAAA,KAAA,CAAM,cAAA,EAAe;AAErB,MAAA,KAAA,CAAM,WAAA,GAAc,EAAA;AAAA,IACtB,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,gBAAgB,cAAc,CAAA;AACtD,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,cAAA,EAAgB,cAAc,CAAA;AAAA,EACxE,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,YAAA,GAAe,YAAY,MAAe;AAC9C,IAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,EAAS,OAAO,IAAA;AAC7B,IAAA,MAAM,MACJ,UAAA,CAAW,OAAA,KACV,OAAO,MAAA,KAAW,eAAe,OAAO,MAAA,CAAO,OAAA,KAAY,UAAA,GACxD,CAAC,IAAA,KAAiB,MAAA,CAAO,OAAA,CAAQ,IAAI,IACrC,MAAM,IAAA,CAAA;AACZ,IAAA,OAAO,GAAA,CAAI,WAAW,OAAO,CAAA;AAAA,EAC/B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,UAAA,EAAY,IAAA,EAAM,YAAA,EAAa;AAC1C","file":"chunk-XQO4OH2N.js","sourcesContent":["import { useCallback, useEffect, useRef } from \"react\";\n\nexport const DEFAULT_UNSAVED_CHANGES_MESSAGE =\n \"You have unsaved changes. Leave without saving?\";\n\nexport interface UseUnsavedChangesGuardOptions {\n /** Guard while this is true (e.g. a form is dirty). */\n when: boolean;\n /** The question `confirmLeave` asks. */\n message?: string;\n /**\n * How to ask. Default `window.confirm`. Must answer synchronously, since\n * router blockers and `beforeunload` cannot wait.\n */\n confirm?: (message: string) => boolean;\n}\n\nexport interface UseUnsavedChangesGuardReturn {\n /** True while leaving would lose changes. */\n isBlocking: boolean;\n /**\n * Call before leaving in-app (closing a dialog, a router blocker). Returns\n * true when it is fine to leave: nothing to lose, or the user said so.\n */\n confirmLeave: () => boolean;\n}\n\n/**\n * Warns before unsaved changes are lost, without depending on a router.\n *\n * - While `when` is true, closing or reloading the tab shows the browser's\n * own \"Leave site?\" prompt (`beforeunload`).\n * - `confirmLeave()` is for in-app navigation: wire it to your router's\n * blocker (React Router `useBlocker`, TanStack Router `useBlocker`), or call\n * it before closing a dialog. See SKILL.md \"Unsaved changes\".\n */\nexport function useUnsavedChangesGuard(\n options: UseUnsavedChangesGuardOptions,\n): UseUnsavedChangesGuardReturn {\n const { when, message = DEFAULT_UNSAVED_CHANGES_MESSAGE, confirm } = options;\n const whenRef = useRef(when);\n whenRef.current = when;\n const messageRef = useRef(message);\n messageRef.current = message;\n const confirmRef = useRef(confirm);\n confirmRef.current = confirm;\n\n useEffect(() => {\n if (!when || typeof window === \"undefined\") return;\n const onBeforeUnload = (event: BeforeUnloadEvent) => {\n event.preventDefault();\n // Older browsers need returnValue set to show the prompt.\n event.returnValue = \"\";\n };\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n return () => window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }, [when]);\n\n const confirmLeave = useCallback((): boolean => {\n if (!whenRef.current) return true;\n const ask =\n confirmRef.current ??\n (typeof window !== \"undefined\" && typeof window.confirm === \"function\"\n ? (text: string) => window.confirm(text)\n : () => true);\n return ask(messageRef.current);\n }, []);\n\n return { isBlocking: when, confirmLeave };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { useDataProvider } from './chunk-6V2TIN3G.js';
1
+ import { useDataProvider } from './chunk-GGSXSI6Z.js';
2
2
  import { useQuery } from '@tanstack/react-query';
3
3
 
4
4
  function useOne(resource, id, options = {}) {
@@ -25,5 +25,5 @@ function useOne(resource, id, options = {}) {
25
25
  }
26
26
 
27
27
  export { useOne };
28
- //# sourceMappingURL=chunk-3P4PHPMJ.js.map
29
- //# sourceMappingURL=chunk-3P4PHPMJ.js.map
28
+ //# sourceMappingURL=chunk-ZK5C3OOT.js.map
29
+ //# sourceMappingURL=chunk-ZK5C3OOT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/use-one.ts"],"names":[],"mappings":";;;AAyBO,SAAS,MAAA,CACd,QAAA,EACA,EAAA,EACA,OAAA,GAAyB,EAAC,EACP;AACnB,EAAA,MAAM,WAAW,eAAA,EAAgB;AAEjC,EAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAQ,GAAI,OAAA;AAC5B,EAAA,MAAM,SAAA,GAAY,OAAA,IAAW,CAAC,CAAC,EAAA;AAE/B,EAAA,OAAO,QAAA,CAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASjB,QAAA,EAAU,CAAC,QAAA,EAAU,QAAA,EAAU,IAAI,MAAM,CAAA;AAAA,IACzC,SAAS,YAAY;AACnB,MAAA,IAAI,EAAA,IAAM,IAAA,EAAM,MAAM,IAAI,MAAM,YAAY,CAAA;AAC5C,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,MAAA,CAAU,UAAU,EAAE,EAAA,EAAI,QAAQ,CAAA;AAChE,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB,CAAA;AAAA,IACA,OAAA,EAAS;AAAA,GACV,CAAA;AACH","file":"chunk-3P4PHPMJ.js","sourcesContent":["import { useQuery, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useDataProvider } from \"../provider/context\";\nimport type { BaseRecord, Identifier } from \"../provider/types\";\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface UseOneOptions {\n /** Supabase select/join syntax, e.g. \"*, author:profiles(name)\". */\n select?: string;\n /** Defaults to `!!id` — won't fetch when id is falsy. */\n enabled?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch a single record by id, powered by TanStack Query.\n *\n * Returns the full `UseQueryResult<T>` with the data unwrapped from the\n * provider's `GetOneResult` wrapper.\n */\nexport function useOne<T extends BaseRecord = BaseRecord>(\n resource: string,\n id: Identifier | undefined | null,\n options: UseOneOptions = {},\n): UseQueryResult<T> {\n const provider = useDataProvider();\n\n const { select, enabled } = options;\n const isEnabled = enabled ?? !!id;\n\n return useQuery<T>({\n // `select` is part of the key because it is part of the REQUEST: two pages\n // asking for the same record with different projections get different\n // rows back, and keying only on the id makes them share one cache entry.\n // Whichever mounted first wins for the whole staleTime window, and the\n // other silently renders a record missing the joined fields it asked for\n // — no error, no refetch, just absent data. `useResource`'s list key has\n // always included `select` (it keys on the whole `params` object); this\n // was the one place it did not.\n queryKey: [resource, \"detail\", id, select],\n queryFn: async () => {\n if (id == null) throw new Error(\"Missing id\");\n const result = await provider.getOne<T>(resource, { id, select });\n return result.data;\n },\n enabled: isEnabled,\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/use-one.ts"],"names":[],"mappings":";;;AAyBO,SAAS,MAAA,CACd,QAAA,EACA,EAAA,EACA,OAAA,GAAyB,EAAC,EACP;AACnB,EAAA,MAAM,WAAW,eAAA,EAAgB;AAEjC,EAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAQ,GAAI,OAAA;AAC5B,EAAA,MAAM,SAAA,GAAY,OAAA,IAAW,CAAC,CAAC,EAAA;AAE/B,EAAA,OAAO,QAAA,CAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASjB,QAAA,EAAU,CAAC,QAAA,EAAU,QAAA,EAAU,IAAI,MAAM,CAAA;AAAA,IACzC,SAAS,YAAY;AACnB,MAAA,IAAI,EAAA,IAAM,IAAA,EAAM,MAAM,IAAI,MAAM,YAAY,CAAA;AAC5C,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,MAAA,CAAU,UAAU,EAAE,EAAA,EAAI,QAAQ,CAAA;AAChE,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB,CAAA;AAAA,IACA,OAAA,EAAS;AAAA,GACV,CAAA;AACH","file":"chunk-ZK5C3OOT.js","sourcesContent":["import { useQuery, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useDataProvider } from \"../provider/context\";\nimport type { BaseRecord, Identifier } from \"../provider/types\";\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface UseOneOptions {\n /** Supabase select/join syntax, e.g. \"*, author:profiles(name)\". */\n select?: string;\n /** Defaults to `!!id` — won't fetch when id is falsy. */\n enabled?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch a single record by id, powered by TanStack Query.\n *\n * Returns the full `UseQueryResult<T>` with the data unwrapped from the\n * provider's `GetOneResult` wrapper.\n */\nexport function useOne<T extends BaseRecord = BaseRecord>(\n resource: string,\n id: Identifier | undefined | null,\n options: UseOneOptions = {},\n): UseQueryResult<T> {\n const provider = useDataProvider();\n\n const { select, enabled } = options;\n const isEnabled = enabled ?? !!id;\n\n return useQuery<T>({\n // `select` is part of the key because it is part of the REQUEST: two pages\n // asking for the same record with different projections get different\n // rows back, and keying only on the id makes them share one cache entry.\n // Whichever mounted first wins for the whole staleTime window, and the\n // other silently renders a record missing the joined fields it asked for\n // — no error, no refetch, just absent data. `useResource`'s list key has\n // always included `select` (it keys on the whole `params` object); this\n // was the one place it did not.\n queryKey: [resource, \"detail\", id, select],\n queryFn: async () => {\n if (id == null) throw new Error(\"Missing id\");\n const result = await provider.getOne<T>(resource, { id, select });\n return result.data;\n },\n enabled: isEnabled,\n });\n}\n"]}
@@ -17,6 +17,12 @@ interface ResourceFormFieldDef {
17
17
  min?: number;
18
18
  max?: number;
19
19
  rows?: number;
20
+ /**
21
+ * Longest value accepted by a text-like field (text, email, url, textarea).
22
+ * Validated on save, and the web renderer also sets the input's
23
+ * `maxLength` so the user cannot type past it.
24
+ */
25
+ maxLength?: number;
20
26
  /** Relation field configuration. */
21
27
  relation?: {
22
28
  resource: string;