@flaredev/core 0.4.0 → 0.4.1

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.
@@ -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
- archive: ["application/zip"]
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-CvfE-wCK.js';
2
- export { B as BelongsToField, a as BooleanField, D as DEFAULT_FILE_MAX_BYTES, b as DateField, c as DateTimeField, E as EnumField, d as FILE_CATEGORIES, e as FieldKind, f as FieldValue, g as FileCategory, h as FileField, i as FloatField, H as HasManyField, I as IntField, M as MultiSelectField, P as Policy, j as PolicyAction, k as PolicyConfig, l as PolicyError, m as RESERVED_FIELD_NAMES, n as RecordOf, o as ResourceConfig, p as ResourceDefinitionError, q as STRING_FORMATS, r as StringField, s as StringFormat, T as TextField, t as allowedActions, u as can, v as columnName, w as defaultFieldLabel, x as definePolicy, y as defineResource, z as field, A as fileMaxBytes, G as mimeTypesFor, J as storedFields } from './policy-CvfE-wCK.js';
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
  /**
@@ -286,7 +286,7 @@ declare function fieldSchema(def: StoredField): z.ZodType;
286
286
  * edit a record could attach another resource's private file and read it through theirs.
287
287
  */
288
288
  declare const fileKeyPrefix: (resource: Pick<Resource, "table">, fieldKey: string) => string;
289
- declare function createValidators<Fields extends Record<string, Field>>(resource: Resource<Fields>): ResourceValidators<Fields>;
289
+ declare function createValidators<Fields extends Record<string, Field>>(resource: Pick<Resource<Fields>, "fields" | "table">): ResourceValidators<Fields>;
290
290
 
291
291
  /** Naming helpers shared by descriptors and the generator. */
292
292
  /** "OrderItem" / "order item" / "order-item" → ["order", "item"] */
