@palbase/backend 9.0.1 → 10.1.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-SO6YV35D.js → chunk-4WOQWFUP.js} +38 -6
- package/dist/chunk-4WOQWFUP.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +18 -4
- package/dist/db/env.d.ts +18 -4
- package/dist/db/index.cjs +37 -5
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +1 -1
- package/dist/{endpoint-CGI5cZMg.d.cts → endpoint-Cn3ICGTf.d.cts} +26 -4
- package/dist/{endpoint-CGI5cZMg.d.ts → endpoint-Cn3ICGTf.d.ts} +26 -4
- package/dist/{index-osv2jMmZ.d.cts → index-Bt7UHkAM.d.cts} +40 -5
- package/dist/{index--dH5i4Nu.d.ts → index-V0ealeY-.d.ts} +40 -5
- package/dist/index.cjs +262 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +279 -14
- package/dist/index.d.ts +279 -14
- package/dist/index.js +220 -3
- package/dist/index.js.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/docs/README.md +12 -10
- package/docs/background.md +4 -4
- package/docs/events.md +4 -4
- package/docs/getting-started.md +6 -5
- package/docs/llms-full.txt +39 -36
- package/docs/migrations.md +10 -10
- package/docs/resources.md +3 -3
- package/package.json +1 -1
- package/dist/chunk-SO6YV35D.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
text,
|
|
32
32
|
timestamp,
|
|
33
33
|
uuid
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-4WOQWFUP.js";
|
|
35
35
|
|
|
36
36
|
// src/db/env-gen.ts
|
|
37
37
|
function baseTsType(def) {
|
|
@@ -64,7 +64,50 @@ function rowType(def) {
|
|
|
64
64
|
function optionalOnInsert(def) {
|
|
65
65
|
return def.nullable === true || def.defaultRandom === true || def.defaultNow === true || def.defaultValue !== void 0;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
var AUTH_USERS_TABLE = "auth.users";
|
|
68
|
+
function ownerColumnOf(table) {
|
|
69
|
+
for (const [col, builder] of Object.entries(table.columns)) {
|
|
70
|
+
if (builder._def.references?.table === AUTH_USERS_TABLE) return col;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
function buildChildEdges(schema) {
|
|
75
|
+
const edges = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const name of Object.keys(schema.tables)) edges.set(name, []);
|
|
77
|
+
for (const childName of Object.keys(schema.tables)) {
|
|
78
|
+
const table = schema.tables[childName];
|
|
79
|
+
if (table === void 0) continue;
|
|
80
|
+
for (const [col, builder] of Object.entries(table.columns)) {
|
|
81
|
+
const ref = builder._def.references;
|
|
82
|
+
if (ref === void 0) continue;
|
|
83
|
+
if (ref.table === childName) continue;
|
|
84
|
+
const bucket2 = edges.get(ref.table);
|
|
85
|
+
if (bucket2 === void 0) continue;
|
|
86
|
+
if (bucket2.some((e) => e.child === childName)) continue;
|
|
87
|
+
bucket2.push({ child: childName, column: col });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return edges;
|
|
91
|
+
}
|
|
92
|
+
function pruneCycles(raw2) {
|
|
93
|
+
const pruned = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const table of raw2.keys()) pruned.set(table, []);
|
|
95
|
+
const state = /* @__PURE__ */ new Map();
|
|
96
|
+
function visit(table) {
|
|
97
|
+
state.set(table, "visiting");
|
|
98
|
+
for (const edge of raw2.get(table) ?? []) {
|
|
99
|
+
if (state.get(edge.child) === "visiting") continue;
|
|
100
|
+
pruned.get(table)?.push(edge);
|
|
101
|
+
if (state.get(edge.child) === void 0) visit(edge.child);
|
|
102
|
+
}
|
|
103
|
+
state.set(table, "done");
|
|
104
|
+
}
|
|
105
|
+
for (const table of raw2.keys()) {
|
|
106
|
+
if (state.get(table) === void 0) visit(table);
|
|
107
|
+
}
|
|
108
|
+
return pruned;
|
|
109
|
+
}
|
|
110
|
+
function tableBlock(table, children, indent) {
|
|
68
111
|
const cols = Object.entries(table.columns);
|
|
69
112
|
const rowLines = cols.map(([col, builder]) => {
|
|
70
113
|
return `${indent} ${col}: ${rowType(builder._def)};`;
|
|
@@ -74,6 +117,8 @@ function tableBlock(table, indent) {
|
|
|
74
117
|
const opt = optionalOnInsert(def) ? "?" : "";
|
|
75
118
|
return `${indent} ${col}${opt}: ${rowType(def)};`;
|
|
76
119
|
});
|
|
120
|
+
const owner = ownerColumnOf(table);
|
|
121
|
+
const childEntries = children.map((e) => `${JSON.stringify(e.child)}: ${JSON.stringify(e.column)}`).join("; ");
|
|
77
122
|
return [
|
|
78
123
|
`${indent}${table.name}: {`,
|
|
79
124
|
`${indent} row: {`,
|
|
@@ -82,12 +127,17 @@ function tableBlock(table, indent) {
|
|
|
82
127
|
`${indent} insert: {`,
|
|
83
128
|
...insertLines,
|
|
84
129
|
`${indent} };`,
|
|
130
|
+
`${indent} owner: ${owner === null ? "null" : JSON.stringify(owner)};`,
|
|
131
|
+
`${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
|
|
85
132
|
`${indent}};`
|
|
86
133
|
].join("\n");
|
|
87
134
|
}
|
|
88
135
|
function makeEnvDts(schema) {
|
|
89
136
|
const tableNames = Object.keys(schema.tables);
|
|
90
|
-
const
|
|
137
|
+
const children = pruneCycles(buildChildEdges(schema));
|
|
138
|
+
const blocks = tableNames.map(
|
|
139
|
+
(name) => tableBlock(schema.tables[name], children.get(name) ?? [], " ")
|
|
140
|
+
);
|
|
91
141
|
const body = blocks.length > 0 ? `
|
|
92
142
|
${blocks.join("\n")}
|
|
93
143
|
` : "";
|
|
@@ -182,6 +232,22 @@ function defineStorage(input) {
|
|
|
182
232
|
return { __config: STORAGE_CONFIG_KIND, buckets };
|
|
183
233
|
}
|
|
184
234
|
|
|
235
|
+
// src/config/egress.ts
|
|
236
|
+
var EGRESS_CONFIG_KIND = "egress";
|
|
237
|
+
function defineEgress(input) {
|
|
238
|
+
if (input === null || typeof input !== "object" || !Array.isArray(input.hosts)) {
|
|
239
|
+
throw new Error("defineEgress expects { hosts: string[] }");
|
|
240
|
+
}
|
|
241
|
+
const hosts = [];
|
|
242
|
+
for (const h of input.hosts) {
|
|
243
|
+
if (typeof h !== "string" || h.trim().length === 0) {
|
|
244
|
+
throw new Error("defineEgress: every host must be a non-empty string");
|
|
245
|
+
}
|
|
246
|
+
hosts.push(h.trim().toLowerCase());
|
|
247
|
+
}
|
|
248
|
+
return { __config: EGRESS_CONFIG_KIND, hosts };
|
|
249
|
+
}
|
|
250
|
+
|
|
185
251
|
// src/config/notifications.ts
|
|
186
252
|
var NOTIFICATIONS_CONFIG_KIND = "notifications";
|
|
187
253
|
var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
|
|
@@ -293,6 +359,124 @@ function camelToUpperSnake(s) {
|
|
|
293
359
|
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
|
|
294
360
|
}
|
|
295
361
|
|
|
362
|
+
// src/config/test-users.ts
|
|
363
|
+
var TEST_USERS_CONFIG_KIND = "test-users";
|
|
364
|
+
var TEST_USER_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
365
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
366
|
+
var E164_RE = /^\+[1-9]\d{1,14}$/;
|
|
367
|
+
var OTP_LENGTH = 6;
|
|
368
|
+
var OTP_RE = /^\d{6}$/;
|
|
369
|
+
var MIN_PASSWORD_LENGTH = 8;
|
|
370
|
+
function isPlainObject(value) {
|
|
371
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
372
|
+
}
|
|
373
|
+
function normalizeSeed(seed, userName) {
|
|
374
|
+
if (seed === void 0) return {};
|
|
375
|
+
if (!isPlainObject(seed)) {
|
|
376
|
+
throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
|
|
377
|
+
}
|
|
378
|
+
const out = {};
|
|
379
|
+
for (const [table, rows] of Object.entries(seed)) {
|
|
380
|
+
if (!Array.isArray(rows)) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`testUser ${JSON.stringify(userName)}: seed.${table} must be an array of rows`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (rows.length === 0) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`testUser ${JSON.stringify(userName)}: seed.${table} is empty \u2014 remove it or add a row`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
for (const row of rows) {
|
|
391
|
+
if (!isPlainObject(row)) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
out[table] = rows;
|
|
398
|
+
}
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function testUser(opts = {}) {
|
|
402
|
+
if (!isPlainObject(opts)) {
|
|
403
|
+
throw new Error("testUser() expects { email?, password?, phone?, otp?, seed? }");
|
|
404
|
+
}
|
|
405
|
+
const hasEmail = opts.email !== void 0;
|
|
406
|
+
const hasPassword = opts.password !== void 0;
|
|
407
|
+
if (hasEmail !== hasPassword) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
"testUser() needs email AND password together (a fixture with stable credentials) or neither (a template the server generates credentials for)"
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
let email = null;
|
|
413
|
+
let password = null;
|
|
414
|
+
if (hasEmail) {
|
|
415
|
+
if (typeof opts.email !== "string" || !EMAIL_RE.test(opts.email)) {
|
|
416
|
+
throw new Error(`testUser email ${JSON.stringify(opts.email)} is not a valid address`);
|
|
417
|
+
}
|
|
418
|
+
if (typeof opts.password !== "string" || opts.password.length < MIN_PASSWORD_LENGTH) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
`testUser password must be at least ${MIN_PASSWORD_LENGTH} characters (got ${typeof opts.password === "string" ? opts.password.length : 0})`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
email = opts.email;
|
|
424
|
+
password = opts.password;
|
|
425
|
+
}
|
|
426
|
+
const hasPhone = opts.phone !== void 0;
|
|
427
|
+
const hasOTP = opts.otp !== void 0;
|
|
428
|
+
if (hasPhone !== hasOTP) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
"testUser() needs phone AND otp together \u2014 nothing can receive an SMS at a fabricated number, so a phone fixture without its fixed code can never be signed in as"
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
let phone = null;
|
|
434
|
+
let otp = null;
|
|
435
|
+
if (hasPhone) {
|
|
436
|
+
if (typeof opts.phone !== "string" || !E164_RE.test(opts.phone)) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`testUser phone ${JSON.stringify(opts.phone)} must be E.164, e.g. "+905551112233"`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
if (typeof opts.otp !== "string" || !OTP_RE.test(opts.otp)) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`testUser otp ${JSON.stringify(opts.otp)} must be ${OTP_LENGTH} digits`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
phone = opts.phone;
|
|
447
|
+
otp = opts.otp;
|
|
448
|
+
}
|
|
449
|
+
return { email, password, phone, otp, seed: normalizeSeed(opts.seed, email ?? "<template>") };
|
|
450
|
+
}
|
|
451
|
+
function defineTestUsers(input) {
|
|
452
|
+
if (!isPlainObject(input) || !isPlainObject(input.users)) {
|
|
453
|
+
throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
|
|
454
|
+
}
|
|
455
|
+
const users = {};
|
|
456
|
+
const emails = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const name of Object.keys(input.users)) {
|
|
458
|
+
if (!TEST_USER_NAME_RE.test(name)) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`test user name ${JSON.stringify(name)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
const def = input.users[name];
|
|
464
|
+
if (def === void 0) continue;
|
|
465
|
+
if (def.email !== null) {
|
|
466
|
+
const lower = def.email.toLowerCase();
|
|
467
|
+
const already = emails.get(lower);
|
|
468
|
+
if (already !== void 0) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
`test users ${JSON.stringify(already)} and ${JSON.stringify(name)} declare the same e-mail ${JSON.stringify(def.email)}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
emails.set(lower, name);
|
|
474
|
+
}
|
|
475
|
+
users[name] = def;
|
|
476
|
+
}
|
|
477
|
+
return { __config: TEST_USERS_CONFIG_KIND, users };
|
|
478
|
+
}
|
|
479
|
+
|
|
296
480
|
// src/config/flags.ts
|
|
297
481
|
var FLAGS_CONFIG_KIND = "flags";
|
|
298
482
|
var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
@@ -465,6 +649,33 @@ function recordThrows(target, fnName, throws) {
|
|
|
465
649
|
const throwsBuffer = carrier[THROWS_BUFFER];
|
|
466
650
|
if (throwsBuffer) throwsBuffer[fnName] = throws;
|
|
467
651
|
}
|
|
652
|
+
function getRoutes(ctor) {
|
|
653
|
+
const carrier = carrierOf(ctor);
|
|
654
|
+
const routes = carrier[ROUTES] ?? [];
|
|
655
|
+
const returnBuffer = carrier[RETURN_BUFFER];
|
|
656
|
+
if (returnBuffer) {
|
|
657
|
+
for (const route of routes) {
|
|
658
|
+
const buffered = returnBuffer[route.fnName];
|
|
659
|
+
if (buffered && route.returnSchema === void 0) {
|
|
660
|
+
route.returnSchema = buffered;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const throwsBuffer = carrier[THROWS_BUFFER];
|
|
665
|
+
if (throwsBuffer) {
|
|
666
|
+
for (const route of routes) {
|
|
667
|
+
const buffered = throwsBuffer[route.fnName];
|
|
668
|
+
if (buffered && route.throws === void 0) {
|
|
669
|
+
route.throws = buffered;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return routes.map((r) => ({
|
|
674
|
+
...r,
|
|
675
|
+
params: r.params.slice(),
|
|
676
|
+
...r.throws !== void 0 ? { throws: r.throws.slice() } : {}
|
|
677
|
+
}));
|
|
678
|
+
}
|
|
468
679
|
|
|
469
680
|
// src/decorators/methods.ts
|
|
470
681
|
function makeMethodDecorator(method) {
|
|
@@ -1076,6 +1287,7 @@ export {
|
|
|
1076
1287
|
Database,
|
|
1077
1288
|
Delete,
|
|
1078
1289
|
Documents,
|
|
1290
|
+
EGRESS_CONFIG_KIND,
|
|
1079
1291
|
EXTENSION_DEPENDENCIES,
|
|
1080
1292
|
FLAGS_CONFIG_KIND,
|
|
1081
1293
|
Flags,
|
|
@@ -1106,6 +1318,7 @@ export {
|
|
|
1106
1318
|
Resource,
|
|
1107
1319
|
STORAGE_CONFIG_KIND,
|
|
1108
1320
|
Storage,
|
|
1321
|
+
TEST_USERS_CONFIG_KIND,
|
|
1109
1322
|
TooManyRequests,
|
|
1110
1323
|
TraceId,
|
|
1111
1324
|
Unauthorized,
|
|
@@ -1124,6 +1337,7 @@ export {
|
|
|
1124
1337
|
boolean,
|
|
1125
1338
|
bucket,
|
|
1126
1339
|
buildProvider,
|
|
1340
|
+
defineEgress,
|
|
1127
1341
|
defineError,
|
|
1128
1342
|
defineFlags,
|
|
1129
1343
|
defineJob,
|
|
@@ -1131,12 +1345,14 @@ export {
|
|
|
1131
1345
|
defineNotifications,
|
|
1132
1346
|
defineSchema,
|
|
1133
1347
|
defineStorage,
|
|
1348
|
+
defineTestUsers,
|
|
1134
1349
|
defineWebhook,
|
|
1135
1350
|
defineWorker,
|
|
1136
1351
|
documents,
|
|
1137
1352
|
enumType,
|
|
1138
1353
|
flag,
|
|
1139
1354
|
getErrorRegistry,
|
|
1355
|
+
getRoutes,
|
|
1140
1356
|
integer,
|
|
1141
1357
|
isPalbaseExtension,
|
|
1142
1358
|
jsonb,
|
|
@@ -1149,6 +1365,7 @@ export {
|
|
|
1149
1365
|
recordThrows,
|
|
1150
1366
|
reservedSecretKey,
|
|
1151
1367
|
storage,
|
|
1368
|
+
testUser,
|
|
1152
1369
|
text,
|
|
1153
1370
|
timestamp,
|
|
1154
1371
|
uuid,
|