@marktiderman/genesis-core 1.1.1 → 1.2.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/LICENSE +21 -0
- package/dist/chunk-4W4W7F4I.js +20 -0
- package/dist/chunk-4W4W7F4I.js.map +1 -0
- package/dist/{chunk-RKR5K7Q3.js → chunk-6AA4ISUA.js} +2 -2
- package/dist/{chunk-RKR5K7Q3.js.map → chunk-6AA4ISUA.js.map} +1 -1
- package/dist/chunk-KV2HW4G4.js +41 -0
- package/dist/chunk-KV2HW4G4.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/use-one.d.ts +2 -1
- package/dist/hooks/use-resource.d.ts +2 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -1
- package/dist/provider/index.d.ts +7 -5
- package/dist/provider/index.js +2 -1
- package/dist/schema/index.d.ts +24 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/types-C7UVaF87.d.ts +319 -0
- package/dist/write-outcome-D8kjoQ1A.d.ts +38 -0
- package/package.json +14 -7
- package/dist/types-BzcZrTjl.d.ts +0 -119
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Tiderman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/schema/write-outcome.ts
|
|
2
|
+
var DataWriteError = class extends Error {
|
|
3
|
+
outcome;
|
|
4
|
+
current;
|
|
5
|
+
currentVersion;
|
|
6
|
+
constructor(outcome, message, options = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "DataWriteError";
|
|
9
|
+
this.outcome = outcome;
|
|
10
|
+
this.current = options.current;
|
|
11
|
+
this.currentVersion = options.currentVersion;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function isDataWriteError(error) {
|
|
15
|
+
return error instanceof Error && error.name === "DataWriteError" && typeof error.outcome === "string";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { DataWriteError, isDataWriteError };
|
|
19
|
+
//# sourceMappingURL=chunk-4W4W7F4I.js.map
|
|
20
|
+
//# sourceMappingURL=chunk-4W4W7F4I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema/write-outcome.ts"],"names":[],"mappings":";AAyCO,IAAM,cAAA,GAAN,cAA6B,KAAA,CAAM;AAAA,EAC/B,OAAA;AAAA,EACA,OAAA;AAAA,EACA,cAAA;AAAA,EAET,WAAA,CAAY,OAAA,EAAuB,OAAA,EAAiB,OAAA,GAAiC,EAAC,EAAG;AACvF,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,cAAA;AAAA,EAChC;AACF;AAMO,SAAS,iBAAiB,KAAA,EAAyC;AACxE,EAAA,OACE,iBAAiB,KAAA,IACjB,KAAA,CAAM,SAAS,gBAAA,IACf,OAAQ,MAAgC,OAAA,KAAY,QAAA;AAExD","file":"chunk-4W4W7F4I.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Why a write did not save (DATA-STUDIO.md \"Write safety\")\n//\n// A write that changes nothing is never reported as \"permission denied\" by\n// assumption. The provider re-reads the row and says what actually happened.\n// ---------------------------------------------------------------------------\n\nimport type { DataRow } from \"./types\";\n\n/**\n * - `conflict`: someone else changed the row since it was read. `current`\n * carries the row as it is now.\n * - `gone`: the row was deleted, or is no longer visible to this user. The\n * two cannot be told apart without a privileged look at the table, which\n * would show this user a fact about a row they cannot see.\n * - `forbidden`: the row is visible and unchanged, but the app or the\n * database does not let this user make this change. Only reported when\n * that is proven, never assumed from \"zero rows changed\".\n * - `invalid`: the store rejected the value (type, constraint, not-null).\n * - `audit_failed`: the audit entry could not be written, so the edit was\n * rolled back with it.\n * - `unauthenticated`: no signed-in user, or the session expired.\n * - `error`: anything else. The edit did not commit.\n */\nexport type WriteOutcome =\n | \"conflict\"\n | \"gone\"\n | \"forbidden\"\n | \"invalid\"\n | \"audit_failed\"\n | \"unauthenticated\"\n | \"error\";\n\nexport interface DataWriteErrorOptions {\n /** The row as it is now, for a `conflict`. Only fields the user may read. */\n current?: DataRow;\n /** The row's current version token, for a `conflict`. */\n currentVersion?: string | null;\n}\n\n/** Thrown by a provider when a write did not commit. */\nexport class DataWriteError extends Error {\n readonly outcome: WriteOutcome;\n readonly current?: DataRow;\n readonly currentVersion?: string | null;\n\n constructor(outcome: WriteOutcome, message: string, options: DataWriteErrorOptions = {}) {\n super(message);\n this.name = \"DataWriteError\";\n this.outcome = outcome;\n this.current = options.current;\n this.currentVersion = options.currentVersion;\n }\n}\n\n/**\n * `instanceof` fails when two copies of this package are installed, so check\n * the shape instead.\n */\nexport function isDataWriteError(error: unknown): error is DataWriteError {\n return (\n error instanceof Error &&\n error.name === \"DataWriteError\" &&\n typeof (error as { outcome?: unknown }).outcome === \"string\"\n );\n}\n"]}
|
|
@@ -121,5 +121,5 @@ function createSupabaseProvider(client) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export { createSupabaseProvider };
|
|
124
|
-
//# sourceMappingURL=chunk-
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
124
|
+
//# sourceMappingURL=chunk-6AA4ISUA.js.map
|
|
125
|
+
//# sourceMappingURL=chunk-6AA4ISUA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/supabase-provider.ts"],"names":[],"mappings":"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","file":"chunk-RKR5K7Q3.js","sourcesContent":["import type {\n DataProvider,\n FilterParam,\n} from \"./types\";\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\nfunction escapeIlike(value: string): string {\n return value.replace(/[%_\\\\]/g, (char) => `\\\\${char}`);\n}\n\nexport function createSupabaseProvider(client: SupabaseClientInput): 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 // 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 result = (await (client.from(resource) as SupabaseQueryBuilder)\n .insert(params.data as Record<string, unknown>)\n .select(\"*\")\n .single()) as { data: unknown; error: unknown };\n throwIfError(result);\n return { data: result.data } as never;\n },\n\n async update(resource, params) {\n const result = (await (client.from(resource) as SupabaseQueryBuilder)\n .update(params.data as Record<string, unknown>)\n .eq(\"id\", params.id)\n .select(\"*\")\n .single()) as { data: unknown; error: unknown };\n throwIfError(result);\n return { data: result.data } as never;\n },\n\n async deleteOne(resource, params) {\n const result = (await (client.from(resource) as SupabaseQueryBuilder)\n .delete()\n .eq(\"id\", params.id)\n .select(\"id\")\n .single()) as { data: { id: string | number } | null; error: unknown };\n throwIfError(result);\n if (!result.data) {\n throw new Error(`[SupabaseProvider] Delete failed: no row found with id \"${params.id}\" in \"${resource}\"`);\n }\n return { data: result.data };\n },\n };\n\n return provider;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider/supabase-provider.ts"],"names":[],"mappings":"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","file":"chunk-6AA4ISUA.js","sourcesContent":["import type {\n DataProvider,\n FilterParam,\n} from \"./types\";\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\nfunction escapeIlike(value: string): string {\n return value.replace(/[%_\\\\]/g, (char) => `\\\\${char}`);\n}\n\nexport function createSupabaseProvider(client: SupabaseClientInput): 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 // 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 result = (await (client.from(resource) as SupabaseQueryBuilder)\n .insert(params.data as Record<string, unknown>)\n .select(\"*\")\n .single()) as { data: unknown; error: unknown };\n throwIfError(result);\n return { data: result.data } as never;\n },\n\n async update(resource, params) {\n const result = (await (client.from(resource) as SupabaseQueryBuilder)\n .update(params.data as Record<string, unknown>)\n .eq(\"id\", params.id)\n .select(\"*\")\n .single()) as { data: unknown; error: unknown };\n throwIfError(result);\n return { data: result.data } as never;\n },\n\n async deleteOne(resource, params) {\n const result = (await (client.from(resource) as SupabaseQueryBuilder)\n .delete()\n .eq(\"id\", params.id)\n .select(\"id\")\n .single()) as { data: { id: string | number } | null; error: unknown };\n throwIfError(result);\n if (!result.data) {\n throw new Error(`[SupabaseProvider] Delete failed: no row found with id \"${params.id}\" in \"${resource}\"`);\n }\n return { data: result.data };\n },\n };\n\n return provider;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/schema/keys.ts
|
|
2
|
+
function keyFields(contract) {
|
|
3
|
+
return typeof contract.key === "string" ? [contract.key] : contract.key;
|
|
4
|
+
}
|
|
5
|
+
function keyValue(row, field) {
|
|
6
|
+
const value = row[field];
|
|
7
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
8
|
+
if (typeof value === "bigint" || typeof value === "boolean") return String(value);
|
|
9
|
+
throw new Error(
|
|
10
|
+
`Record key field "${field}" is ${value === null ? "null" : typeof value}; a key value must be a string or a number.`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function encodeRecordKey(contract, row) {
|
|
14
|
+
const fields = keyFields(contract);
|
|
15
|
+
if (fields.length === 0) throw new Error("This resource has no key, so its rows cannot be addressed.");
|
|
16
|
+
if (fields.length === 1) return keyValue(row, fields[0]);
|
|
17
|
+
return JSON.stringify(fields.map((field) => keyValue(row, field)));
|
|
18
|
+
}
|
|
19
|
+
function decodeRecordKey(contract, id) {
|
|
20
|
+
const fields = keyFields(contract);
|
|
21
|
+
if (fields.length === 0) throw new Error("This resource has no key, so its rows cannot be addressed.");
|
|
22
|
+
if (fields.length === 1) return { [fields[0]]: id };
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(String(id));
|
|
26
|
+
} catch {
|
|
27
|
+
throw new Error(`"${String(id)}" is not a composite key for (${fields.join(", ")}).`);
|
|
28
|
+
}
|
|
29
|
+
if (!Array.isArray(parsed) || parsed.length !== fields.length || !parsed.every((v) => typeof v === "string" || typeof v === "number")) {
|
|
30
|
+
throw new Error(`"${String(id)}" is not a composite key for (${fields.join(", ")}).`);
|
|
31
|
+
}
|
|
32
|
+
const out = {};
|
|
33
|
+
fields.forEach((field, i) => {
|
|
34
|
+
out[field] = parsed[i];
|
|
35
|
+
});
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { decodeRecordKey, encodeRecordKey, keyFields };
|
|
40
|
+
//# sourceMappingURL=chunk-KV2HW4G4.js.map
|
|
41
|
+
//# sourceMappingURL=chunk-KV2HW4G4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema/keys.ts"],"names":[],"mappings":";AAYO,SAAS,UAAU,QAAA,EAA4D;AACpF,EAAA,OAAO,OAAO,SAAS,GAAA,KAAQ,QAAA,GAAW,CAAC,QAAA,CAAS,GAAG,IAAI,QAAA,CAAS,GAAA;AACtE;AAEA,SAAS,QAAA,CAAS,KAAc,KAAA,EAAgC;AAC9D,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAK,CAAA;AACvB,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACnE,EAAA,IAAI,OAAO,UAAU,QAAA,IAAY,OAAO,UAAU,SAAA,EAAW,OAAO,OAAO,KAAK,CAAA;AAChF,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,qBAAqB,KAAK,CAAA,KAAA,EAAQ,UAAU,IAAA,GAAO,MAAA,GAAS,OAAO,KAAK,CAAA,2CAAA;AAAA,GAE1E;AACF;AASO,SAAS,eAAA,CACd,UACA,GAAA,EACY;AACZ,EAAA,MAAM,MAAA,GAAS,UAAU,QAAQ,CAAA;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,CAAA,EAAG,MAAM,IAAI,MAAM,4DAA4D,CAAA;AACrG,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG,OAAO,SAAS,GAAA,EAAK,MAAA,CAAO,CAAC,CAAE,CAAA;AACxD,EAAA,OAAO,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,GAAA,CAAI,CAAC,UAAU,QAAA,CAAS,GAAA,EAAK,KAAK,CAAC,CAAC,CAAA;AACnE;AAQO,SAAS,eAAA,CACd,UACA,EAAA,EACiC;AACjC,EAAA,MAAM,MAAA,GAAS,UAAU,QAAQ,CAAA;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,CAAA,EAAG,MAAM,IAAI,MAAM,4DAA4D,CAAA;AACrG,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,EAAE,CAAC,MAAA,CAAO,CAAC,CAAE,GAAG,EAAA,EAAG;AAEnD,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,EAAE,CAAC,CAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,CAAC,CAAA,8BAAA,EAAiC,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,EACtF;AACA,EAAA,IACE,CAAC,MAAM,OAAA,CAAQ,MAAM,KACrB,MAAA,CAAO,MAAA,KAAW,OAAO,MAAA,IACzB,CAAC,OAAO,KAAA,CAAM,CAAC,MAAM,OAAO,CAAA,KAAM,YAAY,OAAO,CAAA,KAAM,QAAQ,CAAA,EACnE;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,CAAC,CAAA,8BAAA,EAAiC,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,EACtF;AACA,EAAA,MAAM,MAAuC,EAAC;AAC9C,EAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,CAAA,KAAM;AAC3B,IAAA,GAAA,CAAI,KAAK,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AAAA,EACvB,CAAC,CAAA;AACD,EAAA,OAAO,GAAA;AACT","file":"chunk-KV2HW4G4.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Record identity for single and composite keys (DATA-STUDIO.md decision 13)\n//\n// `DataProvider` methods take one `Identifier`. A composite key travels\n// through that same slot as a canonical string, so every existing provider\n// signature stays as it is and a junction table still has one id per row.\n// ---------------------------------------------------------------------------\n\nimport type { Identifier } from \"../provider/types\";\nimport type { DataRow, ResourceContract } from \"./types\";\n\n/** The key of a contract as a list, whichever form `key` was declared in. */\nexport function keyFields(contract: Pick<ResourceContract, \"key\">): readonly string[] {\n return typeof contract.key === \"string\" ? [contract.key] : contract.key;\n}\n\nfunction keyValue(row: DataRow, field: string): string | number {\n const value = row[field];\n if (typeof value === \"string\" || typeof value === \"number\") return value;\n if (typeof value === \"bigint\" || typeof value === \"boolean\") return String(value);\n throw new Error(\n `Record key field \"${field}\" is ${value === null ? \"null\" : typeof value}; ` +\n \"a key value must be a string or a number.\",\n );\n}\n\n/**\n * One `Identifier` for a row.\n *\n * A single-field key returns the raw value, so `id`-keyed resources look\n * exactly as they always have. A composite key returns a JSON array of the\n * values in key order, e.g. `'[\"c-1\",\"u-7\"]'`.\n */\nexport function encodeRecordKey(\n contract: Pick<ResourceContract, \"key\">,\n row: DataRow,\n): Identifier {\n const fields = keyFields(contract);\n if (fields.length === 0) throw new Error(\"This resource has no key, so its rows cannot be addressed.\");\n if (fields.length === 1) return keyValue(row, fields[0]!);\n return JSON.stringify(fields.map((field) => keyValue(row, field)));\n}\n\n/**\n * The inverse of {@link encodeRecordKey}: field name → value.\n *\n * Throws on an id that does not fit the key, rather than guessing, because a\n * guessed key addresses the wrong row.\n */\nexport function decodeRecordKey(\n contract: Pick<ResourceContract, \"key\">,\n id: Identifier,\n): Record<string, string | number> {\n const fields = keyFields(contract);\n if (fields.length === 0) throw new Error(\"This resource has no key, so its rows cannot be addressed.\");\n if (fields.length === 1) return { [fields[0]!]: id };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(String(id));\n } catch {\n throw new Error(`\"${String(id)}\" is not a composite key for (${fields.join(\", \")}).`);\n }\n if (\n !Array.isArray(parsed) ||\n parsed.length !== fields.length ||\n !parsed.every((v) => typeof v === \"string\" || typeof v === \"number\")\n ) {\n throw new Error(`\"${String(id)}\" is not a composite key for (${fields.join(\", \")}).`);\n }\n const out: Record<string, string | number> = {};\n fields.forEach((field, i) => {\n out[field] = parsed[i] as string | number;\n });\n return out;\n}\n"]}
|
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-
|
|
7
|
-
import 'react-hook-form';
|
|
6
|
+
import '../types-C7UVaF87.js';
|
|
8
7
|
import '../form-types.js';
|
|
8
|
+
import 'react-hook-form';
|
package/dist/hooks/use-one.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UseQueryResult } from '@tanstack/react-query';
|
|
2
|
-
import { B as BaseRecord, I as Identifier } from '../types-
|
|
2
|
+
import { B as BaseRecord, I as Identifier } from '../types-C7UVaF87.js';
|
|
3
|
+
import '../form-types.js';
|
|
3
4
|
|
|
4
5
|
interface UseOneOptions {
|
|
5
6
|
/** Supabase select/join syntax, e.g. "*, author:profiles(name)". */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
|
|
2
|
-
import {
|
|
2
|
+
import { s as SortParam, i as FilterParam, m as ListResult, L as ListParams, I as Identifier, B as BaseRecord } from '../types-C7UVaF87.js';
|
|
3
|
+
import '../form-types.js';
|
|
3
4
|
|
|
4
5
|
interface UseResourceOptions<T> {
|
|
5
6
|
defaultSort?: SortParam;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { A as ArrayFilter, B as BaseRecord, C as CreateParams,
|
|
1
|
+
export { A as ArrayFilter, a as AuditEntry, B as BaseRecord, C as CreateParams, b as CreateResult, D as DataProvider, c as DataRow, d as DeleteParams, e as DeleteResult, F as FieldPermissions, f as FieldReference, g as FieldSchema, h as FilterOperator, i as FilterParam, G as GetManyParams, j as GetManyResult, k as GetOneParams, l as GetOneResult, I as Identifier, L as ListParams, m as ListResult, N as NullFilter, P as PaginationParam, n as ProviderCapabilities, R as RangeFilter, o as ResourceAccess, p as ResourceContract, q as ResourceSchema, r as ResourceSummary, S as ScalarFilter, s as SortParam, t as StorageType, U as UpdateParams, u as UpdateResult, V as VersionField } from './types-C7UVaF87.js';
|
|
2
|
+
export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from './write-outcome-D8kjoQ1A.js';
|
|
2
3
|
export { DataProviderRoot, DataProviderRootProps, GenesisProvider, GenesisProviderProps, MockProviderOptions, SupabaseClient, SupabaseClientInput, createMockProvider, createSupabaseProvider, useDataProvider, useStorage } from './provider/index.js';
|
|
3
4
|
export { S as StorageAdapter } from './types-DKcxHXxq.js';
|
|
4
5
|
export { createLocalStorageAdapter, createMemoryStorageAdapter, createNoopStorageAdapter } from './storage/index.js';
|
|
@@ -6,9 +7,9 @@ export { UseResourceOptions, UseResourceReturn, useResource } from './hooks/use-
|
|
|
6
7
|
export { UseOneOptions, useOne } from './hooks/use-one.js';
|
|
7
8
|
export { UseResourceFormOptions, UseResourceFormReturn, buildArrayItemDefaults, useResourceForm } from './hooks/use-resource-form.js';
|
|
8
9
|
export { UseWizardReturn, WizardStep, useWizard } from './hooks/use-wizard.js';
|
|
9
|
-
export { FieldType, ResourceFormFieldDef, SelectOption } from './form-types.js';
|
|
10
|
+
export { FieldType, ResourceFormFieldDef, SelectOption, FieldType as WidgetHint } from './form-types.js';
|
|
11
|
+
export { decodeRecordKey, encodeRecordKey, keyFields } from './schema/index.js';
|
|
10
12
|
export { titleCase } from './utils.js';
|
|
11
|
-
import 'react/jsx-runtime';
|
|
12
13
|
import 'react';
|
|
13
14
|
import '@tanstack/react-query';
|
|
14
15
|
import 'react-hook-form';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { createSupabaseProvider } from './chunk-
|
|
1
|
+
export { createSupabaseProvider } from './chunk-6AA4ISUA.js';
|
|
2
|
+
export { decodeRecordKey, encodeRecordKey, keyFields } from './chunk-KV2HW4G4.js';
|
|
3
|
+
export { DataWriteError, isDataWriteError } from './chunk-4W4W7F4I.js';
|
|
2
4
|
import './chunk-XCX2TRBL.js';
|
|
3
5
|
import './chunk-65SXNHWE.js';
|
|
4
6
|
import './chunk-EL7YGOTY.js';
|
package/dist/provider/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { D as DataProvider } from '../types-
|
|
2
|
-
export { A as ArrayFilter, B as BaseRecord, C as CreateParams,
|
|
3
|
-
|
|
1
|
+
import { D as DataProvider } from '../types-C7UVaF87.js';
|
|
2
|
+
export { A as ArrayFilter, B as BaseRecord, C as CreateParams, b as CreateResult, d as DeleteParams, e as DeleteResult, i as FilterParam, G as GetManyParams, j as GetManyResult, k as GetOneParams, l as GetOneResult, I as Identifier, L as ListParams, m as ListResult, N as NullFilter, P as PaginationParam, R as RangeFilter, S as ScalarFilter, s as SortParam, U as UpdateParams, u as UpdateResult } from '../types-C7UVaF87.js';
|
|
3
|
+
export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-D8kjoQ1A.js';
|
|
4
|
+
import * as react from 'react';
|
|
4
5
|
import { ReactNode } from 'react';
|
|
5
6
|
import { S as StorageAdapter } from '../types-DKcxHXxq.js';
|
|
7
|
+
import '../form-types.js';
|
|
6
8
|
|
|
7
9
|
interface MockProviderOptions {
|
|
8
10
|
/** Simulated latency in ms (default 0). */
|
|
@@ -74,7 +76,7 @@ interface DataProviderRootProps {
|
|
|
74
76
|
provider: DataProvider;
|
|
75
77
|
children: ReactNode;
|
|
76
78
|
}
|
|
77
|
-
declare function DataProviderRoot({ provider, children }: DataProviderRootProps):
|
|
79
|
+
declare function DataProviderRoot({ provider, children }: DataProviderRootProps): react.JSX.Element;
|
|
78
80
|
declare function useDataProvider(): DataProvider;
|
|
79
81
|
declare function useStorage(): StorageAdapter;
|
|
80
82
|
type Row = Record<string, unknown>;
|
|
@@ -96,6 +98,6 @@ type GenesisProviderProps = {
|
|
|
96
98
|
/** Platform-specific storage adapter. Defaults to localStorage (SSR-safe). */
|
|
97
99
|
storage?: StorageAdapter;
|
|
98
100
|
};
|
|
99
|
-
declare function GenesisProvider({ children, provider, mock, storage, }: GenesisProviderProps):
|
|
101
|
+
declare function GenesisProvider({ children, provider, mock, storage, }: GenesisProviderProps): react.JSX.Element;
|
|
100
102
|
|
|
101
103
|
export { DataProvider, DataProviderRoot, type DataProviderRootProps, GenesisProvider, type GenesisProviderProps, type MockProviderOptions, type SupabaseClient, type SupabaseClientInput, createMockProvider, createSupabaseProvider, useDataProvider, useStorage };
|
package/dist/provider/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { createSupabaseProvider } from '../chunk-
|
|
1
|
+
export { createSupabaseProvider } from '../chunk-6AA4ISUA.js';
|
|
2
|
+
export { DataWriteError, isDataWriteError } from '../chunk-4W4W7F4I.js';
|
|
2
3
|
export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage } from '../chunk-6V2TIN3G.js';
|
|
3
4
|
import '../chunk-ZYJUEJ4Z.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { p as ResourceContract, I as Identifier, c as DataRow } from '../types-C7UVaF87.js';
|
|
2
|
+
export { a as AuditEntry, F as FieldPermissions, f as FieldReference, g as FieldSchema, h as FilterOperator, n as ProviderCapabilities, o as ResourceAccess, q as ResourceSchema, r as ResourceSummary, t as StorageType, V as VersionField } from '../types-C7UVaF87.js';
|
|
3
|
+
export { D as DataWriteError, a as DataWriteErrorOptions, W as WriteOutcome, i as isDataWriteError } from '../write-outcome-D8kjoQ1A.js';
|
|
4
|
+
export { FieldType as WidgetHint } from '../form-types.js';
|
|
5
|
+
|
|
6
|
+
/** The key of a contract as a list, whichever form `key` was declared in. */
|
|
7
|
+
declare function keyFields(contract: Pick<ResourceContract, "key">): readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* One `Identifier` for a row.
|
|
10
|
+
*
|
|
11
|
+
* A single-field key returns the raw value, so `id`-keyed resources look
|
|
12
|
+
* exactly as they always have. A composite key returns a JSON array of the
|
|
13
|
+
* values in key order, e.g. `'["c-1","u-7"]'`.
|
|
14
|
+
*/
|
|
15
|
+
declare function encodeRecordKey(contract: Pick<ResourceContract, "key">, row: DataRow): Identifier;
|
|
16
|
+
/**
|
|
17
|
+
* The inverse of {@link encodeRecordKey}: field name → value.
|
|
18
|
+
*
|
|
19
|
+
* Throws on an id that does not fit the key, rather than guessing, because a
|
|
20
|
+
* guessed key addresses the wrong row.
|
|
21
|
+
*/
|
|
22
|
+
declare function decodeRecordKey(contract: Pick<ResourceContract, "key">, id: Identifier): Record<string, string | number>;
|
|
23
|
+
|
|
24
|
+
export { DataRow, ResourceContract, decodeRecordKey, encodeRecordKey, keyFields };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { FieldType } from './form-types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portable storage type of a field — what kind of value the store holds,
|
|
5
|
+
* independent of how a UI should edit it. The exact native type, when the
|
|
6
|
+
* store reports one, is on {@link FieldSchema.dbType}.
|
|
7
|
+
*
|
|
8
|
+
* Exactly the six types the switchboard shipped as `FieldType`, so existing
|
|
9
|
+
* exhaustive maps over it keep compiling. Finer distinctions (date versus
|
|
10
|
+
* timestamp, enum, uuid) live in `dbType` and `widget`, not here.
|
|
11
|
+
*/
|
|
12
|
+
type StorageType = "string" | "number" | "boolean" | "datetime" | "json" | "relation";
|
|
13
|
+
/** A canonical, provider-agnostic row. Keys are field names from the schema. */
|
|
14
|
+
type DataRow = Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* What the CURRENT user may do with one field. Reported by a provider's
|
|
17
|
+
* `describe()`; never inferred in the browser.
|
|
18
|
+
*
|
|
19
|
+
* `read: false` means the value is never sent to this user, so the column
|
|
20
|
+
* must not render at all (an answer key, a payout amount). `update: false`
|
|
21
|
+
* with `read: true` renders the column read-only.
|
|
22
|
+
*/
|
|
23
|
+
interface FieldPermissions {
|
|
24
|
+
readonly read: boolean;
|
|
25
|
+
readonly update: boolean;
|
|
26
|
+
/** Why `update` (or `read`) is false, in one human sentence. */
|
|
27
|
+
readonly reason?: string;
|
|
28
|
+
}
|
|
29
|
+
/** A foreign-key style link from this field to another resource's field. */
|
|
30
|
+
interface FieldReference {
|
|
31
|
+
readonly resource: string;
|
|
32
|
+
readonly field: string;
|
|
33
|
+
}
|
|
34
|
+
interface FieldSchema {
|
|
35
|
+
/** Canonical field name in the resource's shape (provider-agnostic). */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
/** Portable storage type. */
|
|
38
|
+
readonly type: StorageType;
|
|
39
|
+
/** True when a row may omit this field. */
|
|
40
|
+
readonly optional?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* True when the field holds an ORDERED LIST of `type` values (e.g. a
|
|
43
|
+
* multi-relation like `deps`/`serves`). Promotion emits a Postgres ARRAY
|
|
44
|
+
* column (`<base>[]`) instead of a lossy scalar, so the cardinality survives
|
|
45
|
+
* L1→L3 (CMT-1742-014).
|
|
46
|
+
*/
|
|
47
|
+
readonly array?: boolean;
|
|
48
|
+
/** Human note (surfaces in describe()/drift reports). */
|
|
49
|
+
readonly description?: string;
|
|
50
|
+
/** The store's exact type, e.g. `timestamp with time zone` or `public.status`. */
|
|
51
|
+
readonly dbType?: string;
|
|
52
|
+
/** Which control fits this field. A hint: a UI may still choose another. */
|
|
53
|
+
readonly widget?: FieldType;
|
|
54
|
+
/** True when the store accepts null for this field. */
|
|
55
|
+
readonly nullable?: boolean;
|
|
56
|
+
/** True when the store fills the field itself on insert. */
|
|
57
|
+
readonly hasDefault?: boolean;
|
|
58
|
+
/** True when the store computes the field; it is never written. */
|
|
59
|
+
readonly generated?: boolean;
|
|
60
|
+
/** Allowed values, for enum-like fields. */
|
|
61
|
+
readonly enumValues?: readonly string[];
|
|
62
|
+
/** The resource and field this one points at, when it is a link. */
|
|
63
|
+
readonly references?: FieldReference;
|
|
64
|
+
/** The current user's rights on this field, when a provider reports them. */
|
|
65
|
+
readonly permissions?: FieldPermissions;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* One declaration of a resource: its fields and its identity.
|
|
69
|
+
*
|
|
70
|
+
* `key` names the identity field, or several for a composite key (a junction
|
|
71
|
+
* table keyed on `(coach_id, client_id)`). Use {@link keyFields} to read it as
|
|
72
|
+
* a list without caring which form it took.
|
|
73
|
+
*/
|
|
74
|
+
interface ResourceContract<Row extends DataRow = DataRow> {
|
|
75
|
+
/** Resource name the app addresses, e.g. "tickets" or "public.tickets". */
|
|
76
|
+
readonly name: string;
|
|
77
|
+
/**
|
|
78
|
+
* Identity field name, or names for a composite key, in key order.
|
|
79
|
+
*
|
|
80
|
+
* Typed `string` (not `keyof Row & string`) on purpose: `keyof Row` is a
|
|
81
|
+
* dependent type that TypeScript checks contravariantly, which would make
|
|
82
|
+
* `ResourceContract<WorkCycleRow>` non-assignable to the erased
|
|
83
|
+
* `ResourceContract<DataRow>` that generic consumers accept. The key is
|
|
84
|
+
* validated against the field schema at runtime instead.
|
|
85
|
+
*/
|
|
86
|
+
readonly key: string | readonly string[];
|
|
87
|
+
readonly fields: readonly FieldSchema[];
|
|
88
|
+
/** Optional doc string. */
|
|
89
|
+
readonly description?: string;
|
|
90
|
+
/** Phantom marker so `Row` is inferable from a contract. */
|
|
91
|
+
readonly __row?: Row;
|
|
92
|
+
}
|
|
93
|
+
/** What the current user may do with a whole resource. */
|
|
94
|
+
interface ResourceAccess {
|
|
95
|
+
readonly read: boolean;
|
|
96
|
+
/** True when at least one field can be updated on rows this user can see. */
|
|
97
|
+
readonly update: boolean;
|
|
98
|
+
/** Why `update` (or `read`) is false, in one human sentence. */
|
|
99
|
+
readonly reason?: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The column a provider checks on every update so an edit never silently
|
|
103
|
+
* overwrites someone else's. Only reported when the store itself advances it
|
|
104
|
+
* on every successful update (a trigger such as `moddatetime`), because a
|
|
105
|
+
* column nothing advances detects nothing.
|
|
106
|
+
*/
|
|
107
|
+
interface VersionField {
|
|
108
|
+
/**
|
|
109
|
+
* The field holding the version. A provider with versioned writes returns
|
|
110
|
+
* each row with its exact version token in this field, as a string, and
|
|
111
|
+
* `expectedVersion` echoes `String(row[field])` back unchanged.
|
|
112
|
+
*/
|
|
113
|
+
readonly field: string;
|
|
114
|
+
readonly kind: "integer" | "timestamp";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A resource as `describe()` reports it for the CURRENT user: the contract,
|
|
118
|
+
* plus what this user may do. Fields the user may not read are left out
|
|
119
|
+
* entirely, not marked hidden.
|
|
120
|
+
*/
|
|
121
|
+
interface ResourceSchema<Row extends DataRow = DataRow> extends ResourceContract<Row> {
|
|
122
|
+
/** Human label, e.g. "Clients". */
|
|
123
|
+
readonly label?: string;
|
|
124
|
+
readonly access: ResourceAccess;
|
|
125
|
+
/** Present only when writes can be version-checked. */
|
|
126
|
+
readonly version?: VersionField;
|
|
127
|
+
}
|
|
128
|
+
/** One entry of `listResources()`: enough to draw a sidebar. */
|
|
129
|
+
interface ResourceSummary {
|
|
130
|
+
readonly name: string;
|
|
131
|
+
readonly label?: string;
|
|
132
|
+
readonly description?: string;
|
|
133
|
+
readonly access: ResourceAccess;
|
|
134
|
+
}
|
|
135
|
+
/** Filter operators a provider may support. Mirrors `FilterParam["operator"]`. */
|
|
136
|
+
type FilterOperator = "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains" | "in" | "between" | "is_null" | "is_not_null";
|
|
137
|
+
/**
|
|
138
|
+
* What a provider can do. A UI hides any control the provider cannot serve,
|
|
139
|
+
* instead of letting it error (DATA-STUDIO.md decision 9).
|
|
140
|
+
*/
|
|
141
|
+
interface ProviderCapabilities {
|
|
142
|
+
/** Server-side text search across fields. */
|
|
143
|
+
readonly search: boolean;
|
|
144
|
+
/** Server-side sort. */
|
|
145
|
+
readonly sort: boolean;
|
|
146
|
+
/** Server-side filter operators. Empty means no server filtering. */
|
|
147
|
+
readonly filterOperators: readonly FilterOperator[];
|
|
148
|
+
/** `updateMany` / `deleteMany` in one transaction. */
|
|
149
|
+
readonly bulkWrites: boolean;
|
|
150
|
+
/** Updates honour `expectedVersion` and reject stale writes. */
|
|
151
|
+
readonly versionedWrites: boolean;
|
|
152
|
+
/** Records may be keyed by more than one field. */
|
|
153
|
+
readonly compositeKeys: boolean;
|
|
154
|
+
}
|
|
155
|
+
/** One audit record, as committed alongside the edit it describes. */
|
|
156
|
+
interface AuditEntry {
|
|
157
|
+
readonly id: string;
|
|
158
|
+
/** ISO 8601 timestamp, as the store recorded it. */
|
|
159
|
+
readonly occurredAt: string;
|
|
160
|
+
/** The signed-in user who made the edit (the token's `sub`). */
|
|
161
|
+
readonly actor: string | null;
|
|
162
|
+
readonly resource: string;
|
|
163
|
+
/** The record's key, field name → value. */
|
|
164
|
+
readonly key: DataRow;
|
|
165
|
+
readonly action: "create" | "update" | "delete";
|
|
166
|
+
/** Values of the changed fields before the edit. */
|
|
167
|
+
readonly before: DataRow | null;
|
|
168
|
+
/** Values of the changed fields after the edit. */
|
|
169
|
+
readonly after: DataRow | null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Record identifier — supports both string UUIDs and numeric auto-increment IDs. */
|
|
173
|
+
type Identifier = string | number;
|
|
174
|
+
/** Base record type — all resources must have an id. */
|
|
175
|
+
type BaseRecord = {
|
|
176
|
+
id: Identifier;
|
|
177
|
+
[key: string]: unknown;
|
|
178
|
+
};
|
|
179
|
+
/** Pagination parameters (offset-based, with optional cursor for future use). */
|
|
180
|
+
interface PaginationParam {
|
|
181
|
+
page: number;
|
|
182
|
+
perPage: number;
|
|
183
|
+
cursor?: string;
|
|
184
|
+
}
|
|
185
|
+
/** Sort direction for a single field. */
|
|
186
|
+
interface SortParam {
|
|
187
|
+
field: string;
|
|
188
|
+
order: "asc" | "desc";
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Filter expressions — discriminated union by operator.
|
|
192
|
+
* Filters are AND-ed together in queries.
|
|
193
|
+
*/
|
|
194
|
+
/** Operators that compare a field against a single scalar value. */
|
|
195
|
+
interface ScalarFilter {
|
|
196
|
+
field: string;
|
|
197
|
+
operator: "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains";
|
|
198
|
+
value: string | number | boolean;
|
|
199
|
+
}
|
|
200
|
+
/** Operator that checks field membership in a list. */
|
|
201
|
+
interface ArrayFilter {
|
|
202
|
+
field: string;
|
|
203
|
+
operator: "in";
|
|
204
|
+
value: (string | number | boolean)[];
|
|
205
|
+
}
|
|
206
|
+
/** Operator that checks field is within a range. */
|
|
207
|
+
interface RangeFilter {
|
|
208
|
+
field: string;
|
|
209
|
+
operator: "between";
|
|
210
|
+
value: [string | number, string | number];
|
|
211
|
+
}
|
|
212
|
+
/** Operators that check for null/non-null — no value needed. */
|
|
213
|
+
interface NullFilter {
|
|
214
|
+
field: string;
|
|
215
|
+
operator: "is_null" | "is_not_null";
|
|
216
|
+
}
|
|
217
|
+
type FilterParam = ScalarFilter | ArrayFilter | RangeFilter | NullFilter;
|
|
218
|
+
/** Parameters for `getList`. */
|
|
219
|
+
interface ListParams {
|
|
220
|
+
pagination?: PaginationParam;
|
|
221
|
+
sort?: SortParam;
|
|
222
|
+
filters?: FilterParam[];
|
|
223
|
+
/** Supabase select syntax, e.g. "*, author:profiles(name)". Mock provider ignores this. */
|
|
224
|
+
select?: string;
|
|
225
|
+
/** Multi-field text search (case-insensitive). */
|
|
226
|
+
search?: {
|
|
227
|
+
query: string;
|
|
228
|
+
fields: string[];
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Result of `getList`. */
|
|
232
|
+
interface ListResult<T> {
|
|
233
|
+
data: T[];
|
|
234
|
+
total: number;
|
|
235
|
+
}
|
|
236
|
+
/** Parameters for `getOne`. */
|
|
237
|
+
interface GetOneParams {
|
|
238
|
+
id: Identifier;
|
|
239
|
+
/** Supabase select syntax, e.g. "*, author:profiles(name)". Defaults to "*". */
|
|
240
|
+
select?: string;
|
|
241
|
+
}
|
|
242
|
+
/** Result of `getOne`. */
|
|
243
|
+
interface GetOneResult<T> {
|
|
244
|
+
data: T;
|
|
245
|
+
}
|
|
246
|
+
/** Parameters for `getMany`. */
|
|
247
|
+
interface GetManyParams {
|
|
248
|
+
ids: Identifier[];
|
|
249
|
+
}
|
|
250
|
+
/** Result of `getMany`. */
|
|
251
|
+
interface GetManyResult<T> {
|
|
252
|
+
data: T[];
|
|
253
|
+
}
|
|
254
|
+
/** Parameters for `create`. */
|
|
255
|
+
interface CreateParams<T> {
|
|
256
|
+
data: Partial<T>;
|
|
257
|
+
}
|
|
258
|
+
/** Result of `create`. */
|
|
259
|
+
interface CreateResult<T> {
|
|
260
|
+
data: T;
|
|
261
|
+
}
|
|
262
|
+
/** Parameters for `update`. */
|
|
263
|
+
interface UpdateParams<T> {
|
|
264
|
+
/**
|
|
265
|
+
* The record's identifier. For a composite key, the canonical string from
|
|
266
|
+
* `encodeRecordKey` (`@marktiderman/genesis-core/schema`).
|
|
267
|
+
*/
|
|
268
|
+
id: Identifier;
|
|
269
|
+
data: Partial<T>;
|
|
270
|
+
/**
|
|
271
|
+
* The version token the caller read the row at. A provider that reports
|
|
272
|
+
* `versionedWrites` in its capabilities rejects the update with a
|
|
273
|
+
* `DataWriteError` (`conflict`) when the row has moved on since, instead of
|
|
274
|
+
* overwriting someone else's edit. Providers without versioned writes
|
|
275
|
+
* ignore it.
|
|
276
|
+
*/
|
|
277
|
+
expectedVersion?: string | null;
|
|
278
|
+
}
|
|
279
|
+
/** Result of `update`. */
|
|
280
|
+
interface UpdateResult<T> {
|
|
281
|
+
data: T;
|
|
282
|
+
/** The row's new version token, from a provider with versioned writes. */
|
|
283
|
+
version?: string | null;
|
|
284
|
+
/** The audit entry committed with the edit, from a provider that audits. */
|
|
285
|
+
audit?: AuditEntry;
|
|
286
|
+
}
|
|
287
|
+
/** Parameters for `deleteOne`. */
|
|
288
|
+
interface DeleteParams {
|
|
289
|
+
id: Identifier;
|
|
290
|
+
}
|
|
291
|
+
/** Result of `deleteOne`. */
|
|
292
|
+
interface DeleteResult {
|
|
293
|
+
data: {
|
|
294
|
+
id: Identifier;
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
interface DataProvider {
|
|
298
|
+
getList<T extends BaseRecord = BaseRecord>(resource: string, params: ListParams): Promise<ListResult<T>>;
|
|
299
|
+
getOne<T extends BaseRecord = BaseRecord>(resource: string, params: GetOneParams): Promise<GetOneResult<T>>;
|
|
300
|
+
getMany<T extends BaseRecord = BaseRecord>(resource: string, params: GetManyParams): Promise<GetManyResult<T>>;
|
|
301
|
+
create<T extends BaseRecord = BaseRecord>(resource: string, params: CreateParams<T>): Promise<CreateResult<T>>;
|
|
302
|
+
update<T extends BaseRecord = BaseRecord>(resource: string, params: UpdateParams<T>): Promise<UpdateResult<T>>;
|
|
303
|
+
deleteOne(resource: string, params: DeleteParams): Promise<DeleteResult>;
|
|
304
|
+
/**
|
|
305
|
+
* The resource's real shape for the CURRENT user: fields, key, version
|
|
306
|
+
* field, and per-field read and update rights. Fields the user may not read
|
|
307
|
+
* are omitted entirely.
|
|
308
|
+
*/
|
|
309
|
+
describe?(resource: string): Promise<ResourceSchema>;
|
|
310
|
+
/** Every resource this user may see, for a sidebar or a picker. */
|
|
311
|
+
listResources?(): Promise<ResourceSummary[]>;
|
|
312
|
+
/**
|
|
313
|
+
* What this provider can do, optionally for one resource. A UI hides the
|
|
314
|
+
* controls a provider cannot serve instead of letting them error.
|
|
315
|
+
*/
|
|
316
|
+
capabilities?(resource?: string): Promise<ProviderCapabilities>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FieldPermissions as F, GetManyParams as G, Identifier as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, VersionField as V, AuditEntry as a, CreateResult as b, DataRow as c, DeleteParams as d, DeleteResult as e, FieldReference as f, FieldSchema as g, FilterOperator as h, FilterParam as i, GetManyResult as j, GetOneParams as k, GetOneResult as l, ListResult as m, ProviderCapabilities as n, ResourceAccess as o, ResourceContract as p, ResourceSchema as q, ResourceSummary as r, SortParam as s, StorageType as t, UpdateResult as u };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { c as DataRow } from './types-C7UVaF87.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* - `conflict`: someone else changed the row since it was read. `current`
|
|
5
|
+
* carries the row as it is now.
|
|
6
|
+
* - `gone`: the row was deleted, or is no longer visible to this user. The
|
|
7
|
+
* two cannot be told apart without a privileged look at the table, which
|
|
8
|
+
* would show this user a fact about a row they cannot see.
|
|
9
|
+
* - `forbidden`: the row is visible and unchanged, but the app or the
|
|
10
|
+
* database does not let this user make this change. Only reported when
|
|
11
|
+
* that is proven, never assumed from "zero rows changed".
|
|
12
|
+
* - `invalid`: the store rejected the value (type, constraint, not-null).
|
|
13
|
+
* - `audit_failed`: the audit entry could not be written, so the edit was
|
|
14
|
+
* rolled back with it.
|
|
15
|
+
* - `unauthenticated`: no signed-in user, or the session expired.
|
|
16
|
+
* - `error`: anything else. The edit did not commit.
|
|
17
|
+
*/
|
|
18
|
+
type WriteOutcome = "conflict" | "gone" | "forbidden" | "invalid" | "audit_failed" | "unauthenticated" | "error";
|
|
19
|
+
interface DataWriteErrorOptions {
|
|
20
|
+
/** The row as it is now, for a `conflict`. Only fields the user may read. */
|
|
21
|
+
current?: DataRow;
|
|
22
|
+
/** The row's current version token, for a `conflict`. */
|
|
23
|
+
currentVersion?: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** Thrown by a provider when a write did not commit. */
|
|
26
|
+
declare class DataWriteError extends Error {
|
|
27
|
+
readonly outcome: WriteOutcome;
|
|
28
|
+
readonly current?: DataRow;
|
|
29
|
+
readonly currentVersion?: string | null;
|
|
30
|
+
constructor(outcome: WriteOutcome, message: string, options?: DataWriteErrorOptions);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `instanceof` fails when two copies of this package are installed, so check
|
|
34
|
+
* the shape instead.
|
|
35
|
+
*/
|
|
36
|
+
declare function isDataWriteError(error: unknown): error is DataWriteError;
|
|
37
|
+
|
|
38
|
+
export { DataWriteError as D, type WriteOutcome as W, type DataWriteErrorOptions as a, isDataWriteError as i };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marktiderman/genesis-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"url": "git+https://github.com/marktiderman/genesis.git",
|
|
11
11
|
"directory": "packages/core"
|
|
12
12
|
},
|
|
13
|
-
"license": "
|
|
13
|
+
"license": "MIT",
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public",
|
|
16
16
|
"provenance": false
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"./storage": {
|
|
39
39
|
"types": "./dist/storage/index.d.ts",
|
|
40
40
|
"import": "./dist/storage/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./schema": {
|
|
43
|
+
"types": "./dist/schema/index.d.ts",
|
|
44
|
+
"import": "./dist/schema/index.js"
|
|
41
45
|
}
|
|
42
46
|
},
|
|
43
47
|
"files": [
|
|
@@ -52,16 +56,19 @@
|
|
|
52
56
|
"@hookform/resolvers": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
59
|
+
"@hookform/resolvers": "^5.0.1",
|
|
55
60
|
"@tanstack/react-query": "^5.75.0",
|
|
56
61
|
"@types/react": "^19.2.14",
|
|
57
62
|
"react-hook-form": "^7.56.4",
|
|
58
|
-
"zod": "^3.25.67",
|
|
59
|
-
"@hookform/resolvers": "^5.0.1",
|
|
60
63
|
"tsup": "^8.3.0",
|
|
61
|
-
"typescript": "^5.9.3"
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"vitest": "^2.1.0",
|
|
66
|
+
"zod": "^3.25.67"
|
|
62
67
|
},
|
|
63
68
|
"scripts": {
|
|
64
|
-
"build": "tsup",
|
|
65
|
-
"clean": "rm -rf dist"
|
|
69
|
+
"build": "tsup && node ../../scripts/check-exports-resolve.mjs",
|
|
70
|
+
"clean": "rm -rf dist",
|
|
71
|
+
"typecheck": "tsc --noEmit",
|
|
72
|
+
"test": "vitest run"
|
|
66
73
|
}
|
|
67
74
|
}
|
package/dist/types-BzcZrTjl.d.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/** Record identifier — supports both string UUIDs and numeric auto-increment IDs. */
|
|
2
|
-
type Identifier = string | number;
|
|
3
|
-
/** Base record type — all resources must have an id. */
|
|
4
|
-
type BaseRecord = {
|
|
5
|
-
id: Identifier;
|
|
6
|
-
[key: string]: unknown;
|
|
7
|
-
};
|
|
8
|
-
/** Pagination parameters (offset-based, with optional cursor for future use). */
|
|
9
|
-
interface PaginationParam {
|
|
10
|
-
page: number;
|
|
11
|
-
perPage: number;
|
|
12
|
-
cursor?: string;
|
|
13
|
-
}
|
|
14
|
-
/** Sort direction for a single field. */
|
|
15
|
-
interface SortParam {
|
|
16
|
-
field: string;
|
|
17
|
-
order: "asc" | "desc";
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Filter expressions — discriminated union by operator.
|
|
21
|
-
* Filters are AND-ed together in queries.
|
|
22
|
-
*/
|
|
23
|
-
/** Operators that compare a field against a single scalar value. */
|
|
24
|
-
interface ScalarFilter {
|
|
25
|
-
field: string;
|
|
26
|
-
operator: "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains";
|
|
27
|
-
value: string | number | boolean;
|
|
28
|
-
}
|
|
29
|
-
/** Operator that checks field membership in a list. */
|
|
30
|
-
interface ArrayFilter {
|
|
31
|
-
field: string;
|
|
32
|
-
operator: "in";
|
|
33
|
-
value: (string | number | boolean)[];
|
|
34
|
-
}
|
|
35
|
-
/** Operator that checks field is within a range. */
|
|
36
|
-
interface RangeFilter {
|
|
37
|
-
field: string;
|
|
38
|
-
operator: "between";
|
|
39
|
-
value: [string | number, string | number];
|
|
40
|
-
}
|
|
41
|
-
/** Operators that check for null/non-null — no value needed. */
|
|
42
|
-
interface NullFilter {
|
|
43
|
-
field: string;
|
|
44
|
-
operator: "is_null" | "is_not_null";
|
|
45
|
-
}
|
|
46
|
-
type FilterParam = ScalarFilter | ArrayFilter | RangeFilter | NullFilter;
|
|
47
|
-
/** Parameters for `getList`. */
|
|
48
|
-
interface ListParams {
|
|
49
|
-
pagination?: PaginationParam;
|
|
50
|
-
sort?: SortParam;
|
|
51
|
-
filters?: FilterParam[];
|
|
52
|
-
/** Supabase select syntax, e.g. "*, author:profiles(name)". Mock provider ignores this. */
|
|
53
|
-
select?: string;
|
|
54
|
-
/** Multi-field text search (case-insensitive). */
|
|
55
|
-
search?: {
|
|
56
|
-
query: string;
|
|
57
|
-
fields: string[];
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
/** Result of `getList`. */
|
|
61
|
-
interface ListResult<T> {
|
|
62
|
-
data: T[];
|
|
63
|
-
total: number;
|
|
64
|
-
}
|
|
65
|
-
/** Parameters for `getOne`. */
|
|
66
|
-
interface GetOneParams {
|
|
67
|
-
id: Identifier;
|
|
68
|
-
/** Supabase select syntax, e.g. "*, author:profiles(name)". Defaults to "*". */
|
|
69
|
-
select?: string;
|
|
70
|
-
}
|
|
71
|
-
/** Result of `getOne`. */
|
|
72
|
-
interface GetOneResult<T> {
|
|
73
|
-
data: T;
|
|
74
|
-
}
|
|
75
|
-
/** Parameters for `getMany`. */
|
|
76
|
-
interface GetManyParams {
|
|
77
|
-
ids: Identifier[];
|
|
78
|
-
}
|
|
79
|
-
/** Result of `getMany`. */
|
|
80
|
-
interface GetManyResult<T> {
|
|
81
|
-
data: T[];
|
|
82
|
-
}
|
|
83
|
-
/** Parameters for `create`. */
|
|
84
|
-
interface CreateParams<T> {
|
|
85
|
-
data: Partial<T>;
|
|
86
|
-
}
|
|
87
|
-
/** Result of `create`. */
|
|
88
|
-
interface CreateResult<T> {
|
|
89
|
-
data: T;
|
|
90
|
-
}
|
|
91
|
-
/** Parameters for `update`. */
|
|
92
|
-
interface UpdateParams<T> {
|
|
93
|
-
id: Identifier;
|
|
94
|
-
data: Partial<T>;
|
|
95
|
-
}
|
|
96
|
-
/** Result of `update`. */
|
|
97
|
-
interface UpdateResult<T> {
|
|
98
|
-
data: T;
|
|
99
|
-
}
|
|
100
|
-
/** Parameters for `deleteOne`. */
|
|
101
|
-
interface DeleteParams {
|
|
102
|
-
id: Identifier;
|
|
103
|
-
}
|
|
104
|
-
/** Result of `deleteOne`. */
|
|
105
|
-
interface DeleteResult {
|
|
106
|
-
data: {
|
|
107
|
-
id: Identifier;
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
interface DataProvider {
|
|
111
|
-
getList<T extends BaseRecord = BaseRecord>(resource: string, params: ListParams): Promise<ListResult<T>>;
|
|
112
|
-
getOne<T extends BaseRecord = BaseRecord>(resource: string, params: GetOneParams): Promise<GetOneResult<T>>;
|
|
113
|
-
getMany<T extends BaseRecord = BaseRecord>(resource: string, params: GetManyParams): Promise<GetManyResult<T>>;
|
|
114
|
-
create<T extends BaseRecord = BaseRecord>(resource: string, params: CreateParams<T>): Promise<CreateResult<T>>;
|
|
115
|
-
update<T extends BaseRecord = BaseRecord>(resource: string, params: UpdateParams<T>): Promise<UpdateResult<T>>;
|
|
116
|
-
deleteOne(resource: string, params: DeleteParams): Promise<DeleteResult>;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export type { ArrayFilter as A, BaseRecord as B, CreateParams as C, DataProvider as D, FilterParam as F, GetManyParams as G, Identifier as I, ListParams as L, NullFilter as N, PaginationParam as P, RangeFilter as R, ScalarFilter as S, UpdateParams as U, CreateResult as a, DeleteParams as b, DeleteResult as c, GetManyResult as d, GetOneParams as e, GetOneResult as f, ListResult as g, SortParam as h, UpdateResult as i };
|