@lunora/server 1.0.0-alpha.21 → 1.0.0-alpha.210

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 (97) hide show
  1. package/LICENSE.md +213 -0
  2. package/README.md +18 -12
  3. package/dist/data-model.d.mts +392 -170
  4. package/dist/data-model.d.ts +392 -170
  5. package/dist/data-model.mjs +0 -1
  6. package/dist/drizzle.mjs +1 -1
  7. package/dist/in-process.d.mts +101 -0
  8. package/dist/in-process.d.ts +101 -0
  9. package/dist/in-process.mjs +1 -0
  10. package/dist/index.d.mts +3618 -1548
  11. package/dist/index.d.ts +3618 -1548
  12. package/dist/index.mjs +1 -29
  13. package/dist/otel.d.mts +554 -0
  14. package/dist/otel.d.ts +554 -0
  15. package/dist/otel.mjs +1 -0
  16. package/dist/packem_shared/ACTION_CACHE_DEFAULT_TTL_MS-mmP-6BcT.mjs +1 -0
  17. package/dist/packem_shared/DEFAULT_LIMIT-pT_tV8R0.mjs +1 -0
  18. package/dist/packem_shared/DOCUMENT_HISTORY_REDACTED_FIELDS-B5Zp-7SI.mjs +1 -0
  19. package/dist/packem_shared/LunoraEnvError-bkdMu_-N.mjs +4 -0
  20. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-DFpy5J8G.mjs +1 -0
  21. package/dist/packem_shared/allowAll-DkRAgItV.mjs +1 -0
  22. package/dist/packem_shared/apply-output-C5wZ5EAL.mjs +1 -0
  23. package/dist/packem_shared/asBucketStorage-DoGYOjq3.mjs +1 -0
  24. package/dist/packem_shared/assertShapesDeclareReadPolicies-Bg4Ter7Q.mjs +1 -0
  25. package/dist/packem_shared/beginDeferredDeletes-DhwFBbvs.mjs +1 -0
  26. package/dist/packem_shared/beginDeferredSchedules-C7QLYr6C.mjs +1 -0
  27. package/dist/packem_shared/bindOrm-9fbLRbOb.mjs +1 -0
  28. package/dist/packem_shared/buildMaskRegistry-DpWMtalG.mjs +1 -0
  29. package/dist/packem_shared/compose-middleware-BK9FlblN.mjs +1 -0
  30. package/dist/packem_shared/composePluginMiddleware-ZtDIkbvJ.mjs +1 -0
  31. package/dist/packem_shared/context-identity-B2XYGA0B.mjs +1 -0
  32. package/dist/packem_shared/createPolicyDsl-BZa6SqLJ.mjs +1 -0
  33. package/dist/packem_shared/createSecrets-D6rLB42U.mjs +1 -0
  34. package/dist/packem_shared/createServices-IJvoqi0e.mjs +1 -0
  35. package/dist/packem_shared/defineAggregateIndex-DF8NiKHr.mjs +1 -0
  36. package/dist/packem_shared/defineIdentity-DwkNKwYa.mjs +1 -0
  37. package/dist/packem_shared/defineIdentityGuard-Bt8N65Zs.mjs +1 -0
  38. package/dist/packem_shared/defineMigration-CXOS0Bvq.mjs +1 -0
  39. package/dist/packem_shared/defineModule-OecXWM3U.mjs +1 -0
  40. package/dist/packem_shared/defineMutator-B4ipDtDR.mjs +1 -0
  41. package/dist/packem_shared/defineRoom-CoGi-xwm.mjs +1 -0
  42. package/dist/packem_shared/defineShape-CwKm5KLp.mjs +1 -0
  43. package/dist/packem_shared/defineStorageRule-Dv4nJE0H.mjs +1 -0
  44. package/dist/packem_shared/fnv1a-D3ueNTWB.mjs +1 -0
  45. package/dist/packem_shared/functions-B2-H4PQT.mjs +1 -0
  46. package/dist/packem_shared/httpAction-D2S7bQzl.mjs +6 -0
  47. package/dist/packem_shared/initLunora-B-7Xs3Qq.mjs +1 -0
  48. package/dist/packem_shared/isPerDispatchMiddleware-DDQPatSm.mjs +1 -0
  49. package/dist/packem_shared/mask-D-jRCAhy.mjs +1 -0
  50. package/dist/packem_shared/middleware-3ySZVrpt.mjs +1 -0
  51. package/dist/packem_shared/onConnect-BLRoOpv2.mjs +1 -0
  52. package/dist/packem_shared/onQueryChange-iuLzmyPs.mjs +1 -0
  53. package/dist/packem_shared/onWhisper-CfDbFLQh.mjs +1 -0
  54. package/dist/packem_shared/platformAdmin-kczXQme8.mjs +1 -0
  55. package/dist/packem_shared/plugin-Bm9wIpiI.mjs +1 -0
  56. package/dist/packem_shared/policy-tag-V0lqFzgS.mjs +1 -0
  57. package/dist/packem_shared/protectPublic-Bi5_95aP.mjs +1 -0
  58. package/dist/packem_shared/rls-Dwj6TlY_.mjs +1 -0
  59. package/dist/packem_shared/run-middleware-BefYq7Xd.mjs +1 -0
  60. package/dist/packem_shared/serveStorageObject-aqBFrLyo.mjs +1 -0
  61. package/dist/packem_shared/storageRules-CBLioxF7.mjs +1 -0
  62. package/dist/packem_shared/types.d-B7FLdYUD.d.mts +148 -0
  63. package/dist/packem_shared/types.d-BCtjbIzl.d.ts +148 -0
  64. package/dist/rls/testing.d.mts +42 -34
  65. package/dist/rls/testing.d.ts +42 -34
  66. package/dist/rls/testing.mjs +1 -49
  67. package/dist/types.d.mts +2234 -623
  68. package/dist/types.d.ts +2234 -623
  69. package/dist/types.mjs +0 -31
  70. package/package.json +24 -6
  71. package/dist/packem_shared/LunoraEnvError-BGmd1Qs0.mjs +0 -187
  72. package/dist/packem_shared/LunoraError-WbxmrpxR.mjs +0 -9
  73. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-BtPCiNLW.mjs +0 -114
  74. package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +0 -11
  75. package/dist/packem_shared/bindOrm-Bfgl7f-Q.mjs +0 -146
  76. package/dist/packem_shared/buildRlsReadRegistry-D54vUQe4.mjs +0 -107
  77. package/dist/packem_shared/composePluginMiddleware-CcTj1_v2.mjs +0 -103
  78. package/dist/packem_shared/createPolicyDsl-By3QB4he.mjs +0 -32
  79. package/dist/packem_shared/createSecrets-DwaR2rNG.mjs +0 -58
  80. package/dist/packem_shared/defineAggregateIndex-znq4ygKQ.mjs +0 -295
  81. package/dist/packem_shared/defineIdentity-DiX4zM9x.mjs +0 -35
  82. package/dist/packem_shared/defineMigration-Hx01yIht.mjs +0 -10
  83. package/dist/packem_shared/defineMutator-EIXAWhs9.mjs +0 -11
  84. package/dist/packem_shared/defineShape-C5scNOrf.mjs +0 -18
  85. package/dist/packem_shared/defineStorageRule-B5nL4Z1P.mjs +0 -23
  86. package/dist/packem_shared/functions-Di9FUNkf.mjs +0 -5
  87. package/dist/packem_shared/httpAction-DCXoYPIk.mjs +0 -355
  88. package/dist/packem_shared/initLunora-sQUqaejx.mjs +0 -100
  89. package/dist/packem_shared/mask-BT1YgRbF.mjs +0 -291
  90. package/dist/packem_shared/onConnect-CIPXKPyw.mjs +0 -13
  91. package/dist/packem_shared/policy-tag-DvpVH2tv.mjs +0 -13
  92. package/dist/packem_shared/protectPublic-BlcGpiRc.mjs +0 -15
  93. package/dist/packem_shared/rls-B_ZWgslr.mjs +0 -595
  94. package/dist/packem_shared/run-middleware-I6EiQfxL.mjs +0 -20
  95. package/dist/packem_shared/storageRules-6QxzDOcx.mjs +0 -88
  96. package/dist/packem_shared/types.d-BB3pjV0m.d.mts +0 -141
  97. package/dist/packem_shared/types.d-Cxl6ndhm.d.ts +0 -141
