@flaredev/core 0.7.3 → 0.8.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,5 +1,21 @@
1
1
  // src/resource/fields.ts
2
- var STRING_FORMATS = ["email", "url", "tel", "domain", "country", "color", "slug"];
2
+ var STRING_FORMATS = [
3
+ "email",
4
+ "url",
5
+ "tel",
6
+ "domain",
7
+ "country",
8
+ "color",
9
+ "slug",
10
+ "username",
11
+ "ip",
12
+ "uuid",
13
+ "timezone",
14
+ "locale",
15
+ "currency",
16
+ "postcode"
17
+ ];
18
+ var NUMBER_FORMATS = ["money", "percent", "rating"];
3
19
  var FILE_CATEGORIES = {
4
20
  // For a field that holds whatever someone has: a drive, an attachment, a backup.
5
21
  // Nothing is checked beyond the size limit, so don't reach for it out of convenience.
@@ -259,6 +275,27 @@ var isSlug = (value) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
259
275
  function slugify(value) {
260
276
  return value.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
261
277
  }
278
+ var isIpAddress = (value) => /^(?:\d{1,3}\.){3}\d{1,3}$/.test(value) ? value.split(".").every((part) => Number(part) <= 255) : /^[0-9a-f:]+$/i.test(value) && value.includes(":") && value.split("::").length <= 2;
279
+ var isUuid = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
280
+ function isTimezone(value) {
281
+ try {
282
+ new Intl.DateTimeFormat("en", { timeZone: value });
283
+ return true;
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+ function isLocale(value) {
289
+ if (!/^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|\d{3}))?$/.test(value)) return false;
290
+ try {
291
+ return new Intl.Locale(value).language.length >= 2;
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+ var isCurrencyCode = (value) => /^[A-Z]{3}$/.test(value);
297
+ var isUsername = (value) => /^[A-Za-z0-9_][A-Za-z0-9_.]{1,31}$/.test(value);
298
+ var isPostcode = (value) => /^[A-Za-z0-9][A-Za-z0-9 -]{0,10}[A-Za-z0-9]$/.test(value);
262
299
 
263
300
  // src/resource/validators.ts
264
301
  import { z } from "zod";
@@ -295,6 +332,20 @@ function fieldSchema(def) {
295
332
  return s.transform((value) => value.toLowerCase()).refine((value) => value === "" || isColor(value), "Enter a colour such as #f2541d");
296
333
  case "slug":
297
334
  return s.refine((value) => value === "" || isSlug(value), "Use lowercase letters, numbers and hyphens, e.g. my-first-post");
335
+ case "username":
336
+ return s.refine((value) => value === "" || isUsername(value), "2 to 32 characters: letters, digits, underscore and dot");
337
+ case "ip":
338
+ return s.refine((value) => value === "" || isIpAddress(value), "Enter an IP address, e.g. 192.168.1.1");
339
+ case "uuid":
340
+ return s.transform((value) => value.toLowerCase()).refine((value) => value === "" || isUuid(value), "Enter a UUID");
341
+ case "timezone":
342
+ return s.refine((value) => value === "" || isTimezone(value), "Choose a time zone, e.g. Africa/Kampala");
343
+ case "locale":
344
+ return s.refine((value) => value === "" || isLocale(value), "Enter a language tag, e.g. en-GB");
345
+ case "currency":
346
+ return s.transform((value) => value.toUpperCase()).refine((value) => value === "" || isCurrencyCode(value), "Enter a three-letter currency code, e.g. UGX");
347
+ case "postcode":
348
+ return s.transform((value) => value.toUpperCase()).refine((value) => value === "" || isPostcode(value), "Enter a postal code");
298
349
  default:
299
350
  return s;
300
351
  }
@@ -305,6 +356,10 @@ function fieldSchema(def) {
305
356
  case "float": {
306
357
  let s = z.number({ error: typeError("Enter a number") });
307
358
  if (def.kind === "int") s = s.int("Enter a whole number");
359
+ if (def.format === "rating") {
360
+ s = s.min(def.min ?? 0, `At least ${def.min ?? 0}`).max(def.max ?? 5, `At most ${def.max ?? 5}`);
361
+ }
362
+ if (def.format === "money" && def.min === void 0) s = s.min(0, "Cannot be negative");
308
363
  if (def.min !== void 0) s = s.min(def.min, `Must be at least ${def.min}`);
309
364
  if (def.max !== void 0) s = s.max(def.max, `Must be at most ${def.max}`);
310
365
  return s;
@@ -393,6 +448,7 @@ function allowedActions(policy, role) {
393
448
 
394
449
  export {
395
450
  STRING_FORMATS,
451
+ NUMBER_FORMATS,
396
452
  FILE_CATEGORIES,
397
453
  mimeTypesFor,
398
454
  DEFAULT_FILE_MAX_BYTES,
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-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';
1
+ import { F as Field, C as CreateInput, U as UpdateInput, R as Resource, S as StoredField } from './policy-dc9NbbgZ.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, N as NUMBER_FORMATS, k as NumberFormat, P as Policy, l as PolicyAction, m as PolicyConfig, n as PolicyError, o as RESERVED_FIELD_NAMES, p as RecordOf, q as ResourceConfig, r as ResourceDefinitionError, s as STRING_FORMATS, t as StringField, u as StringFormat, T as TextField, v as allowedActions, w as can, x as clientResource, y as columnName, z as defaultFieldLabel, A as definePolicy, G as defineResource, J as field, K as fileMaxBytes, L as mimeTypesFor, O as storedFields } from './policy-dc9NbbgZ.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  /**
@@ -529,6 +529,6 @@ interface SeedContext {
529
529
  type Seed = (context: SeedContext) => void | Promise<void>;
530
530
  declare function defineSeed(seed: Seed): Seed;
531
531
 
532
- declare const FLARE_VERSION = "0.7.3";
532
+ declare const FLARE_VERSION = "0.8.1";
533
533
 
534
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
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DEFAULT_FILE_MAX_BYTES,
3
3
  FILE_CATEGORIES,
4
+ NUMBER_FORMATS,
4
5
  PolicyError,
5
6
  RESERVED_FIELD_NAMES,
6
7
  ResourceDefinitionError,
@@ -39,7 +40,7 @@ import {
39
40
  storedFields,
40
41
  toE164,
41
42
  words
42
- } from "./chunk-VLHPSNQN.js";
43
+ } from "./chunk-JYWV3AF6.js";
43
44
 
44
45
  // src/password.ts
45
46
  var ALGORITHM = "pbkdf2-sha256";
@@ -994,11 +995,12 @@ function createFake(seed = 20260101) {
994
995
  var fake = createFake();
995
996
 
996
997
  // src/index.ts
997
- var FLARE_VERSION = "0.7.3";
998
+ var FLARE_VERSION = "0.8.1";
998
999
  export {
999
1000
  DEFAULT_FILE_MAX_BYTES,
1000
1001
  FILE_CATEGORIES,
1001
1002
  FLARE_VERSION,
1003
+ NUMBER_FORMATS,
1002
1004
  PBKDF2_ITERATIONS,
1003
1005
  PolicyError,
1004
1006
  RESERVED_FIELD_NAMES,
@@ -34,8 +34,15 @@ interface CommonOptions<T> {
34
34
  * - country: an ISO 3166-1 alpha-2 code (`UG`), chosen from a searchable list
35
35
  * - color: a hex colour (`#f2541d`)
36
36
  * - slug: lowercase words joined by hyphens (`my-first-post`)
37
+ * - username: a handle (`ada_lovelace`), letters, digits, underscore and dot
38
+ * - ip: an IPv4 or IPv6 address
39
+ * - uuid: a UUID of any version
40
+ * - timezone: an IANA zone (`Africa/Kampala`), checked against the runtime's own list
41
+ * - locale: a BCP-47 tag (`en-GB`), chosen from a list
42
+ * - currency: an ISO 4217 code (`UGX`), chosen from a list
43
+ * - postcode: a postal code, stored upper case
37
44
  */
38
- type StringFormat = "email" | "url" | "tel" | "domain" | "country" | "color" | "slug";
45
+ type StringFormat = "email" | "url" | "tel" | "domain" | "country" | "color" | "slug" | "username" | "ip" | "uuid" | "timezone" | "locale" | "currency" | "postcode";
39
46
  declare const STRING_FORMATS: readonly StringFormat[];
40
47
  interface StringOptions extends CommonOptions<string> {
41
48
  maxLength?: number;
@@ -47,9 +54,24 @@ interface TextOptions extends CommonOptions<string> {
47
54
  maxLength?: number;
48
55
  minLength?: number;
49
56
  }
57
+ /**
58
+ * What a number means, which decides how it is shown and entered.
59
+ *
60
+ * The column is the same either way — a number is a number. The format changes the
61
+ * input's step and affixes, and how the value is rendered in a table.
62
+ *
63
+ * - money: an amount, grouped and shown to two decimal places
64
+ * - percent: shown with a % sign; stored as entered, so 20 means 20%
65
+ * - rating: a small whole number, entered as stars
66
+ */
67
+ type NumberFormat = "money" | "percent" | "rating";
68
+ declare const NUMBER_FORMATS: readonly NumberFormat[];
50
69
  interface NumberOptions extends CommonOptions<number> {
51
70
  min?: number;
52
71
  max?: number;
72
+ format?: NumberFormat;
73
+ /** money only: an ISO 4217 code for display, e.g. "UGX". Defaults to the app's. */
74
+ currency?: string;
53
75
  }
54
76
  type BooleanOptions = CommonOptions<boolean>;
55
77
  type DateOptions = CommonOptions<string>;
@@ -377,4 +399,4 @@ declare function can(policy: Policy | undefined | null, role: string | null | un
377
399
  /** Actions `role` may perform, for hiding UI it can't use. */
378
400
  declare function allowedActions(policy: Policy | undefined | null, role: string | null | undefined): Record<Exclude<PolicyAction, "list">, boolean>;
379
401
 
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 };
402
+ export { definePolicy as A, type BelongsToField as B, type CreateInput as C, DEFAULT_FILE_MAX_BYTES as D, type EnumField as E, type Field as F, defineResource as G, type HasManyField as H, type IntField as I, field as J, fileMaxBytes as K, mimeTypesFor as L, type MultiSelectField as M, NUMBER_FORMATS as N, storedFields as O, 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 NumberFormat as k, type PolicyAction as l, type PolicyConfig as m, PolicyError as n, RESERVED_FIELD_NAMES as o, type RecordOf as p, type ResourceConfig as q, ResourceDefinitionError as r, STRING_FORMATS as s, type StringField as t, type StringFormat as u, allowedActions as v, can as w, clientResource as x, columnName as y, defaultFieldLabel 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, SQLiteColumn } from 'drizzle-orm/sqlite-core';
3
- import { S as StoredField, R as Resource, F as Field, P as Policy } from './policy-DaKcWZ5g.js';
3
+ import { S as StoredField, R as Resource, F as Field, P as Policy } from './policy-dc9NbbgZ.js';
4
4
 
5
5
  /**
6
6
  * The handful of things a resource store needs a database to do.
@@ -122,7 +122,18 @@ declare function isSearchable(def: StoredField): boolean;
122
122
  * resource. Only fields the descriptor marks sortable/filterable/searchable are
123
123
  * accepted, so clients can't probe arbitrary columns.
124
124
  */
125
- declare function parseListQuery(resource: Resource, params: URLSearchParams): {
125
+ declare function parseListQuery(resource: Resource, params: URLSearchParams,
126
+ /**
127
+ * The largest page this caller may ask for.
128
+ *
129
+ * The cap exists to stop a request from the outside asking for ten thousand rows at
130
+ * once. Server-side callers that page through everything deliberately — the CSV
131
+ * export — pass their own, which is why this is an argument rather than a constant
132
+ * everything obeys.
133
+ */
134
+ options?: {
135
+ maxPerPage?: number;
136
+ }): {
126
137
  query: ListQuery;
127
138
  } | {
128
139
  issues: QueryIssue[];
@@ -215,7 +226,9 @@ declare function createResourceStore(options: ResourceStoreOptions): {
215
226
  resource: Resource<Record<string, Field>>;
216
227
  table: SQLiteTable<drizzle_orm_sqlite_core.TableConfig> | undefined;
217
228
  /** `params`: page, perPage, sort, q, filter[field], cursor (as parsed by parseListQuery). */
218
- list(params: URLSearchParams): Promise<Result<ListResult>>;
229
+ list(params: URLSearchParams, options?: {
230
+ maxPerPage?: number;
231
+ }): Promise<Result<ListResult>>;
219
232
  get(id: string): Promise<Result<Record<string, unknown>>>;
220
233
  /** Title-field values for a set of ids (for showing relations). Missing ids are omitted. */
221
234
  titles(ids: string[]): Promise<Record<string, string>>;
@@ -271,7 +284,9 @@ declare function createResourceHandlers(options: ResourceHandlerOptions): {
271
284
  store: {
272
285
  resource: Resource<Record<string, Field>>;
273
286
  table: SQLiteTable<drizzle_orm_sqlite_core.TableConfig> | undefined;
274
- list(params: URLSearchParams): Promise<Result<ListResult>>;
287
+ list(params: URLSearchParams, options?: {
288
+ maxPerPage?: number;
289
+ }): Promise<Result<ListResult>>;
275
290
  get(id: string): Promise<Result<Record<string, unknown>>>;
276
291
  titles(ids: string[]): Promise<Record<string, string>>;
277
292
  create(raw: unknown): Promise<Result<Record<string, unknown>>>;
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  columnName,
4
4
  createValidators,
5
5
  storedFields
6
- } from "./chunk-VLHPSNQN.js";
6
+ } from "./chunk-JYWV3AF6.js";
7
7
 
8
8
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@cloudfl_21866ab18c10d2d988b916e06105fbce/node_modules/drizzle-orm/entity.js
9
9
  var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
@@ -1319,7 +1319,7 @@ function coerce(def, raw) {
1319
1319
  return raw;
1320
1320
  }
1321
1321
  }
1322
- function parseListQuery(resource, params) {
1322
+ function parseListQuery(resource, params, options = {}) {
1323
1323
  const issues = [];
1324
1324
  const fields = new Map(storedFields(resource));
1325
1325
  const positiveInt = (name, fallback, max = Number.MAX_SAFE_INTEGER) => {
@@ -1333,7 +1333,7 @@ function parseListQuery(resource, params) {
1333
1333
  return value;
1334
1334
  };
1335
1335
  const page = positiveInt("page", 1);
1336
- const perPage = positiveInt("perPage", resource.perPage, MAX_PER_PAGE);
1336
+ const perPage = positiveInt("perPage", resource.perPage, options.maxPerPage ?? MAX_PER_PAGE);
1337
1337
  let sort = resource.defaultSort;
1338
1338
  const rawSort = params.get("sort");
1339
1339
  if (rawSort) {
@@ -1449,8 +1449,8 @@ function createResourceStore(options) {
1449
1449
  resource,
1450
1450
  table,
1451
1451
  /** `params`: page, perPage, sort, q, filter[field], cursor (as parsed by parseListQuery). */
1452
- async list(params) {
1453
- const parsed = parseListQuery(resource, params);
1452
+ async list(params, options2 = {}) {
1453
+ const parsed = parseListQuery(resource, params, options2);
1454
1454
  if ("issues" in parsed) return fail(400, "Invalid query.", { queryIssues: parsed.issues });
1455
1455
  const { page, perPage, sort, q, filters, cursor } = parsed.query;
1456
1456
  const isDateField = (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flaredev/core",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "Flare runtime: resource descriptors, field grammar, and Cloudflare-native helpers",
5
5
  "license": "MIT",
6
6
  "author": "Muke Johnbaptist",