@flaredev/core 0.7.2 → 0.8.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.
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
// src/resource/fields.ts
|
|
2
|
-
var STRING_FORMATS = [
|
|
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-
|
|
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,
|
|
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.
|
|
532
|
+
declare const FLARE_VERSION = "0.8.0";
|
|
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-
|
|
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.
|
|
998
|
+
var FLARE_VERSION = "0.8.0";
|
|
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 {
|
|
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-
|
|
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.
|
|
@@ -39,6 +39,14 @@ interface RowsQuery {
|
|
|
39
39
|
value: unknown;
|
|
40
40
|
id: string;
|
|
41
41
|
greaterThan: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the sort column holds a date.
|
|
44
|
+
*
|
|
45
|
+
* A cursor travels through a URL, so a Date arrives back as a number. SQLite stores
|
|
46
|
+
* dates as milliseconds and wants that number; Postgres wants a Date. Neither
|
|
47
|
+
* adapter can tell which a column is, and the store can — so it says.
|
|
48
|
+
*/
|
|
49
|
+
isDate?: boolean;
|
|
42
50
|
};
|
|
43
51
|
limit: number;
|
|
44
52
|
/** Offset paging, for the page-number case. Never combined with a cursor. */
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
columnName,
|
|
4
4
|
createValidators,
|
|
5
5
|
storedFields
|
|
6
|
-
} from "./chunk-
|
|
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");
|
|
@@ -1453,6 +1453,11 @@ function createResourceStore(options) {
|
|
|
1453
1453
|
const parsed = parseListQuery(resource, params);
|
|
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
|
+
const isDateField = (key) => {
|
|
1457
|
+
if (key === "createdAt" || key === "updatedAt") return true;
|
|
1458
|
+
const def = fields.find(([name]) => name === key)?.[1];
|
|
1459
|
+
return def?.kind === "date" || def?.kind === "datetime";
|
|
1460
|
+
};
|
|
1456
1461
|
const searchable = fields.filter(([, def]) => isSearchable(def)).map(([key]) => key);
|
|
1457
1462
|
const matching = { search: q ? { term: q, fields: searchable } : void 0, filters };
|
|
1458
1463
|
const backwards = cursor?.direction === "before";
|
|
@@ -1462,7 +1467,7 @@ function createResourceStore(options) {
|
|
|
1462
1467
|
rows.find({
|
|
1463
1468
|
...matching,
|
|
1464
1469
|
sort: { field: sort.field, direction: descending ? "desc" : "asc" },
|
|
1465
|
-
cursor: cursor ? { field: sort.field, value: cursor.value, id: cursor.id, greaterThan: !descending } : void 0,
|
|
1470
|
+
cursor: cursor ? { field: sort.field, value: cursor.value, id: cursor.id, greaterThan: !descending, isDate: isDateField(sort.field) } : void 0,
|
|
1466
1471
|
limit: perPage + 1,
|
|
1467
1472
|
offset: cursor ? void 0 : (page - 1) * perPage
|
|
1468
1473
|
}),
|
|
@@ -1680,8 +1685,9 @@ function prismaRows(delegate, client) {
|
|
|
1680
1685
|
async find(query) {
|
|
1681
1686
|
const where = matching(query);
|
|
1682
1687
|
if (query.cursor) {
|
|
1683
|
-
const { field,
|
|
1688
|
+
const { field, id, greaterThan, isDate } = query.cursor;
|
|
1684
1689
|
const op = greaterThan ? "gt" : "lt";
|
|
1690
|
+
const value = isDate && (typeof query.cursor.value === "number" || typeof query.cursor.value === "string") ? new Date(query.cursor.value) : query.cursor.value;
|
|
1685
1691
|
const keyset = [{ [field]: { [op]: value } }, { AND: [{ [field]: value }, { id: { [op]: id } }] }];
|
|
1686
1692
|
where.AND = [...where.AND ?? [], { OR: keyset }];
|
|
1687
1693
|
}
|