package/dist/types.mjs CHANGED
@@ -1,31 +0,0 @@
1
- const namespaceCache = /* @__PURE__ */ new Map();
2
- const anyApi = /* @__PURE__ */ new Proxy(
3
- {},
4
- {
5
- get(_target, namespace) {
6
- const cached = namespaceCache.get(namespace);
7
- if (cached) {
8
- return cached;
9
- }
10
- const refCache = /* @__PURE__ */ new Map();
11
- const nsProxy = /* @__PURE__ */ new Proxy(
12
- {},
13
- {
14
- get(_inner, functionName) {
15
- const cachedRef = refCache.get(functionName);
16
- if (cachedRef) {
17
- return cachedRef;
18
- }
19
- const ref = { __lunoraRef: `${String(namespace)}:${String(functionName)}` };
20
- refCache.set(functionName, ref);
21
- return ref;
22
- }
23
- }
24
- );
25
- namespaceCache.set(namespace, nsProxy);
26
- return nsProxy;
27
- }
28
- }
29
- );
30
-
31
- export { anyApi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/server",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.210",
4
4
  "description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
5
5
  "keywords": [
6
6
  "backend",
@@ -56,17 +56,35 @@
56
56
  "types": "./dist/rls/testing.d.ts",
57
57
  "import": "./dist/rls/testing.mjs"
58
58
  },
59
+ "./otel": {
60
+ "types": "./dist/otel.d.ts",
61
+ "import": "./dist/otel.mjs"
62
+ },
63
+ "./in-process": {
64
+ "types": "./dist/in-process.d.ts",
65
+ "import": "./dist/in-process.mjs"
66
+ },
59
67
  "./package.json": "./package.json"
60
68
  },
