@lunora/server 1.0.0-alpha.4 → 1.0.0-alpha.40

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 (68) hide show
  1. package/LICENSE.md +219 -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/data-model.mjs +0 -1
  6. package/dist/drizzle.mjs +1 -1
  7. package/dist/index.d.mts +1304 -831
  8. package/dist/index.d.ts +1304 -831
  9. package/dist/index.mjs +1 -24
  10. package/dist/otel.d.mts +543 -0
  11. package/dist/otel.d.ts +543 -0
  12. package/dist/otel.mjs +1 -0
  13. package/dist/packem_shared/LunoraEnvError-CgpI2Mm_.mjs +3 -0
  14. package/dist/packem_shared/LunoraError-LVhdU0Lo.mjs +1 -0
  15. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-CF_9uiu1.mjs +1 -0
  16. package/dist/packem_shared/allowAll-BnyNbJZT.mjs +1 -0
  17. package/dist/packem_shared/asBucketStorage-1pFfH-Tn.mjs +1 -0
  18. package/dist/packem_shared/bindOrm-Bp9hsM2q.mjs +1 -0
  19. package/dist/packem_shared/buildRlsReadRegistry-WdiqSj87.mjs +1 -0
  20. package/dist/packem_shared/composePluginMiddleware-COr09CXA.mjs +1 -0
  21. package/dist/packem_shared/context-identity-GunnA6La.mjs +1 -0
  22. package/dist/packem_shared/createPolicyDsl-sV1swpkD.mjs +1 -0
  23. package/dist/packem_shared/createSecrets-CgVPiW2C.mjs +1 -0
  24. package/dist/packem_shared/defineAggregateIndex-CYuH6IbJ.mjs +1 -0
  25. package/dist/packem_shared/defineIdentity-B7gfAgxx.mjs +1 -0
  26. package/dist/packem_shared/defineMigration-Bfpwxv2f.mjs +1 -0
  27. package/dist/packem_shared/defineMutator-BgpQ-xUo.mjs +1 -0
  28. package/dist/packem_shared/defineShape-Ds8uNqzX.mjs +1 -0
  29. package/dist/packem_shared/defineStorageRule-BDu01PUn.mjs +1 -0
  30. package/dist/packem_shared/functions-CDC08CWY.mjs +1 -0
  31. package/dist/packem_shared/httpAction-C14NuF3V.mjs +4 -0
  32. package/dist/packem_shared/initLunora-CHh1EmR4.mjs +1 -0
  33. package/dist/packem_shared/mask-C6Bi78qj.mjs +1 -0
  34. package/dist/packem_shared/onConnect-CEtRmUpJ.mjs +1 -0
  35. package/dist/packem_shared/policy-tag-Dprt9JWo.mjs +1 -0
  36. package/dist/packem_shared/protectPublic-BhKewPqm.mjs +1 -0
  37. package/dist/packem_shared/rls-_iVsPvhX.mjs +1 -0
  38. package/dist/packem_shared/run-middleware-BeEEqmdE.mjs +1 -0
  39. package/dist/packem_shared/storageRules-BptPZbi8.mjs +1 -0
  40. package/dist/packem_shared/types.d-C4CMJK8x.d.mts +141 -0
  41. package/dist/packem_shared/types.d-DdYF8E18.d.ts +141 -0
  42. package/dist/rls/testing.d.mts +31 -31
  43. package/dist/rls/testing.d.ts +31 -31
  44. package/dist/rls/testing.mjs +1 -49
  45. package/dist/types.d.mts +1163 -440
  46. package/dist/types.d.ts +1163 -440
  47. package/dist/types.mjs +1 -31
  48. package/package.json +18 -5
  49. package/dist/packem_shared/LunoraEnvError-DjFkpkSP.mjs +0 -187
  50. package/dist/packem_shared/LunoraError-DhggBJZF.mjs +0 -51
  51. package/dist/packem_shared/PRESENCE_DEFAULT_TTL_MS-BgBQsqQ-.mjs +0 -114
  52. package/dist/packem_shared/asBucketStorage-Cnxd9y2q.mjs +0 -11
  53. package/dist/packem_shared/bindOrm-Ce57S3N9.mjs +0 -128
  54. package/dist/packem_shared/composePluginMiddleware-Ck5_TUO8.mjs +0 -100
  55. package/dist/packem_shared/createPolicyDsl-De67zPDS.mjs +0 -29
  56. package/dist/packem_shared/defineAggregateIndex-C2gT1GzM.mjs +0 -252
  57. package/dist/packem_shared/defineMigration-CAJLr6fx.mjs +0 -8
  58. package/dist/packem_shared/defineStorageRule-qu0mpilX.mjs +0 -20
  59. package/dist/packem_shared/httpAction-B7FYUEgr.mjs +0 -340
  60. package/dist/packem_shared/initLunora-CATvPsVt.mjs +0 -86
  61. package/dist/packem_shared/mask-eCUYOwhd.mjs +0 -211
  62. package/dist/packem_shared/onConnect-CIPXKPyw.mjs +0 -13
  63. package/dist/packem_shared/protectPublic-BjFkQ_Or.mjs +0 -15
  64. package/dist/packem_shared/rls-Bi9HiyDC.mjs +0 -567
  65. package/dist/packem_shared/run-middleware-CYQOuoV6.mjs +0 -18
  66. package/dist/packem_shared/storageRules-4a30FSpI.mjs +0 -88
  67. package/dist/packem_shared/types.d-BDY0FYHK.d.ts +0 -135
  68. package/dist/packem_shared/types.d-DmvyEMD6.d.mts +0 -135
