@lunora/server 1.0.0-alpha.2 → 1.0.0-alpha.21
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/LICENSE.md +6 -0
- package/README.md +51 -1
- package/__assets__/package-og.svg +1 -1
- package/dist/data-model.d.mts +158 -32
- package/dist/data-model.d.ts +158 -32
- package/dist/index.d.mts +459 -117
- package/dist/index.d.ts +459 -117
- package/dist/index.mjs +20 -15
- package/dist/packem_shared/{LunoraEnvError-DjFkpkSP.mjs → LunoraEnvError-BGmd1Qs0.mjs} +4 -4
- package/dist/packem_shared/LunoraError-WbxmrpxR.mjs +9 -0
- package/dist/packem_shared/{PRESENCE_DEFAULT_TTL_MS-BZYd5-uo.mjs → PRESENCE_DEFAULT_TTL_MS-BtPCiNLW.mjs} +4 -4
- package/dist/packem_shared/bindOrm-Bfgl7f-Q.mjs +146 -0
- package/dist/packem_shared/buildRlsReadRegistry-D54vUQe4.mjs +107 -0
- package/dist/packem_shared/{composePluginMiddleware-Ck5_TUO8.mjs → composePluginMiddleware-CcTj1_v2.mjs} +9 -6
- package/dist/packem_shared/{createPolicyDsl-De67zPDS.mjs → createPolicyDsl-By3QB4he.mjs} +4 -1
- package/dist/packem_shared/createSecrets-DwaR2rNG.mjs +58 -0
- package/dist/packem_shared/{defineAggregateIndex-DxSso0rH.mjs → defineAggregateIndex-znq4ygKQ.mjs} +69 -10
- package/dist/packem_shared/defineIdentity-DiX4zM9x.mjs +35 -0
- package/dist/packem_shared/{defineMigration-CAJLr6fx.mjs → defineMigration-Hx01yIht.mjs} +3 -1
- package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +11 -0
- package/dist/packem_shared/defineShape-C5scNOrf.mjs +18 -0
- package/dist/packem_shared/{defineStorageRule-qu0mpilX.mjs → defineStorageRule-B5nL4Z1P.mjs} +4 -1
- package/dist/packem_shared/functions-Di9FUNkf.mjs +5 -0
- package/dist/packem_shared/{httpAction-B7FYUEgr.mjs → httpAction-DCXoYPIk.mjs} +42 -27
- package/dist/packem_shared/{initLunora-CATvPsVt.mjs → initLunora-sQUqaejx.mjs} +18 -4
- package/dist/packem_shared/{mask-Jc84C_hK.mjs → mask-BT1YgRbF.mjs} +89 -9
- package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +13 -0
- package/dist/packem_shared/{protectPublic-BjFkQ_Or.mjs → protectPublic-BlcGpiRc.mjs} +1 -1
- package/dist/packem_shared/{rls-BDKRbMCA.mjs → rls-B_ZWgslr.mjs} +51 -7
- package/dist/packem_shared/{run-middleware-CYQOuoV6.mjs → run-middleware-I6EiQfxL.mjs} +3 -1
- package/dist/packem_shared/{storageRules-4a30FSpI.mjs → storageRules-6QxzDOcx.mjs} +1 -1
- package/dist/packem_shared/{types.d-DmvyEMD6.d.mts → types.d-BB3pjV0m.d.mts} +10 -4
- package/dist/packem_shared/{types.d-BDY0FYHK.d.ts → types.d-Cxl6ndhm.d.ts} +10 -4
- package/dist/rls/testing.d.mts +1 -1
- package/dist/rls/testing.d.ts +1 -1
- package/dist/rls/testing.mjs +1 -1
- package/dist/types.d.mts +206 -5
- package/dist/types.d.ts +206 -5
- package/package.json +6 -5
- package/dist/packem_shared/LunoraError-DhggBJZF.mjs +0 -51
- package/dist/packem_shared/bindOrm-DCuyr46L.mjs +0 -71
package/dist/index.mjs
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
export { default as asBucketStorage } from './packem_shared/asBucketStorage-Cnxd9y2q.mjs';
|
|
2
|
-
export { initLunora } from './packem_shared/initLunora-
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
2
|
+
export { initLunora } from './packem_shared/initLunora-sQUqaejx.mjs';
|
|
3
|
+
export { createSecrets } from './packem_shared/createSecrets-DwaR2rNG.mjs';
|
|
4
|
+
export { LunoraEnvError, defineEnv, redactSecrets } from './packem_shared/LunoraEnvError-BGmd1Qs0.mjs';
|
|
5
|
+
export { LunoraError } from './packem_shared/LunoraError-WbxmrpxR.mjs';
|
|
6
|
+
export { bindOrm, bindTableFacade } from './packem_shared/bindOrm-Bfgl7f-Q.mjs';
|
|
7
|
+
export { httpAction, httpRoute, httpRouter, isSafeHeaderValue, serveStorageObject } from './packem_shared/httpAction-DCXoYPIk.mjs';
|
|
8
|
+
export { defineIdentity } from './packem_shared/defineIdentity-DiX4zM9x.mjs';
|
|
7
9
|
export { onConnect, onDisconnect } from './packem_shared/onConnect-CIPXKPyw.mjs';
|
|
8
|
-
export { defineMigration } from './packem_shared/defineMigration-
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
10
|
+
export { defineMigration } from './packem_shared/defineMigration-Hx01yIht.mjs';
|
|
11
|
+
export { defineMutator } from './packem_shared/defineMutator-EIXAWhs9.mjs';
|
|
12
|
+
export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension } from './packem_shared/composePluginMiddleware-CcTj1_v2.mjs';
|
|
13
|
+
export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-BtPCiNLW.mjs';
|
|
14
|
+
export { protectPublic } from './packem_shared/protectPublic-BlcGpiRc.mjs';
|
|
15
|
+
export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-znq4ygKQ.mjs';
|
|
16
|
+
export { defineShape } from './packem_shared/defineShape-C5scNOrf.mjs';
|
|
13
17
|
export { anyApi } from './types.mjs';
|
|
14
18
|
export { cronJobs } from '@lunora/scheduler';
|
|
15
19
|
export { ValidationError, v } from '@lunora/values';
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
20
|
+
export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-D54vUQe4.mjs';
|
|
21
|
+
export { createPolicyDsl, definePermission, definePolicies, definePolicy, defineRole } from './packem_shared/createPolicyDsl-By3QB4he.mjs';
|
|
22
|
+
export { defineStorageRule, defineStorageRules } from './packem_shared/defineStorageRule-B5nL4Z1P.mjs';
|
|
23
|
+
export { mask } from './packem_shared/mask-BT1YgRbF.mjs';
|
|
24
|
+
export { rls } from './packem_shared/rls-B_ZWgslr.mjs';
|
|
25
|
+
export { storageRules } from './packem_shared/storageRules-6QxzDOcx.mjs';
|
|
21
26
|
|
|
22
27
|
const VERSION = "0.0.0";
|
|
23
28
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
1
2
|
import { optionalInner } from '@lunora/values';
|
|
2
3
|
|
|
3
4
|
const SECRET_VALUE_PREFIXES = [
|
|
@@ -53,13 +54,12 @@ const redactValueForKey = (message, key, raw) => {
|
|
|
53
54
|
}
|
|
54
55
|
return masked;
|
|
55
56
|
};
|
|
56
|
-
class LunoraEnvError extends
|
|
57
|
-
name = "LunoraEnvError";
|
|
57
|
+
class LunoraEnvError extends LunoraError$1 {
|
|
58
58
|
failures;
|
|
59
59
|
constructor(failures) {
|
|
60
60
|
const summary = failures.map((failure) => ` - ${failure.key}: ${failure.message}`).join("\n");
|
|
61
|
-
super(`Invalid environment (${String(failures.length)} key(s)):
|
|
62
|
-
${summary}
|
|
61
|
+
super("ENV_INVALID", `Invalid environment (${String(failures.length)} key(s)):
|
|
62
|
+
${summary}`, { name: "LunoraEnvError" });
|
|
63
63
|
this.failures = failures;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { v } from '@lunora/values';
|
|
2
|
-
import { initLunora } from './initLunora-
|
|
3
|
-
import { LunoraError } from './LunoraError-
|
|
2
|
+
import { initLunora } from './initLunora-sQUqaejx.mjs';
|
|
3
|
+
import { LunoraError } from './LunoraError-WbxmrpxR.mjs';
|
|
4
4
|
import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
|
|
5
|
-
import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-
|
|
6
|
-
import { defineTable } from './defineAggregateIndex-
|
|
5
|
+
import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-CcTj1_v2.mjs';
|
|
6
|
+
import { defineTable } from './defineAggregateIndex-znq4ygKQ.mjs';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_TTL_MS = 3e4;
|
|
9
9
|
const MAX_DATA_BYTES = 4096;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
const isUniqueConflict = (error) => typeof error === "object" && error !== null && error.code === "CONFLICT" && error.kind === "unique";
|
|
4
|
+
const buildUpsertWhere = (tableName, target, create) => {
|
|
5
|
+
const fields = typeof target === "string" ? [target] : target;
|
|
6
|
+
if (fields.length === 0) {
|
|
7
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.upsert: "target" must name at least one field`);
|
|
8
|
+
}
|
|
9
|
+
const where = {};
|
|
10
|
+
for (const field of fields) {
|
|
11
|
+
if (!(field in create)) {
|
|
12
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.upsert: target field "${field}" is missing from the create document`);
|
|
13
|
+
}
|
|
14
|
+
where[field] = create[field];
|
|
15
|
+
}
|
|
16
|
+
return where;
|
|
17
|
+
};
|
|
18
|
+
const bindTableFacade = (writer, tableName) => {
|
|
19
|
+
const insert = async (document, options) => {
|
|
20
|
+
if (options?.skipDuplicates !== true) {
|
|
21
|
+
return writer.insert(tableName, document);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return await writer.insert(tableName, document);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (isUniqueConflict(error)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const upsert = async ({ create, target, update }) => {
|
|
33
|
+
const where = buildUpsertWhere(tableName, target, create);
|
|
34
|
+
const existing = await writer.findFirst(tableName, { where });
|
|
35
|
+
if (existing && typeof existing["_id"] === "string") {
|
|
36
|
+
await writer.patch(existing["_id"], update ?? create, tableName);
|
|
37
|
+
return { created: false, id: existing["_id"] };
|
|
38
|
+
}
|
|
39
|
+
const id = await writer.insert(tableName, create);
|
|
40
|
+
return { created: true, id };
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
aggregate: (options) => writer.aggregate(tableName, options),
|
|
44
|
+
count: (where) => writer.count(tableName, where),
|
|
45
|
+
delete: (id) => writer.delete(id, tableName),
|
|
46
|
+
// `deleteMany`/`patchMany` forward the bound `tableName` as `expectedTable`
|
|
47
|
+
// for id-based calls (threaded through the writer + the RLS middleware's
|
|
48
|
+
// per-id gate) so every batched id is scoped to this table — the same IDOR
|
|
49
|
+
// guard the single-row `delete`/`patch` apply. The where-based form routes
|
|
50
|
+
// through the structural writer's `deleteMany(tableName, { where })`.
|
|
51
|
+
// `patchMany` maps the facade's `values` payload to the writer's
|
|
52
|
+
// `{ id, patch }` shape.
|
|
53
|
+
deleteMany: (first, options) => {
|
|
54
|
+
if (Array.isArray(first)) {
|
|
55
|
+
if (writer.deleteMany === void 0) {
|
|
56
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.deleteMany is unavailable: this writer has no batch delete`);
|
|
57
|
+
}
|
|
58
|
+
return writer.deleteMany(first, options, tableName);
|
|
59
|
+
}
|
|
60
|
+
if (writer.deleteWhere === void 0) {
|
|
61
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.deleteMany({ where }) is unavailable: this writer has no where-based delete`);
|
|
62
|
+
}
|
|
63
|
+
const whereArgs = first;
|
|
64
|
+
return writer.deleteWhere(tableName, whereArgs.where, { limit: whereArgs.limit });
|
|
65
|
+
},
|
|
66
|
+
// `exists` reuses `findFirst` (RLS-filtered, indexed when a `.withIndex`-able
|
|
67
|
+
// `where` is supplied) and only asks whether a row came back — no count scan.
|
|
68
|
+
exists: async (where) => await writer.findFirst(tableName, where === void 0 ? void 0 : { where }) !== null,
|
|
69
|
+
findFirst: (args) => writer.findFirst(tableName, args),
|
|
70
|
+
findFirstOrThrow: (args) => writer.findFirstOrThrow(tableName, args),
|
|
71
|
+
findMany: (args) => writer.findMany(tableName, args),
|
|
72
|
+
get: (id) => writer.get(id, tableName),
|
|
73
|
+
groupBy: (options) => writer.groupBy(tableName, options),
|
|
74
|
+
// Physical removal — bypasses `.softDelete()`. RLS gates it as a delete.
|
|
75
|
+
hardDelete: (id) => writer.delete(id, tableName, { hard: true }),
|
|
76
|
+
insert,
|
|
77
|
+
insertMany: (documents, options) => {
|
|
78
|
+
if (writer.insertMany === void 0) {
|
|
79
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.insertMany is unavailable: this writer has no batch insert`);
|
|
80
|
+
}
|
|
81
|
+
return writer.insertMany(tableName, documents, options);
|
|
82
|
+
},
|
|
83
|
+
patch: (id, patch) => writer.patch(id, patch, tableName),
|
|
84
|
+
patchMany: (first, options) => {
|
|
85
|
+
if (Array.isArray(first)) {
|
|
86
|
+
if (writer.patchMany === void 0) {
|
|
87
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.patchMany is unavailable: this writer has no batch patch`);
|
|
88
|
+
}
|
|
89
|
+
return writer.patchMany(
|
|
90
|
+
first.map((entry) => {
|
|
91
|
+
return { id: entry.id, patch: entry.values };
|
|
92
|
+
}),
|
|
93
|
+
options,
|
|
94
|
+
tableName
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (writer.patchWhere === void 0) {
|
|
98
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.patchMany({ where, values }) is unavailable: this writer has no where-based patch`);
|
|
99
|
+
}
|
|
100
|
+
const whereArgs = first;
|
|
101
|
+
return writer.patchWhere(tableName, { patch: whereArgs.values, where: whereArgs.where }, { limit: whereArgs.limit });
|
|
102
|
+
},
|
|
103
|
+
rank: (indexName, options) => writer.rank(tableName, indexName, options),
|
|
104
|
+
rankPage: (indexName, options) => writer.rankPage(tableName, indexName, options),
|
|
105
|
+
replace: (id, document) => writer.replace(id, document, tableName),
|
|
106
|
+
restore: async (id) => {
|
|
107
|
+
if (!writer.restore) {
|
|
108
|
+
throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.restore is unavailable: this writer has no restore (is the table .softDelete()?)`);
|
|
109
|
+
}
|
|
110
|
+
await writer.restore(id, tableName);
|
|
111
|
+
},
|
|
112
|
+
upsert,
|
|
113
|
+
upsertMany: async ({ rows, target }) => {
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
results.push(await upsert({ create: row.create, target, update: row.update }));
|
|
117
|
+
}
|
|
118
|
+
return results;
|
|
119
|
+
},
|
|
120
|
+
withSearchIndex: (indexName, search) => writer.query(tableName).withSearchIndex(indexName, search)
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const bindOrm = (facade) => {
|
|
124
|
+
const resolve = (table) => {
|
|
125
|
+
const bound = facade[table];
|
|
126
|
+
if (!bound) {
|
|
127
|
+
throw new LunoraError$1("INTERNAL", `unknown table: ${table}`);
|
|
128
|
+
}
|
|
129
|
+
return bound;
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
delete: (table, id) => resolve(table).delete(id),
|
|
133
|
+
insert: (table) => {
|
|
134
|
+
return { values: (document) => resolve(table).insert(document) };
|
|
135
|
+
},
|
|
136
|
+
query: facade,
|
|
137
|
+
replace: (table, id) => {
|
|
138
|
+
return { with: (document) => resolve(table).replace(id, document) };
|
|
139
|
+
},
|
|
140
|
+
update: (table, id) => {
|
|
141
|
+
return { set: (values) => resolve(table).patch(id, values) };
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export { bindOrm, bindTableFacade };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-B_ZWgslr.mjs';
|
|
2
|
+
import { r as readRlsTag } from './policy-tag-DvpVH2tv.mjs';
|
|
3
|
+
|
|
4
|
+
const FALSE_PREDICATE = { OR: [] };
|
|
5
|
+
const readEntryTags = (entry) => {
|
|
6
|
+
const hoisted = entry?.rls?.tags;
|
|
7
|
+
if (hoisted) {
|
|
8
|
+
return hoisted;
|
|
9
|
+
}
|
|
10
|
+
const tag = readRlsTag(entry);
|
|
11
|
+
return tag ? [tag] : [];
|
|
12
|
+
};
|
|
13
|
+
const addTagToRegistry = (byTable, tag) => {
|
|
14
|
+
const rolePermissions = indexRolePermissions(tag.roles);
|
|
15
|
+
const seenWhenByTable = /* @__PURE__ */ new Map();
|
|
16
|
+
const policiesByTable = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const policy of tag.policies) {
|
|
18
|
+
if (policy.on !== "read") {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const seen = seenWhenByTable.get(policy.table) ?? /* @__PURE__ */ new Set();
|
|
22
|
+
if (seen.has(policy.when)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
seen.add(policy.when);
|
|
26
|
+
seenWhenByTable.set(policy.table, seen);
|
|
27
|
+
const list = policiesByTable.get(policy.table) ?? [];
|
|
28
|
+
list.push(policy);
|
|
29
|
+
policiesByTable.set(policy.table, list);
|
|
30
|
+
}
|
|
31
|
+
for (const [table, policies] of policiesByTable) {
|
|
32
|
+
const groups = byTable.get(table) ?? [];
|
|
33
|
+
groups.push({ policies, rolePermissions });
|
|
34
|
+
byTable.set(table, groups);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const isFalsePredicate = (where) => {
|
|
38
|
+
const or = where.OR;
|
|
39
|
+
return Array.isArray(or) && or.length === 0 && Object.keys(where).length === 1;
|
|
40
|
+
};
|
|
41
|
+
const andMerge = (injected, caller) => {
|
|
42
|
+
if (!injected || Object.keys(injected).length === 0) {
|
|
43
|
+
return caller;
|
|
44
|
+
}
|
|
45
|
+
if (isFalsePredicate(injected)) {
|
|
46
|
+
return injected;
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(caller).length === 0) {
|
|
49
|
+
return injected;
|
|
50
|
+
}
|
|
51
|
+
return { AND: [injected, caller] };
|
|
52
|
+
};
|
|
53
|
+
const evaluateGroupBaseWhere = (group, request) => {
|
|
54
|
+
const granted = /* @__PURE__ */ new Set();
|
|
55
|
+
for (const roleName of request.roles) {
|
|
56
|
+
for (const name of group.rolePermissions.get(roleName) ?? []) {
|
|
57
|
+
granted.add(name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return computeReadBaseWhere(group.policies, {
|
|
61
|
+
auth: {
|
|
62
|
+
can: (permission) => granted.has(permissionName(permission)),
|
|
63
|
+
identity: request.identity,
|
|
64
|
+
roles: request.roles,
|
|
65
|
+
userId: request.userId
|
|
66
|
+
},
|
|
67
|
+
ctx: request.ctx
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const resolveReadBaseWhere = (registry, request) => {
|
|
71
|
+
const groups = registry.byTable.get(request.table);
|
|
72
|
+
if (!groups || groups.length === 0) {
|
|
73
|
+
return request.rlsRequired && !request.tablePublic ? FALSE_PREDICATE : void 0;
|
|
74
|
+
}
|
|
75
|
+
const predicates = [];
|
|
76
|
+
for (const group of groups) {
|
|
77
|
+
const base = evaluateGroupBaseWhere(group, request);
|
|
78
|
+
if (base === void 0) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
if (isFalsePredicate(base)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
predicates.push(base);
|
|
85
|
+
}
|
|
86
|
+
if (predicates.length === 0) {
|
|
87
|
+
return FALSE_PREDICATE;
|
|
88
|
+
}
|
|
89
|
+
return predicates.length === 1 ? predicates[0] : { OR: predicates };
|
|
90
|
+
};
|
|
91
|
+
const buildRlsReadRegistry = (functions) => {
|
|
92
|
+
const byTable = /* @__PURE__ */ new Map();
|
|
93
|
+
const seenTags = /* @__PURE__ */ new Set();
|
|
94
|
+
for (const entry of functions) {
|
|
95
|
+
for (const tag of readEntryTags(entry)) {
|
|
96
|
+
if (seenTags.has(tag)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
seenTags.add(tag);
|
|
100
|
+
addTagToRegistry(byTable, tag);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { byTable };
|
|
104
|
+
};
|
|
105
|
+
const composeShapeReadWhere = (registry, request) => andMerge(resolveReadBaseWhere(registry, request), request.shapeWhere);
|
|
106
|
+
|
|
107
|
+
export { buildRlsReadRegistry, composeShapeReadWhere };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
|
+
import { r as runMiddlewareChain } from './run-middleware-I6EiQfxL.mjs';
|
|
2
3
|
|
|
3
4
|
const prefixTableName = (key, bareName) => `${key}_${bareName}`;
|
|
4
5
|
const rewriteReference = (target, key, bareNames) => bareNames.has(target) ? prefixTableName(key, target) : target;
|
|
@@ -19,7 +20,7 @@ const rewriteTableReferences = (table, key, bareNames, ownBareName) => {
|
|
|
19
20
|
};
|
|
20
21
|
const defineSchemaExtension = (key, options) => {
|
|
21
22
|
if (!key) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new LunoraError$1("INTERNAL", "defineSchemaExtension: `key` is required and must be a non-empty string");
|
|
23
24
|
}
|
|
24
25
|
return {
|
|
25
26
|
key,
|
|
@@ -29,10 +30,10 @@ const defineSchemaExtension = (key, options) => {
|
|
|
29
30
|
};
|
|
30
31
|
const definePlugin = (key, options) => {
|
|
31
32
|
if (!key) {
|
|
32
|
-
throw new
|
|
33
|
+
throw new LunoraError$1("INTERNAL", "definePlugin: `key` is required and must be a non-empty string");
|
|
33
34
|
}
|
|
34
35
|
if (options.extension && options.extension.key !== key) {
|
|
35
|
-
throw new
|
|
36
|
+
throw new LunoraError$1("INTERNAL", `definePlugin("${key}"): extension key "${options.extension.key}" does not match plugin key`);
|
|
36
37
|
}
|
|
37
38
|
return {
|
|
38
39
|
key,
|
|
@@ -57,7 +58,8 @@ const mergeSchemaExtension = (base, extension) => {
|
|
|
57
58
|
for (const [bareName, table] of Object.entries(extension.tables)) {
|
|
58
59
|
const prefixed = prefixTableName(key, bareName);
|
|
59
60
|
if (Object.hasOwn(merged, prefixed)) {
|
|
60
|
-
throw new
|
|
61
|
+
throw new LunoraError$1(
|
|
62
|
+
"INTERNAL",
|
|
61
63
|
`defineSchema(...).extend("${key}"): table "${prefixed}" already exists in the base schema — another extension with the same key already contributed it`
|
|
62
64
|
);
|
|
63
65
|
}
|
|
@@ -68,7 +70,8 @@ const mergeSchemaExtension = (base, extension) => {
|
|
|
68
70
|
for (const [bareIndexName, index] of Object.entries(extension.vectorIndexes)) {
|
|
69
71
|
const prefixed = prefixTableName(key, bareIndexName);
|
|
70
72
|
if (Object.hasOwn(mergedVectorIndexes, prefixed)) {
|
|
71
|
-
throw new
|
|
73
|
+
throw new LunoraError$1(
|
|
74
|
+
"INTERNAL",
|
|
72
75
|
`defineSchema(...).extend("${key}"): vector index "${prefixed}" already exists in the base schema — another extension with the same key already contributed it`
|
|
73
76
|
);
|
|
74
77
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const definePolicy = (input) => {
|
|
2
4
|
return { on: input.on, table: input.table, when: input.when };
|
|
3
5
|
};
|
|
@@ -13,7 +15,8 @@ const definePolicies = (policies) => {
|
|
|
13
15
|
const key = JSON.stringify([policy.table, policy.on]);
|
|
14
16
|
const whens = seenWhenByKey.get(key) ?? /* @__PURE__ */ new Set();
|
|
15
17
|
if (whens.has(policy.when)) {
|
|
16
|
-
throw new
|
|
18
|
+
throw new LunoraError$1(
|
|
19
|
+
"INTERNAL",
|
|
17
20
|
`definePolicies: duplicate policy for (table "${policy.table}", on "${policy.on}") — the same decision function is registered more than once. Multiple distinct policies per (table, on) are allowed (reads OR, writes AND); remove the duplicate.`
|
|
18
21
|
);
|
|
19
22
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
const NON_SECRET_BINDING_METHODS = [
|
|
4
|
+
"put",
|
|
5
|
+
// KV / R2
|
|
6
|
+
"list",
|
|
7
|
+
// KV / R2
|
|
8
|
+
"delete",
|
|
9
|
+
// KV / R2
|
|
10
|
+
"getWithMetadata",
|
|
11
|
+
// KV
|
|
12
|
+
"head",
|
|
13
|
+
// R2
|
|
14
|
+
"createMultipartUpload",
|
|
15
|
+
// R2
|
|
16
|
+
"idFromName",
|
|
17
|
+
// Durable Object namespace
|
|
18
|
+
"newUniqueId",
|
|
19
|
+
// Durable Object namespace
|
|
20
|
+
"getByName",
|
|
21
|
+
// Durable Object namespace
|
|
22
|
+
"send",
|
|
23
|
+
// Queue producer
|
|
24
|
+
"sendBatch",
|
|
25
|
+
// Queue producer
|
|
26
|
+
"writeDataPoint",
|
|
27
|
+
// Analytics Engine dataset
|
|
28
|
+
"create",
|
|
29
|
+
// Workflows binding (get(id) + create/createBatch)
|
|
30
|
+
"createBatch"
|
|
31
|
+
// Workflows binding
|
|
32
|
+
];
|
|
33
|
+
const isSecretBinding = (value) => {
|
|
34
|
+
if (typeof value !== "object" || value === null) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const record = value;
|
|
38
|
+
if (typeof record.get !== "function") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return !NON_SECRET_BINDING_METHODS.some((method) => typeof record[method] === "function");
|
|
42
|
+
};
|
|
43
|
+
const createSecrets = (env) => {
|
|
44
|
+
return {
|
|
45
|
+
get: async (name) => {
|
|
46
|
+
const binding = env[name];
|
|
47
|
+
if (!isSecretBinding(binding)) {
|
|
48
|
+
throw new LunoraError$1(
|
|
49
|
+
"INTERNAL",
|
|
50
|
+
`ctx.secrets: no Secrets Store binding named "${name}". Add a \`secrets_store_secrets[]\` entry (binding "${name}", pointing at your store + secret) to wrangler.jsonc — \`ctx.secrets\` reads a Secrets Store binding, not a plain \`.dev.vars\` value.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return binding.get();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export { createSecrets };
|
package/dist/packem_shared/{defineAggregateIndex-DxSso0rH.mjs → defineAggregateIndex-znq4ygKQ.mjs}
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
|
+
import { isOrWrapsFromValidator, v } from '@lunora/values';
|
|
3
|
+
import { mergeSchemaExtension } from './composePluginMiddleware-CcTj1_v2.mjs';
|
|
3
4
|
|
|
4
5
|
const relationBuilder = {
|
|
5
6
|
many: (table, options) => {
|
|
@@ -22,10 +23,11 @@ const createTriggerBuilder = () => {
|
|
|
22
23
|
beforeUpdate: (handler) => makeTrigger("before", "update", handler)
|
|
23
24
|
};
|
|
24
25
|
};
|
|
25
|
-
const defineTable = (
|
|
26
|
+
const defineTable = (inputShape) => {
|
|
27
|
+
const shape = { ...inputShape };
|
|
26
28
|
for (const [columnName, validator] of Object.entries(shape)) {
|
|
27
29
|
if (isOrWrapsFromValidator(validator)) {
|
|
28
|
-
throw new
|
|
30
|
+
throw new LunoraError$1("INTERNAL", `defineTable: column "${columnName}" uses v.from() which is args-only — table columns need a concrete v.* type`);
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
const aggregateIndexes = [];
|
|
@@ -39,11 +41,13 @@ const defineTable = (shape) => {
|
|
|
39
41
|
let shardMode = { kind: "root" };
|
|
40
42
|
let isExternallyManaged = false;
|
|
41
43
|
let isPublic = false;
|
|
44
|
+
let softDelete;
|
|
45
|
+
let externalSource;
|
|
42
46
|
const builder = {
|
|
43
47
|
aggregateIndex(name, options) {
|
|
44
48
|
const op = options?.op ?? "count";
|
|
45
49
|
if (op !== "count" && !options?.field) {
|
|
46
|
-
throw new
|
|
50
|
+
throw new LunoraError$1("INTERNAL", `aggregateIndex "${name}": op "${op}" requires a "field"`);
|
|
47
51
|
}
|
|
48
52
|
aggregateIndexes.push({
|
|
49
53
|
by: options?.by,
|
|
@@ -61,6 +65,9 @@ const defineTable = (shape) => {
|
|
|
61
65
|
get aggregateIndexes() {
|
|
62
66
|
return aggregateIndexes;
|
|
63
67
|
},
|
|
68
|
+
get externalSource() {
|
|
69
|
+
return externalSource;
|
|
70
|
+
},
|
|
64
71
|
externallyManaged() {
|
|
65
72
|
isExternallyManaged = true;
|
|
66
73
|
return builder;
|
|
@@ -88,7 +95,7 @@ const defineTable = (shape) => {
|
|
|
88
95
|
},
|
|
89
96
|
rankIndex(name, options) {
|
|
90
97
|
if (!options.sortBy || options.sortBy.length === 0) {
|
|
91
|
-
throw new
|
|
98
|
+
throw new LunoraError$1("INTERNAL", `rankIndex "${name}": "sortBy" is required and must list at least one key`);
|
|
92
99
|
}
|
|
93
100
|
const sortBy = options.sortBy.map((key) => {
|
|
94
101
|
return {
|
|
@@ -132,6 +139,28 @@ const defineTable = (shape) => {
|
|
|
132
139
|
get shardMode() {
|
|
133
140
|
return shardMode;
|
|
134
141
|
},
|
|
142
|
+
get softDeleteMode() {
|
|
143
|
+
return softDelete;
|
|
144
|
+
},
|
|
145
|
+
source(definition) {
|
|
146
|
+
if (!definition.binding) {
|
|
147
|
+
throw new LunoraError$1("INTERNAL", "source: `binding` is required (the wrangler Hyperdrive binding name)");
|
|
148
|
+
}
|
|
149
|
+
if (!definition.query) {
|
|
150
|
+
throw new LunoraError$1("INTERNAL", "source: `query` is required (the tenant-membership SQL)");
|
|
151
|
+
}
|
|
152
|
+
externalSource = definition;
|
|
153
|
+
isExternallyManaged = true;
|
|
154
|
+
return builder;
|
|
155
|
+
},
|
|
156
|
+
softDelete(options) {
|
|
157
|
+
const field = options?.field ?? "deletedAt";
|
|
158
|
+
softDelete = { field };
|
|
159
|
+
if (!(field in shape)) {
|
|
160
|
+
shape[field] = v.optional(v.number().nullable());
|
|
161
|
+
}
|
|
162
|
+
return builder;
|
|
163
|
+
},
|
|
135
164
|
get triggerMap() {
|
|
136
165
|
return triggers;
|
|
137
166
|
},
|
|
@@ -170,13 +199,13 @@ const defineVectorIndex = (options) => {
|
|
|
170
199
|
const defineAggregateIndex = (name, options) => {
|
|
171
200
|
const op = options.op ?? "count";
|
|
172
201
|
if (op !== "count" && !options.field) {
|
|
173
|
-
throw new
|
|
202
|
+
throw new LunoraError$1("INTERNAL", `aggregateIndex "${name}": op "${op}" requires a "field"`);
|
|
174
203
|
}
|
|
175
204
|
return { by: options.by, field: options.field, name, on: options.on, op, where: options.where };
|
|
176
205
|
};
|
|
177
206
|
const defineRankIndex = (name, options) => {
|
|
178
207
|
if (!options.sortBy || options.sortBy.length === 0) {
|
|
179
|
-
throw new
|
|
208
|
+
throw new LunoraError$1("INTERNAL", `rankIndex "${name}": "sortBy" is required and must list at least one key`);
|
|
180
209
|
}
|
|
181
210
|
const sortBy = options.sortBy.map((key) => {
|
|
182
211
|
return {
|
|
@@ -192,6 +221,9 @@ const withExtend = (schema) => {
|
|
|
192
221
|
extend(extension) {
|
|
193
222
|
return withExtend(mergeSchemaExtension(schema, extension));
|
|
194
223
|
},
|
|
224
|
+
jurisdiction(_jurisdiction) {
|
|
225
|
+
return withExtend(schema);
|
|
226
|
+
},
|
|
195
227
|
rls(mode) {
|
|
196
228
|
return withExtend({ ...schema, rlsMode: mode });
|
|
197
229
|
}
|
|
@@ -215,21 +247,48 @@ const attachStandaloneIndexes = (tables, aggregateIndexes, rankIndexes) => {
|
|
|
215
247
|
for (const index of Object.values(aggregateIndexes)) {
|
|
216
248
|
const table = tables[index.on];
|
|
217
249
|
if (!table) {
|
|
218
|
-
throw new
|
|
250
|
+
throw new LunoraError$1("INTERNAL", `defineAggregateIndex "${index.name}": unknown table "${index.on}"`);
|
|
219
251
|
}
|
|
220
252
|
table.aggregateIndexes.push(index);
|
|
221
253
|
}
|
|
222
254
|
for (const index of Object.values(rankIndexes)) {
|
|
223
255
|
const table = tables[index.on];
|
|
224
256
|
if (!table) {
|
|
225
|
-
throw new
|
|
257
|
+
throw new LunoraError$1("INTERNAL", `defineRankIndex "${index.name}": unknown table "${index.on}"`);
|
|
226
258
|
}
|
|
227
259
|
table.rankIndexes.push(index);
|
|
228
260
|
}
|
|
229
261
|
};
|
|
262
|
+
const validateExternalSources = (tables) => {
|
|
263
|
+
for (const [name, table] of Object.entries(tables)) {
|
|
264
|
+
const source = table.externalSource;
|
|
265
|
+
if (!source) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (table.shardMode.kind === "global") {
|
|
269
|
+
throw new LunoraError$1(
|
|
270
|
+
"INTERNAL",
|
|
271
|
+
`defineSchema: table "${name}" cannot be both .source() and .global() — a sourced table materializes into a shard DO's SQLite, a global table lives in the external tier`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
if (table.shardMode.kind === "shardBy" && !source.tenantBy) {
|
|
275
|
+
throw new LunoraError$1(
|
|
276
|
+
"INTERNAL",
|
|
277
|
+
`defineSchema: sourced + .shardBy() table "${name}" needs a \`tenantBy\` mapper — without it every tenant's DO would run the same unscoped query and replicate the whole multitenant table (a cross-tenant leak). Add \`tenantBy: (shardKey) => [shardKey]\` binding the shard key into the query's parameters.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (source.mode === "incremental") {
|
|
281
|
+
throw new LunoraError$1(
|
|
282
|
+
"INTERNAL",
|
|
283
|
+
`defineSchema: table "${name}" uses \`mode: "incremental"\`, which is not yet implemented — only "full-pull" (the default) is supported. Remove \`mode\` or set it to "full-pull".`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
230
288
|
const defineSchema = (tables, vectorIndexes = {}, aggregateIndexes = {}, rankIndexes = {}) => {
|
|
231
289
|
fillIndexTableNames(tables);
|
|
232
290
|
attachStandaloneIndexes(tables, aggregateIndexes, rankIndexes);
|
|
291
|
+
validateExternalSources(tables);
|
|
233
292
|
return withExtend({ tables, vectorIndexes });
|
|
234
293
|
};
|
|
235
294
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { LunoraError as LunoraError$1 } from '@lunora/errors';
|
|
2
|
+
import { parseValidatorMap, ValidationError } from '@lunora/values';
|
|
3
|
+
|
|
4
|
+
const NON_STRING_USER_ID_KINDS = /* @__PURE__ */ new Set([
|
|
5
|
+
"array",
|
|
6
|
+
"bigint",
|
|
7
|
+
"boolean",
|
|
8
|
+
"bytes",
|
|
9
|
+
"date",
|
|
10
|
+
"null",
|
|
11
|
+
"number",
|
|
12
|
+
"object",
|
|
13
|
+
"optional",
|
|
14
|
+
"record",
|
|
15
|
+
"timestamp"
|
|
16
|
+
]);
|
|
17
|
+
const defineIdentity = (claims, options = {}) => {
|
|
18
|
+
const map = claims;
|
|
19
|
+
const userIdValidator = map["userId"];
|
|
20
|
+
if (userIdValidator === void 0 || NON_STRING_USER_ID_KINDS.has(userIdValidator.kind)) {
|
|
21
|
+
throw new LunoraError$1("INTERNAL", "defineIdentity: the claim map must declare a required string `userId` validator (e.g. `v.string()` or `v.id(...)`)");
|
|
22
|
+
}
|
|
23
|
+
const onInvalid = options.onInvalid ?? "anonymous";
|
|
24
|
+
const validate = (identity) => {
|
|
25
|
+
try {
|
|
26
|
+
parseValidatorMap(map, identity, "identity");
|
|
27
|
+
return { ok: true };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return { error: error instanceof ValidationError ? error.message : String(error), ok: false };
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return { __lunoraIdentity: true, claims: map, onInvalid, validate };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { defineIdentity };
|