61
69
  "publishConfig": {
62
70
  "access": "public"
63
71
  },
64
72
  "dependencies": {
65
- "@lunora/errors": "1.0.0-alpha.3",
66
- "@lunora/scheduler": "1.0.0-alpha.7",
67
- "@lunora/values": "1.0.0-alpha.6",
68
- "drizzle-orm": "^0.45.2",
69
- "hono": "^4.12.28"
73
+ "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
74
+ "@lunora/scheduler": "1.0.0-alpha.131",
75
+ "@lunora/shard-engine": "1.0.0-alpha.133",
76
+ "@lunora/values": "1.0.0-alpha.67",
77
+ "@lunora/wire": "1.0.0-alpha.1",
78
+ "drizzle-orm": "^0.45.3",
79
+ "hono": "^4.13.13"
80
+ },
81
+ "peerDependencies": {
82
+ "@opentelemetry/api": "^1.9.0"
83
+ },
84
+ "peerDependenciesMeta": {
85
+ "@opentelemetry/api": {
86
+ "optional": true
87
+ }
70
88
  },
71
89
  "engines": {
72
90
  "node": "^22.15.0 || >=24.11.0"
@@ -1,187 +0,0 @@
1
- import { LunoraError as LunoraError$1 } from '@lunora/errors';
2
- import { optionalInner } from '@lunora/values';
3
-
4
- const SECRET_VALUE_PREFIXES = [
5
- /^sk_/u,
6
- /^pk_/u,
7
- /^rk_/u,
8
- /^ghp_/u,
9
- /^gho_/u,
10
- /^ghs_/u,
11
- /^ghr_/u,
12
- /^github_pat_/u,
13
- /^xox[baprs]-/u,
14
- /^AKIA/u,
15
- /^AIza/u,
16
- /^Bearer\s/u
17
- ];
18
- const HIGH_ENTROPY_TOKEN = /[\w./+-]{24,}/gu;
19
- const STANDALONE_TOKEN = /^[\w./+-]+$/u;
20
- const EMBEDDED_PREFIXED_TOKEN_SHORT = /\b(?:(?:sk|pk|rk|ghp|gho|ghs|ghr)_[\w./+-]*|github_pat_[\w./+-]*|xox[baprs]-[\w./+-]*)/gu;
21
- const EMBEDDED_PREFIXED_TOKEN_LONG = /\b(?:AKIA|AIza)[\w./+-]+|Bearer\s+[\w./+-]+/gu;
22
- const URL_CREDENTIAL = /\b([a-z][\w.+-]*:\/\/[\w.%+-]+):[\w.%+-]+@/gu;
23
- const REDACTED = "[redacted]";
24
- const SECRET_KEY = /(?:KEY|PASSWORD|SECRET|TOKEN)$/u;
25
- const looksLikeSecretValue = (value) => {
26
- if (SECRET_VALUE_PREFIXES.some((prefix) => prefix.test(value))) {
27
- return true;
28
- }
29
- const trimmed = value.trim();
30
- return trimmed.length >= 24 && STANDALONE_TOKEN.test(trimmed);
31
- };
32
- const QUOTED_VALUE = /(["'])(?<inner>(?:\\.|(?!\1).)*)\1/gu;
33
- const KEYED_VALUE = /\b(?<key>[A-Za-z_]\w*)\s*[=:]\s*\S+/gu;
34
- const redactSecrets = (message) => {
35
- let out = message;
36
- out = out.replaceAll(QUOTED_VALUE, (match, ...groups) => {
37
- const named = groups.at(-1);
38
- return named?.inner !== void 0 && looksLikeSecretValue(named.inner) ? REDACTED : match;
39
- });
40
- out = out.replaceAll(URL_CREDENTIAL, (_match, prefix) => `${prefix}:${REDACTED}@`);
41
- out = out.replaceAll(EMBEDDED_PREFIXED_TOKEN_SHORT, REDACTED);
42
- out = out.replaceAll(EMBEDDED_PREFIXED_TOKEN_LONG, REDACTED);
43
- out = out.replaceAll(KEYED_VALUE, (match, ...groups) => {
44
- const named = groups.at(-1);
45
- return named?.key !== void 0 && SECRET_KEY.test(named.key) ? `${named.key}=${REDACTED}` : match;
46
- });
47
- out = out.replaceAll(HIGH_ENTROPY_TOKEN, REDACTED);
48
- return out;
49
- };
50
- const redactValueForKey = (message, key, raw) => {
51
- const masked = redactSecrets(message);
52
- if (typeof raw === "string" && raw !== "" && SECRET_KEY.test(key)) {
53
- return masked.replaceAll(raw, REDACTED);
54
- }
55
- return masked;
56
- };
57
- class LunoraEnvError extends LunoraError$1 {
58
- failures;
59
- constructor(failures) {
60
- const summary = failures.map((failure) => ` - ${failure.key}: ${failure.message}`).join("\n");
61
- super("ENV_INVALID", `Invalid environment (${String(failures.length)} key(s)):
62
- ${summary}`, { name: "LunoraEnvError" });
63
- this.failures = failures;
64
- }
65
- }
66
- const TRUE_VALUES = /* @__PURE__ */ new Set(["1", "on", "true", "yes"]);
67
- const FALSE_VALUES = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
68
- const INTEGER_LITERAL = /^-?\d+$/u;
69
- const unwrapKind = (validator) => {
70
- if (validator.kind !== "optional") {
71
- return validator.kind;
72
- }
73
- const inner = optionalInner(validator);
74
- return inner ? unwrapKind(inner) : validator.kind;
75
- };
76
- const coerce = (validator, raw) => {
77
- if (typeof raw !== "string") {
78
- return raw;
79
- }
80
- switch (unwrapKind(validator)) {
81
- case "bigint": {
82
- return INTEGER_LITERAL.test(raw.trim()) ? BigInt(raw.trim()) : raw;
83
- }
84
- case "boolean": {
85
- const lowered = raw.trim().toLowerCase();
86
- if (TRUE_VALUES.has(lowered)) {
87
- return true;
88
- }
89
- if (FALSE_VALUES.has(lowered)) {
90
- return false;
91
- }
92
- return raw;
93
- }
94
- case "number": {
95
- const trimmed = raw.trim();
96
- if (trimmed === "") {
97
- return raw;
98
- }
99
- const parsed = Number(trimmed);
100
- return Number.isNaN(parsed) ? raw : parsed;
101
- }
102
- default: {
103
- return raw;
104
- }
105
- }
106
- };
107
- const validateKey = (key, validator, env, failures) => {
108
- const raw = env[key];
109
- if (raw === void 0 && validator.kind === "optional") {
110
- return { ok: true, value: void 0 };
111
- }
112
- const result = validator.safeParse(coerce(validator, raw));
113
- if (result.ok) {
114
- return { ok: true, value: result.value };
115
- }
116
- failures.push({ key, message: redactValueForKey(result.error.message, key, raw) });
117
- return { ok: false };
118
- };
119
- const requireEnvObject = (env) => {
120
- if (typeof env !== "object" || env === null) {
121
- throw new LunoraEnvError([{ key: "<env>", message: `expected an object, received ${env === null ? "null" : typeof env}` }]);
122
- }
123
- return env;
124
- };
125
- const defineEnv = (shape) => {
126
- const keys = Object.keys(shape);
127
- const cache = /* @__PURE__ */ new WeakMap();
128
- const accessor = ((env) => {
129
- const source = requireEnvObject(env);
130
- let resolved = cache.get(source);
131
- if (resolved === void 0) {
132
- resolved = /* @__PURE__ */ new Map();
133
- cache.set(source, resolved);
134
- }
135
- const cached = resolved;
136
- const read = (property) => {
137
- if (cached.has(property)) {
138
- return cached.get(property);
139
- }
140
- const failures = [];
141
- const outcome = validateKey(property, shape[property], source, failures);
142
- if (!outcome.ok) {
143
- throw new LunoraEnvError(failures);
144
- }
145
- cached.set(property, outcome.value);
146
- return outcome.value;
147
- };
148
- return /* @__PURE__ */ new Proxy({}, {
149
- get(_target, property) {
150
- if (typeof property !== "string" || !(property in shape)) {
151
- return void 0;
152
- }
153
- return read(property);
154
- },
155
- getOwnPropertyDescriptor(_target, property) {
156
- if (typeof property === "string" && property in shape) {
157
- return { configurable: true, enumerable: true, value: read(property), writable: false };
158
- }
159
- return void 0;
160
- },
161
- has(_target, property) {
162
- return typeof property === "string" && property in shape;
163
- },
164
- ownKeys() {
165
- return keys;
166
- }
167
- });
168
- });
169
- accessor.parse = (env) => {
170
- const source = requireEnvObject(env);
171
- const failures = [];
172
- const out = {};
173
- for (const key of keys) {
174
- const outcome = validateKey(key, shape[key], source, failures);
175
- if (outcome.ok && outcome.value !== void 0) {
176
- out[key] = outcome.value;
177
- }
178
- }
179
- if (failures.length > 0) {
180
- throw new LunoraEnvError(failures);
181
- }
182
- return out;
183
- };
184
- return accessor;
185
- };
186
-
187
- export { LunoraEnvError, defineEnv, redactSecrets };
@@ -1,9 +0,0 @@
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,114 +0,0 @@
1
- import { v } from '@lunora/values';
2
- import { initLunora } from './initLunora-sQUqaejx.mjs';
3
- import { LunoraError } from './LunoraError-WbxmrpxR.mjs';
4
- import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
5
- import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-CcTj1_v2.mjs';
6
- import { defineTable } from './defineAggregateIndex-znq4ygKQ.mjs';
7
-
8
- const DEFAULT_TTL_MS = 3e4;
9
- const MAX_DATA_BYTES = 4096;
10
- const PRESENCE_KEY = "presence";
11
- const PRESENCE_BARE_TABLE = "present";
12
- const PRESENCE_TABLE = `${PRESENCE_KEY}_${PRESENCE_BARE_TABLE}`;
13
- const presenceExtension = defineSchemaExtension(PRESENCE_KEY, {
14
- tables: {
15
- [PRESENCE_BARE_TABLE]: defineTable({
16
- data: v.optional(v.record(v.string(), v.any())),
17
- lastSeen: v.number(),
18
- roomId: v.string(),
19
- sessionId: v.string(),
20
- userId: v.optional(v.string())
21
- }).index("byRoomSession", ["roomId", "sessionId"]).index("byRoom", ["roomId"])
22
- }
23
- });
24
- const { mutation, query } = initLunora.dataModel().create();
25
- const definePresence = (options = {}) => {
26
- const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
27
- const disconnectGraceMs = Math.max(0, Math.min(options.disconnectGraceMs ?? 0, ttlMs));
28
- const heartbeat = mutation.input({
29
- data: v.optional(v.record(v.string(), v.any())),
30
- roomId: v.string(),
31
- sessionId: v.string()
32
- }).mutation(async ({ args, ctx: context }) => {
33
- const lastSeen = Date.now();
34
- const userId = context.auth.userId ?? void 0;
35
- if (args.data !== void 0 && JSON.stringify(args.data).length > MAX_DATA_BYTES) {
36
- throw new LunoraError("BAD_REQUEST", `presence data exceeds the ${String(MAX_DATA_BYTES)}-byte limit`);
37
- }
38
- const existing = await context.db.query(PRESENCE_TABLE).withIndex("byRoomSession", (q) => q.eq("roomId", args.roomId).eq("sessionId", args.sessionId)).first();
39
- if (existing && (existing["userId"] ?? void 0) !== userId) {
40
- throw new LunoraError("FORBIDDEN", "presence heartbeat denied: this (roomId, sessionId) is held by another identity");
41
- }
42
- const row = {
43
- lastSeen,
44
- roomId: args.roomId,
45
- sessionId: args.sessionId,
46
- ...args.data === void 0 ? {} : { data: args.data },
47
- ...userId === void 0 ? {} : { userId }
48
- };
49
- await (existing ? context.db.patch(existing["_id"], row) : context.db.insert(PRESENCE_TABLE, row));
50
- return { lastSeen };
51
- });
52
- const listPresent = query.input({ roomId: v.string() }).query(async ({ args, ctx: context }) => {
53
- const cutoff = Date.now() - ttlMs;
54
- const rows = await context.db.query(PRESENCE_TABLE).withIndex("byRoom", (q) => q.eq("roomId", args.roomId)).collect();
55
- const live = rows.filter((row) => row["lastSeen"] > cutoff).toSorted((a, b) => b["lastSeen"] - a["lastSeen"]);
56
- const seenUsers = /* @__PURE__ */ new Set();
57
- const members = [];
58
- for (const row of live) {
59
- const userId = row["userId"];
60
- if (userId !== void 0) {
61
- if (seenUsers.has(userId)) {
62
- continue;
63
- }
64
- seenUsers.add(userId);
65
- }
66
- const member = {
67
- lastSeen: row["lastSeen"],
68
- roomId: row["roomId"]
69
- };
70
- if (userId !== void 0) {
71
- member.userId = userId;
72
- }
73
- if (row["data"] !== void 0) {
74
- member.data = row["data"];
75
- }
76
- members.push(member);
77
- }
78
- return members;
79
- });
80
- const sweep = mutation.input({ roomId: v.string() }).mutation(async ({ args, ctx: context }) => {
81
- const cutoff = Date.now() - ttlMs;
82
- const stale = await context.db.query(PRESENCE_TABLE).withIndex("byRoom", (q) => q.eq("roomId", args.roomId)).filter((row) => row["lastSeen"] <= cutoff).collect();
83
- await Promise.all(stale.map((row) => context.db.delete(row["_id"])));
84
- return { deleted: stale.length };
85
- });
86
- const internalSweep = { ...sweep, visibility: "internal" };
87
- const disconnect = onDisconnect(async (context, event) => {
88
- const roomId = event.context?.["roomId"];
89
- const sessionId = event.context?.["sessionId"];
90
- if (typeof roomId !== "string" || typeof sessionId !== "string") {
91
- return;
92
- }
93
- const existing = await context.db.query(PRESENCE_TABLE).withIndex("byRoomSession", (q) => q.eq("roomId", roomId).eq("sessionId", sessionId)).first();
94
- if (!existing) {
95
- return;
96
- }
97
- const verifiedUserId = event.userId ?? void 0;
98
- if ((existing["userId"] ?? void 0) !== verifiedUserId) {
99
- return;
100
- }
101
- if (disconnectGraceMs === 0) {
102
- await context.db.delete(existing["_id"]);
103
- return;
104
- }
105
- const agedLastSeen = Math.min(existing["lastSeen"], Date.now() + disconnectGraceMs - ttlMs);
106
- await context.db.patch(existing["_id"], { lastSeen: agedLastSeen });
107
- });
108
- return defineComponent(PRESENCE_KEY, {
109
- extension: presenceExtension,
110
- functions: { disconnect, heartbeat, listPresent, sweep: internalSweep }
111
- });
112
- };
113
-
114
- export { DEFAULT_TTL_MS as PRESENCE_DEFAULT_TTL_MS, PRESENCE_TABLE, definePresence, presenceExtension };
@@ -1,11 +0,0 @@
1
- const asBucketStorage = (raw) => {
2
- const candidate = raw ?? {};
3
- if (typeof candidate.bucket === "function") {
4
- return candidate;
5
- }
6
- const self = { ...candidate, bucketName: "default" };
7
- self.bucket = () => self;
8
- return self;
9
- };
10
-
11
- export { asBucketStorage as default };
@@ -1,146 +0,0 @@
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 };
@@ -1,107 +0,0 @@
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 };