@lunora/server 1.0.0-alpha.31 → 1.0.0-alpha.32

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.
Files changed (61) hide show
  1. package/dist/data-model.mjs +0 -1
  2. package/dist/drizzle.mjs +1 -1
  3. package/dist/index.d.mts +105 -8
  4. package/dist/index.d.ts +105 -8
  5. package/dist/index.mjs +1 -29
  6. package/dist/packem_shared/LunoraEnvError-CgpI2Mm_.mjs +3 -0
  7. package/dist/packem_shared/LunoraError-LVhdU0Lo.mjs +1 -0
  8. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-CF_9uiu1.mjs +1 -0
  9. package/dist/packem_shared/allowAll-BnyNbJZT.mjs +1 -0
  10. package/dist/packem_shared/asBucketStorage-1pFfH-Tn.mjs +1 -0
  11. package/dist/packem_shared/bindOrm-Bp9hsM2q.mjs +1 -0
  12. package/dist/packem_shared/buildRlsReadRegistry-WdiqSj87.mjs +1 -0
  13. package/dist/packem_shared/composePluginMiddleware-COr09CXA.mjs +1 -0
  14. package/dist/packem_shared/createPolicyDsl-sV1swpkD.mjs +1 -0
  15. package/dist/packem_shared/createSecrets-CgVPiW2C.mjs +1 -0
  16. package/dist/packem_shared/defineAggregateIndex-CYuH6IbJ.mjs +1 -0
  17. package/dist/packem_shared/defineIdentity-B7gfAgxx.mjs +1 -0
  18. package/dist/packem_shared/defineMigration-Bfpwxv2f.mjs +1 -0
  19. package/dist/packem_shared/defineMutator-8Kz0j1Kt.mjs +1 -0
  20. package/dist/packem_shared/defineShape-CyuRfwHz.mjs +1 -0
  21. package/dist/packem_shared/defineStorageRule-BDu01PUn.mjs +1 -0
  22. package/dist/packem_shared/functions-CDC08CWY.mjs +1 -0
  23. package/dist/packem_shared/httpAction-C14NuF3V.mjs +4 -0
  24. package/dist/packem_shared/initLunora-CHh1EmR4.mjs +1 -0
  25. package/dist/packem_shared/mask-C6Bi78qj.mjs +1 -0
  26. package/dist/packem_shared/onConnect-CEtRmUpJ.mjs +1 -0
  27. package/dist/packem_shared/policy-tag-Dprt9JWo.mjs +1 -0
  28. package/dist/packem_shared/protectPublic-BhKewPqm.mjs +1 -0
  29. package/dist/packem_shared/rls-_iVsPvhX.mjs +1 -0
  30. package/dist/packem_shared/run-middleware-BeEEqmdE.mjs +1 -0
  31. package/dist/packem_shared/storageRules-BptPZbi8.mjs +1 -0
  32. package/dist/rls/testing.mjs +1 -49
  33. package/dist/types.d.mts +76 -0
  34. package/dist/types.d.ts +76 -0
  35. package/dist/types.mjs +1 -31
  36. package/package.json +1 -1
  37. package/dist/packem_shared/LunoraEnvError-BGmd1Qs0.mjs +0 -187
  38. package/dist/packem_shared/LunoraError-WbxmrpxR.mjs +0 -9
  39. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-Cr0i4mTv.mjs +0 -114
  40. package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +0 -11
  41. package/dist/packem_shared/bindOrm-CaY7Wq9Z.mjs +0 -147
  42. package/dist/packem_shared/buildRlsReadRegistry-2uk_GfiH.mjs +0 -107
  43. package/dist/packem_shared/composePluginMiddleware-z62dttBo.mjs +0 -103
  44. package/dist/packem_shared/createPolicyDsl-By3QB4he.mjs +0 -32
  45. package/dist/packem_shared/createSecrets-DwaR2rNG.mjs +0 -58
  46. package/dist/packem_shared/defineAggregateIndex-cdo0g-un.mjs +0 -349
  47. package/dist/packem_shared/defineIdentity-DiX4zM9x.mjs +0 -35
  48. package/dist/packem_shared/defineMigration-Hx01yIht.mjs +0 -10
  49. package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +0 -11
  50. package/dist/packem_shared/defineShape-C5scNOrf.mjs +0 -18
  51. package/dist/packem_shared/defineStorageRule-B5nL4Z1P.mjs +0 -23
  52. package/dist/packem_shared/functions-Di9FUNkf.mjs +0 -5
  53. package/dist/packem_shared/httpAction-DCXoYPIk.mjs +0 -355
  54. package/dist/packem_shared/initLunora-D0Wuki7S.mjs +0 -115
  55. package/dist/packem_shared/mask-BepaW7YN.mjs +0 -334
  56. package/dist/packem_shared/onConnect-CIPXKPyw.mjs +0 -13
  57. package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +0 -13
  58. package/dist/packem_shared/protectPublic-BlcGpiRc.mjs +0 -15
  59. package/dist/packem_shared/rls-BaDQf7MG.mjs +0 -595
  60. package/dist/packem_shared/run-middleware-I6EiQfxL.mjs +0 -20
  61. package/dist/packem_shared/storageRules-6QxzDOcx.mjs +0 -88