package/dist/types.mjs CHANGED
@@ -1,31 +1 @@
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 };
1
+ const a=new Map,u=new Proxy({},{get(i,t){const e=a.get(t);if(e)return e;const r=new Map,o=new Proxy({},{get(c,n){const s=r.get(n);if(s)return s;const g={__lunoraRef:`${String(t)}:${String(n)}`};return r.set(n,g),g}});return a.set(t,o),o}});export{u as anyApi};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/server",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.40",
4
4
  "description": "Server primitives for Lunora: defineSchema, defineTable, query, mutation, and action",
5
5
  "keywords": [
6
6
  "backend",
@@ -25,7 +25,7 @@
25
25
  "directory": "packages/server"
26
26
  },
27
27
  "files": [
28
- "dist",
28
+ "./dist",
29
29
  "README.md",
30
30
  "LICENSE.md",
31
31
  "__assets__"
@@ -56,16 +56,29 @@
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
+ },
59
63
  "./package.json": "./package.json"
60
64
  },
61
65
  "publishConfig": {
62
66
  "access": "public"
63
67
  },
64
68
  "dependencies": {
65
- "@lunora/scheduler": "1.0.0-alpha.2",
66
- "@lunora/values": "1.0.0-alpha.2",
69
+ "@lunora/errors": "1.0.0-alpha.8",
70
+ "@lunora/scheduler": "1.0.0-alpha.12",
71
+ "@lunora/values": "1.0.0-alpha.11",
67
72
  "drizzle-orm": "^0.45.2",
68
- "hono": "^4.12.26"
73
+ "hono": "^4.12.32"
74
+ },
75
+ "peerDependencies": {
76
+ "@opentelemetry/api": "^1.9.1"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "@opentelemetry/api": {
80
+ "optional": true
81
+ }
69
82
  },
