@marktiderman/genesis-core 1.2.0 → 1.4.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.
- package/README.md +142 -0
- package/dist/{chunk-4W4W7F4I.js → chunk-6MBACQYA.js} +6 -3
- package/dist/chunk-6MBACQYA.js.map +1 -0
- package/dist/chunk-7NUL32W7.js +14 -0
- package/dist/chunk-7NUL32W7.js.map +1 -0
- package/dist/{chunk-6V2TIN3G.js → chunk-GGSXSI6Z.js} +6 -5
- package/dist/chunk-GGSXSI6Z.js.map +1 -0
- package/dist/{chunk-5FTUB2M6.js → chunk-IHWWJUEN.js} +47 -15
- package/dist/chunk-IHWWJUEN.js.map +1 -0
- package/dist/chunk-NW26Z3LZ.js +303 -0
- package/dist/chunk-NW26Z3LZ.js.map +1 -0
- package/dist/{chunk-QFTMHOEH.js → chunk-P4OIIRQS.js} +22 -13
- package/dist/chunk-P4OIIRQS.js.map +1 -0
- package/dist/{chunk-3P4PHPMJ.js → chunk-ZK5C3OOT.js} +3 -3
- package/dist/{chunk-3P4PHPMJ.js.map → chunk-ZK5C3OOT.js.map} +1 -1
- package/dist/form-types.d.ts +6 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +6 -5
- package/dist/hooks/related-keys.d.ts +11 -0
- package/dist/hooks/related-keys.js +3 -0
- package/dist/hooks/related-keys.js.map +1 -0
- package/dist/hooks/use-one.d.ts +1 -1
- package/dist/hooks/use-one.js +2 -2
- package/dist/hooks/use-resource-form.d.ts +19 -1
- package/dist/hooks/use-resource-form.js +4 -3
- package/dist/hooks/use-resource.d.ts +14 -1
- package/dist/hooks/use-resource.js +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -7
- package/dist/provider/index.d.ts +140 -6
- package/dist/provider/index.js +3 -3
- package/dist/schema/index.d.ts +3 -3
- package/dist/schema/index.js +1 -1
- package/dist/{types-C7UVaF87.d.ts → types-zILrlHuV.d.ts} +226 -4
- package/dist/{write-outcome-D8kjoQ1A.d.ts → write-outcome-BE3Tk6bA.d.ts} +10 -1
- package/package.json +2 -2
- package/dist/chunk-4W4W7F4I.js.map +0 -1
- package/dist/chunk-5FTUB2M6.js.map +0 -1
- package/dist/chunk-6AA4ISUA.js +0 -125
- package/dist/chunk-6AA4ISUA.js.map +0 -1
- package/dist/chunk-6V2TIN3G.js.map +0 -1
- package/dist/chunk-QFTMHOEH.js.map +0 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { isDataWriteError, DataWriteError } from './chunk-6MBACQYA.js';
|
|
2
|
+
|
|
3
|
+
// src/provider/postgrest-errors.ts
|
|
4
|
+
function writeOutcomeFromCode(code) {
|
|
5
|
+
if (!code) return "error";
|
|
6
|
+
if (code === "42501") return "forbidden";
|
|
7
|
+
if (code === "PGRST116") return "gone";
|
|
8
|
+
if (code === "PGRST301" || code === "PGRST302" || code === "PGRST303") return "unauthenticated";
|
|
9
|
+
if (code === "28000" || code === "28P01") return "unauthenticated";
|
|
10
|
+
if (/^2[23][0-9A-Z]{3}$/.test(code)) return "invalid";
|
|
11
|
+
return "error";
|
|
12
|
+
}
|
|
13
|
+
function toDataWriteError(error) {
|
|
14
|
+
if (isDataWriteError(error)) return error;
|
|
15
|
+
const shape = typeof error === "object" && error !== null ? error : {};
|
|
16
|
+
const code = typeof shape.code === "string" ? shape.code : void 0;
|
|
17
|
+
const message = typeof shape.message === "string" && shape.message ? shape.message : error instanceof Error ? error.message : String(error);
|
|
18
|
+
return new DataWriteError(writeOutcomeFromCode(code), message, { code, cause: error });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/provider/supabase-provider.ts
|
|
22
|
+
function escapeIlike(value) {
|
|
23
|
+
return value.replace(/[%_\\]/g, (char) => `\\${char}`);
|
|
24
|
+
}
|
|
25
|
+
function isIdentifier(value) {
|
|
26
|
+
return typeof value === "string" || typeof value === "number";
|
|
27
|
+
}
|
|
28
|
+
function createSupabaseProvider(client, options = {}) {
|
|
29
|
+
function applyFilters(query, filters) {
|
|
30
|
+
if (!filters) return query;
|
|
31
|
+
for (const f of filters) {
|
|
32
|
+
switch (f.operator) {
|
|
33
|
+
case "eq":
|
|
34
|
+
query = query.eq(f.field, f.value);
|
|
35
|
+
break;
|
|
36
|
+
case "neq":
|
|
37
|
+
query = query.neq(f.field, f.value);
|
|
38
|
+
break;
|
|
39
|
+
case "gt":
|
|
40
|
+
query = query.gt(f.field, f.value);
|
|
41
|
+
break;
|
|
42
|
+
case "lt":
|
|
43
|
+
query = query.lt(f.field, f.value);
|
|
44
|
+
break;
|
|
45
|
+
case "gte":
|
|
46
|
+
query = query.gte(f.field, f.value);
|
|
47
|
+
break;
|
|
48
|
+
case "lte":
|
|
49
|
+
query = query.lte(f.field, f.value);
|
|
50
|
+
break;
|
|
51
|
+
case "contains":
|
|
52
|
+
query = query.ilike(f.field, `%${escapeIlike(String(f.value))}%`);
|
|
53
|
+
break;
|
|
54
|
+
case "in":
|
|
55
|
+
query = query.in(f.field, f.value);
|
|
56
|
+
break;
|
|
57
|
+
case "is_null":
|
|
58
|
+
query = query.is(f.field, null);
|
|
59
|
+
break;
|
|
60
|
+
case "is_not_null":
|
|
61
|
+
query = query.not(f.field, "is", null);
|
|
62
|
+
break;
|
|
63
|
+
case "between": {
|
|
64
|
+
const [lo, hi] = f.value;
|
|
65
|
+
query = query.gte(f.field, lo).lte(f.field, hi);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`[SupabaseProvider] Unknown filter operator "${f.operator}"`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return query;
|
|
73
|
+
}
|
|
74
|
+
function throwIfError(result) {
|
|
75
|
+
if (result.error) {
|
|
76
|
+
const pgError = result.error;
|
|
77
|
+
const message = pgError.message ?? String(result.error);
|
|
78
|
+
const err = new Error(message, { cause: result.error });
|
|
79
|
+
Object.assign(err, { code: pgError.code, hint: pgError.hint });
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const from = (resource) => client.from(resource);
|
|
84
|
+
const configFor = (resource) => options.resources?.[resource] ?? {};
|
|
85
|
+
const nounFor = (resource) => configFor(resource).noun ?? "record";
|
|
86
|
+
function isBinTable(resource) {
|
|
87
|
+
const mode = configFor(resource).deleteMode;
|
|
88
|
+
if (mode) return mode === "bin";
|
|
89
|
+
const bin = options.binTables;
|
|
90
|
+
if (!bin) return false;
|
|
91
|
+
return typeof bin === "function" ? bin(resource) : bin.includes(resource);
|
|
92
|
+
}
|
|
93
|
+
async function run(query) {
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = await query;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw toDataWriteError(error);
|
|
99
|
+
}
|
|
100
|
+
if (result.error) throw toDataWriteError(result.error);
|
|
101
|
+
if (Array.isArray(result.data)) return result.data;
|
|
102
|
+
return result.data == null ? [] : [result.data];
|
|
103
|
+
}
|
|
104
|
+
async function isVisible(resource, id) {
|
|
105
|
+
return (await run(from(resource).select("id").eq("id", id))).length > 0;
|
|
106
|
+
}
|
|
107
|
+
async function readBack(resource, id) {
|
|
108
|
+
return (await run(from(resource).select("*").eq("id", id)))[0];
|
|
109
|
+
}
|
|
110
|
+
function goneError(resource) {
|
|
111
|
+
return new DataWriteError(
|
|
112
|
+
"gone",
|
|
113
|
+
`This ${nounFor(resource)} no longer exists, or you can no longer see it.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
async function noRowsError(resource, id, verb) {
|
|
117
|
+
let visible;
|
|
118
|
+
try {
|
|
119
|
+
visible = await isVisible(resource, id);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const cause = toDataWriteError(error);
|
|
122
|
+
return new DataWriteError(
|
|
123
|
+
cause.outcome === "unauthenticated" ? "unauthenticated" : "error",
|
|
124
|
+
`This ${nounFor(resource)} was not ${verb === "change" ? "changed" : "deleted"}, and reading it back failed (${cause.message}).`,
|
|
125
|
+
{ code: cause.code, cause: error }
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (visible) {
|
|
129
|
+
return new DataWriteError("forbidden", `You can't ${verb} this ${nounFor(resource)}.`);
|
|
130
|
+
}
|
|
131
|
+
return goneError(resource);
|
|
132
|
+
}
|
|
133
|
+
async function prepare(resource, values, context) {
|
|
134
|
+
const rule = configFor(resource).prepare;
|
|
135
|
+
if (!rule) return values;
|
|
136
|
+
try {
|
|
137
|
+
return await rule({ ...values }, context);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw toDataWriteError(error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function committed(event) {
|
|
143
|
+
if (!options.onWrite) return;
|
|
144
|
+
try {
|
|
145
|
+
await options.onWrite(event);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(`[SupabaseProvider] onWrite failed for "${event.resource}":`, error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const provider = {
|
|
151
|
+
async getList(resource, params) {
|
|
152
|
+
let query = client.from(resource).select(
|
|
153
|
+
params.select ?? "*",
|
|
154
|
+
{ count: "exact" }
|
|
155
|
+
);
|
|
156
|
+
query = applyFilters(query, params.filters);
|
|
157
|
+
if (params.search && params.search.query && params.search.fields.length > 0) {
|
|
158
|
+
const escaped = escapeIlike(params.search.query);
|
|
159
|
+
const safeFieldPattern = /^[a-zA-Z_][a-zA-Z0-9_.]*$/;
|
|
160
|
+
const clauses = params.search.fields.filter((f) => safeFieldPattern.test(f)).map((f) => `${f}.ilike.%${escaped}%`).join(",");
|
|
161
|
+
if (clauses) {
|
|
162
|
+
query = query.or(clauses);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (params.sort) {
|
|
166
|
+
query = query.order(params.sort.field, {
|
|
167
|
+
ascending: params.sort.order === "asc"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (params.pagination) {
|
|
171
|
+
const { page, perPage } = params.pagination;
|
|
172
|
+
const from2 = (page - 1) * perPage;
|
|
173
|
+
const to = from2 + perPage - 1;
|
|
174
|
+
query = query.range(from2, to);
|
|
175
|
+
}
|
|
176
|
+
const result = await query;
|
|
177
|
+
throwIfError(result);
|
|
178
|
+
return { data: result.data ?? [], total: result.count ?? 0 };
|
|
179
|
+
},
|
|
180
|
+
async getOne(resource, params) {
|
|
181
|
+
const result = await client.from(resource).select(params.select ?? "*").eq("id", params.id).single();
|
|
182
|
+
throwIfError(result);
|
|
183
|
+
return { data: result.data };
|
|
184
|
+
},
|
|
185
|
+
async getMany(resource, params) {
|
|
186
|
+
if (params.ids.length === 0) return { data: [] };
|
|
187
|
+
const result = await client.from(resource).select("*").in("id", params.ids);
|
|
188
|
+
throwIfError(result);
|
|
189
|
+
return { data: result.data ?? [] };
|
|
190
|
+
},
|
|
191
|
+
async create(resource, params) {
|
|
192
|
+
const row = await prepare(resource, params.data, {
|
|
193
|
+
resource,
|
|
194
|
+
action: "create"
|
|
195
|
+
});
|
|
196
|
+
const override = configFor(resource).create;
|
|
197
|
+
let record;
|
|
198
|
+
if (override) {
|
|
199
|
+
let returned;
|
|
200
|
+
try {
|
|
201
|
+
returned = await override(row);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw toDataWriteError(error);
|
|
204
|
+
}
|
|
205
|
+
if (isIdentifier(returned)) {
|
|
206
|
+
record = await readBack(resource, returned).catch(() => void 0) ?? { id: returned };
|
|
207
|
+
} else if (returned && typeof returned === "object" && isIdentifier(returned.id)) {
|
|
208
|
+
record = returned;
|
|
209
|
+
} else {
|
|
210
|
+
throw new DataWriteError(
|
|
211
|
+
"error",
|
|
212
|
+
`[SupabaseProvider] The create rule for "${resource}" returned neither the new row nor its id.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
const rows = await run(from(resource).insert(row).select("*").single());
|
|
217
|
+
record = rows[0];
|
|
218
|
+
}
|
|
219
|
+
await committed({ resource, kind: "create", id: record.id, record });
|
|
220
|
+
return { data: record };
|
|
221
|
+
},
|
|
222
|
+
async update(resource, params) {
|
|
223
|
+
const { id } = params;
|
|
224
|
+
const row = await prepare(resource, params.data, {
|
|
225
|
+
resource,
|
|
226
|
+
action: "update",
|
|
227
|
+
id
|
|
228
|
+
});
|
|
229
|
+
const override = configFor(resource).update;
|
|
230
|
+
let record;
|
|
231
|
+
if (override) {
|
|
232
|
+
let returned;
|
|
233
|
+
try {
|
|
234
|
+
returned = await override(id, row);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
throw toDataWriteError(error);
|
|
237
|
+
}
|
|
238
|
+
const result = returned;
|
|
239
|
+
if (result == null || result === false) throw await noRowsError(resource, id, "change");
|
|
240
|
+
if (isIdentifier(result) || result === true) {
|
|
241
|
+
const readId = result === true ? id : result;
|
|
242
|
+
record = await readBack(resource, readId).catch(() => void 0) ?? { ...row, id: readId };
|
|
243
|
+
} else if (typeof result === "object") {
|
|
244
|
+
record = { id, ...result };
|
|
245
|
+
} else {
|
|
246
|
+
throw new DataWriteError(
|
|
247
|
+
"error",
|
|
248
|
+
`[SupabaseProvider] The update rule for "${resource}" returned neither the row, its id, nor nothing.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
const rows = await run(from(resource).update(row).eq("id", id).select("*"));
|
|
253
|
+
if (rows.length === 0) throw await noRowsError(resource, id, "change");
|
|
254
|
+
record = rows[0];
|
|
255
|
+
}
|
|
256
|
+
await committed({ resource, kind: "update", id, record });
|
|
257
|
+
return { data: record };
|
|
258
|
+
},
|
|
259
|
+
async deleteOne(resource, params) {
|
|
260
|
+
const { id } = params;
|
|
261
|
+
let deletedId = id;
|
|
262
|
+
if (isBinTable(resource)) {
|
|
263
|
+
if (!await isVisible(resource, id)) throw goneError(resource);
|
|
264
|
+
await run(from(resource).delete().eq("id", id));
|
|
265
|
+
let stillVisible;
|
|
266
|
+
try {
|
|
267
|
+
stillVisible = await isVisible(resource, id);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const cause = toDataWriteError(error);
|
|
270
|
+
throw new DataWriteError(
|
|
271
|
+
"error",
|
|
272
|
+
`The ${nounFor(resource)} may not have been deleted: checking it failed (${cause.message}).`,
|
|
273
|
+
{ code: cause.code, cause: error }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
if (stillVisible) {
|
|
277
|
+
throw new DataWriteError("forbidden", `You can't delete this ${nounFor(resource)}.`);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
const rows = await run(from(resource).delete().eq("id", id).select("id"));
|
|
281
|
+
if (rows.length === 0) throw await noRowsError(resource, id, "delete");
|
|
282
|
+
const returnedId = rows[0].id;
|
|
283
|
+
if (isIdentifier(returnedId)) deletedId = returnedId;
|
|
284
|
+
}
|
|
285
|
+
await committed({ resource, kind: "delete", id: deletedId });
|
|
286
|
+
return { data: { id: deletedId } };
|
|
287
|
+
},
|
|
288
|
+
toFormValues(resource, record) {
|
|
289
|
+
const map = configFor(resource).toFormValues;
|
|
290
|
+
return map ? map(record) : record;
|
|
291
|
+
},
|
|
292
|
+
invalidates(event) {
|
|
293
|
+
const keys = configFor(event.resource).invalidates;
|
|
294
|
+
if (!keys) return [];
|
|
295
|
+
return typeof keys === "function" ? keys(event) : keys;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
return provider;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export { createSupabaseProvider, toDataWriteError, writeOutcomeFromCode };
|
|
302
|
+
//# sourceMappingURL=chunk-NW26Z3LZ.js.map
|
|
303
|
+
//# sourceMappingURL=chunk-NW26Z3LZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider/postgrest-errors.ts","../src/provider/supabase-provider.ts"],"names":["from"],"mappings":"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","file":"chunk-NW26Z3LZ.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\";\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/**\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 */\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 };\n const code = typeof shape.code === \"string\" ? shape.code : undefined;\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 { 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 /**\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\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 function throwIfError(result: { error: unknown }): void {\n if (result.error) {\n const pgError = result.error as { message?: string; code?: string; hint?: string };\n const message = pgError.message ?? String(result.error);\n const err = new Error(message, { cause: result.error });\n Object.assign(err, { code: pgError.code, hint: pgError.hint });\n throw err;\n }\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 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.\n if (params.search && params.search.query && params.search.fields.length > 0) {\n const escaped = escapeIlike(params.search.query);\n const safeFieldPattern = /^[a-zA-Z_][a-zA-Z0-9_.]*$/;\n const clauses = params.search.fields\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.\n if (params.sort) {\n query = query.order(params.sort.field, {\n ascending: params.sort.order === \"asc\",\n });\n }\n\n // Pagination (offset).\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 const result = (await query) as {\n data: unknown[];\n error: unknown;\n count?: number | null;\n };\n throwIfError(result);\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 throwIfError(result);\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 throwIfError(result);\n return { data: result.data ?? [] } as never;\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"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { relatedKeys } from './chunk-7NUL32W7.js';
|
|
2
|
+
import { useDataProvider } from './chunk-GGSXSI6Z.js';
|
|
2
3
|
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
-
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
|
|
4
|
+
import { useQueryClient, useQuery, keepPreviousData, useMutation } from '@tanstack/react-query';
|
|
4
5
|
|
|
5
6
|
function stableFilterKey(filters) {
|
|
6
7
|
if (!filters) return void 0;
|
|
@@ -22,6 +23,8 @@ function useResource(resource, options = {}) {
|
|
|
22
23
|
select,
|
|
23
24
|
searchFields: searchFieldsOption,
|
|
24
25
|
enabled = true,
|
|
26
|
+
invalidates,
|
|
27
|
+
keepPreviousData: keepPreviousData$1 = false,
|
|
25
28
|
onCreateSuccess,
|
|
26
29
|
onUpdateSuccess,
|
|
27
30
|
onDeleteSuccess,
|
|
@@ -76,11 +79,17 @@ function useResource(resource, options = {}) {
|
|
|
76
79
|
const list = useQuery({
|
|
77
80
|
queryKey: listQueryKey,
|
|
78
81
|
queryFn: () => provider.getList(resource, params),
|
|
79
|
-
enabled
|
|
82
|
+
enabled,
|
|
83
|
+
placeholderData: keepPreviousData$1 ? keepPreviousData : void 0
|
|
80
84
|
});
|
|
81
|
-
const
|
|
82
|
-
() =>
|
|
83
|
-
|
|
85
|
+
const invalidateAfter = useCallback(
|
|
86
|
+
(event) => {
|
|
87
|
+
void queryClient.invalidateQueries({ queryKey: [resource] });
|
|
88
|
+
for (const queryKey of relatedKeys(provider, event, invalidates)) {
|
|
89
|
+
void queryClient.invalidateQueries({ queryKey: [...queryKey] });
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
[queryClient, resource, provider, invalidates]
|
|
84
93
|
);
|
|
85
94
|
const defaultOnError = useCallback(
|
|
86
95
|
(error) => {
|
|
@@ -94,7 +103,7 @@ function useResource(resource, options = {}) {
|
|
|
94
103
|
return result.data;
|
|
95
104
|
},
|
|
96
105
|
onSuccess: (data) => {
|
|
97
|
-
|
|
106
|
+
invalidateAfter({ resource, kind: "create", id: data.id, record: data });
|
|
98
107
|
onCreateSuccess?.(data);
|
|
99
108
|
},
|
|
100
109
|
onError: onError ?? defaultOnError
|
|
@@ -104,8 +113,8 @@ function useResource(resource, options = {}) {
|
|
|
104
113
|
const result = await provider.update(resource, { id, data });
|
|
105
114
|
return result.data;
|
|
106
115
|
},
|
|
107
|
-
onSuccess: (data) => {
|
|
108
|
-
|
|
116
|
+
onSuccess: (data, { id }) => {
|
|
117
|
+
invalidateAfter({ resource, kind: "update", id, record: data });
|
|
109
118
|
onUpdateSuccess?.(data);
|
|
110
119
|
},
|
|
111
120
|
onError: onError ?? defaultOnError
|
|
@@ -114,8 +123,8 @@ function useResource(resource, options = {}) {
|
|
|
114
123
|
mutationFn: async (id) => {
|
|
115
124
|
await provider.deleteOne(resource, { id });
|
|
116
125
|
},
|
|
117
|
-
onSuccess: () => {
|
|
118
|
-
|
|
126
|
+
onSuccess: (_data, id) => {
|
|
127
|
+
invalidateAfter({ resource, kind: "delete", id });
|
|
119
128
|
onDeleteSuccess?.();
|
|
120
129
|
},
|
|
121
130
|
onError: onError ?? defaultOnError
|
|
@@ -152,5 +161,5 @@ function useResource(resource, options = {}) {
|
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
export { useResource };
|
|
155
|
-
//# sourceMappingURL=chunk-
|
|
156
|
-
//# sourceMappingURL=chunk-
|
|
164
|
+
//# sourceMappingURL=chunk-P4OIIRQS.js.map
|
|
165
|
+
//# sourceMappingURL=chunk-P4OIIRQS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-resource.ts"],"names":["keepPreviousData","keepPreviousPlaceholder"],"mappings":"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","file":"chunk-P4OIIRQS.js","sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport {\n keepPreviousData as keepPreviousPlaceholder,\n useQuery,\n useMutation,\n useQueryClient,\n type UseQueryResult,\n type UseMutationResult,\n} from \"@tanstack/react-query\";\nimport { useDataProvider } from \"../provider/context\";\nimport type {\n BaseRecord,\n Identifier,\n SortParam,\n FilterParam,\n ListParams,\n ListResult,\n InvalidationKey,\n WriteEvent,\n} from \"../provider/types\";\nimport { relatedKeys } from \"./related-keys\";\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface UseResourceOptions<T> {\n defaultSort?: SortParam;\n /**\n * Re-synced whenever its value changes (deep-equal, order-independent),\n * not just its identity — safe to pass a scope/audience-derived array that\n * changes across renders. This only covers CHANGES on an already-mounted\n * hook, though: if this starts `undefined` while the real value resolves\n * asynchronously, gate `enabled` on that same readiness signal too — the\n * hook cannot tell \"no defaultFilters intended\" apart from \"not resolved\n * yet,\" so an ungated hook's very first fetch can still run unscoped.\n */\n defaultFilters?: FilterParam[];\n defaultPerPage?: number;\n /** Supabase select/join syntax, e.g. \"*, author:profiles(name)\". */\n select?: string;\n /** Fields to search across when setSearch is called. Required for server-side search. */\n searchFields?: string[];\n /** Disable auto-fetching. */\n enabled?: boolean;\n /**\n * Other query keys to invalidate after a create, update or delete, on top\n * of `[resource]` and the provider's own `invalidates` (e.g. `[[\"me\"]]`\n * when a write changes the signed-in user's roles).\n */\n invalidates?: InvalidationKey[];\n /**\n * While the next page / sort / search is loading, keep returning the\n * previous result (`list.isPlaceholderData` is true meanwhile) instead of\n * dropping to `undefined`. A paged list then stays on screen between pages\n * rather than flashing a skeleton. Default false.\n */\n keepPreviousData?: boolean;\n onCreateSuccess?: (data: T) => void;\n onUpdateSuccess?: (data: T) => void;\n onDeleteSuccess?: () => void;\n onError?: (error: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport interface UseResourceReturn<T> {\n /** TanStack Query result for the list query. */\n list: UseQueryResult<ListResult<T>>;\n /** Current list params (pagination, sort, filters, search). */\n params: ListParams;\n setSort: (sort: SortParam | undefined) => void;\n setFilters: (filters: FilterParam[]) => void;\n setPage: (page: number) => void;\n setPerPage: (perPage: number) => void;\n setSearch: (query: string) => void;\n /** Create a new record. Returns the created item. */\n create: (data: Partial<T>) => Promise<T>;\n /** Update an existing record by id. Returns the updated item. */\n update: (id: Identifier, data: Partial<T>) => Promise<T>;\n /** Delete a record by id. */\n remove: (id: Identifier) => Promise<void>;\n /** True when any mutation is in-flight. */\n isMutating: boolean;\n createMutation: UseMutationResult<T, Error, Partial<T>>;\n updateMutation: UseMutationResult<T, Error, { id: Identifier; data: Partial<T> }>;\n deleteMutation: UseMutationResult<void, Error, Identifier>;\n}\n\n// ---------------------------------------------------------------------------\n// Filter identity — a deep-equality key independent of object-key order, so\n// a caller that rebuilds the same logical filters via a differently-ordered\n// spread (e.g. `{...base, operator: \"eq\"}` vs `{operator: \"eq\", ...base}`)\n// doesn't trip a spurious resync + pagination reset on an unrelated rerender.\n// ---------------------------------------------------------------------------\n\nfunction stableFilterKey(filters: FilterParam[] | undefined): string | undefined {\n if (!filters) return undefined;\n return JSON.stringify(\n filters.map((f) => {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(f).sort()) {\n sorted[key] = (f as unknown as Record<string, unknown>)[key];\n }\n return sorted;\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useResource<T extends BaseRecord = BaseRecord>(\n resource: string,\n options: UseResourceOptions<T> = {},\n): UseResourceReturn<T> {\n const {\n defaultSort,\n defaultFilters,\n defaultPerPage = 25,\n select,\n searchFields: searchFieldsOption,\n enabled = true,\n invalidates,\n keepPreviousData = false,\n onCreateSuccess,\n onUpdateSuccess,\n onDeleteSuccess,\n onError,\n } = options;\n\n const provider = useDataProvider();\n const queryClient = useQueryClient();\n\n // -------------------------------------------------------------------------\n // State\n // -------------------------------------------------------------------------\n\n const [sort, setSortRaw] = useState<SortParam | undefined>(defaultSort);\n const [filters, setFiltersRaw] = useState<FilterParam[]>(defaultFilters ?? []);\n const [page, setPage] = useState(1);\n const [perPage, setPerPageRaw] = useState(defaultPerPage);\n const [searchQuery, setSearchQuery] = useState(\"\");\n\n // `defaultFilters` often resolves asynchronously — a caller's scope/audience\n // (team, project, ...) can still be loading on first render, so the array\n // it passes in changes value (not just identity) after mount, and may later\n // clear back to undefined (e.g. the scope itself is cleared). Re-syncing\n // here, during render rather than in an effect, means `filters` is already\n // correct by the time `params`/`list` below compute for this pass — no\n // stray unscoped fetch on the frame before an effect would have caught up.\n //\n // This only closes the gap for `defaultFilters` CHANGING across renders on\n // an already-mounted, already-enabled hook (e.g. a scope switch). It cannot\n // help the very first render: `filters` still seeds from whatever\n // `defaultFilters` is on mount, and `undefined` here is genuinely ambiguous\n // between \"this caller never uses defaultFilters\" (in which case forcing a\n // fetch to wait would break it forever) and \"the real value hasn't resolved\n // yet.\" A caller in the second case MUST also gate `enabled` on that same\n // readiness signal for its very first fetch to be correctly scoped — this\n // hook has no way to tell the two cases apart on its own.\n const defaultFiltersKey = stableFilterKey(defaultFilters);\n const [syncedFiltersKey, setSyncedFiltersKey] = useState(defaultFiltersKey);\n if (defaultFiltersKey !== syncedFiltersKey) {\n setSyncedFiltersKey(defaultFiltersKey);\n setFiltersRaw(defaultFilters ?? []);\n setPage(1);\n }\n\n const setSort = useCallback((s: SortParam | undefined) => {\n setSortRaw(s);\n setPage(1);\n }, []);\n\n const setFilters = useCallback((f: FilterParam[]) => {\n setFiltersRaw(f);\n setPage(1);\n }, []);\n\n const setPerPage = useCallback((value: number) => {\n setPerPageRaw(value);\n setPage(1);\n }, []);\n\n const setSearch = useCallback((query: string) => {\n setSearchQuery(query);\n setPage(1);\n }, []);\n\n // -------------------------------------------------------------------------\n // Params\n // -------------------------------------------------------------------------\n\n const params = useMemo<ListParams>(() => {\n const p: ListParams = {\n pagination: { page, perPage },\n sort,\n filters: filters.length > 0 ? filters : undefined,\n select,\n };\n if (searchQuery && searchFieldsOption && searchFieldsOption.length > 0) {\n p.search = { query: searchQuery, fields: searchFieldsOption };\n }\n return p;\n }, [page, perPage, sort, filters, select, searchQuery, searchFieldsOption]);\n\n // -------------------------------------------------------------------------\n // List query\n // -------------------------------------------------------------------------\n\n const listQueryKey = useMemo(\n () => [resource, \"list\", params] as const,\n [resource, params],\n );\n\n const list = useQuery<ListResult<T>>({\n queryKey: listQueryKey,\n queryFn: () => provider.getList<T>(resource, params),\n enabled,\n placeholderData: keepPreviousData ? keepPreviousPlaceholder : undefined,\n });\n\n // -------------------------------------------------------------------------\n // Mutations\n // -------------------------------------------------------------------------\n\n const invalidateAfter = useCallback(\n (event: WriteEvent) => {\n void queryClient.invalidateQueries({ queryKey: [resource] });\n for (const queryKey of relatedKeys(provider, event, invalidates)) {\n void queryClient.invalidateQueries({ queryKey: [...queryKey] });\n }\n },\n [queryClient, resource, provider, invalidates],\n );\n\n const defaultOnError = useCallback(\n (error: Error) => {\n console.error(`[useResource] ${resource} mutation failed:`, error.message);\n },\n [resource],\n );\n\n const createMutation = useMutation<T, Error, Partial<T>>({\n mutationFn: async (data) => {\n const result = await provider.create<T>(resource, { data });\n return result.data;\n },\n onSuccess: (data) => {\n invalidateAfter({ resource, kind: \"create\", id: data.id, record: data });\n onCreateSuccess?.(data);\n },\n onError: onError ?? defaultOnError,\n });\n\n const updateMutation = useMutation<\n T,\n Error,\n { id: Identifier; data: Partial<T> }\n >({\n mutationFn: async ({ id, data }) => {\n const result = await provider.update<T>(resource, { id, data });\n return result.data;\n },\n onSuccess: (data, { id }) => {\n invalidateAfter({ resource, kind: \"update\", id, record: data });\n onUpdateSuccess?.(data);\n },\n onError: onError ?? defaultOnError,\n });\n\n const deleteMutation = useMutation<void, Error, Identifier>({\n mutationFn: async (id) => {\n await provider.deleteOne(resource, { id });\n },\n onSuccess: (_data, id) => {\n invalidateAfter({ resource, kind: \"delete\", id });\n onDeleteSuccess?.();\n },\n onError: onError ?? defaultOnError,\n });\n\n // -------------------------------------------------------------------------\n // Convenience wrappers\n // -------------------------------------------------------------------------\n\n const create = useCallback(\n (data: Partial<T>) => createMutation.mutateAsync(data),\n [createMutation],\n );\n\n const update = useCallback(\n (id: Identifier, data: Partial<T>) =>\n updateMutation.mutateAsync({ id, data }),\n [updateMutation],\n );\n\n const remove = useCallback(\n (id: Identifier) => deleteMutation.mutateAsync(id),\n [deleteMutation],\n );\n\n const isMutating =\n createMutation.isPending ||\n updateMutation.isPending ||\n deleteMutation.isPending;\n\n return {\n list,\n params,\n setSort,\n setFilters,\n setPage,\n setPerPage,\n setSearch,\n create,\n update,\n remove,\n isMutating,\n createMutation,\n updateMutation,\n deleteMutation,\n };\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useDataProvider } from './chunk-
|
|
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-
|
|
29
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
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"]}
|
package/dist/form-types.d.ts
CHANGED
|
@@ -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;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ export { UseOneOptions, useOne } from './use-one.js';
|
|
|
3
3
|
export { UseResourceFormOptions, UseResourceFormReturn, buildArrayItemDefaults, useResourceForm } from './use-resource-form.js';
|
|
4
4
|
export { UseWizardReturn, WizardStep, useWizard } from './use-wizard.js';
|
|
5
5
|
import '@tanstack/react-query';
|
|
6
|
-
import '../types-
|
|
6
|
+
import '../types-zILrlHuV.js';
|
|
7
7
|
import '../form-types.js';
|
|
8
8
|
import 'react-hook-form';
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import '../chunk-EL7YGOTY.js';
|
|
2
|
-
export {
|
|
2
|
+
export { useWizard } from '../chunk-7KOLYF27.js';
|
|
3
|
+
export { buildArrayItemDefaults, useResourceForm } from '../chunk-IHWWJUEN.js';
|
|
3
4
|
import '../chunk-RYEXWQFD.js';
|
|
4
|
-
export { useOne } from '../chunk-
|
|
5
|
-
export { useResource } from '../chunk-
|
|
6
|
-
import '../chunk-
|
|
5
|
+
export { useOne } from '../chunk-ZK5C3OOT.js';
|
|
6
|
+
export { useResource } from '../chunk-P4OIIRQS.js';
|
|
7
|
+
import '../chunk-7NUL32W7.js';
|
|
8
|
+
import '../chunk-GGSXSI6Z.js';
|
|
7
9
|
import '../chunk-ZYJUEJ4Z.js';
|
|
8
|
-
export { useWizard } from '../chunk-7KOLYF27.js';
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { D as DataProvider, W as WriteEvent, p as InvalidationKey } from '../types-zILrlHuV.js';
|
|
2
|
+
import '../form-types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The cache keys a committed write makes stale beyond `[resource]`: the
|
|
6
|
+
* provider's own (`DataProvider.invalidates`) plus the caller's. A provider
|
|
7
|
+
* that throws here is logged and skipped; the write already committed.
|
|
8
|
+
*/
|
|
9
|
+
declare function relatedKeys(provider: DataProvider, event: WriteEvent, extra: InvalidationKey[] | undefined): InvalidationKey[];
|
|
10
|
+
|
|
11
|
+
export { relatedKeys };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"related-keys.js"}
|
package/dist/hooks/use-one.d.ts
CHANGED
package/dist/hooks/use-one.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { useOne } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { useOne } from '../chunk-ZK5C3OOT.js';
|
|
2
|
+
import '../chunk-GGSXSI6Z.js';
|
|
3
3
|
import '../chunk-ZYJUEJ4Z.js';
|
|
4
4
|
//# sourceMappingURL=use-one.js.map
|
|
5
5
|
//# sourceMappingURL=use-one.js.map
|