@flaredev/core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-434JXSDV.js +410 -0
- package/dist/index.d.ts +39 -39
- package/dist/index.js +63 -81
- package/dist/{define-D5CtCkOm.d.ts → policy-BqRg9RVM.d.ts} +80 -4
- package/dist/server.d.ts +29 -2
- package/dist/server.js +1448 -3
- package/package.json +2 -13
- package/dist/chunk-DARDHZPX.js +0 -192
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flaredev/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Flare runtime: resource descriptors, field grammar, and Cloudflare-native helpers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Muke Johnbaptist",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"README.md"
|
|
67
67
|
],
|
|
68
68
|
"dependencies": {
|
|
69
|
+
"libphonenumber-js": "1.13.13",
|
|
69
70
|
"zod": "^4.6.5"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
@@ -74,18 +75,6 @@
|
|
|
74
75
|
"drizzle-orm": "^0.45.2",
|
|
75
76
|
"sql.js": "^1.14.2"
|
|
76
77
|
},
|
|
77
|
-
"peerDependencies": {
|
|
78
|
-
"drizzle-orm": "^0.45.2",
|
|
79
|
-
"react": "^18.2 || ^19.0.0"
|
|
80
|
-
},
|
|
81
|
-
"peerDependenciesMeta": {
|
|
82
|
-
"drizzle-orm": {
|
|
83
|
-
"optional": true
|
|
84
|
-
},
|
|
85
|
-
"react": {
|
|
86
|
-
"optional": true
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
78
|
"engines": {
|
|
90
79
|
"node": ">=22"
|
|
91
80
|
},
|
package/dist/chunk-DARDHZPX.js
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
// src/resource/naming.ts
|
|
2
|
-
function words(value) {
|
|
3
|
-
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word.toLowerCase());
|
|
4
|
-
}
|
|
5
|
-
var pascalCase = (value) => words(value).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
6
|
-
var camelCase = (value) => {
|
|
7
|
-
const pascal = pascalCase(value);
|
|
8
|
-
return pascal ? pascal[0].toLowerCase() + pascal.slice(1) : pascal;
|
|
9
|
-
};
|
|
10
|
-
var snakeCase = (value) => words(value).join("_");
|
|
11
|
-
var kebabCase = (value) => words(value).join("-");
|
|
12
|
-
var humanize = (value) => {
|
|
13
|
-
const text = words(value).join(" ");
|
|
14
|
-
return text ? text[0].toUpperCase() + text.slice(1) : text;
|
|
15
|
-
};
|
|
16
|
-
var UNCOUNTABLE = /* @__PURE__ */ new Set(["equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "data", "news"]);
|
|
17
|
-
var IRREGULAR = { person: "people", man: "men", woman: "women", child: "children", mouse: "mice" };
|
|
18
|
-
function pluralize(value) {
|
|
19
|
-
const match = /([A-Za-z]+)$/.exec(value);
|
|
20
|
-
if (!match) return value;
|
|
21
|
-
const word = match[1];
|
|
22
|
-
const lower = word.toLowerCase();
|
|
23
|
-
const head = value.slice(0, value.length - word.length);
|
|
24
|
-
const keepCase = (plural) => word[0] === word[0].toUpperCase() ? plural[0].toUpperCase() + plural.slice(1) : plural;
|
|
25
|
-
if (UNCOUNTABLE.has(lower)) return value;
|
|
26
|
-
if (IRREGULAR[lower]) return head + keepCase(IRREGULAR[lower]);
|
|
27
|
-
if (/[^aeiou]y$/i.test(word)) return head + word.slice(0, -1) + "ies";
|
|
28
|
-
if (/(s|x|z|ch|sh)$/i.test(word)) return head + word + "es";
|
|
29
|
-
return head + word + "s";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// src/resource/define.ts
|
|
33
|
-
var RESERVED_FIELD_NAMES = ["id", "createdAt", "updatedAt"];
|
|
34
|
-
var ResourceDefinitionError = class extends Error {
|
|
35
|
-
constructor(resource, message) {
|
|
36
|
-
super(`Resource "${resource}": ${message}`);
|
|
37
|
-
this.name = "ResourceDefinitionError";
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
var IDENTIFIER = /^[a-z][A-Za-z0-9]*$/;
|
|
41
|
-
function defaultFieldLabel(key, field) {
|
|
42
|
-
return humanize(field.kind === "belongsTo" ? key.replace(/Id$/, "") : key);
|
|
43
|
-
}
|
|
44
|
-
function defineResource(config) {
|
|
45
|
-
const name = config.name;
|
|
46
|
-
const fail = (message) => {
|
|
47
|
-
throw new ResourceDefinitionError(name, message);
|
|
48
|
-
};
|
|
49
|
-
if (!/^[A-Z][A-Za-z0-9]*$/.test(name) || pascalCase(name) !== name) fail('name must be PascalCase, e.g. "OrderItem".');
|
|
50
|
-
const entries = Object.entries(config.fields);
|
|
51
|
-
if (entries.length === 0) fail("define at least one field.");
|
|
52
|
-
const fields = {};
|
|
53
|
-
for (const [key, def] of entries) {
|
|
54
|
-
if (!IDENTIFIER.test(key)) fail(`field "${key}" must be camelCase (letters and digits, starting lowercase).`);
|
|
55
|
-
if (RESERVED_FIELD_NAMES.includes(key)) fail(`"${key}" is added automatically; don't declare it.`);
|
|
56
|
-
if (def.kind === "enum") {
|
|
57
|
-
if (def.options.length === 0) fail(`enum field "${key}" needs at least one option.`);
|
|
58
|
-
if (new Set(def.options).size !== def.options.length) fail(`enum field "${key}" has duplicate options.`);
|
|
59
|
-
if (def.default !== void 0 && !def.options.includes(def.default)) fail(`default of "${key}" isn't one of its options.`);
|
|
60
|
-
}
|
|
61
|
-
if (def.kind === "belongsTo" && !key.endsWith("Id")) fail(`belongsTo field "${key}" must end in "Id", e.g. "${camelCase(def.target)}Id".`);
|
|
62
|
-
if ((def.kind === "belongsTo" || def.kind === "hasMany") && pascalCase(def.target) !== def.target) {
|
|
63
|
-
fail(`relation "${key}" must target a PascalCase resource name.`);
|
|
64
|
-
}
|
|
65
|
-
if (def.kind === "belongsTo" && def.onDelete === "set null" && def.required) {
|
|
66
|
-
fail(`"${key}" uses onDelete "set null" but is required; mark it required: false.`);
|
|
67
|
-
}
|
|
68
|
-
if (def.kind === "file" && def.accept.length === 0) fail(`file field "${key}" must accept at least one category.`);
|
|
69
|
-
fields[key] = { ...def, label: def.label ?? defaultFieldLabel(key, def) };
|
|
70
|
-
}
|
|
71
|
-
const stored = entries.filter(([, def]) => def.kind !== "hasMany");
|
|
72
|
-
const titleField = config.titleField ?? stored.find(([, def]) => def.kind === "string")?.[0] ?? stored[0]?.[0] ?? "id";
|
|
73
|
-
if (config.titleField && !stored.some(([key]) => key === config.titleField)) {
|
|
74
|
-
fail(`titleField "${config.titleField}" isn't a stored field.`);
|
|
75
|
-
}
|
|
76
|
-
const defaultSort = config.defaultSort ?? { field: "createdAt", direction: "desc" };
|
|
77
|
-
if (!["createdAt", "updatedAt"].includes(defaultSort.field) && !stored.some(([key]) => key === defaultSort.field)) {
|
|
78
|
-
fail(`defaultSort field "${defaultSort.field}" isn't a stored field.`);
|
|
79
|
-
}
|
|
80
|
-
const perPage = config.perPage ?? 25;
|
|
81
|
-
if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) fail("perPage must be an integer from 1 to 100.");
|
|
82
|
-
const pluralName = pluralize(name);
|
|
83
|
-
return {
|
|
84
|
-
name,
|
|
85
|
-
table: config.table ?? snakeCase(pluralName),
|
|
86
|
-
slug: config.slug ?? kebabCase(pluralName),
|
|
87
|
-
label: config.label ?? humanize(name),
|
|
88
|
-
pluralLabel: config.pluralLabel ?? humanize(pluralName),
|
|
89
|
-
icon: config.icon,
|
|
90
|
-
titleField,
|
|
91
|
-
defaultSort,
|
|
92
|
-
perPage,
|
|
93
|
-
fields,
|
|
94
|
-
$types: void 0
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function storedFields(resource) {
|
|
98
|
-
return Object.entries(resource.fields).filter(([, def]) => def.kind !== "hasMany");
|
|
99
|
-
}
|
|
100
|
-
var columnName = (key) => snakeCase(key);
|
|
101
|
-
|
|
102
|
-
// src/resource/validators.ts
|
|
103
|
-
import { z } from "zod";
|
|
104
|
-
var DEFAULT_STRING_MAX = 255;
|
|
105
|
-
var DEFAULT_TEXT_MAX = 65535;
|
|
106
|
-
var OBJECT_KEY = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$)).{1,1024}$/;
|
|
107
|
-
var typeError = (message) => (issue) => issue.input === void 0 || issue.input === null || issue.input === "" ? "Required" : message;
|
|
108
|
-
function stringSchema(max, min, required) {
|
|
109
|
-
let s = z.string({ error: typeError("Enter text") });
|
|
110
|
-
if (max) s = s.max(max, `At most ${max} characters`);
|
|
111
|
-
if (min !== void 0) s = s.min(min, min === 1 ? "Required" : `At least ${min} characters`);
|
|
112
|
-
else if (required) s = s.min(1, "Required");
|
|
113
|
-
return s;
|
|
114
|
-
}
|
|
115
|
-
function fieldSchema(def) {
|
|
116
|
-
switch (def.kind) {
|
|
117
|
-
case "string": {
|
|
118
|
-
let s = z.string({ error: typeError("Enter text") }).trim().max(def.maxLength ?? DEFAULT_STRING_MAX, `At most ${def.maxLength ?? DEFAULT_STRING_MAX} characters`);
|
|
119
|
-
if (def.minLength !== void 0) s = s.min(def.minLength, `At least ${def.minLength} characters`);
|
|
120
|
-
else if (def.required) s = s.min(1, "Required");
|
|
121
|
-
if (def.pattern) s = s.regex(new RegExp(def.pattern), "Invalid format");
|
|
122
|
-
if (def.format === "email") return s.pipe(z.email({ error: "Enter a valid email address" }));
|
|
123
|
-
if (def.format === "url") return s.pipe(z.url({ protocol: /^https?$/, error: "Enter a valid URL (http or https)" }));
|
|
124
|
-
return s;
|
|
125
|
-
}
|
|
126
|
-
case "text":
|
|
127
|
-
return stringSchema(def.maxLength ?? DEFAULT_TEXT_MAX, def.minLength, def.required);
|
|
128
|
-
case "int":
|
|
129
|
-
case "float": {
|
|
130
|
-
let s = z.number({ error: typeError("Enter a number") });
|
|
131
|
-
if (def.kind === "int") s = s.int("Enter a whole number");
|
|
132
|
-
if (def.min !== void 0) s = s.min(def.min, `Must be at least ${def.min}`);
|
|
133
|
-
if (def.max !== void 0) s = s.max(def.max, `Must be at most ${def.max}`);
|
|
134
|
-
return s;
|
|
135
|
-
}
|
|
136
|
-
case "boolean":
|
|
137
|
-
return z.boolean({ error: typeError("Choose yes or no") });
|
|
138
|
-
case "date":
|
|
139
|
-
return z.iso.date({ error: typeError("Enter a valid date") });
|
|
140
|
-
case "datetime":
|
|
141
|
-
return z.iso.datetime({ offset: true, error: typeError("Enter a valid date and time") });
|
|
142
|
-
case "enum":
|
|
143
|
-
return z.enum(def.options, { error: typeError("Choose one of the options") });
|
|
144
|
-
case "file":
|
|
145
|
-
return z.string({ error: typeError("Upload a file") }).regex(OBJECT_KEY, "Invalid file");
|
|
146
|
-
case "belongsTo":
|
|
147
|
-
return z.string({ error: typeError("Choose a record") }).min(1, "Required");
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
var fileKeyPrefix = (resource, fieldKey) => `${resource.table}/${fieldKey}/`;
|
|
151
|
-
function createValidators(resource) {
|
|
152
|
-
const create = {};
|
|
153
|
-
const update = {};
|
|
154
|
-
for (const [key, def] of storedFields(resource)) {
|
|
155
|
-
let base = fieldSchema(def);
|
|
156
|
-
if (def.kind === "file") {
|
|
157
|
-
const prefix = fileKeyPrefix(resource, key);
|
|
158
|
-
base = base.refine(
|
|
159
|
-
(value2) => typeof value2 !== "string" || !OBJECT_KEY.test(value2) || value2.startsWith(prefix),
|
|
160
|
-
"This file wasn't uploaded for this field"
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
const value = def.required ? base : base.nullable();
|
|
164
|
-
update[key] = value.optional();
|
|
165
|
-
const hasDefault = "default" in def && def.default !== void 0;
|
|
166
|
-
create[key] = def.required && !hasDefault ? value : value.optional();
|
|
167
|
-
}
|
|
168
|
-
const unknownKeys = { error: (issue) => issue.code === "unrecognized_keys" ? `Unknown field(s): ${issue.keys?.join(", ")}` : void 0 };
|
|
169
|
-
return {
|
|
170
|
-
create: z.strictObject(create, unknownKeys),
|
|
171
|
-
update: z.strictObject(update, unknownKeys)
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export {
|
|
176
|
-
words,
|
|
177
|
-
pascalCase,
|
|
178
|
-
camelCase,
|
|
179
|
-
snakeCase,
|
|
180
|
-
kebabCase,
|
|
181
|
-
humanize,
|
|
182
|
-
pluralize,
|
|
183
|
-
RESERVED_FIELD_NAMES,
|
|
184
|
-
ResourceDefinitionError,
|
|
185
|
-
defaultFieldLabel,
|
|
186
|
-
defineResource,
|
|
187
|
-
storedFields,
|
|
188
|
-
columnName,
|
|
189
|
-
fieldSchema,
|
|
190
|
-
fileKeyPrefix,
|
|
191
|
-
createValidators
|
|
192
|
-
};
|