70
83
  "engines": {
71
84
  "node": "^22.15.0 || >=24.11.0"
@@ -1,187 +0,0 @@
1
- import { optionalInner } from '@lunora/values';
2
-
3
- const SECRET_VALUE_PREFIXES = [
4
- /^sk_/u,
5
- /^pk_/u,
6
- /^rk_/u,
7
- /^ghp_/u,
8
- /^gho_/u,
9
- /^ghs_/u,
10
- /^ghr_/u,
11
- /^github_pat_/u,
12
- /^xox[baprs]-/u,
13
- /^AKIA/u,
14
- /^AIza/u,
15
- /^Bearer\s/u
16
- ];
17
- const HIGH_ENTROPY_TOKEN = /[\w./+-]{24,}/gu;
18
- const STANDALONE_TOKEN = /^[\w./+-]+$/u;
19
- const EMBEDDED_PREFIXED_TOKEN_SHORT = /\b(?:(?:sk|pk|rk|ghp|gho|ghs|ghr)_[\w./+-]*|github_pat_[\w./+-]*|xox[baprs]-[\w./+-]*)/gu;
20
- const EMBEDDED_PREFIXED_TOKEN_LONG = /\b(?:AKIA|AIza)[\w./+-]+|Bearer\s+[\w./+-]+/gu;
21
- const URL_CREDENTIAL = /\b([a-z][\w.+-]*:\/\/[\w.%+-]+):[\w.%+-]+@/gu;
22
- const REDACTED = "[redacted]";
23
- const SECRET_KEY = /(?:KEY|PASSWORD|SECRET|TOKEN)$/u;
24
- const looksLikeSecretValue = (value) => {
25
- if (SECRET_VALUE_PREFIXES.some((prefix) => prefix.test(value))) {
26
- return true;
27
- }
28
- const trimmed = value.trim();
29
- return trimmed.length >= 24 && STANDALONE_TOKEN.test(trimmed);
30
- };
31
- const QUOTED_VALUE = /(["'])(?<inner>(?:\\.|(?!\1).)*)\1/gu;
32
- const KEYED_VALUE = /\b(?<key>[A-Za-z_]\w*)\s*[=:]\s*\S+/gu;
33
- const redactSecrets = (message) => {
34
- let out = message;
35
- out = out.replaceAll(QUOTED_VALUE, (match, ...groups) => {
36
- const named = groups.at(-1);
37
- return named?.inner !== void 0 && looksLikeSecretValue(named.inner) ? REDACTED : match;
38
- });
39
- out = out.replaceAll(URL_CREDENTIAL, (_match, prefix) => `${prefix}:${REDACTED}@`);
40
- out = out.replaceAll(EMBEDDED_PREFIXED_TOKEN_SHORT, REDACTED);
41
- out = out.replaceAll(EMBEDDED_PREFIXED_TOKEN_LONG, REDACTED);
42
- out = out.replaceAll(KEYED_VALUE, (match, ...groups) => {
43
- const named = groups.at(-1);
44
- return named?.key !== void 0 && SECRET_KEY.test(named.key) ? `${named.key}=${REDACTED}` : match;
45
- });
46
- out = out.replaceAll(HIGH_ENTROPY_TOKEN, REDACTED);
47
- return out;
48
- };
49
- const redactValueForKey = (message, key, raw) => {
50
- const masked = redactSecrets(message);
51
- if (typeof raw === "string" && raw !== "" && SECRET_KEY.test(key)) {
52
- return masked.replaceAll(raw, REDACTED);
53
- }
54
- return masked;
55
- };
56
- class LunoraEnvError extends Error {
57
- name = "LunoraEnvError";
58
- failures;
59
- constructor(failures) {
60
- const summary = failures.map((failure) => ` - ${failure.key}: ${failure.message}`).join("\n");
61
- super(`Invalid environment (${String(failures.length)} key(s)):
62
- ${summary}`);
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,51 +0,0 @@
1
- const CODE_STATUS = {
2
- BAD_REQUEST: 400,
3
- CONFLICT: 409,
4
- /**
5
- * `count()` invoked against a table whose context carries an active RLS
6
- * policy. The operation itself is unsupported in an RLS-restricted reader
7
- * (kitcn's documented constraint) — the request is well-formed and the
8
- * caller is authorized, so this is a 422 (semantic conflict) rather than a
9
- * 403 (policy denial).
10
- */
11
- COUNT_RLS_UNSUPPORTED: 422,
12
- FORBIDDEN: 403,
13
- INTERNAL_SERVER_ERROR: 500,
14
- /**
15
- * An analytical reduction (`aggregate` / `groupBy`) was invoked over a
16
- * column that the procedure's `mask()` middleware redacts. A masked column
17
- * can't be summed, averaged, or grouped without leaking the very values the
18
- * mask hides (a group key *is* the raw value; an aggregate is computed from
19
- * it), so the operation fails closed. The request is well-formed and the
20
- * caller is authorized — this is a 422 (semantic conflict), mirroring
21
- * `COUNT_RLS_UNSUPPORTED`.
22
- */
23
- MASK_UNSUPPORTED: 422,
24
- NOT_FOUND: 404,
25
- NOT_IMPLEMENTED: 501,
26
- /**
27
- * A write policy's `when` returned a relation-crossing predicate
28
- * (`some`/`none`/`every`/`is`/`isNot`). The in-memory write-policy evaluator
29
- * has no child fetcher and cannot resolve a relation node, so the policy is
30
- * unsupported as written. Relation predicates are valid in *read* policies
31
- * and query `where` clauses (the pre-resolver handles them there). The
32
- * request is well-formed; this is a 422 (semantic conflict), mirroring the
33
- * sibling `*_UNSUPPORTED` codes.
34
- */
35
- RELATION_PREDICATE_UNSUPPORTED: 422,
36
- TOO_MANY_REQUESTS: 429,
37
- UNAUTHORIZED: 401,
38
- UNPROCESSABLE: 422
39
- };
40
- class LunoraError extends Error {
41
- name = "LunoraError";
42
- code;
43
- status;
44
- constructor(code, message) {
45
- super(message ?? code);
46
- this.code = code;
47
- this.status = CODE_STATUS[code];
48
- }
49
- }
50
-
51
- export { LunoraError };
@@ -1,114 +0,0 @@
1
- import { v } from '@lunora/values';
2
- import { initLunora } from './initLunora-CATvPsVt.mjs';
3
- import { LunoraError } from './LunoraError-DhggBJZF.mjs';
4
- import { onDisconnect } from './onConnect-CIPXKPyw.mjs';
5
- import { defineSchemaExtension, defineComponent } from './composePluginMiddleware-Ck5_TUO8.mjs';
6
- import { defineTable } from './defineAggregateIndex-C2gT1GzM.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,128 +0,0 @@
1
- const isUniqueConflict = (error) => typeof error === "object" && error !== null && error.code === "CONFLICT" && error.kind === "unique";
2
- const buildUpsertWhere = (tableName, target, create) => {
3
- const fields = typeof target === "string" ? [target] : target;
4
- if (fields.length === 0) {
5
- throw new Error(`ctx.db.${tableName}.upsert: "target" must name at least one field`);
6
- }
7
- const where = {};
8
- for (const field of fields) {
9
- if (!(field in create)) {
10
- throw new Error(`ctx.db.${tableName}.upsert: target field "${field}" is missing from the create document`);
11
- }
12
- where[field] = create[field];
13
- }
14
- return where;
15
- };
16
- const bindTableFacade = (writer, tableName) => {
17
- const insert = async (document, options) => {
18
- if (options?.skipDuplicates !== true) {
19
- return writer.insert(tableName, document);
20
- }
21
- try {
22
- return await writer.insert(tableName, document);
23
- } catch (error) {
24
- if (isUniqueConflict(error)) {
25
- return null;
26
- }
27
- throw error;
28
- }
29
- };
30
- const upsert = async ({ create, target, update }) => {
31
- const where = buildUpsertWhere(tableName, target, create);
32
- const existing = await writer.findFirst(tableName, { where });
33
- if (existing && typeof existing["_id"] === "string") {
34
- await writer.patch(existing["_id"], update ?? create, tableName);
35
- return { created: false, id: existing["_id"] };
36
- }
37
- const id = await writer.insert(tableName, create);
38
- return { created: true, id };
39
- };
40
- return {
41
- aggregate: (options) => writer.aggregate(tableName, options),
42
- count: (where) => writer.count(tableName, where),
43
- delete: (id) => writer.delete(id, tableName),
44
- // `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`);
52
- }
53
- return writer.deleteMany(ids, options, tableName);
54
- },
55
- // `exists` reuses `findFirst` (RLS-filtered, indexed when a `.withIndex`-able
56
- // `where` is supplied) and only asks whether a row came back — no count scan.
57
- exists: async (where) => await writer.findFirst(tableName, where === void 0 ? void 0 : { where }) !== null,
58
- findFirst: (args) => writer.findFirst(tableName, args),
59
- findFirstOrThrow: (args) => writer.findFirstOrThrow(tableName, args),
60
- findMany: (args) => writer.findMany(tableName, args),
61
- get: (id) => writer.get(id, tableName),
62
- groupBy: (options) => writer.groupBy(tableName, options),
63
- // Physical removal — bypasses `.softDelete()`. RLS gates it as a delete.
64
- hardDelete: (id) => writer.delete(id, tableName, { hard: true }),
65
- insert,
66
- insertMany: (documents, options) => {
67
- if (writer.insertMany === void 0) {
68
- throw new Error(`ctx.db.${tableName}.insertMany is unavailable: this writer has no batch insert`);
69
- }
70
- return writer.insertMany(tableName, documents, options);
71
- },
72
- 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`);
76
- }
77
- return writer.patchMany(
78
- patches.map((entry) => {
79
- return { id: entry.id, patch: entry.values };
80
- }),
81
- options,
82
- tableName
83
- );
84
- },
85
- rank: (indexName, options) => writer.rank(tableName, indexName, options),
86
- rankPage: (indexName, options) => writer.rankPage(tableName, indexName, options),
87
- replace: (id, document) => writer.replace(id, document, tableName),
88
- restore: async (id) => {
89
- if (!writer.restore) {
90
- throw new Error(`ctx.db.${tableName}.restore is unavailable: this writer has no restore (is the table .softDelete()?)`);
91
- }
92
- await writer.restore(id, tableName);
93
- },
94
- upsert,
95
- upsertMany: async ({ rows, target }) => {
96
- const results = [];
97
- for (const row of rows) {
98
- results.push(await upsert({ create: row.create, target, update: row.update }));
99
- }
100
- return results;
101
- },
102
- withSearchIndex: (indexName, search) => writer.query(tableName).withSearchIndex(indexName, search)
103
- };
104
- };
105
- const bindOrm = (facade) => {
106
- const resolve = (table) => {
107
- const bound = facade[table];
108
- if (!bound) {
109
- throw new Error(`unknown table: ${table}`);
110
- }
111
- return bound;
112
- };
113
- return {
114
- delete: (table, id) => resolve(table).delete(id),
115
- insert: (table) => {
116
- return { values: (document) => resolve(table).insert(document) };
117
- },
118
- query: facade,
119
- replace: (table, id) => {
120
- return { with: (document) => resolve(table).replace(id, document) };
121
- },
122
- update: (table, id) => {
123
- return { set: (values) => resolve(table).patch(id, values) };
124
- }
125
- };
126
- };
127
-
128
- export { bindOrm, bindTableFacade };
@@ -1,100 +0,0 @@
1
- import { r as runMiddlewareChain } from './run-middleware-CYQOuoV6.mjs';
2
-
3
- const prefixTableName = (key, bareName) => `${key}_${bareName}`;
4
- const rewriteReference = (target, key, bareNames) => bareNames.has(target) ? prefixTableName(key, target) : target;
5
- const rewriteTableReferences = (table, key, bareNames, ownBareName) => {
6
- const ownPrefixed = prefixTableName(key, ownBareName);
7
- const rewriteOn = (on) => on === "" ? ownPrefixed : rewriteReference(on, key, bareNames);
8
- const relationMap = {};
9
- for (const [accessor, relation] of Object.entries(table.relationMap)) {
10
- relationMap[accessor] = { ...relation, table: rewriteReference(relation.table, key, bareNames) };
11
- }
12
- const aggregateIndexes = table.aggregateIndexes.map((index) => {
13
- return { ...index, on: rewriteOn(index.on) };
14
- });
15
- const rankIndexes = table.rankIndexes.map((index) => {
16
- return { ...index, on: rewriteOn(index.on) };
17
- });
18
- return { ...table, aggregateIndexes, rankIndexes, relationMap };
19
- };
20
- const defineSchemaExtension = (key, options) => {
21
- if (!key) {
22
- throw new Error("defineSchemaExtension: `key` is required and must be a non-empty string");
23
- }
24
- return {
25
- key,
26
- tables: options.tables,
27
- ...options.vectorIndexes ? { vectorIndexes: options.vectorIndexes } : {}
28
- };
29
- };
30
- const definePlugin = (key, options) => {
31
- if (!key) {
32
- throw new Error("definePlugin: `key` is required and must be a non-empty string");
33
- }
34
- if (options.extension && options.extension.key !== key) {
35
- throw new Error(`definePlugin("${key}"): extension key "${options.extension.key}" does not match plugin key`);
36
- }
37
- return {
38
- key,
39
- ...options.extension ? { extension: options.extension } : {},
40
- ...options.middleware ? { middleware: options.middleware } : {}
41
- };
42
- };
43
- const defineComponent = (key, options) => {
44
- const plugin = definePlugin(key, {
45
- ...options.extension ? { extension: options.extension } : {},
46
- ...options.middleware ? { middleware: options.middleware } : {}
47
- });
48
- return {
49
- ...plugin,
50
- functions: options.functions ?? {}
51
- };
52
- };
53
- const mergeSchemaExtension = (base, extension) => {
54
- const { key } = extension;
55
- const bareNames = new Set(Object.keys(extension.tables));
56
- const merged = { ...base.tables };
57
- for (const [bareName, table] of Object.entries(extension.tables)) {
58
- const prefixed = prefixTableName(key, bareName);
59
- if (Object.hasOwn(merged, prefixed)) {
60
- throw new Error(
61
- `defineSchema(...).extend("${key}"): table "${prefixed}" already exists in the base schema — another extension with the same key already contributed it`
62
- );
63
- }
64
- merged[prefixed] = rewriteTableReferences(table, key, bareNames, bareName);
65
- }
66
- const mergedVectorIndexes = { ...base.vectorIndexes };
67
- if (extension.vectorIndexes) {
68
- for (const [bareIndexName, index] of Object.entries(extension.vectorIndexes)) {
69
- const prefixed = prefixTableName(key, bareIndexName);
70
- if (Object.hasOwn(mergedVectorIndexes, prefixed)) {
71
- throw new Error(
72
- `defineSchema(...).extend("${key}"): vector index "${prefixed}" already exists in the base schema — another extension with the same key already contributed it`
73
- );
74
- }
75
- mergedVectorIndexes[prefixed] = { ...index, table: rewriteReference(index.table, key, bareNames) };
76
- }
77
- }
78
- return {
79
- // Preserve secure-by-default RLS across `.extend(...)` — a plugin's
80
- // tables join an `.rls("required")` schema as protected-by-default too.
81
- rlsMode: base.rlsMode,
82
- tables: merged,
83
- vectorIndexes: mergedVectorIndexes
84
- };
85
- };
86
- const installPlugins = (base, plugins) => {
87
- let schema = base;
88
- for (const plugin of plugins) {
89
- if (plugin.extension) {
90
- schema = mergeSchemaExtension(schema, plugin.extension);
91
- }
92
- }
93
- return schema;
94
- };
95
- const composePluginMiddleware = (plugins) => {
96
- const middlewares = plugins.map((plugin) => plugin.middleware).filter((middleware) => middleware !== void 0);
97
- return (async ({ ctx, next }) => runMiddlewareChain(middlewares, ctx, (context) => next({ ctx: context })));
98
- };
99
-
100
- export { composePluginMiddleware, defineComponent, definePlugin, defineSchemaExtension, installPlugins, mergeSchemaExtension };