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

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 (41) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +50 -0
  3. package/dist/data-model.d.mts +231 -156
  4. package/dist/data-model.d.ts +231 -156
  5. package/dist/index.d.mts +1191 -811
  6. package/dist/index.d.ts +1191 -811
  7. package/dist/index.mjs +20 -15
  8. package/dist/packem_shared/{LunoraEnvError-DjFkpkSP.mjs → LunoraEnvError-BGmd1Qs0.mjs} +4 -4
  9. package/dist/packem_shared/LunoraError-WbxmrpxR.mjs +9 -0
  10. package/dist/packem_shared/{PRESENCE_DEFAULT_TTL_MS-UQuUI5sV.mjs → PRESENCE_DEFAULT_TTL_MS-Cr0i4mTv.mjs} +5 -5
  11. package/dist/packem_shared/{bindOrm-Ce57S3N9.mjs → bindOrm-CaY7Wq9Z.mjs} +42 -23
  12. package/dist/packem_shared/buildRlsReadRegistry-2uk_GfiH.mjs +107 -0
  13. package/dist/packem_shared/{composePluginMiddleware-Ck5_TUO8.mjs → composePluginMiddleware-z62dttBo.mjs} +11 -8
  14. package/dist/packem_shared/{createPolicyDsl-De67zPDS.mjs → createPolicyDsl-By3QB4he.mjs} +4 -1
  15. package/dist/packem_shared/createSecrets-DwaR2rNG.mjs +58 -0
  16. package/dist/packem_shared/{defineAggregateIndex-B20MIOmj.mjs → defineAggregateIndex-cdo0g-un.mjs} +108 -8
  17. package/dist/packem_shared/defineIdentity-DiX4zM9x.mjs +35 -0
  18. package/dist/packem_shared/{defineMigration-CAJLr6fx.mjs → defineMigration-Hx01yIht.mjs} +3 -1
  19. package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +11 -0
  20. package/dist/packem_shared/defineShape-C5scNOrf.mjs +18 -0
  21. package/dist/packem_shared/{defineStorageRule-qu0mpilX.mjs → defineStorageRule-B5nL4Z1P.mjs} +4 -1
  22. package/dist/packem_shared/functions-Di9FUNkf.mjs +5 -0
  23. package/dist/packem_shared/{httpAction-B7FYUEgr.mjs → httpAction-DCXoYPIk.mjs} +42 -27
  24. package/dist/packem_shared/{initLunora-CATvPsVt.mjs → initLunora-D0Wuki7S.mjs} +36 -7
  25. package/dist/packem_shared/{mask-eCUYOwhd.mjs → mask-BepaW7YN.mjs} +132 -9
  26. package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +13 -0
  27. package/dist/packem_shared/{protectPublic-BjFkQ_Or.mjs → protectPublic-BlcGpiRc.mjs} +1 -1
  28. package/dist/packem_shared/{rls-Bi9HiyDC.mjs → rls-BaDQf7MG.mjs} +34 -6
  29. package/dist/packem_shared/{run-middleware-CYQOuoV6.mjs → run-middleware-I6EiQfxL.mjs} +3 -1
  30. package/dist/packem_shared/{storageRules-4a30FSpI.mjs → storageRules-6QxzDOcx.mjs} +1 -1
  31. package/dist/packem_shared/types.d-C4CMJK8x.d.mts +141 -0
  32. package/dist/packem_shared/types.d-DdYF8E18.d.ts +141 -0
  33. package/dist/rls/testing.d.mts +31 -31
  34. package/dist/rls/testing.d.ts +31 -31
  35. package/dist/rls/testing.mjs +1 -1
  36. package/dist/types.d.mts +957 -433
  37. package/dist/types.d.ts +957 -433
  38. package/package.json +6 -5
  39. package/dist/packem_shared/LunoraError-DhggBJZF.mjs +0 -51
  40. package/dist/packem_shared/types.d-BDY0FYHK.d.ts +0 -135
  41. package/dist/packem_shared/types.d-DmvyEMD6.d.mts +0 -135
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-CATvPsVt.mjs';
3
- export { LunoraEnvError, defineEnv, redactSecrets } from './packem_shared/LunoraEnvError-DjFkpkSP.mjs';
4
- export { LunoraError } from './packem_shared/LunoraError-DhggBJZF.mjs';
5
- export { bindOrm, bindTableFacade } from './packem_shared/bindOrm-Ce57S3N9.mjs';
6
- export { httpAction, httpRoute, httpRouter, serveStorageObject } from './packem_shared/httpAction-B7FYUEgr.mjs';
2
+ export { initLunora } from './packem_shared/initLunora-D0Wuki7S.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-CaY7Wq9Z.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-CAJLr6fx.mjs';
9
- export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension } from './packem_shared/composePluginMiddleware-Ck5_TUO8.mjs';
10
- export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-UQuUI5sV.mjs';
11
- export { protectPublic } from './packem_shared/protectPublic-BjFkQ_Or.mjs';
12
- export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-B20MIOmj.mjs';
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-z62dttBo.mjs';
13
+ export { PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension } from './packem_shared/PRESENCE_DEFAULT_TTL_MS-Cr0i4mTv.mjs';
14
+ export { protectPublic } from './packem_shared/protectPublic-BlcGpiRc.mjs';
15
+ export { defineAggregateIndex, defineRankIndex, defineSchema, defineTable, defineVectorIndex } from './packem_shared/defineAggregateIndex-cdo0g-un.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 { createPolicyDsl, definePermission, definePolicies, definePolicy, defineRole } from './packem_shared/createPolicyDsl-De67zPDS.mjs';
17
- export { defineStorageRule, defineStorageRules } from './packem_shared/defineStorageRule-qu0mpilX.mjs';
18
- export { mask } from './packem_shared/mask-eCUYOwhd.mjs';
19
- export { rls } from './packem_shared/rls-Bi9HiyDC.mjs';
20
- export { storageRules } from './packem_shared/storageRules-4a30FSpI.mjs';
20
+ export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-2uk_GfiH.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-BepaW7YN.mjs';
24
+ export { rls } from './packem_shared/rls-BaDQf7MG.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 Error {
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
  }