@@ -365,13 +365,13 @@ declare function formatValue(def: StoredField | {
365
365
  */
366
366
  type FormValues = Record<string, string | boolean>;
367
367
  /** 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;
368
+ declare function initialFormValues(resource: Pick<Resource, "fields">, record?: Record<string, unknown> | null): FormValues;
369
369
  /**
370
370
  * API input from form state. Empty optional fields become null (clearing them);
371
371
  * empty fields that have a default are omitted on create so the default applies;
372
372
  * empty required fields are left for validation to report as "Required".
373
373
  */
374
- declare function formValuesToInput(resource: Resource, values: FormValues, mode: "create" | "edit"): Record<string, unknown>;
374
+ declare function formValuesToInput(resource: Pick<Resource, "fields">, values: FormValues, mode: "create" | "edit"): Record<string, unknown>;
375
375
  /** A multiselect's form state (a JSON array string) as the picked values. */
376
376
  declare function parseMultiValue(raw: string | boolean | undefined): string[];
377
377
  /** Field errors keyed by field name, from validation or API issues (first message wins). */
@@ -439,6 +439,8 @@ interface Fake {
439
439
  email(name?: string): string;
440
440
  company(): string;
441
441
  jobTitle(): string;
442
+ /** "Brushed steel desk lamp", or a digital one: "Starter kit licence". */
443
+ product(digital?: boolean): string;
442
444
  city(): string;
443
445
  /** ISO 3166-1 alpha-2, e.g. "UG". */
444
446
  country(): string;
@@ -507,6 +509,6 @@ interface SeedContext {
507
509
  type Seed = (context: SeedContext) => void | Promise<void>;
508
510
  declare function defineSeed(seed: Seed): Seed;
509
511
 
510
- declare const FLARE_VERSION = "0.4.0";
512
+ declare const FLARE_VERSION = "0.4.1";
511
513
 
512
514
  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-PWP4QEZV.js";
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.4.0";
997
+ var FLARE_VERSION = "0.4.1";
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
- /** Change or check the input before it's written. Return the input (changed or not), or throw to refuse. */
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 { fileMaxBytes as A, type BelongsToField as B, type CreateInput as C, DEFAULT_FILE_MAX_BYTES as D, type EnumField as E, type Field as F, mimeTypesFor as G, type HasManyField as H, type IntField as I, storedFields as J, 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 DateField as b, type DateTimeField as c, FILE_CATEGORIES as d, type FieldKind as e, type FieldValue as f, type FileCategory as g, type FileField as h, type FloatField as i, type PolicyAction as j, type PolicyConfig as k, PolicyError as l, RESERVED_FIELD_NAMES as m, type RecordOf as n, type ResourceConfig as o, ResourceDefinitionError as p, STRING_FORMATS as q, type StringField as r, type StringFormat as s, allowedActions as t, can as u, columnName as v, defaultFieldLabel as w, definePolicy as x, defineResource as y, field as z };
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,6 @@
1
1
  import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
2
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-CvfE-wCK.js';
3
+ import { S as StoredField, R as Resource, F as Field, P as Policy } from './policy-DaKcWZ5g.js';
4
4
 
5
5
  interface ListQuery {
6
6
  page: number;
@@ -130,9 +130,9 @@ declare function createResourceStore(options: ResourceStoreOptions): {
130
130
  get(id: string): Promise<Result<Record<string, unknown>>>;
131
131
  /** Title-field values for a set of ids (for showing relations). Missing ids are omitted. */
132
132
  titles(ids: string[]): Promise<Record<string, string>>;
133
- create(input: unknown): Promise<Result<Record<string, unknown>>>;
133
+ create(raw: unknown): Promise<Result<Record<string, unknown>>>;
134
134
  /** Partial update (PATCH semantics). */
135
- update(id: string, input: unknown): Promise<Result<Record<string, unknown>>>;
135
+ update(id: string, raw: unknown): Promise<Result<Record<string, unknown>>>;
136
136
  /** Full replacement (PUT semantics): omitted fields go back to their default, or null when optional. */
137
137
  replace(id: string, input: unknown): Promise<Result<Record<string, unknown>>>;
138
138
  delete(id: string): Promise<Result<{
@@ -182,8 +182,8 @@ declare function createResourceHandlers(options: ResourceHandlerOptions): {
182
182
  list(params: URLSearchParams): Promise<Result<ListResult>>;
183
183
  get(id: string): Promise<Result<Record<string, unknown>>>;
184
184
  titles(ids: string[]): Promise<Record<string, string>>;
185
- create(input: unknown): Promise<Result<Record<string, unknown>>>;
186
- update(id: string, input: unknown): Promise<Result<Record<string, unknown>>>;
185
+ create(raw: unknown): Promise<Result<Record<string, unknown>>>;
186
+ update(id: string, raw: unknown): Promise<Result<Record<string, unknown>>>;
187
187
  replace(id: string, input: unknown): Promise<Result<Record<string, unknown>>>;
188
188
  delete(id: string): Promise<Result<{
189
189
  id: string;
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  can,
3
3
  createValidators,
4
4
  storedFields
5
- } from "./chunk-PWP4QEZV.js";
5
+ } from "./chunk-VLHPSNQN.js";
6
6
 
7
7
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_b372b29e6d5511672c7f9f9d2ac40572/node_modules/drizzle-orm/entity.js
8
8
  var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
@@ -1440,29 +1440,31 @@ function createResourceStore(options) {
1440
1440
  const rows = await getDb().select({ id: idColumn, title: titleColumn }).from(table).where(inArray(idColumn, unique));
1441
1441
  return Object.fromEntries(rows.map((row) => [row.id, row.title == null ? row.id : String(row.title)]));
1442
1442
  },
1443
- create(input) {
1444
- const result = validators.create.safeParse(input);
1445
- if (!result.success) return Promise.resolve(invalid(result.error.issues));
1443
+ create(raw) {
1446
1444
  return writing("create", async () => {
1447
1445
  const context = await hookContext();
1448
- const input2 = hooks.beforeCreate ? await hooks.beforeCreate({ ...result.data }, context) : result.data;
1446
+ const supplied = hooks.beforeCreate ? await hooks.beforeCreate({ ...raw ?? {} }, context) : raw;
1447
+ const result = validators.create.safeParse(supplied);
1448
+ if (!result.success) return invalid(result.error.issues);
1449
+ const input = result.data;
1449
1450
  const now = /* @__PURE__ */ new Date();
1450
- const [record] = await getDb().insert(table).values({ ...input2, id: crypto.randomUUID(), createdAt: now, updatedAt: now }).returning();
1451
+ const [record] = await getDb().insert(table).values({ ...input, id: crypto.randomUUID(), createdAt: now, updatedAt: now }).returning();
1451
1452
  const saved = withComputed(record);
1452
1453
  await hooks.afterCreate?.(saved, context);
1453
1454
  return { ok: true, data: saved };
1454
1455
  });
1455
1456
  },
1456
1457
  /** Partial update (PATCH semantics). */
1457
- update(id, input) {
1458
- const result = validators.update.safeParse(input);
1459
- if (!result.success) return Promise.resolve(invalid(result.error.issues));
1458
+ update(id, raw) {
1460
1459
  return writing("update", async () => {
1461
1460
  const context = await hookContext();
1462
1461
  const [existing] = hooks.beforeUpdate || hooks.afterUpdate ? await getDb().select().from(table).where(byId(id)).limit(1) : [];
1463
1462
  const current = existing ?? null;
1464
- const input2 = hooks.beforeUpdate ? await hooks.beforeUpdate({ ...result.data }, { ...context, id, current }) : result.data;
1465
- const [record] = await getDb().update(table).set({ ...input2, updatedAt: /* @__PURE__ */ new Date() }).where(byId(id)).returning();
1463
+ const supplied = hooks.beforeUpdate ? await hooks.beforeUpdate({ ...raw ?? {} }, { ...context, id, current }) : raw;
1464
+ const result = validators.update.safeParse(supplied);
1465
+ if (!result.success) return invalid(result.error.issues);
1466
+ const input = result.data;
1467
+ const [record] = await getDb().update(table).set({ ...input, updatedAt: /* @__PURE__ */ new Date() }).where(byId(id)).returning();
1466
1468
  if (!record) return notFound();
1467
1469
  const saved = withComputed(record);
1468
1470
  await hooks.afterUpdate?.(saved, { ...context, previous: current });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flaredev/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Flare runtime: resource descriptors, field grammar, and Cloudflare-native helpers",
5
5
  "license": "MIT",
6
6
  "author": "Muke Johnbaptist",