@@ -1,334 +0,0 @@
1
- import { LunoraError } from './LunoraError-WbxmrpxR.mjs';
2
- import { bindTableFacade, bindOrm } from './bindOrm-CaY7Wq9Z.mjs';
3
-
4
- const permissionName = (permission) => typeof permission === "string" ? permission : permission.name;
5
- const indexRolePermissions = (roles) => {
6
- const map = /* @__PURE__ */ new Map();
7
- for (const role of roles ?? []) {
8
- map.set(role.name, new Set((role.permissions ?? []).map((permission) => permissionName(permission))));
9
- }
10
- return map;
11
- };
12
- const fnv1aHex = (input) => {
13
- let hash = 2166136261;
14
- for (let index = 0; index < input.length; index += 1) {
15
- hash ^= input.codePointAt(index) ?? 0;
16
- hash = Math.imul(hash, 16777619);
17
- }
18
- return (hash >>> 0).toString(16).padStart(8, "0");
19
- };
20
- const applyStrategy = (strategy, value, context) => {
21
- try {
22
- if (strategy === "redact") {
23
- return null;
24
- }
25
- if (strategy === "hash") {
26
- if (value === null || value === void 0) {
27
- return value;
28
- }
29
- return fnv1aHex(typeof value === "string" ? value : JSON.stringify(value));
30
- }
31
- return strategy(value, context);
32
- } catch {
33
- return null;
34
- }
35
- };
36
- const maskRow = (row, columns, base) => {
37
- const out = { ...row };
38
- for (const [column, strategy] of Object.entries(columns)) {
39
- if (!(column in out)) {
40
- continue;
41
- }
42
- out[column] = applyStrategy(strategy, row[column], { ...base, column, row });
43
- }
44
- return out;
45
- };
46
- const maskPage = (page, columns, base) => {
47
- return { ...page, page: page.page.map((row) => maskRow(row, columns, base)) };
48
- };
49
- const assertIndexFieldsAllowed = (builderCallback, columns, tableName, method) => {
50
- if (typeof builderCallback !== "function") {
51
- return;
52
- }
53
- const referenced = /* @__PURE__ */ new Set();
54
- const makeRecorder = () => /* @__PURE__ */ new Proxy(
55
- {},
56
- {
57
- get: () => (field) => {
58
- if (typeof field === "string") {
59
- referenced.add(field);
60
- }
61
- return makeRecorder();
62
- }
63
- }
64
- );
65
- builderCallback(makeRecorder());
66
- for (const field of referenced) {
67
- if (field in columns) {
68
- throw new LunoraError("MASK_UNSUPPORTED", `${method}() filtering "${tableName}" by masked column "${field}" is not supported`);
69
- }
70
- }
71
- };
72
- const isFacadeEntry = (value) => {
73
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
74
- return false;
75
- }
76
- const candidate = value;
77
- return typeof candidate["findMany"] === "function" && typeof candidate["withSearchIndex"] === "function";
78
- };
79
- const wrapDatabase = (base, perTable, context) => {
80
- const wrapReader = (reader, columns, tableName) => {
81
- return {
82
- collect: async () => {
83
- const rows = await reader.collect();
84
- return rows.map((row) => maskRow(row, columns, context));
85
- },
86
- // SECURITY (value oracle): the predicate must see the MASKED row, not
87
- // the raw stored row — otherwise a caller can `.filter(d => d.ssn ===
88
- // guess)` to read the value the mask hides. Masking before the
89
- // predicate keeps filtering on non-masked columns working while
90
- // redacting masked cells the predicate can observe.
91
- filter: (predicate) => wrapReader(
92
- reader.filter((document) => predicate(maskRow(document, columns, context))),
93
- columns,
94
- tableName
95
- ),
96
- first: async () => {
97
- const row = await reader.first();
98
- return row ? maskRow(row, columns, context) : null;
99
- },
100
- order: (direction) => wrapReader(reader.order(direction), columns, tableName),
101
- paginate: async (options) => maskPage(await reader.paginate(options), columns, context),
102
- take: async (limit) => {
103
- const rows = await reader.take(limit);
104
- return rows.map((row) => maskRow(row, columns, context));
105
- },
106
- unique: async () => {
107
- const row = await reader.unique();
108
- return row ? maskRow(row, columns, context) : null;
109
- },
110
- // SECURITY (value oracle): reject before delegating when the range /
111
- // search references a masked column — an index range or search term
112
- // over a masked column is the same value oracle as a masked-column
113
- // `where`, so it must fail closed (see `assertIndexFieldsAllowed`).
114
- // Reads over NON-masked columns pass through and still mask output.
115
- withIndex: (indexName, range) => {
116
- assertIndexFieldsAllowed(range, columns, tableName, "withIndex");
117
- return wrapReader(reader.withIndex(indexName, range), columns, tableName);
118
- },
119
- withSearchIndex: (indexName, search) => {
120
- assertIndexFieldsAllowed(search, columns, tableName, "withSearchIndex");
121
- return wrapReader(reader.withSearchIndex(indexName, search), columns, tableName);
122
- },
123
- // A geo query's builder (`.near`/`.within`) exposes no column name, so
124
- // there's no masked-column value oracle to guard — just mask the output.
125
- withGeoIndex: (indexName, build) => wrapReader(reader.withGeoIndex(indexName, build), columns, tableName)
126
- };
127
- };
128
- const locate = async (id, expectedTable) => {
129
- if (base.lookupById) {
130
- const located = await base.lookupById(id, expectedTable);
131
- if (!located) {
132
- return { row: null, tableName: void 0 };
133
- }
134
- return { row: located.row, tableName: perTable.has(located.tableName) ? located.tableName : void 0 };
135
- }
136
- const row = await base.get(id, expectedTable);
137
- if (!row) {
138
- return { row: null, tableName: void 0 };
139
- }
140
- let probeTables;
141
- if (expectedTable === void 0) {
142
- probeTables = [...perTable.keys()];
143
- } else if (perTable.has(expectedTable)) {
144
- probeTables = [expectedTable];
145
- } else {
146
- probeTables = [];
147
- }
148
- const probes = await Promise.all(
149
- probeTables.map(async (tableName) => {
150
- const probe = await base.findFirst(tableName, { limit: 1, where: { _id: id } });
151
- return probe?.["_id"] === id ? tableName : void 0;
152
- })
153
- );
154
- return { row, tableName: probes.find((entry) => entry !== void 0) };
155
- };
156
- const assertReductionAllowed = (tableName, fields, method) => {
157
- const columns = perTable.get(tableName);
158
- if (!columns) {
159
- return;
160
- }
161
- const offending = fields.find((field) => typeof field === "string" && field in columns);
162
- if (offending !== void 0) {
163
- throw new LunoraError("MASK_UNSUPPORTED", `${method}() over masked column "${offending}" on "${tableName}" is not supported`);
164
- }
165
- };
166
- const collectWhereFields = (where, into) => {
167
- if (!where || typeof where !== "object" || Array.isArray(where)) {
168
- return;
169
- }
170
- for (const [key, value] of Object.entries(where)) {
171
- if (key === "AND" || key === "OR") {
172
- if (Array.isArray(value)) {
173
- for (const clause of value) {
174
- collectWhereFields(clause, into);
175
- }
176
- }
177
- } else if (key === "NOT") {
178
- collectWhereFields(value, into);
179
- } else if (!key.startsWith("__")) {
180
- into.add(key);
181
- }
182
- }
183
- };
184
- const assertWhereAllowed = (tableName, where, method) => {
185
- const columns = perTable.get(tableName);
186
- if (!columns || where === void 0) {
187
- return;
188
- }
189
- const referenced = /* @__PURE__ */ new Set();
190
- collectWhereFields(where, referenced);
191
- for (const field of referenced) {
192
- if (field in columns) {
193
- throw new LunoraError("MASK_UNSUPPORTED", `${method}() filtering "${tableName}" by masked column "${field}" is not supported`);
194
- }
195
- }
196
- };
197
- const assertOrderByAllowed = (tableName, orderBy, method) => {
198
- const columns = perTable.get(tableName);
199
- if (!columns || !Array.isArray(orderBy)) {
200
- return;
201
- }
202
- for (const entry of orderBy) {
203
- if (entry && typeof entry === "object" && !Array.isArray(entry)) {
204
- for (const field of Object.keys(entry)) {
205
- if (field in columns) {
206
- throw new LunoraError("MASK_UNSUPPORTED", `${method}() ordering "${tableName}" by masked column "${field}" is not supported`);
207
- }
208
- }
209
- }
210
- }
211
- };
212
- const wrapped = {
213
- ...base,
214
- async deleteWhere(tableName, where, options) {
215
- assertWhereAllowed(tableName, where, "deleteMany({ where })");
216
- if (base.deleteWhere === void 0) {
217
- throw new LunoraError("INTERNAL", `ctx.db.${tableName}.deleteMany({ where }) is unavailable: this writer has no where-based delete`);
218
- }
219
- return base.deleteWhere(tableName, where, options);
220
- },
221
- async patchWhere(tableName, args, options) {
222
- assertWhereAllowed(tableName, args.where, "patchMany({ where })");
223
- if (base.patchWhere === void 0) {
224
- throw new LunoraError("INTERNAL", `ctx.db.${tableName}.patchMany({ where }) is unavailable: this writer has no where-based patch`);
225
- }
226
- return base.patchWhere(tableName, args, options);
227
- },
228
- aggregate(tableName, options) {
229
- assertReductionAllowed(tableName, [options.field], "aggregate");
230
- assertWhereAllowed(tableName, options.where, "aggregate");
231
- return base.aggregate(tableName, options);
232
- },
233
- count(tableName, whereOrArgs) {
234
- const wrapper = whereOrArgs && typeof whereOrArgs === "object" && !Array.isArray(whereOrArgs) ? whereOrArgs : void 0;
235
- const where = wrapper && ("where" in wrapper || "baseWhere" in wrapper || "restrictsCounts" in wrapper) ? wrapper.where : whereOrArgs;
236
- assertWhereAllowed(tableName, where, "count");
237
- if (wrapper) {
238
- assertWhereAllowed(tableName, wrapper.baseWhere, "count");
239
- }
240
- return base.count(tableName, whereOrArgs);
241
- },
242
- async findFirst(tableName, args) {
243
- assertWhereAllowed(tableName, args?.where, "findFirst");
244
- assertWhereAllowed(tableName, args?.baseWhere, "findFirst");
245
- assertOrderByAllowed(tableName, args?.orderBy, "findFirst");
246
- const row = await base.findFirst(tableName, args);
247
- const columns = perTable.get(tableName);
248
- return row && columns ? maskRow(row, columns, context) : row;
249
- },
250
- async findFirstOrThrow(tableName, args) {
251
- assertWhereAllowed(tableName, args?.where, "findFirstOrThrow");
252
- assertWhereAllowed(tableName, args?.baseWhere, "findFirstOrThrow");
253
- assertOrderByAllowed(tableName, args?.orderBy, "findFirstOrThrow");
254
- const row = await base.findFirstOrThrow(tableName, args);
255
- const columns = perTable.get(tableName);
256
- return columns ? maskRow(row, columns, context) : row;
257
- },
258
- async findMany(tableName, args) {
259
- assertWhereAllowed(tableName, args?.where, "findMany");
260
- assertWhereAllowed(tableName, args?.baseWhere, "findMany");
261
- assertOrderByAllowed(tableName, args?.orderBy, "findMany");
262
- const page = await base.findMany(tableName, args);
263
- const columns = perTable.get(tableName);
264
- return columns ? maskPage(page, columns, context) : page;
265
- },
266
- async get(id, expectedTable) {
267
- const { row, tableName } = await locate(id, expectedTable);
268
- const columns = tableName === void 0 ? void 0 : perTable.get(tableName);
269
- if (!row || !columns) {
270
- return row;
271
- }
272
- return maskRow(row, columns, context);
273
- },
274
- groupBy(tableName, options) {
275
- assertReductionAllowed(tableName, [...options.by, options.agg?.field], "groupBy");
276
- assertWhereAllowed(tableName, options.where, "groupBy");
277
- return base.groupBy(tableName, options);
278
- },
279
- query(tableName) {
280
- const reader = base.query(tableName);
281
- const columns = perTable.get(tableName);
282
- return columns ? wrapReader(reader, columns, tableName) : reader;
283
- },
284
- async rankPage(tableName, indexName, options) {
285
- const page = await base.rankPage(tableName, indexName, options);
286
- const columns = perTable.get(tableName);
287
- return columns ? maskPage(page, columns, context) : page;
288
- }
289
- };
290
- const writableFacade = wrapped;
291
- for (const tableName of perTable.keys()) {
292
- if (isFacadeEntry(base[tableName])) {
293
- writableFacade[tableName] = bindTableFacade(wrapped, tableName);
294
- }
295
- }
296
- return wrapped;
297
- };
298
- const mask = (policies, options = {}) => {
299
- const perTable = new Map(Object.entries(policies));
300
- const rolePermissions = indexRolePermissions(options.roles);
301
- return async ({ ctx, next }) => {
302
- const auth = ctx.auth ?? {};
303
- const identity = await auth.getIdentity?.() ?? null;
304
- const roles = auth.roles ?? [];
305
- const granted = /* @__PURE__ */ new Set();
306
- for (const roleName of roles) {
307
- for (const name of rolePermissions.get(roleName) ?? []) {
308
- granted.add(name);
309
- }
310
- }
311
- const maskContext = {
312
- auth: {
313
- can: (permission) => granted.has(permissionName(permission)),
314
- identity,
315
- roles,
316
- // eslint-disable-next-line unicorn/no-null -- MaskContext.auth.userId is a public `null | string` type
317
- userId: auth.userId ?? null
318
- },
319
- ctx
320
- };
321
- if (options.bypass?.(maskContext)) {
322
- return next();
323
- }
324
- const wrapped = wrapDatabase(ctx.db, perTable, maskContext);
325
- const extension = { db: wrapped };
326
- const { orm } = ctx;
327
- if (orm !== null && typeof orm === "object") {
328
- extension.orm = bindOrm(wrapped);
329
- }
330
- return next({ ctx: extension });
331
- };
332
- };
333
-
334
- export { mask };
@@ -1,13 +0,0 @@
1
- const wrapLifecycle = (lifecycle, handler) => {
2
- return {
3
- args: {},
4
- handler: (context, event) => handler(context, event),
5
- kind: "mutation",
6
- lifecycle,
7
- visibility: "internal"
8
- };
9
- };
10
- const onConnect = (handler) => wrapLifecycle("connect", handler);
11
- const onDisconnect = (handler) => wrapLifecycle("disconnect", handler);
12
-
13
- export { onConnect, onDisconnect };
@@ -1,13 +0,0 @@
1
- const RLS_TAG = /* @__PURE__ */ Symbol.for("lunora.rls.middleware-policies");
2
- const tagRlsMiddleware = (middleware, tag) => {
3
- Object.defineProperty(middleware, RLS_TAG, { configurable: true, enumerable: false, value: tag });
4
- return middleware;
5
- };
6
- const readRlsTag = (middleware) => {
7
- if (middleware === null || typeof middleware !== "function" && typeof middleware !== "object") {
8
- return void 0;
9
- }
10
- return middleware[RLS_TAG];
11
- };
12
-
13
- export { readRlsTag as r, tagRlsMiddleware as t };
@@ -1,15 +0,0 @@
1
- import { r as runMiddlewareChain } from './run-middleware-I6EiQfxL.mjs';
2
-
3
- const protectPublic = (options) => {
4
- const chain = [options.rateLimit, options.captcha, ...options.use ?? []].filter(
5
- (middleware) => middleware !== void 0
6
- );
7
- const composed = async ({ ctx, next }) => runMiddlewareChain(
8
- chain,
9
- ctx,
10
- (context) => next({ ctx: context })
11
- );
12
- return composed;
13
- };
14
-
15
- export { protectPublic };