@@ -0,0 +1,9 @@
1
+ import { LunoraError as LunoraError$1 } from '@lunora/errors';
2
+
3
+ class LunoraError extends LunoraError$1 {
4
+ constructor(code, message, data) {
5
+ super(code, message, { data });
6
+ }
7
+ }
8
+
9
+ export { LunoraError };
@@ -1,9 +1,9 @@
1
1
  import { v } from '@lunora/values';
2
- import { initLunora } from './initLunora-CATvPsVt.mjs';
3
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
2
+ import { initLunora } from './initLunora-D0Wuki7S.mjs';
3
+ import { LunoraError } from './LunoraError-WbxmrpxR.mjs';
4
4
  import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
5
- import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-Ck5_TUO8.mjs';
6
- import { defineTable } from './defineAggregateIndex-B20MIOmj.mjs';
5
+ import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-z62dttBo.mjs';
6
+ import { defineTable } from './defineAggregateIndex-cdo0g-un.mjs';
7
7
 
8
8
  const DEFAULT_TTL_MS = 3e4;
9
9
  const MAX_DATA_BYTES = 4096;
@@ -32,7 +32,7 @@ const definePresence = (options = {}) => {
32
32
  }).mutation(async ({ args, ctx: context }) => {
33
33
  const lastSeen = Date.now();
34
34
  const userId = context.auth.userId ?? void 0;
35
- if (args.data !== void 0 && JSON.stringify(args.data).length > MAX_DATA_BYTES) {
35
+ if (args.data !== void 0 && new TextEncoder().encode(JSON.stringify(args.data)).length > MAX_DATA_BYTES) {
36
36
  throw new LunoraError("BAD_REQUEST", `presence data exceeds the ${String(MAX_DATA_BYTES)}-byte limit`);
37
37
  }
38
38
  const existing = await context.db.query(PRESENCE_TABLE).withIndex("byRoomSession", (q) => q.eq("roomId", args.roomId).eq("sessionId", args.sessionId)).first();
@@ -1,13 +1,15 @@
1
+ import { LunoraError as LunoraError$1 } from '@lunora/errors';
2
+
1
3
  const isUniqueConflict = (error) => typeof error === "object" && error !== null && error.code === "CONFLICT" && error.kind === "unique";
2
4
  const buildUpsertWhere = (tableName, target, create) => {
3
5
  const fields = typeof target === "string" ? [target] : target;
4
6
  if (fields.length === 0) {
5
- throw new Error(`ctx.db.${tableName}.upsert: "target" must name at least one field`);
7
+ throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.upsert: "target" must name at least one field`);
6
8
  }
7
9
  const where = {};
8
10
  for (const field of fields) {
9
11
  if (!(field in create)) {
10
- throw new Error(`ctx.db.${tableName}.upsert: target field "${field}" is missing from the create document`);
12
+ throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.upsert: target field "${field}" is missing from the create document`);
11
13
  }
12
14
  where[field] = create[field];
13
15
  }
@@ -42,15 +44,24 @@ const bindTableFacade = (writer, tableName) => {
42
44
  count: (where) => writer.count(tableName, where),
43
45
  delete: (id) => writer.delete(id, tableName),
44
46
  // `deleteMany`/`patchMany` forward the bound `tableName` as `expectedTable`
45
- // (threaded through the writer + the RLS middleware's per-id gate) so every
46
- // batched id is scoped to this table — the same IDOR guard the single-row
47
- // `delete`/`patch` apply. `patchMany` maps the facade's `values` payload to
48
- // the writer's `{ id, patch }` shape.
49
- deleteMany: (ids, options) => {
50
- if (writer.deleteMany === void 0) {
51
- throw new Error(`ctx.db.${tableName}.deleteMany is unavailable: this writer has no batch delete`);
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`);
52
62
  }
53
- return writer.deleteMany(ids, options, tableName);
63
+ const whereArgs = first;
64
+ return writer.deleteWhere(tableName, whereArgs.where, { limit: whereArgs.limit });
54
65
  },
55
66
  // `exists` reuses `findFirst` (RLS-filtered, indexed when a `.withIndex`-able
56
67
  // `where` is supplied) and only asks whether a row came back — no count scan.
@@ -65,29 +76,36 @@ const bindTableFacade = (writer, tableName) => {
65
76
  insert,
66
77
  insertMany: (documents, options) => {
67
78
  if (writer.insertMany === void 0) {
68
- throw new Error(`ctx.db.${tableName}.insertMany is unavailable: this writer has no batch insert`);
79
+ throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.insertMany is unavailable: this writer has no batch insert`);
69
80
  }
70
81
  return writer.insertMany(tableName, documents, options);
71
82
  },
72
83
  patch: (id, patch) => writer.patch(id, patch, tableName),
73
- patchMany: (patches, options) => {
74
- if (writer.patchMany === void 0) {
75
- throw new Error(`ctx.db.${tableName}.patchMany is unavailable: this writer has no batch patch`);
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`);
76
99
  }
77
- return writer.patchMany(
78
- patches.map((entry) => {
79
- return { id: entry.id, patch: entry.values };
80
- }),
81
- options,
82
- tableName
83
- );
100
+ const whereArgs = first;
101
+ return writer.patchWhere(tableName, { patch: whereArgs.values, where: whereArgs.where }, { limit: whereArgs.limit });
84
102
  },
85
103
  rank: (indexName, options) => writer.rank(tableName, indexName, options),
86
104
  rankPage: (indexName, options) => writer.rankPage(tableName, indexName, options),
87
105
  replace: (id, document) => writer.replace(id, document, tableName),
88
106
  restore: async (id) => {
89
107
  if (!writer.restore) {
90
- throw new Error(`ctx.db.${tableName}.restore is unavailable: this writer has no restore (is the table .softDelete()?)`);
108
+ throw new LunoraError$1("INTERNAL", `ctx.db.${tableName}.restore is unavailable: this writer has no restore (is the table .softDelete()?)`);
91
109
  }
92
110
  await writer.restore(id, tableName);
93
111
  },
@@ -99,6 +117,7 @@ const bindTableFacade = (writer, tableName) => {
99
117
  }
100
118
  return results;
101
119
  },
120
+ withGeoIndex: (indexName, build) => writer.query(tableName).withGeoIndex(indexName, build),
102
121
  withSearchIndex: (indexName, search) => writer.query(tableName).withSearchIndex(indexName, search)
103
122
  };
104
123
  };
@@ -106,7 +125,7 @@ const bindOrm = (facade) => {
106
125
  const resolve = (table) => {
107
126
  const bound = facade[table];
108
127
  if (!bound) {
109
- throw new Error(`unknown table: ${table}`);
128
+ throw new LunoraError$1("INTERNAL", `unknown table: ${table}`);
110
129
  }
111
130
  return bound;
112
131
  };
@@ -0,0 +1,107 @@
1
+ import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-BaDQf7MG.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 { r as runMiddlewareChain } from './run-middleware-CYQOuoV6.mjs';
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 Error("defineSchemaExtension: `key` is required and must be a non-empty string");
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,
@@ -27,19 +28,19 @@ const defineSchemaExtension = (key, options) => {
27
28
  ...options.vectorIndexes ? { vectorIndexes: options.vectorIndexes } : {}
28
29
  };
29
30
  };
30
- const definePlugin = (key, options) => {
31
+ const definePlugin = ((key, options) => {
31
32
  if (!key) {
32
- throw new Error("definePlugin: `key` is required and must be a non-empty string");
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 Error(`definePlugin("${key}"): extension key "${options.extension.key}" does not match plugin key`);
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,
39
40
  ...options.extension ? { extension: options.extension } : {},
40
41
  ...options.middleware ? { middleware: options.middleware } : {}
41
42
  };
42
- };
43
+ });
43
44
  const defineComponent = (key, options) => {
44
45
  const plugin = definePlugin(key, {
45
46
  ...options.extension ? { extension: options.extension } : {},
@@ -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 Error(
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 Error(
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 Error(
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 };