@flaredev/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PWP4QEZV.js → chunk-VLHPSNQN.js} +23 -1
- package/dist/index.d.ts +29 -7
- package/dist/index.js +35 -2
- package/dist/{policy-CvfE-wCK.d.ts → policy-DaKcWZ5g.d.ts} +25 -4
- package/dist/server.d.ts +129 -14
- package/dist/server.js +265 -106
- package/package.json +1 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// src/resource/fields.ts
|
|
2
2
|
var STRING_FORMATS = ["email", "url", "tel", "domain", "country", "color", "slug"];
|
|
3
3
|
var FILE_CATEGORIES = {
|
|
4
|
+
// For a field that holds whatever someone has: a drive, an attachment, a backup.
|
|
5
|
+
// Nothing is checked beyond the size limit, so don't reach for it out of convenience.
|
|
6
|
+
any: ["*/*"],
|
|
4
7
|
image: ["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"],
|
|
5
8
|
pdf: ["application/pdf"],
|
|
6
9
|
video: ["video/*"],
|
|
@@ -17,7 +20,18 @@ var FILE_CATEGORIES = {
|
|
|
17
20
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
18
21
|
"application/vnd.oasis.opendocument.spreadsheet"
|
|
19
22
|
],
|
|
20
|
-
|
|
23
|
+
// Browsers disagree about what a .zip is — Windows sends x-zip-compressed — and an
|
|
24
|
+
// archive field that refuses an ordinary zip is no use, so the aliases are listed too.
|
|
25
|
+
archive: [
|
|
26
|
+
"application/zip",
|
|
27
|
+
"application/x-zip-compressed",
|
|
28
|
+
"application/x-zip",
|
|
29
|
+
"application/gzip",
|
|
30
|
+
"application/x-gzip",
|
|
31
|
+
"application/x-7z-compressed",
|
|
32
|
+
"application/vnd.rar",
|
|
33
|
+
"application/x-rar-compressed"
|
|
34
|
+
]
|
|
21
35
|
};
|
|
22
36
|
function mimeTypesFor(accept) {
|
|
23
37
|
return [...new Set(accept.flatMap((category) => FILE_CATEGORIES[category]))];
|
|
@@ -87,6 +101,13 @@ function pluralize(value) {
|
|
|
87
101
|
|
|
88
102
|
// src/resource/define.ts
|
|
89
103
|
var RESERVED_FIELD_NAMES = ["id", "createdAt", "updatedAt"];
|
|
104
|
+
function clientResource(resource) {
|
|
105
|
+
const { hooks, computed, $types, ...rest } = resource;
|
|
106
|
+
void hooks;
|
|
107
|
+
void computed;
|
|
108
|
+
void $types;
|
|
109
|
+
return rest;
|
|
110
|
+
}
|
|
90
111
|
var ResourceDefinitionError = class extends Error {
|
|
91
112
|
constructor(resource, message) {
|
|
92
113
|
super(`Resource "${resource}": ${message}`);
|
|
@@ -385,6 +406,7 @@ export {
|
|
|
385
406
|
humanize,
|
|
386
407
|
pluralize,
|
|
387
408
|
RESERVED_FIELD_NAMES,
|
|
409
|
+
clientResource,
|
|
388
410
|
ResourceDefinitionError,
|
|
389
411
|
defaultFieldLabel,
|
|
390
412
|
defineResource,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as Field, C as CreateInput, U as UpdateInput, R as Resource, S as StoredField } from './policy-
|
|
2
|
-
export { B as BelongsToField, a as BooleanField, D as DEFAULT_FILE_MAX_BYTES,
|
|
1
|
+
import { F as Field, C as CreateInput, U as UpdateInput, R as Resource, S as StoredField } from './policy-DaKcWZ5g.js';
|
|
2
|
+
export { B as BelongsToField, a as BooleanField, b as ClientResource, D as DEFAULT_FILE_MAX_BYTES, c as DateField, d as DateTimeField, E as EnumField, e as FILE_CATEGORIES, f as FieldKind, g as FieldValue, h as FileCategory, i as FileField, j as FloatField, H as HasManyField, I as IntField, M as MultiSelectField, P as Policy, k as PolicyAction, l as PolicyConfig, m as PolicyError, n as RESERVED_FIELD_NAMES, o as RecordOf, p as ResourceConfig, q as ResourceDefinitionError, r as STRING_FORMATS, s as StringField, t as StringFormat, T as TextField, u as allowedActions, v as can, w as clientResource, x as columnName, y as defaultFieldLabel, z as definePolicy, A as defineResource, G as field, J as fileMaxBytes, K as mimeTypesFor, L as storedFields } from './policy-DaKcWZ5g.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -125,6 +125,22 @@ interface StorageBucket {
|
|
|
125
125
|
};
|
|
126
126
|
}): Promise<unknown>;
|
|
127
127
|
delete(keys: string | string[]): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* The keys in the bucket, a page at a time. R2 counts this as a Class A operation —
|
|
130
|
+
* the expensive kind — so call it to answer a question, not on a page people reload.
|
|
131
|
+
*/
|
|
132
|
+
list?(options?: {
|
|
133
|
+
limit?: number;
|
|
134
|
+
prefix?: string;
|
|
135
|
+
cursor?: string;
|
|
136
|
+
}): Promise<{
|
|
137
|
+
objects: {
|
|
138
|
+
key: string;
|
|
139
|
+
size: number;
|
|
140
|
+
}[];
|
|
141
|
+
truncated: boolean;
|
|
142
|
+
cursor?: string;
|
|
143
|
+
}>;
|
|
128
144
|
}
|
|
129
145
|
interface StorageObjectBody {
|
|
130
146
|
body: ReadableStream;
|
|
@@ -286,7 +302,7 @@ declare function fieldSchema(def: StoredField): z.ZodType;
|
|
|
286
302
|
* edit a record could attach another resource's private file and read it through theirs.
|
|
287
303
|
*/
|
|
288
304
|
declare const fileKeyPrefix: (resource: Pick<Resource, "table">, fieldKey: string) => string;
|
|
289
|
-
declare function createValidators<Fields extends Record<string, Field>>(resource: Resource<Fields>): ResourceValidators<Fields>;
|
|
305
|
+
declare function createValidators<Fields extends Record<string, Field>>(resource: Pick<Resource<Fields>, "fields" | "table">): ResourceValidators<Fields>;
|
|
290
306
|
|
|
291
307
|
/** Naming helpers shared by descriptors and the generator. */
|
|
292
308
|
/** "OrderItem" / "order item" / "order-item" → ["order", "item"] */
|
|
@@ -365,13 +381,13 @@ declare function formatValue(def: StoredField | {
|
|
|
365
381
|
*/
|
|
366
382
|
type FormValues = Record<string, string | boolean>;
|
|
367
383
|
/** Initial form state: the record's values (edit) or the descriptor's defaults (create). */
|
|
368
|
-
declare function initialFormValues(resource: Resource, record?: Record<string, unknown> | null): FormValues;
|
|
384
|
+
declare function initialFormValues(resource: Pick<Resource, "fields">, record?: Record<string, unknown> | null): FormValues;
|
|
369
385
|
/**
|
|
370
386
|
* API input from form state. Empty optional fields become null (clearing them);
|
|
371
387
|
* empty fields that have a default are omitted on create so the default applies;
|
|
372
388
|
* empty required fields are left for validation to report as "Required".
|
|
373
389
|
*/
|
|
374
|
-
declare function formValuesToInput(resource: Resource, values: FormValues, mode: "create" | "edit"): Record<string, unknown>;
|
|
390
|
+
declare function formValuesToInput(resource: Pick<Resource, "fields">, values: FormValues, mode: "create" | "edit"): Record<string, unknown>;
|
|
375
391
|
/** A multiselect's form state (a JSON array string) as the picked values. */
|
|
376
392
|
declare function parseMultiValue(raw: string | boolean | undefined): string[];
|
|
377
393
|
/** Field errors keyed by field name, from validation or API issues (first message wins). */
|
|
@@ -439,6 +455,8 @@ interface Fake {
|
|
|
439
455
|
email(name?: string): string;
|
|
440
456
|
company(): string;
|
|
441
457
|
jobTitle(): string;
|
|
458
|
+
/** "Brushed steel desk lamp", or a digital one: "Starter kit licence". */
|
|
459
|
+
product(digital?: boolean): string;
|
|
442
460
|
city(): string;
|
|
443
461
|
/** ISO 3166-1 alpha-2, e.g. "UG". */
|
|
444
462
|
country(): string;
|
|
@@ -495,7 +513,11 @@ declare const fake: Fake;
|
|
|
495
513
|
*/
|
|
496
514
|
type InsertMany = <T extends object>(table: unknown, count: number | readonly T[], build?: (index: number) => T) => Promise<number>;
|
|
497
515
|
interface SeedContext {
|
|
498
|
-
/**
|
|
516
|
+
/**
|
|
517
|
+
* The app's database client: Drizzle over the local D1 database on Cloudflare, the
|
|
518
|
+
* app's own Prisma client on Next.js. Untyped, because which one it is depends on
|
|
519
|
+
* the stack and both are generated per app.
|
|
520
|
+
*/
|
|
499
521
|
db: any;
|
|
500
522
|
/** All local bindings and variables (D1, R2, .dev.vars). */
|
|
501
523
|
env: Record<string, unknown>;
|
|
@@ -507,6 +529,6 @@ interface SeedContext {
|
|
|
507
529
|
type Seed = (context: SeedContext) => void | Promise<void>;
|
|
508
530
|
declare function defineSeed(seed: Seed): Seed;
|
|
509
531
|
|
|
510
|
-
declare const FLARE_VERSION = "0.
|
|
532
|
+
declare const FLARE_VERSION = "0.5.0";
|
|
511
533
|
|
|
512
534
|
export { type BelongsToRelation, type Country, CreateInput, FLARE_VERSION, type Fake, Field, type FormValues, type FormatOptions, type HasManyRelation, type InsertMany, type MailMessage, type MailResult, type Mailer, type MailerOptions, PBKDF2_ITERATIONS, type ReadUrlOptions, RelationError, type RelationGraph, Resource, type ResourceRelations, type ResourceValidators, SNIFF_BYTES, SUBSCRIPTION_STATUSES, type Seed, type SeedContext, type Storage, type StorageBucket, type StorageOptions, StoredField, type SubscriptionLike, type SubscriptionState, type SubscriptionStatus, type Tone, type TransactionalEmail, UpdateInput, type UploadUrlOptions, camelCase, checkoutPaid, countries, countryName, createFake, createMailer, createObjectKey, createStorage, createValidators, currentPeriodEnd, defineSeed, fake, fieldSchema, fileKeyPrefix, flagEmoji, formValuesToInput, formatPhone, formatValue, grantsAccess, hashPassword, humanize, importSigningKey, initialFormValues, isColor, isCountryCode, isDomain, isLiveSubscription, isPhoneNumber, isSlug, issuesByField, kebabCase, matchesContentType, normalizeDomain, optionLabel, parseMultiValue, pascalCase, pluralize, randomLetters, relationGraph, renderTransactionalEmail, shouldApplySubscription, signToken, slugify, snakeCase, sniffMatches, splitPhone, statusTone, subscriptionPriceId, subscriptionState, toE164, toSubscriptionStatus, verifyPassword, verifyToken, words };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
allowedActions,
|
|
9
9
|
camelCase,
|
|
10
10
|
can,
|
|
11
|
+
clientResource,
|
|
11
12
|
columnName,
|
|
12
13
|
countries,
|
|
13
14
|
countryName,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
storedFields,
|
|
39
40
|
toE164,
|
|
40
41
|
words
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-VLHPSNQN.js";
|
|
42
43
|
|
|
43
44
|
// src/password.ts
|
|
44
45
|
var ALGORITHM = "pbkdf2-sha256";
|
|
@@ -241,6 +242,8 @@ var EMPTY_ZIP = [80, 75, 5, 6];
|
|
|
241
242
|
var OLE2 = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
242
243
|
var EBML = [26, 69, 223, 163];
|
|
243
244
|
var isZip = (head) => startsWith(head, ZIP) || startsWith(head, EMPTY_ZIP);
|
|
245
|
+
var isGzip = (head) => startsWith(head, [31, 139]);
|
|
246
|
+
var isRar = (head) => startsWith(head, ascii("Rar!"));
|
|
244
247
|
var EXACT = {
|
|
245
248
|
"image/png": (head) => startsWith(head, [137, 80, 78, 71, 13, 10, 26, 10]),
|
|
246
249
|
"image/jpeg": (head) => startsWith(head, [255, 216, 255]),
|
|
@@ -249,6 +252,13 @@ var EXACT = {
|
|
|
249
252
|
"image/avif": (head) => ["avif", "avis", "mif1", "msf1"].includes(isoBrand(head) ?? ""),
|
|
250
253
|
"application/pdf": (head) => startsWith(head, ascii("%PDF-")),
|
|
251
254
|
"application/zip": isZip,
|
|
255
|
+
"application/x-zip-compressed": isZip,
|
|
256
|
+
"application/x-zip": isZip,
|
|
257
|
+
"application/gzip": isGzip,
|
|
258
|
+
"application/x-gzip": isGzip,
|
|
259
|
+
"application/x-7z-compressed": (head) => startsWith(head, [55, 122, 188, 175, 39, 28]),
|
|
260
|
+
"application/vnd.rar": isRar,
|
|
261
|
+
"application/x-rar-compressed": isRar,
|
|
252
262
|
// Office Open XML and OpenDocument files are zip archives.
|
|
253
263
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": isZip,
|
|
254
264
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": isZip,
|
|
@@ -281,6 +291,7 @@ function matchesContentType(contentType, allowed) {
|
|
|
281
291
|
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(type)) return false;
|
|
282
292
|
return allowed.some((pattern) => {
|
|
283
293
|
const p = pattern.trim().toLowerCase();
|
|
294
|
+
if (p === "*/*") return true;
|
|
284
295
|
return p.endsWith("/*") ? type.startsWith(p.slice(0, -1)) : type === p;
|
|
285
296
|
});
|
|
286
297
|
}
|
|
@@ -873,6 +884,26 @@ var WORDS = [
|
|
|
873
884
|
"variance",
|
|
874
885
|
"workflow"
|
|
875
886
|
];
|
|
887
|
+
var PRODUCT_QUALITIES = ["Oak", "Walnut", "Brushed steel", "Canvas", "Merino", "Recycled", "Matte black", "Linen", "Copper", "Frosted glass"];
|
|
888
|
+
var PRODUCT_THINGS = [
|
|
889
|
+
"desk lamp",
|
|
890
|
+
"office chair",
|
|
891
|
+
"notebook",
|
|
892
|
+
"water bottle",
|
|
893
|
+
"backpack",
|
|
894
|
+
"keyboard",
|
|
895
|
+
"mouse mat",
|
|
896
|
+
"coffee grinder",
|
|
897
|
+
"travel mug",
|
|
898
|
+
"wall clock",
|
|
899
|
+
"storage box",
|
|
900
|
+
"cable tidy",
|
|
901
|
+
"monitor stand",
|
|
902
|
+
"planter",
|
|
903
|
+
"door mat",
|
|
904
|
+
"tote bag"
|
|
905
|
+
];
|
|
906
|
+
var DIGITAL_THINGS = ["starter kit", "source code licence", "icon pack", "template bundle", "course", "font family", "preset pack", "e-book"];
|
|
876
907
|
var DOMAINS = ["example.com", "example.org", "example.net", "mail.example.com"];
|
|
877
908
|
var TLDS = ["com", "io", "co", "org", "net"];
|
|
878
909
|
var COLORS = ["#f2541d", "#2563eb", "#16a34a", "#d97706", "#7c3aed", "#0891b2", "#db2777", "#4b5563"];
|
|
@@ -924,6 +955,7 @@ function createFake(seed = 20260101) {
|
|
|
924
955
|
return `${slugify2(local).replace(/-/g, ".")}${step()}@${pick(DOMAINS)}`;
|
|
925
956
|
},
|
|
926
957
|
company: () => `${pick(COMPANY_HEADS)} ${pick(COMPANY_TAILS)}`,
|
|
958
|
+
product: (digital = false) => digital ? `${pick(PRODUCT_QUALITIES)} ${pick(DIGITAL_THINGS)}` : `${pick(PRODUCT_QUALITIES)} ${pick(PRODUCT_THINGS)}`,
|
|
927
959
|
jobTitle: () => `${pick(["Head of", "Senior", "Lead", "Regional", "Junior"])} ${pick(["Operations", "Sales", "Support", "Engineering", "Finance", "Logistics"])}`,
|
|
928
960
|
city: () => pick(CITIES),
|
|
929
961
|
country: () => pick(COUNTRIES)[0],
|
|
@@ -962,7 +994,7 @@ function createFake(seed = 20260101) {
|
|
|
962
994
|
var fake = createFake();
|
|
963
995
|
|
|
964
996
|
// src/index.ts
|
|
965
|
-
var FLARE_VERSION = "0.
|
|
997
|
+
var FLARE_VERSION = "0.5.0";
|
|
966
998
|
export {
|
|
967
999
|
DEFAULT_FILE_MAX_BYTES,
|
|
968
1000
|
FILE_CATEGORIES,
|
|
@@ -979,6 +1011,7 @@ export {
|
|
|
979
1011
|
camelCase,
|
|
980
1012
|
can,
|
|
981
1013
|
checkoutPaid,
|
|
1014
|
+
clientResource,
|
|
982
1015
|
columnName,
|
|
983
1016
|
countries,
|
|
984
1017
|
countryName,
|
|
@@ -116,6 +116,7 @@ type Field = StringField | TextField | IntField | FloatField | BooleanField | Da
|
|
|
116
116
|
type StoredField = Exclude<Field, HasManyField>;
|
|
117
117
|
/** File categories usable in `file:[...]` and the MIME types each allows. */
|
|
118
118
|
declare const FILE_CATEGORIES: {
|
|
119
|
+
readonly any: readonly ["*/*"];
|
|
119
120
|
readonly image: readonly ["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"];
|
|
120
121
|
readonly pdf: readonly ["application/pdf"];
|
|
121
122
|
readonly video: readonly ["video/*"];
|
|
@@ -124,7 +125,7 @@ declare const FILE_CATEGORIES: {
|
|
|
124
125
|
readonly csv: readonly ["text/csv"];
|
|
125
126
|
readonly document: readonly ["application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.oasis.opendocument.text"];
|
|
126
127
|
readonly spreadsheet: readonly ["application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.oasis.opendocument.spreadsheet"];
|
|
127
|
-
readonly archive: readonly ["application/zip"];
|
|
128
|
+
readonly archive: readonly ["application/zip", "application/x-zip-compressed", "application/x-zip", "application/gzip", "application/x-gzip", "application/x-7z-compressed", "application/vnd.rar", "application/x-rar-compressed"];
|
|
128
129
|
};
|
|
129
130
|
type FileCategory = keyof typeof FILE_CATEGORIES;
|
|
130
131
|
declare function mimeTypesFor(accept: readonly FileCategory[]): string[];
|
|
@@ -261,7 +262,11 @@ interface HookContext {
|
|
|
261
262
|
} | null;
|
|
262
263
|
}
|
|
263
264
|
interface ResourceHooks {
|
|
264
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* Change or check the input before it's written. Return the input (changed or not), or
|
|
267
|
+
* throw to refuse. It runs *before* validation, so it can fill in a field the caller
|
|
268
|
+
* couldn't supply — an order number, a slug — and what it returns is then validated.
|
|
269
|
+
*/
|
|
265
270
|
beforeCreate?: (input: Record<string, unknown>, context: HookContext) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
266
271
|
afterCreate?: (record: Record<string, unknown>, context: HookContext) => Promise<void> | void;
|
|
267
272
|
beforeUpdate?: (input: Record<string, unknown>, context: HookContext & {
|
|
@@ -307,6 +312,22 @@ interface Resource<Fields extends Record<string, Field> = Record<string, Field>>
|
|
|
307
312
|
record: RecordOf<Fields>;
|
|
308
313
|
};
|
|
309
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* A resource as a client component can receive it: everything that describes the fields,
|
|
317
|
+
* without the code.
|
|
318
|
+
*
|
|
319
|
+
* `hooks` and `computed` hold functions, and a function can't cross from the server to
|
|
320
|
+
* the browser — React refuses a prop it wasn't told to expose, and the page fails to
|
|
321
|
+
* render. So a server component hands a client one `clientResource(resource)` instead.
|
|
322
|
+
* The `never` types below turn passing the whole resource into a type error, which is
|
|
323
|
+
* cheaper to find than the runtime one.
|
|
324
|
+
*/
|
|
325
|
+
type ClientResource<Fields extends Record<string, Field> = Record<string, Field>> = Omit<Resource<Fields>, "hooks" | "computed" | "$types"> & {
|
|
326
|
+
hooks?: never;
|
|
327
|
+
computed?: never;
|
|
328
|
+
};
|
|
329
|
+
/** The parts of a resource that are safe to send to the browser. */
|
|
330
|
+
declare function clientResource<Fields extends Record<string, Field>>(resource: Resource<Fields>): ClientResource<Fields>;
|
|
310
331
|
declare class ResourceDefinitionError extends Error {
|
|
311
332
|
constructor(resource: string, message: string);
|
|
312
333
|
}
|
|
@@ -314,7 +335,7 @@ declare class ResourceDefinitionError extends Error {
|
|
|
314
335
|
declare function defaultFieldLabel(key: string, field: Field): string;
|
|
315
336
|
declare function defineResource<const Fields extends Record<string, Field>>(config: ResourceConfig<Fields>): Resource<Fields>;
|
|
316
337
|
/** Stored (column-backed) fields of a resource, in declaration order. */
|
|
317
|
-
declare function storedFields(resource: Resource): [string, StoredField & {
|
|
338
|
+
declare function storedFields(resource: Pick<Resource, "fields">): [string, StoredField & {
|
|
318
339
|
label: string;
|
|
319
340
|
}][];
|
|
320
341
|
/** Database column name for a field key ("companyId" → "company_id"). */
|
|
@@ -356,4 +377,4 @@ declare function can(policy: Policy | undefined | null, role: string | null | un
|
|
|
356
377
|
/** Actions `role` may perform, for hiding UI it can't use. */
|
|
357
378
|
declare function allowedActions(policy: Policy | undefined | null, role: string | null | undefined): Record<Exclude<PolicyAction, "list">, boolean>;
|
|
358
379
|
|
|
359
|
-
export {
|
|
380
|
+
export { defineResource as A, type BelongsToField as B, type CreateInput as C, DEFAULT_FILE_MAX_BYTES as D, type EnumField as E, type Field as F, field as G, type HasManyField as H, type IntField as I, fileMaxBytes as J, mimeTypesFor as K, storedFields as L, type MultiSelectField as M, type Policy as P, type Resource as R, type StoredField as S, type TextField as T, type UpdateInput as U, type BooleanField as a, type ClientResource as b, type DateField as c, type DateTimeField as d, FILE_CATEGORIES as e, type FieldKind as f, type FieldValue as g, type FileCategory as h, type FileField as i, type FloatField as j, type PolicyAction as k, type PolicyConfig as l, PolicyError as m, RESERVED_FIELD_NAMES as n, type RecordOf as o, type ResourceConfig as p, ResourceDefinitionError as q, STRING_FORMATS as r, type StringField as s, type StringFormat as t, allowedActions as u, can as v, clientResource as w, columnName as x, defaultFieldLabel as y, definePolicy as z };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
|
|
2
|
-
import { BaseSQLiteDatabase, SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
3
|
-
import { S as StoredField, R as Resource, F as Field, P as Policy } from './policy-
|
|
2
|
+
import { BaseSQLiteDatabase, SQLiteTable, SQLiteColumn } from 'drizzle-orm/sqlite-core';
|
|
3
|
+
import { S as StoredField, R as Resource, F as Field, P as Policy } from './policy-DaKcWZ5g.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The handful of things a resource store needs a database to do.
|
|
7
|
+
*
|
|
8
|
+
* Everything that makes a Flare resource behave the way it does — validation, hooks,
|
|
9
|
+
* computed values, cursors, capped counts, the wording of a constraint error — lives in
|
|
10
|
+
* the store and is the same on every stack. What differs is how rows are actually read
|
|
11
|
+
* and written, and that is this interface: nine operations, described in the
|
|
12
|
+
* descriptor's terms rather than in SQL.
|
|
13
|
+
*
|
|
14
|
+
* Drizzle over SQLite is one implementation; Prisma over Postgres is another. A store
|
|
15
|
+
* built on either behaves identically, which is the whole point — the same descriptor
|
|
16
|
+
* has to mean the same thing on Cloudflare and on Vercel.
|
|
17
|
+
*/
|
|
18
|
+
type Row = Record<string, unknown>;
|
|
19
|
+
/** What a list needs, before it becomes SQL. */
|
|
20
|
+
interface RowsQuery {
|
|
21
|
+
/** Free-text search across the fields the descriptor marks searchable. */
|
|
22
|
+
search?: {
|
|
23
|
+
term: string;
|
|
24
|
+
fields: string[];
|
|
25
|
+
};
|
|
26
|
+
/** field → value, or null for "is null". */
|
|
27
|
+
filters: Record<string, unknown>;
|
|
28
|
+
sort: {
|
|
29
|
+
field: string;
|
|
30
|
+
direction: "asc" | "desc";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Keyset pagination: everything after (or before) this row, compared on
|
|
34
|
+
* `(sort field, id)` together so rows sharing a sort value are neither skipped nor
|
|
35
|
+
* repeated.
|
|
36
|
+
*/
|
|
37
|
+
cursor?: {
|
|
38
|
+
field: string;
|
|
39
|
+
value: unknown;
|
|
40
|
+
id: string;
|
|
41
|
+
greaterThan: boolean;
|
|
42
|
+
};
|
|
43
|
+
limit: number;
|
|
44
|
+
/** Offset paging, for the page-number case. Never combined with a cursor. */
|
|
45
|
+
offset?: number;
|
|
46
|
+
}
|
|
47
|
+
/** A database refusing a write, in terms the store can turn into a message. */
|
|
48
|
+
type ConstraintHit = {
|
|
49
|
+
kind: "unique";
|
|
50
|
+
column?: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: "foreignKey";
|
|
53
|
+
} | {
|
|
54
|
+
kind: "check";
|
|
55
|
+
};
|
|
56
|
+
interface ResourceRows {
|
|
57
|
+
/** The database handle a descriptor's hooks are given. Drizzle, or a Prisma client. */
|
|
58
|
+
readonly db: unknown;
|
|
59
|
+
find(query: RowsQuery): Promise<Row[]>;
|
|
60
|
+
/** How many rows match, giving up at `limit` so a huge table is never fully counted. */
|
|
61
|
+
countUpTo(query: Pick<RowsQuery, "search" | "filters">, limit: number): Promise<number>;
|
|
62
|
+
byId(id: string): Promise<Row | null>;
|
|
63
|
+
titles(ids: string[], titleField: string): Promise<{
|
|
64
|
+
id: string;
|
|
65
|
+
title: unknown;
|
|
66
|
+
}[]>;
|
|
67
|
+
insert(values: Row): Promise<Row>;
|
|
68
|
+
/** The saved row, or null when there was nothing with that id. */
|
|
69
|
+
update(id: string, values: Row): Promise<Row | null>;
|
|
70
|
+
/** Whether a row was actually removed. */
|
|
71
|
+
remove(id: string): Promise<boolean>;
|
|
72
|
+
/** Recognise a constraint violation, or hand the error back by returning undefined. */
|
|
73
|
+
constraint(error: unknown): ConstraintHit | undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type AnyDatabase = BaseSQLiteDatabase<"sync" | "async", any, any>;
|
|
77
|
+
declare function drizzleRows(table: SQLiteTable, getDb: () => AnyDatabase): ResourceRows & {
|
|
78
|
+
columns: Record<string, SQLiteColumn>;
|
|
79
|
+
};
|
|
4
80
|
|
|
5
81
|
interface ListQuery {
|
|
6
82
|
page: number;
|
|
@@ -44,7 +120,6 @@ declare function parseListQuery(resource: Resource, params: URLSearchParams): {
|
|
|
44
120
|
issues: QueryIssue[];
|
|
45
121
|
};
|
|
46
122
|
|
|
47
|
-
type AnyDatabase = BaseSQLiteDatabase<"sync" | "async", any, any>;
|
|
48
123
|
type ResourceAction = "list" | "read" | "create" | "update" | "delete";
|
|
49
124
|
interface FieldIssue {
|
|
50
125
|
/** Dotted field path; "" for the whole body (e.g. unknown keys). */
|
|
@@ -106,8 +181,14 @@ interface ResourceStoreOptions {
|
|
|
106
181
|
email: string;
|
|
107
182
|
role?: string | null;
|
|
108
183
|
} | null;
|
|
109
|
-
|
|
110
|
-
|
|
184
|
+
/**
|
|
185
|
+
* The Drizzle table and database, for the Cloudflare stack. Another stack passes
|
|
186
|
+
* `rows` instead — a Prisma-backed one, say — and everything else is identical.
|
|
187
|
+
*/
|
|
188
|
+
table?: SQLiteTable;
|
|
189
|
+
getDb?: () => AnyDatabase;
|
|
190
|
+
/** Where rows come from, when it isn't Drizzle. */
|
|
191
|
+
rows?: ResourceRows;
|
|
111
192
|
/**
|
|
112
193
|
* Called after a write succeeds, for cache invalidation. Runs before the
|
|
113
194
|
* operation returns, so a caller that revalidates tags can't hand back a
|
|
@@ -124,15 +205,15 @@ interface ResourceStoreOptions {
|
|
|
124
205
|
*/
|
|
125
206
|
declare function createResourceStore(options: ResourceStoreOptions): {
|
|
126
207
|
resource: Resource<Record<string, Field>>;
|
|
127
|
-
table: SQLiteTable<drizzle_orm_sqlite_core.TableConfig
|
|
208
|
+
table: SQLiteTable<drizzle_orm_sqlite_core.TableConfig> | undefined;
|
|
128
209
|
/** `params`: page, perPage, sort, q, filter[field], cursor (as parsed by parseListQuery). */
|
|
129
210
|
list(params: URLSearchParams): Promise<Result<ListResult>>;
|
|
130
211
|
get(id: string): Promise<Result<Record<string, unknown>>>;
|
|
131
212
|
/** Title-field values for a set of ids (for showing relations). Missing ids are omitted. */
|
|
132
213
|
titles(ids: string[]): Promise<Record<string, string>>;
|
|
133
|
-
create(
|
|
214
|
+
create(raw: unknown): Promise<Result<Record<string, unknown>>>;
|
|
134
215
|
/** Partial update (PATCH semantics). */
|
|
135
|
-
update(id: string,
|
|
216
|
+
update(id: string, raw: unknown): Promise<Result<Record<string, unknown>>>;
|
|
136
217
|
/** Full replacement (PUT semantics): omitted fields go back to their default, or null when optional. */
|
|
137
218
|
replace(id: string, input: unknown): Promise<Result<Record<string, unknown>>>;
|
|
138
219
|
delete(id: string): Promise<Result<{
|
|
@@ -151,8 +232,11 @@ interface AuthorizeContext {
|
|
|
151
232
|
type Authorize = (context: AuthorizeContext) => Response | void | Promise<Response | void>;
|
|
152
233
|
interface ResourceHandlerOptions {
|
|
153
234
|
resource: Resource;
|
|
154
|
-
|
|
155
|
-
|
|
235
|
+
/** Drizzle and D1, on the Cloudflare stack. Another stack passes `rows` instead. */
|
|
236
|
+
table?: SQLiteTable;
|
|
237
|
+
getDb?: () => AnyDatabase;
|
|
238
|
+
/** Where rows come from, when it isn't Drizzle — a Prisma-backed adapter, say. */
|
|
239
|
+
rows?: ResourceRows;
|
|
156
240
|
authorize: Authorize;
|
|
157
241
|
/** Who is making the request, for the descriptor's hooks. */
|
|
158
242
|
currentUser?: ResourceStoreOptions["currentUser"];
|
|
@@ -178,12 +262,12 @@ declare function createResourceHandlers(options: ResourceHandlerOptions): {
|
|
|
178
262
|
};
|
|
179
263
|
store: {
|
|
180
264
|
resource: Resource<Record<string, Field>>;
|
|
181
|
-
table: SQLiteTable<drizzle_orm_sqlite_core.TableConfig
|
|
265
|
+
table: SQLiteTable<drizzle_orm_sqlite_core.TableConfig> | undefined;
|
|
182
266
|
list(params: URLSearchParams): Promise<Result<ListResult>>;
|
|
183
267
|
get(id: string): Promise<Result<Record<string, unknown>>>;
|
|
184
268
|
titles(ids: string[]): Promise<Record<string, string>>;
|
|
185
|
-
create(
|
|
186
|
-
update(id: string,
|
|
269
|
+
create(raw: unknown): Promise<Result<Record<string, unknown>>>;
|
|
270
|
+
update(id: string, raw: unknown): Promise<Result<Record<string, unknown>>>;
|
|
187
271
|
replace(id: string, input: unknown): Promise<Result<Record<string, unknown>>>;
|
|
188
272
|
delete(id: string): Promise<Result<{
|
|
189
273
|
id: string;
|
|
@@ -191,6 +275,37 @@ declare function createResourceHandlers(options: ResourceHandlerOptions): {
|
|
|
191
275
|
};
|
|
192
276
|
};
|
|
193
277
|
|
|
278
|
+
/**
|
|
279
|
+
* {@link ResourceRows} over Prisma and Postgres — the Next.js stack's half.
|
|
280
|
+
*
|
|
281
|
+
* The same nine operations the Drizzle adapter implements, expressed as Prisma queries.
|
|
282
|
+
* Everything a resource *means* — validation, hooks, computed values, the capped count,
|
|
283
|
+
* the wording of a unique-constraint error — stays in the store and is shared, so a
|
|
284
|
+
* descriptor behaves the same on either stack.
|
|
285
|
+
*
|
|
286
|
+
* Nothing here imports `@prisma/client`: a generated client is app-specific and this
|
|
287
|
+
* package can't depend on one. The delegate is described structurally instead, which is
|
|
288
|
+
* also what makes it easy to test without a database.
|
|
289
|
+
*/
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The part of a Prisma model delegate a resource store uses.
|
|
293
|
+
*
|
|
294
|
+
* The arguments are loose on purpose. Prisma generates a precise argument type per
|
|
295
|
+
* model — `Prisma.ProductFindManyArgs` and so on — and describing them here would mean
|
|
296
|
+
* reproducing Prisma's generics for a client this package can't import. The queries are
|
|
297
|
+
* all built in this file, so there is exactly one place to get them right.
|
|
298
|
+
*/
|
|
299
|
+
interface PrismaDelegate {
|
|
300
|
+
findMany(args: any): Promise<any[]>;
|
|
301
|
+
findUnique(args: any): Promise<any>;
|
|
302
|
+
count(args: any): Promise<number>;
|
|
303
|
+
create(args: any): Promise<any>;
|
|
304
|
+
update(args: any): Promise<any>;
|
|
305
|
+
delete(args: any): Promise<any>;
|
|
306
|
+
}
|
|
307
|
+
declare function prismaRows(delegate: PrismaDelegate, client: unknown): ResourceRows;
|
|
308
|
+
|
|
194
309
|
/**
|
|
195
310
|
* An OpenAPI 3.1 document for an app's generated REST API, built at request time
|
|
196
311
|
* from the same descriptors and policies the API itself runs on, so it can't drift.
|
|
@@ -218,4 +333,4 @@ interface OpenApiOptions {
|
|
|
218
333
|
declare function fieldJsonSchema(def: StoredField): Schema;
|
|
219
334
|
declare function openApiDocument(options: OpenApiOptions): Schema;
|
|
220
335
|
|
|
221
|
-
export { type AnyDatabase, type Authorize, type AuthorizeContext, type ChangeEvent, type Failure, type FieldIssue, type ListQuery, type ListResult, type OpenApiOptions, type QueryIssue, type ResourceAction, type ResourceHandlerOptions, type ResourceStore, type ResourceStoreOptions, type Result, createResourceHandlers, createResourceStore, fieldJsonSchema, isFilterable, isSearchable, isSortable, openApiDocument, parseListQuery };
|
|
336
|
+
export { type AnyDatabase, type Authorize, type AuthorizeContext, type ChangeEvent, type ConstraintHit, type Failure, type FieldIssue, type ListQuery, type ListResult, type OpenApiOptions, type PrismaDelegate, type QueryIssue, type ResourceAction, type ResourceHandlerOptions, type ResourceRows, type ResourceStore, type ResourceStoreOptions, type Result, type Row, type RowsQuery, createResourceHandlers, createResourceStore, drizzleRows, fieldJsonSchema, isFilterable, isSearchable, isSortable, openApiDocument, parseListQuery, prismaRows };
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
can,
|
|
3
|
+
columnName,
|
|
3
4
|
createValidators,
|
|
4
5
|
storedFields
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VLHPSNQN.js";
|
|
6
7
|
|
|
7
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
8
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/entity.js
|
|
8
9
|
var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
|
|
9
10
|
function is(value, type) {
|
|
10
11
|
if (!value || typeof value !== "object") {
|
|
@@ -30,7 +31,7 @@ function is(value, type) {
|
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
34
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/column.js
|
|
34
35
|
var Column = class {
|
|
35
36
|
constructor(table, config) {
|
|
36
37
|
this.table = table;
|
|
@@ -81,7 +82,7 @@ var Column = class {
|
|
|
81
82
|
}
|
|
82
83
|
};
|
|
83
84
|
|
|
84
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
85
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/column-builder.js
|
|
85
86
|
var ColumnBuilder = class {
|
|
86
87
|
static [entityKind] = "ColumnBuilder";
|
|
87
88
|
config;
|
|
@@ -184,10 +185,10 @@ var ColumnBuilder = class {
|
|
|
184
185
|
}
|
|
185
186
|
};
|
|
186
187
|
|
|
187
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
188
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/table.utils.js
|
|
188
189
|
var TableName = /* @__PURE__ */ Symbol.for("drizzle:Name");
|
|
189
190
|
|
|
190
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
191
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/pg-core/foreign-keys.js
|
|
191
192
|
var ForeignKeyBuilder = class {
|
|
192
193
|
static [entityKind] = "PgForeignKeyBuilder";
|
|
193
194
|
/** @internal */
|
|
@@ -244,12 +245,12 @@ var ForeignKey = class {
|
|
|
244
245
|
}
|
|
245
246
|
};
|
|
246
247
|
|
|
247
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
248
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/tracing-utils.js
|
|
248
249
|
function iife(fn, ...args) {
|
|
249
250
|
return fn(...args);
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
253
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/pg-core/unique-constraint.js
|
|
253
254
|
function uniqueKeyName(table, columns) {
|
|
254
255
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
255
256
|
}
|
|
@@ -299,7 +300,7 @@ var UniqueConstraint = class {
|
|
|
299
300
|
}
|
|
300
301
|
};
|
|
301
302
|
|
|
302
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
303
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/pg-core/utils/array.js
|
|
303
304
|
function parsePgArrayValue(arrayString, startFrom, inQuotes) {
|
|
304
305
|
for (let i = startFrom; i < arrayString.length; i++) {
|
|
305
306
|
const char = arrayString[i];
|
|
@@ -375,7 +376,7 @@ function makePgArray(array) {
|
|
|
375
376
|
}).join(",")}}`;
|
|
376
377
|
}
|
|
377
378
|
|
|
378
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
379
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/pg-core/columns/common.js
|
|
379
380
|
var PgColumnBuilder = class extends ColumnBuilder {
|
|
380
381
|
foreignKeyConfigs = [];
|
|
381
382
|
static [entityKind] = "PgColumnBuilder";
|
|
@@ -559,7 +560,7 @@ var PgArray = class _PgArray extends PgColumn {
|
|
|
559
560
|
}
|
|
560
561
|
};
|
|
561
562
|
|
|
562
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
563
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/pg-core/columns/enum.js
|
|
563
564
|
var PgEnumObjectColumnBuilder = class extends PgColumnBuilder {
|
|
564
565
|
static [entityKind] = "PgEnumObjectColumnBuilder";
|
|
565
566
|
constructor(name, enumInstance) {
|
|
@@ -617,7 +618,7 @@ var PgEnumColumn = class extends PgColumn {
|
|
|
617
618
|
}
|
|
618
619
|
};
|
|
619
620
|
|
|
620
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
621
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/subquery.js
|
|
621
622
|
var Subquery = class {
|
|
622
623
|
static [entityKind] = "Subquery";
|
|
623
624
|
constructor(sql2, fields, alias, isWith = false, usedTables = []) {
|
|
@@ -638,10 +639,10 @@ var WithSubquery = class extends Subquery {
|
|
|
638
639
|
static [entityKind] = "WithSubquery";
|
|
639
640
|
};
|
|
640
641
|
|
|
641
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
642
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/version.js
|
|
642
643
|
var version = "0.45.2";
|
|
643
644
|
|
|
644
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
645
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/tracing.js
|
|
645
646
|
var otel;
|
|
646
647
|
var rawTracer;
|
|
647
648
|
var tracer = {
|
|
@@ -676,10 +677,10 @@ var tracer = {
|
|
|
676
677
|
}
|
|
677
678
|
};
|
|
678
679
|
|
|
679
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
680
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/view-common.js
|
|
680
681
|
var ViewBaseConfig = /* @__PURE__ */ Symbol.for("drizzle:ViewBaseConfig");
|
|
681
682
|
|
|
682
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
683
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/table.js
|
|
683
684
|
var Schema = /* @__PURE__ */ Symbol.for("drizzle:Schema");
|
|
684
685
|
var Columns = /* @__PURE__ */ Symbol.for("drizzle:Columns");
|
|
685
686
|
var ExtraConfigColumns = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigColumns");
|
|
@@ -735,7 +736,7 @@ var Table = class {
|
|
|
735
736
|
}
|
|
736
737
|
};
|
|
737
738
|
|
|
738
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
739
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/sql/sql.js
|
|
739
740
|
var FakePrimitiveParam = class {
|
|
740
741
|
static [entityKind] = "FakePrimitiveParam";
|
|
741
742
|
};
|
|
@@ -847,13 +848,13 @@ var SQL = class _SQL {
|
|
|
847
848
|
};
|
|
848
849
|
}
|
|
849
850
|
if (is(chunk, Column)) {
|
|
850
|
-
const
|
|
851
|
+
const columnName2 = casing.getColumnCasing(chunk);
|
|
851
852
|
if (_config.invokeSource === "indexes") {
|
|
852
|
-
return { sql: escapeName(
|
|
853
|
+
return { sql: escapeName(columnName2), params: [] };
|
|
853
854
|
}
|
|
854
855
|
const schemaName = chunk.table[Table.Symbol.Schema];
|
|
855
856
|
return {
|
|
856
|
-
sql: chunk.table[IsAlias] || schemaName === void 0 ? escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(
|
|
857
|
+
sql: chunk.table[IsAlias] || schemaName === void 0 ? escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName2) : escapeName(schemaName) + "." + escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName2),
|
|
857
858
|
params: []
|
|
858
859
|
};
|
|
859
860
|
}
|
|
@@ -1112,13 +1113,13 @@ Subquery.prototype.getSQL = function() {
|
|
|
1112
1113
|
return new SQL([this]);
|
|
1113
1114
|
};
|
|
1114
1115
|
|
|
1115
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
1116
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/utils.js
|
|
1116
1117
|
function getTableColumns(table) {
|
|
1117
1118
|
return table[Table.Symbol.Columns];
|
|
1118
1119
|
}
|
|
1119
1120
|
var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder();
|
|
1120
1121
|
|
|
1121
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
1122
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/sql/expressions/conditions.js
|
|
1122
1123
|
function bindIfParam(value, column) {
|
|
1123
1124
|
if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
|
|
1124
1125
|
return new Param(value, column);
|
|
@@ -1173,7 +1174,7 @@ function isNull(value) {
|
|
|
1173
1174
|
return sql`${value} is null`;
|
|
1174
1175
|
}
|
|
1175
1176
|
|
|
1176
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
1177
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/sql/expressions/select.js
|
|
1177
1178
|
function asc(column) {
|
|
1178
1179
|
return sql`${column} asc`;
|
|
1179
1180
|
}
|
|
@@ -1181,11 +1182,101 @@ function desc(column) {
|
|
|
1181
1182
|
return sql`${column} desc`;
|
|
1182
1183
|
}
|
|
1183
1184
|
|
|
1184
|
-
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@
|
|
1185
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/sql/functions/aggregate.js
|
|
1185
1186
|
function count(expression) {
|
|
1186
1187
|
return sql`count(${expression || sql.raw("*")})`.mapWith(Number);
|
|
1187
1188
|
}
|
|
1188
1189
|
|
|
1190
|
+
// src/server/drizzle-rows.ts
|
|
1191
|
+
function errorText(error) {
|
|
1192
|
+
const parts = [];
|
|
1193
|
+
for (let current = error, depth = 0; current && depth < 5; depth++) {
|
|
1194
|
+
if (current instanceof Error) {
|
|
1195
|
+
parts.push(current.message);
|
|
1196
|
+
current = current.cause;
|
|
1197
|
+
} else {
|
|
1198
|
+
parts.push(String(current));
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return parts.join(" | ");
|
|
1203
|
+
}
|
|
1204
|
+
function drizzleRows(table, getDb) {
|
|
1205
|
+
const columns = getTableColumns(table);
|
|
1206
|
+
const idColumn = columns.id;
|
|
1207
|
+
const byId = (id) => eq(idColumn, id);
|
|
1208
|
+
function matching(query) {
|
|
1209
|
+
const conditions = [];
|
|
1210
|
+
if (query.search && query.search.fields.length > 0) {
|
|
1211
|
+
const pattern = `%${query.search.term.replace(/[\\%_]/g, (char) => `\\${char}`)}%`;
|
|
1212
|
+
const searchable = query.search.fields.map((key) => columns[key]);
|
|
1213
|
+
conditions.push(or(...searchable.map((column) => sql`${column} LIKE ${pattern} ESCAPE '\\'`)));
|
|
1214
|
+
}
|
|
1215
|
+
for (const [key, value] of Object.entries(query.filters)) {
|
|
1216
|
+
conditions.push(value === null ? isNull(columns[key]) : eq(columns[key], value));
|
|
1217
|
+
}
|
|
1218
|
+
return conditions;
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
columns,
|
|
1222
|
+
get db() {
|
|
1223
|
+
return getDb();
|
|
1224
|
+
},
|
|
1225
|
+
async find(query) {
|
|
1226
|
+
const conditions = matching(query);
|
|
1227
|
+
const sortColumn = columns[query.sort.field];
|
|
1228
|
+
const descending = query.sort.direction === "desc";
|
|
1229
|
+
const order = descending ? [desc(sortColumn), desc(idColumn)] : [asc(sortColumn), asc(idColumn)];
|
|
1230
|
+
if (query.cursor) {
|
|
1231
|
+
const { value, id, greaterThan } = query.cursor;
|
|
1232
|
+
conditions.push(
|
|
1233
|
+
greaterThan ? or(sql`${sortColumn} > ${value}`, and(sql`${sortColumn} = ${value}`, sql`${idColumn} > ${id}`)) : or(sql`${sortColumn} < ${value}`, and(sql`${sortColumn} = ${value}`, sql`${idColumn} < ${id}`))
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
const where = conditions.length ? and(...conditions) : void 0;
|
|
1237
|
+
const statement = getDb().select().from(table).where(where).orderBy(...order).limit(query.limit);
|
|
1238
|
+
const rows = query.offset ? await statement.offset(query.offset) : await statement;
|
|
1239
|
+
return rows;
|
|
1240
|
+
},
|
|
1241
|
+
async countUpTo(query, limit) {
|
|
1242
|
+
const conditions = matching(query);
|
|
1243
|
+
const where = conditions.length ? and(...conditions) : void 0;
|
|
1244
|
+
const db = getDb();
|
|
1245
|
+
const [counted] = await db.select({ total: count() }).from(db.select({ id: idColumn }).from(table).where(where).limit(limit).as("capped"));
|
|
1246
|
+
return counted?.total ?? 0;
|
|
1247
|
+
},
|
|
1248
|
+
async byId(id) {
|
|
1249
|
+
const [record] = await getDb().select().from(table).where(byId(id)).limit(1);
|
|
1250
|
+
return record ?? null;
|
|
1251
|
+
},
|
|
1252
|
+
async titles(ids, titleField) {
|
|
1253
|
+
const titleColumn = columns[titleField] ?? idColumn;
|
|
1254
|
+
const rows = await getDb().select({ id: idColumn, title: titleColumn }).from(table).where(inArray(idColumn, ids));
|
|
1255
|
+
return rows;
|
|
1256
|
+
},
|
|
1257
|
+
async insert(values) {
|
|
1258
|
+
const [record] = await getDb().insert(table).values(values).returning();
|
|
1259
|
+
return record;
|
|
1260
|
+
},
|
|
1261
|
+
async update(id, values) {
|
|
1262
|
+
const [record] = await getDb().update(table).set(values).where(byId(id)).returning();
|
|
1263
|
+
return record ?? null;
|
|
1264
|
+
},
|
|
1265
|
+
async remove(id) {
|
|
1266
|
+
const deleted = await getDb().delete(table).where(byId(id)).returning({ id: idColumn });
|
|
1267
|
+
return deleted.length > 0;
|
|
1268
|
+
},
|
|
1269
|
+
constraint(error) {
|
|
1270
|
+
const text = errorText(error);
|
|
1271
|
+
const unique = /UNIQUE constraint failed: ([\w.]+(?:, [\w.]+)*)/.exec(text);
|
|
1272
|
+
if (unique) return { kind: "unique", column: unique[1].split(", ")[0].split(".").pop() };
|
|
1273
|
+
if (/FOREIGN KEY constraint failed/.test(text)) return { kind: "foreignKey" };
|
|
1274
|
+
if (/CHECK constraint failed/.test(text)) return { kind: "check" };
|
|
1275
|
+
return void 0;
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1189
1280
|
// src/server/query.ts
|
|
1190
1281
|
function encodeCursor(cursor) {
|
|
1191
1282
|
const json2 = JSON.stringify([cursor.value, cursor.id, cursor.direction === "before" ? 0 : 1]);
|
|
@@ -1284,45 +1375,36 @@ function toCursorValue(value) {
|
|
|
1284
1375
|
if (typeof value === "object") return String(value);
|
|
1285
1376
|
return value;
|
|
1286
1377
|
}
|
|
1287
|
-
function errorText(error) {
|
|
1288
|
-
const parts = [];
|
|
1289
|
-
for (let current = error, depth = 0; current && depth < 5; depth++) {
|
|
1290
|
-
if (current instanceof Error) {
|
|
1291
|
-
parts.push(current.message);
|
|
1292
|
-
current = current.cause;
|
|
1293
|
-
} else {
|
|
1294
|
-
parts.push(String(current));
|
|
1295
|
-
break;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
return parts.join(" | ");
|
|
1299
|
-
}
|
|
1300
1378
|
function createResourceStore(options) {
|
|
1301
|
-
const { resource, table
|
|
1379
|
+
const { resource, table } = options;
|
|
1302
1380
|
const validators = createValidators(resource);
|
|
1303
|
-
const columns = getTableColumns(table);
|
|
1304
1381
|
const fields = storedFields(resource);
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1382
|
+
if (!options.rows && !(table && options.getDb)) {
|
|
1383
|
+
throw new Error(`Resource "${resource.name}": a store needs either a Drizzle table and getDb, or rows.`);
|
|
1384
|
+
}
|
|
1385
|
+
const rows = options.rows ?? drizzleRows(table, options.getDb);
|
|
1386
|
+
const columns = "columns" in rows ? rows.columns : void 0;
|
|
1387
|
+
if (columns) {
|
|
1388
|
+
for (const key of ["id", "createdAt", "updatedAt", ...fields.map(([key2]) => key2)]) {
|
|
1389
|
+
if (!columns[key]) {
|
|
1390
|
+
throw new Error(`Resource "${resource.name}": table has no column for "${key}". Run \`flare sync-types\` and create a migration.`);
|
|
1391
|
+
}
|
|
1308
1392
|
}
|
|
1309
1393
|
}
|
|
1310
|
-
const
|
|
1311
|
-
const columnKeyByName = new Map(Object.entries(columns).map(([key, column]) => [column.name, key]));
|
|
1394
|
+
const keyForColumn = (name) => columns ? Object.entries(columns).find(([, column]) => column.name === name)?.[0] : ["id", "createdAt", "updatedAt", ...fields.map(([key]) => key)].find((key) => columnName(key) === name || key === name);
|
|
1312
1395
|
const fail = (status, error, extra = {}) => ({ ok: false, status, error, ...extra });
|
|
1313
1396
|
function constraintFailure(error, action) {
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
if (unique) {
|
|
1317
|
-
const key =
|
|
1397
|
+
const hit = rows.constraint(error);
|
|
1398
|
+
if (!hit) return void 0;
|
|
1399
|
+
if (hit.kind === "unique") {
|
|
1400
|
+
const key = hit.column ? keyForColumn(hit.column) : void 0;
|
|
1318
1401
|
const label = key ? resource.fields[key]?.label ?? key : "Value";
|
|
1319
1402
|
return fail(409, `${label} is already taken.`, key ? { field: key } : {});
|
|
1320
1403
|
}
|
|
1321
|
-
if (
|
|
1404
|
+
if (hit.kind === "foreignKey") {
|
|
1322
1405
|
return action === "delete" ? fail(409, `This ${resource.label.toLowerCase()} is still referenced by other records.`) : fail(422, "A related record doesn't exist.");
|
|
1323
1406
|
}
|
|
1324
|
-
|
|
1325
|
-
return void 0;
|
|
1407
|
+
return fail(422, "A value is outside its allowed options.");
|
|
1326
1408
|
}
|
|
1327
1409
|
async function guarded(action, work) {
|
|
1328
1410
|
try {
|
|
@@ -1347,7 +1429,6 @@ function createResourceStore(options) {
|
|
|
1347
1429
|
return result;
|
|
1348
1430
|
}
|
|
1349
1431
|
const invalid = (issues) => fail(422, "Validation failed.", { issues: issues.map((issue) => ({ path: issue.path.map(String).join("."), message: issue.message })) });
|
|
1350
|
-
const byId = (id) => eq(idColumn, id);
|
|
1351
1432
|
const notFound = () => fail(404, `${resource.label} not found.`);
|
|
1352
1433
|
const computed = Object.entries(resource.computed ?? {});
|
|
1353
1434
|
const withComputed = (row) => {
|
|
@@ -1363,7 +1444,7 @@ function createResourceStore(options) {
|
|
|
1363
1444
|
return result;
|
|
1364
1445
|
};
|
|
1365
1446
|
const hooks = resource.hooks ?? {};
|
|
1366
|
-
const hookContext = async () => ({ db:
|
|
1447
|
+
const hookContext = async () => ({ db: rows.db, user: await options.currentUser?.() ?? null });
|
|
1367
1448
|
return {
|
|
1368
1449
|
resource,
|
|
1369
1450
|
table,
|
|
@@ -1372,50 +1453,35 @@ function createResourceStore(options) {
|
|
|
1372
1453
|
const parsed = parseListQuery(resource, params);
|
|
1373
1454
|
if ("issues" in parsed) return fail(400, "Invalid query.", { queryIssues: parsed.issues });
|
|
1374
1455
|
const { page, perPage, sort, q, filters, cursor } = parsed.query;
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
const pattern = `%${q.replace(/[\\%_]/g, (char) => `\\${char}`)}%`;
|
|
1378
|
-
const searchable = fields.filter(([, def]) => isSearchable(def)).map(([key]) => columns[key]);
|
|
1379
|
-
if (searchable.length) conditions.push(or(...searchable.map((column) => sql`${column} LIKE ${pattern} ESCAPE '\\'`)));
|
|
1380
|
-
}
|
|
1381
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
1382
|
-
conditions.push(value === null ? isNull(columns[key]) : eq(columns[key], value));
|
|
1383
|
-
}
|
|
1384
|
-
const filterWhere = conditions.length ? and(...conditions) : void 0;
|
|
1385
|
-
const sortColumn = columns[sort.field];
|
|
1456
|
+
const searchable = fields.filter(([, def]) => isSearchable(def)).map(([key]) => key);
|
|
1457
|
+
const matching = { search: q ? { term: q, fields: searchable } : void 0, filters };
|
|
1386
1458
|
const backwards = cursor?.direction === "before";
|
|
1387
1459
|
const descending = backwards ? sort.direction === "asc" : sort.direction === "desc";
|
|
1388
|
-
const
|
|
1389
|
-
const pageConditions = [...conditions];
|
|
1390
|
-
if (cursor) {
|
|
1391
|
-
const value = cursor.value;
|
|
1392
|
-
pageConditions.push(
|
|
1393
|
-
descending ? or(sql`${sortColumn} < ${value}`, and(sql`${sortColumn} = ${value}`, sql`${idColumn} < ${cursor.id}`)) : or(sql`${sortColumn} > ${value}`, and(sql`${sortColumn} = ${value}`, sql`${idColumn} > ${cursor.id}`))
|
|
1394
|
-
);
|
|
1395
|
-
}
|
|
1396
|
-
const where = pageConditions.length ? and(...pageConditions) : void 0;
|
|
1397
|
-
const db = getDb();
|
|
1398
|
-
const query = db.select().from(table).where(where).orderBy(...order).limit(perPage + 1);
|
|
1399
|
-
const [found, counted] = await Promise.all([
|
|
1400
|
-
cursor ? query : query.offset((page - 1) * perPage),
|
|
1460
|
+
const [found, counting] = await Promise.all([
|
|
1401
1461
|
// One row past the limit is enough to know there's more without counting it all.
|
|
1402
|
-
|
|
1462
|
+
rows.find({
|
|
1463
|
+
...matching,
|
|
1464
|
+
sort: { field: sort.field, direction: descending ? "desc" : "asc" },
|
|
1465
|
+
cursor: cursor ? { field: sort.field, value: cursor.value, id: cursor.id, greaterThan: !descending } : void 0,
|
|
1466
|
+
limit: perPage + 1,
|
|
1467
|
+
offset: cursor ? void 0 : (page - 1) * perPage
|
|
1468
|
+
}),
|
|
1469
|
+
rows.countUpTo(matching, COUNT_LIMIT + 1)
|
|
1403
1470
|
]);
|
|
1404
1471
|
const hasMore = found.length > perPage;
|
|
1405
|
-
const
|
|
1406
|
-
if (backwards)
|
|
1407
|
-
const counting = counted[0]?.total ?? 0;
|
|
1472
|
+
const page_ = (hasMore ? found.slice(0, perPage) : found).map(withComputed);
|
|
1473
|
+
if (backwards) page_.reverse();
|
|
1408
1474
|
const exactTotal = counting <= COUNT_LIMIT;
|
|
1409
1475
|
const total = exactTotal ? counting : COUNT_LIMIT;
|
|
1410
|
-
const first =
|
|
1411
|
-
const last =
|
|
1476
|
+
const first = page_[0];
|
|
1477
|
+
const last = page_[page_.length - 1];
|
|
1412
1478
|
const moreAfter = backwards ? true : hasMore;
|
|
1413
1479
|
const moreBefore = backwards ? hasMore : Boolean(cursor) || page > 1;
|
|
1414
1480
|
const cursorFor = (row, direction) => row ? encodeCursor({ value: toCursorValue(row[sort.field]), id: String(row.id), direction }) : void 0;
|
|
1415
1481
|
return {
|
|
1416
1482
|
ok: true,
|
|
1417
1483
|
data: {
|
|
1418
|
-
data:
|
|
1484
|
+
data: page_,
|
|
1419
1485
|
meta: {
|
|
1420
1486
|
page,
|
|
1421
1487
|
perPage,
|
|
@@ -1429,40 +1495,40 @@ function createResourceStore(options) {
|
|
|
1429
1495
|
};
|
|
1430
1496
|
},
|
|
1431
1497
|
async get(id) {
|
|
1432
|
-
const
|
|
1498
|
+
const record = await rows.byId(id);
|
|
1433
1499
|
return record ? { ok: true, data: withComputed(record) } : notFound();
|
|
1434
1500
|
},
|
|
1435
1501
|
/** Title-field values for a set of ids (for showing relations). Missing ids are omitted. */
|
|
1436
1502
|
async titles(ids) {
|
|
1437
1503
|
const unique = [...new Set(ids.filter(Boolean))];
|
|
1438
1504
|
if (unique.length === 0) return {};
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
return Object.fromEntries(rows.map((row) => [row.id, row.title == null ? row.id : String(row.title)]));
|
|
1505
|
+
const found = await rows.titles(unique, resource.titleField);
|
|
1506
|
+
return Object.fromEntries(found.map((row) => [row.id, row.title == null ? row.id : String(row.title)]));
|
|
1442
1507
|
},
|
|
1443
|
-
create(
|
|
1444
|
-
const result = validators.create.safeParse(input);
|
|
1445
|
-
if (!result.success) return Promise.resolve(invalid(result.error.issues));
|
|
1508
|
+
create(raw) {
|
|
1446
1509
|
return writing("create", async () => {
|
|
1447
1510
|
const context = await hookContext();
|
|
1448
|
-
const
|
|
1511
|
+
const supplied = hooks.beforeCreate ? await hooks.beforeCreate({ ...raw ?? {} }, context) : raw;
|
|
1512
|
+
const result = validators.create.safeParse(supplied);
|
|
1513
|
+
if (!result.success) return invalid(result.error.issues);
|
|
1514
|
+
const input = result.data;
|
|
1449
1515
|
const now = /* @__PURE__ */ new Date();
|
|
1450
|
-
const
|
|
1516
|
+
const record = await rows.insert({ ...input, id: crypto.randomUUID(), createdAt: now, updatedAt: now });
|
|
1451
1517
|
const saved = withComputed(record);
|
|
1452
1518
|
await hooks.afterCreate?.(saved, context);
|
|
1453
1519
|
return { ok: true, data: saved };
|
|
1454
1520
|
});
|
|
1455
1521
|
},
|
|
1456
1522
|
/** Partial update (PATCH semantics). */
|
|
1457
|
-
update(id,
|
|
1458
|
-
const result = validators.update.safeParse(input);
|
|
1459
|
-
if (!result.success) return Promise.resolve(invalid(result.error.issues));
|
|
1523
|
+
update(id, raw) {
|
|
1460
1524
|
return writing("update", async () => {
|
|
1461
1525
|
const context = await hookContext();
|
|
1462
|
-
const
|
|
1463
|
-
const
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1526
|
+
const current = hooks.beforeUpdate || hooks.afterUpdate ? await rows.byId(id) : null;
|
|
1527
|
+
const supplied = hooks.beforeUpdate ? await hooks.beforeUpdate({ ...raw ?? {} }, { ...context, id, current }) : raw;
|
|
1528
|
+
const result = validators.update.safeParse(supplied);
|
|
1529
|
+
if (!result.success) return invalid(result.error.issues);
|
|
1530
|
+
const input = result.data;
|
|
1531
|
+
const record = await rows.update(id, { ...input, updatedAt: /* @__PURE__ */ new Date() });
|
|
1466
1532
|
if (!record) return notFound();
|
|
1467
1533
|
const saved = withComputed(record);
|
|
1468
1534
|
await hooks.afterUpdate?.(saved, { ...context, previous: current });
|
|
@@ -1479,7 +1545,7 @@ function createResourceStore(options) {
|
|
|
1479
1545
|
values[key] = "default" in def && def.default !== void 0 ? def.default : def.required ? void 0 : null;
|
|
1480
1546
|
}
|
|
1481
1547
|
Object.assign(values, result.data, { updatedAt: /* @__PURE__ */ new Date() });
|
|
1482
|
-
const
|
|
1548
|
+
const record = await rows.update(id, values);
|
|
1483
1549
|
return record ? { ok: true, data: withComputed(record) } : notFound();
|
|
1484
1550
|
});
|
|
1485
1551
|
},
|
|
@@ -1487,11 +1553,9 @@ function createResourceStore(options) {
|
|
|
1487
1553
|
return writing("delete", async () => {
|
|
1488
1554
|
const context = await hookContext();
|
|
1489
1555
|
if (hooks.beforeDelete) {
|
|
1490
|
-
|
|
1491
|
-
await hooks.beforeDelete({ ...context, id, current: existing ?? null });
|
|
1556
|
+
await hooks.beforeDelete({ ...context, id, current: await rows.byId(id) });
|
|
1492
1557
|
}
|
|
1493
|
-
|
|
1494
|
-
if (deleted.length === 0) return notFound();
|
|
1558
|
+
if (!await rows.remove(id)) return notFound();
|
|
1495
1559
|
await hooks.afterDelete?.({ ...context, id });
|
|
1496
1560
|
return { ok: true, data: { id } };
|
|
1497
1561
|
});
|
|
@@ -1584,6 +1648,99 @@ function createResourceHandlers(options) {
|
|
|
1584
1648
|
};
|
|
1585
1649
|
}
|
|
1586
1650
|
|
|
1651
|
+
// src/server/prisma-rows.ts
|
|
1652
|
+
var NOT_FOUND = "P2025";
|
|
1653
|
+
var codeOf = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
1654
|
+
function uniqueColumn(error) {
|
|
1655
|
+
const meta = error.meta;
|
|
1656
|
+
if (!meta) return void 0;
|
|
1657
|
+
const target = meta.target;
|
|
1658
|
+
if (Array.isArray(target) && target.length > 0) return String(target[0]);
|
|
1659
|
+
if (typeof target === "string") return fromIndexName(target);
|
|
1660
|
+
const cause = meta.driverAdapterError?.cause;
|
|
1661
|
+
const constraint = cause?.constraint;
|
|
1662
|
+
if (constraint?.fields?.length) return constraint.fields[0];
|
|
1663
|
+
if (constraint?.index) return fromIndexName(constraint.index);
|
|
1664
|
+
return void 0;
|
|
1665
|
+
}
|
|
1666
|
+
function fromIndexName(name) {
|
|
1667
|
+
const match = /^.*?_(.+)_key$/.exec(name);
|
|
1668
|
+
return match ? match[1] : name;
|
|
1669
|
+
}
|
|
1670
|
+
function prismaRows(delegate, client) {
|
|
1671
|
+
function matching(query) {
|
|
1672
|
+
const where = { ...query.filters };
|
|
1673
|
+
if (query.search && query.search.fields.length > 0) {
|
|
1674
|
+
where.OR = query.search.fields.map((field) => ({ [field]: { contains: query.search.term, mode: "insensitive" } }));
|
|
1675
|
+
}
|
|
1676
|
+
return where;
|
|
1677
|
+
}
|
|
1678
|
+
return {
|
|
1679
|
+
db: client,
|
|
1680
|
+
async find(query) {
|
|
1681
|
+
const where = matching(query);
|
|
1682
|
+
if (query.cursor) {
|
|
1683
|
+
const { field, value, id, greaterThan } = query.cursor;
|
|
1684
|
+
const op = greaterThan ? "gt" : "lt";
|
|
1685
|
+
const keyset = [{ [field]: { [op]: value } }, { AND: [{ [field]: value }, { id: { [op]: id } }] }];
|
|
1686
|
+
where.AND = [...where.AND ?? [], { OR: keyset }];
|
|
1687
|
+
}
|
|
1688
|
+
return delegate.findMany({
|
|
1689
|
+
where,
|
|
1690
|
+
orderBy: [{ [query.sort.field]: query.sort.direction }, { id: query.sort.direction }],
|
|
1691
|
+
take: query.limit,
|
|
1692
|
+
...query.offset ? { skip: query.offset } : {}
|
|
1693
|
+
});
|
|
1694
|
+
},
|
|
1695
|
+
countUpTo(query, limit) {
|
|
1696
|
+
return delegate.count({ where: matching(query), take: limit });
|
|
1697
|
+
},
|
|
1698
|
+
byId(id) {
|
|
1699
|
+
return delegate.findUnique({ where: { id } });
|
|
1700
|
+
},
|
|
1701
|
+
async titles(ids, titleField) {
|
|
1702
|
+
const rows = await delegate.findMany({ where: { id: { in: ids } }, select: { id: true, [titleField]: true } });
|
|
1703
|
+
return rows.map((row) => ({ id: String(row.id), title: row[titleField] }));
|
|
1704
|
+
},
|
|
1705
|
+
insert(values) {
|
|
1706
|
+
return delegate.create({ data: values });
|
|
1707
|
+
},
|
|
1708
|
+
async update(id, values) {
|
|
1709
|
+
try {
|
|
1710
|
+
return await delegate.update({ where: { id }, data: values });
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
if (codeOf(error) === NOT_FOUND) return null;
|
|
1713
|
+
throw error;
|
|
1714
|
+
}
|
|
1715
|
+
},
|
|
1716
|
+
async remove(id) {
|
|
1717
|
+
try {
|
|
1718
|
+
await delegate.delete({ where: { id } });
|
|
1719
|
+
return true;
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
if (codeOf(error) === NOT_FOUND) return false;
|
|
1722
|
+
throw error;
|
|
1723
|
+
}
|
|
1724
|
+
},
|
|
1725
|
+
constraint(error) {
|
|
1726
|
+
switch (codeOf(error)) {
|
|
1727
|
+
case "P2002":
|
|
1728
|
+
return { kind: "unique", column: uniqueColumn(error) };
|
|
1729
|
+
case "P2003":
|
|
1730
|
+
case "P2014":
|
|
1731
|
+
return { kind: "foreignKey" };
|
|
1732
|
+
// A value outside its type, too long for its column, or null where it can't be.
|
|
1733
|
+
case "P2000":
|
|
1734
|
+
case "P2011":
|
|
1735
|
+
case "P2020":
|
|
1736
|
+
return { kind: "check" };
|
|
1737
|
+
default:
|
|
1738
|
+
return void 0;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1587
1744
|
// src/server/openapi.ts
|
|
1588
1745
|
var E164 = "^\\+[1-9]\\d{6,14}$";
|
|
1589
1746
|
function fieldJsonSchema(def) {
|
|
@@ -1857,10 +2014,12 @@ function openApiDocument(options) {
|
|
|
1857
2014
|
export {
|
|
1858
2015
|
createResourceHandlers,
|
|
1859
2016
|
createResourceStore,
|
|
2017
|
+
drizzleRows,
|
|
1860
2018
|
fieldJsonSchema,
|
|
1861
2019
|
isFilterable,
|
|
1862
2020
|
isSearchable,
|
|
1863
2021
|
isSortable,
|
|
1864
2022
|
openApiDocument,
|
|
1865
|
-
parseListQuery
|
|
2023
|
+
parseListQuery,
|
|
2024
|
+
prismaRows
|
|
1866
2025
|
};
|