@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,355 +0,0 @@
1
- import { toErrorBody } from '@lunora/errors';
2
- import { parseValidatorMap, ValidationError } from '@lunora/values';
3
- import { Hono } from 'hono';
4
- import { LunoraError } from './LunoraError-WbxmrpxR.mjs';
5
-
6
- const httpAction = (handler) => async (c) => handler(c.get("lunora"), c.req.raw);
7
- const httpRouter = () => {
8
- const app = new Hono();
9
- app.use("*", async (c, next) => {
10
- const injected = c.env.__lunoraCtx;
11
- if (!injected) {
12
- throw new LunoraError(
13
- "INTERNAL_SERVER_ERROR",
14
- "HttpActionCtx was not injected — mount httpRouter() on createWorker(), which supplies it per request."
15
- );
16
- }
17
- c.set("lunora", injected);
18
- await next();
19
- });
20
- return app;
21
- };
22
- const unwrapOptional = (validator) => validator.kind === "optional" ? validator._meta?.inner ?? validator : validator;
23
- const coerceScalar = (kind, raw) => {
24
- switch (kind) {
25
- case "bigint": {
26
- try {
27
- return BigInt(raw);
28
- } catch {
29
- return raw;
30
- }
31
- }
32
- case "boolean": {
33
- if (raw === "true" || raw === "1") {
34
- return true;
35
- }
36
- if (raw === "false" || raw === "0") {
37
- return false;
38
- }
39
- return raw;
40
- }
41
- case "number": {
42
- return raw === "" ? Number.NaN : Number(raw);
43
- }
44
- default: {
45
- return raw;
46
- }
47
- }
48
- };
49
- const coerceSearchParameter = (validator, c, key) => {
50
- const effective = unwrapOptional(validator);
51
- if (effective.kind === "array") {
52
- const values = c.req.queries(key);
53
- if (values === void 0) {
54
- return void 0;
55
- }
56
- const element = effective._meta?.inner;
57
- return values.map((raw2) => coerceScalar(element?.kind ?? "string", raw2));
58
- }
59
- const raw = c.req.query(key);
60
- return raw === void 0 ? void 0 : coerceScalar(effective.kind, raw);
61
- };
62
- const parseSearchParams = (validators, c) => {
63
- const raw = {};
64
- for (const key of Object.keys(validators)) {
65
- const validator = validators[key];
66
- if (!validator) {
67
- continue;
68
- }
69
- raw[key] = coerceSearchParameter(validator, c, key);
70
- }
71
- return parseValidatorMap(validators, raw, "searchParams");
72
- };
73
- const parseParams = (validators, c) => {
74
- const provided = c.req.param();
75
- const raw = {};
76
- for (const key of Object.keys(validators)) {
77
- const validator = validators[key];
78
- if (!validator) {
79
- continue;
80
- }
81
- const value = provided[key];
82
- raw[key] = value === void 0 ? void 0 : coerceScalar(unwrapOptional(validator).kind, value);
83
- }
84
- return parseValidatorMap(validators, raw, "params");
85
- };
86
- const parseBody = async (validators, c) => {
87
- let json;
88
- try {
89
- json = await c.req.json();
90
- } catch {
91
- throw new LunoraError("BAD_REQUEST", "Invalid JSON body");
92
- }
93
- if (typeof json !== "object" || json === null || Array.isArray(json)) {
94
- throw new LunoraError("BAD_REQUEST", "Expected a JSON object body");
95
- }
96
- return parseValidatorMap(validators, json, "body");
97
- };
98
- const applyOutput = (output, result) => {
99
- try {
100
- return output.parse(result);
101
- } catch (error) {
102
- if (error instanceof ValidationError) {
103
- throw new LunoraError("INTERNAL_SERVER_ERROR", `Response did not match the declared output schema: ${error.message}`);
104
- }
105
- throw error;
106
- }
107
- };
108
- const errorResponse = (error) => {
109
- if (error instanceof ValidationError) {
110
- return Response.json({ code: "BAD_REQUEST", error: error.message }, { status: 400 });
111
- }
112
- if (error instanceof LunoraError) {
113
- const { body, redacted, status } = toErrorBody(error, { fallbackCode: "INTERNAL_SERVER_ERROR", redactedMessage: "Internal error" });
114
- if (redacted) {
115
- console.error("[lunora] http action error (redacted on the wire):", error);
116
- }
117
- return Response.json({ code: body.code, error: body.message }, { status });
118
- }
119
- throw error;
120
- };
121
- const buildRouteHandler = (state, userHandler) => async (c) => {
122
- try {
123
- const context = c.get("lunora");
124
- const searchParams = Object.keys(state.searchParams).length > 0 ? parseSearchParams(state.searchParams, c) : {};
125
- const params = Object.keys(state.params).length > 0 ? parseParams(state.params, c) : {};
126
- const body = Object.keys(state.body).length > 0 ? await parseBody(state.body, c) : {};
127
- const result = await userHandler({ body, ctx: context, params, searchParams });
128
- const payload = state.output ? applyOutput(state.output, result) : result;
129
- const headers = {};
130
- if (state.cacheControl) {
131
- headers["cache-control"] = state.cacheControl;
132
- }
133
- if (state.cacheTag) {
134
- headers["cache-tag"] = state.cacheTag;
135
- }
136
- if (state.vary) {
137
- headers.vary = state.vary;
138
- }
139
- const hasCacheHeaders = Object.keys(headers).length > 0;
140
- if (payload === void 0) {
141
- return new Response(null, { headers: hasCacheHeaders ? headers : void 0, status: 204 });
142
- }
143
- return Response.json(payload, { headers: hasCacheHeaders ? headers : void 0 });
144
- } catch (error) {
145
- return errorResponse(error);
146
- }
147
- };
148
- const sseFrame = (chunk, event) => {
149
- const data = JSON.stringify(chunk);
150
- const prefix = event ? `event: ${event}
151
- ` : "";
152
- return `${prefix}data: ${data}
153
-
154
- `;
155
- };
156
- const buildStreamHandler = (state, userHandler) => (
157
- // eslint-disable-next-line @typescript-eslint/require-await -- LunoraRouteHandler is contractually `(c) => Promise<Response>`; this handler returns synchronously (all awaits live inside the ReadableStream pump), so `async` is required by the type, not the body.
158
- async (c) => {
159
- let searchParams;
160
- let params;
161
- try {
162
- searchParams = Object.keys(state.searchParams).length > 0 ? parseSearchParams(state.searchParams, c) : {};
163
- params = Object.keys(state.params).length > 0 ? parseParams(state.params, c) : {};
164
- } catch (error) {
165
- return errorResponse(error);
166
- }
167
- const context = c.get("lunora");
168
- const request = c.req.raw;
169
- const encoder = new TextEncoder();
170
- const ac = new AbortController();
171
- if (request.signal.aborted) {
172
- ac.abort();
173
- return new Response(
174
- new ReadableStream({
175
- start: (controller) => {
176
- controller.close();
177
- }
178
- }),
179
- {
180
- headers: {
181
- "cache-control": "no-cache, no-transform",
182
- "content-type": "text/event-stream; charset=utf-8",
183
- "x-accel-buffering": "no"
184
- }
185
- }
186
- );
187
- }
188
- const onAbort = () => {
189
- ac.abort();
190
- };
191
- request.signal.addEventListener("abort", onAbort, { once: true });
192
- const stream = new ReadableStream({
193
- cancel() {
194
- request.signal.removeEventListener("abort", onAbort);
195
- ac.abort();
196
- },
197
- async start(controller) {
198
- try {
199
- const iterator = userHandler({ ctx: context, params, request, searchParams, signal: ac.signal });
200
- for await (const chunk of iterator) {
201
- if (ac.signal.aborted) {
202
- break;
203
- }
204
- controller.enqueue(encoder.encode(sseFrame(chunk)));
205
- }
206
- controller.enqueue(encoder.encode(sseFrame({}, "complete")));
207
- } catch (error) {
208
- const { body, redacted } = toErrorBody(error, { fallbackCode: "INTERNAL_SERVER_ERROR", redactedMessage: "Internal error" });
209
- if (redacted) {
210
- console.error("[lunora] unhandled stream handler error:", error);
211
- }
212
- controller.enqueue(encoder.encode(sseFrame({ code: body.code, message: body.message }, "error")));
213
- } finally {
214
- request.signal.removeEventListener("abort", onAbort);
215
- controller.close();
216
- }
217
- }
218
- });
219
- const headers = {
220
- // SSE responses must stay uncacheable so proxies don't buffer or
221
- // coalesce live frames. `cacheControl()` is intentionally ignored
222
- // for stream() routes; `cacheTag`/`vary` are also omitted because
223
- // they only make sense alongside a cacheable response.
224
- "cache-control": "no-cache, no-transform",
225
- "content-type": "text/event-stream; charset=utf-8",
226
- // Hint to proxies (including Cloudflare's own buffering layer)
227
- // that this response must not be coalesced.
228
- "x-accel-buffering": "no"
229
- };
230
- return new Response(stream, { headers });
231
- }
232
- );
233
- const makeRouteBuilder = (state) => {
234
- return {
235
- body: (validators) => makeRouteBuilder({ ...state, body: { ...state.body, ...validators } }),
236
- cacheControl: (value) => makeRouteBuilder({ ...state, cacheControl: value }),
237
- cacheTag: (value) => makeRouteBuilder({ ...state, cacheTag: value }),
238
- handler: (userHandler) => buildRouteHandler(state, userHandler),
239
- output: (validator) => makeRouteBuilder({ ...state, output: validator }),
240
- params: (validators) => makeRouteBuilder({ ...state, params: { ...state.params, ...validators } }),
241
- searchParams: (validators) => makeRouteBuilder({ ...state, searchParams: { ...state.searchParams, ...validators } }),
242
- stream: (userHandler) => buildStreamHandler(state, userHandler),
243
- vary: (value) => makeRouteBuilder({ ...state, vary: value })
244
- };
245
- };
246
- const makeRouteFactory = (method) => (path) => makeRouteBuilder({ body: {}, method, params: {}, path, searchParams: {} });
247
- const httpRoute = {
248
- delete: makeRouteFactory("DELETE"),
249
- get: makeRouteFactory("GET"),
250
- head: makeRouteFactory("HEAD"),
251
- options: makeRouteFactory("OPTIONS"),
252
- patch: makeRouteFactory("PATCH"),
253
- post: makeRouteFactory("POST"),
254
- put: makeRouteFactory("PUT")
255
- };
256
- const SINGLE_BYTE_RANGE_RE = /^bytes=(\d*)-(\d*)$/;
257
- const toHttpEtag = (etag) => {
258
- if (etag.startsWith('"') || etag.startsWith('W/"')) {
259
- return etag;
260
- }
261
- return `"${etag}"`;
262
- };
263
- const isSafeHeaderValue = (value) => {
264
- for (let index = 0; index < value.length; index += 1) {
265
- const code = value.codePointAt(index);
266
- if (code === 13 || code === 10 || code === 0) {
267
- return false;
268
- }
269
- }
270
- return true;
271
- };
272
- const parseRange = (header, size) => {
273
- if (header === null) {
274
- return { kind: "full" };
275
- }
276
- const match = SINGLE_BYTE_RANGE_RE.exec(header.trim());
277
- if (!match) {
278
- return { kind: "full" };
279
- }
280
- const startRaw = match[1] ?? "";
281
- const endRaw = match[2] ?? "";
282
- if (startRaw === "" && endRaw === "") {
283
- return { kind: "full" };
284
- }
285
- let start;
286
- let end;
287
- if (startRaw === "") {
288
- const suffix = Number(endRaw);
289
- if (suffix === 0) {
290
- return { kind: "unsatisfiable" };
291
- }
292
- start = Math.max(0, size - suffix);
293
- end = size - 1;
294
- } else {
295
- start = Number(startRaw);
296
- end = endRaw === "" ? size - 1 : Math.min(Number(endRaw), size - 1);
297
- }
298
- if (start > end || start >= size) {
299
- return { kind: "unsatisfiable" };
300
- }
301
- return { end, kind: "partial", start };
302
- };
303
- const serveStorageObject = async (context, key, request) => {
304
- const object = await context.storage.download(key);
305
- if (!object) {
306
- return new Response("Not Found", { status: 404 });
307
- }
308
- const rawContentType = object.httpMetadata?.contentType;
309
- const contentType = rawContentType !== void 0 && isSafeHeaderValue(rawContentType) ? rawContentType : "application/octet-stream";
310
- const baseHeaders = {
311
- "accept-ranges": "bytes",
312
- "content-type": contentType,
313
- etag: toHttpEtag(object.etag)
314
- };
315
- if (object.sha256Base64 !== void 0) {
316
- baseHeaders["repr-digest"] = `sha-256=:${object.sha256Base64}:`;
317
- }
318
- const range = parseRange(request.headers.get("range"), object.size);
319
- if (range.kind === "unsatisfiable") {
320
- object.body?.cancel().catch(() => {
321
- });
322
- return new Response("Range Not Satisfiable", {
323
- headers: {
324
- "accept-ranges": "bytes",
325
- "content-range": `bytes */${String(object.size)}`,
326
- "content-type": "text/plain; charset=utf-8",
327
- etag: toHttpEtag(object.etag)
328
- },
329
- status: 416
330
- });
331
- }
332
- if (range.kind === "full") {
333
- return new Response(object.body, {
334
- headers: { ...baseHeaders, "content-length": String(object.size) },
335
- status: 200
336
- });
337
- }
338
- object.body?.cancel().catch(() => {
339
- });
340
- const length = range.end - range.start + 1;
341
- const slice = await context.storage.download(key, { range: { length, offset: range.start } });
342
- if (!slice) {
343
- return new Response("Not Found", { status: 404 });
344
- }
345
- return new Response(slice.body, {
346
- headers: {
347
- ...baseHeaders,
348
- "content-length": String(length),
349
- "content-range": `bytes ${String(range.start)}-${String(range.end)}/${String(object.size)}`
350
- },
351
- status: 206
352
- });
353
- };
354
-
355
- export { httpAction, httpRoute, httpRouter, isSafeHeaderValue, serveStorageObject };
@@ -1,115 +0,0 @@
1
- import { v as validateArgs } from './functions-Di9FUNkf.mjs';
2
- import { r as readRlsTag } from './policy-tag-DvpVH2tv.mjs';
3
- import { r as runMiddlewareChain } from './run-middleware-I6EiQfxL.mjs';
4
-
5
- const runMiddleware = (middlewares, baseContext) => runMiddlewareChain(middlewares, baseContext, (context) => context);
6
- const makeHandler = (args, middlewares, userHandler, output) => async (context, rawArgs) => {
7
- const parsed = validateArgs(args, rawArgs);
8
- const resolvedContext = await runMiddleware(middlewares, context);
9
- const result = await userHandler({ args: parsed, ctx: resolvedContext });
10
- return output ? output.parse(result) : result;
11
- };
12
- const makeStreamHandler = (args, middlewares, userHandler) => (context, rawArgs, signal) => {
13
- const parsed = validateArgs(args, rawArgs);
14
- return (async function* drive() {
15
- const resolvedContext = await runMiddleware(middlewares, context);
16
- const source = userHandler({ args: parsed, ctx: resolvedContext, signal });
17
- const iterator = source[Symbol.asyncIterator]();
18
- try {
19
- while (true) {
20
- if (signal.aborted) {
21
- return;
22
- }
23
- const next = await iterator.next();
24
- if (next.done) {
25
- return;
26
- }
27
- if (signal.aborted) {
28
- return;
29
- }
30
- yield next.value;
31
- }
32
- } finally {
33
- await iterator.return?.();
34
- }
35
- })();
36
- };
37
- const collectRls = (middlewares) => {
38
- const tags = [];
39
- for (const middleware of middlewares) {
40
- const tag = readRlsTag(middleware);
41
- if (!tag) {
42
- continue;
43
- }
44
- tags.push(tag);
45
- }
46
- return tags.length > 0 ? { tags } : void 0;
47
- };
48
- const makeBuilder = (kind, state, visibility) => {
49
- return {
50
- __lunoraProcedure: kind,
51
- ...visibility ? { __lunoraVisibility: visibility } : {},
52
- input: (validators) => makeBuilder(kind, { ...state, args: { ...state.args, ...validators } }, visibility),
53
- [kind]: (userHandler) => {
54
- const rls = collectRls(state.middlewares);
55
- return {
56
- args: state.args,
57
- ...state.expose ? { expose: state.expose } : {},
58
- handler: makeHandler(state.args, state.middlewares, userHandler, state.output),
59
- kind,
60
- ...rls ? { rls } : {},
61
- ...visibility ? { visibility } : {},
62
- ...state.x402 ? { x402: state.x402 } : {}
63
- };
64
- },
65
- output: (validator) => makeBuilder(kind, { ...state, output: validator }, visibility),
66
- // `.stream()` is meaningful only on query builders. It's harmless to expose
67
- // on every builder shape (callers can't hit it from action/mutation builders
68
- // anyway since the type system narrows it away), but emitting it
69
- // unconditionally keeps the runtime free of per-kind branching.
70
- ...kind === "query" ? {
71
- stream: (userHandler) => {
72
- const rls = collectRls(state.middlewares);
73
- return {
74
- args: state.args,
75
- ...state.expose ? { expose: state.expose } : {},
76
- handler: makeStreamHandler(state.args, state.middlewares, userHandler),
77
- kind: "stream",
78
- ...rls ? { rls } : {},
79
- ...visibility ? { visibility } : {},
80
- ...state.x402 ? { x402: state.x402 } : {}
81
- };
82
- }
83
- } : {},
84
- use: (middleware) => makeBuilder(kind, { ...state, middlewares: [...state.middlewares, middleware] }, visibility),
85
- // `.expose({ rest: true })` publishes a PUBLIC procedure on the REST surface
86
- // (plan 167). Public-only for the same reason as `.x402`: an internal
87
- // function is server-to-server and never reachable over HTTP, so there is
88
- // nothing to expose. Default-closed — omitting the modifier keeps the
89
- // procedure RPC-only.
90
- ...visibility ? {} : { expose: (config) => makeBuilder(kind, { ...state, expose: config }, visibility) },
91
- // `.x402({ price })` marks a public procedure as paid. It's public-only:
92
- // internal functions are server-to-server (cron/scheduler/`ctx.run*`) and
93
- // never reachable via a client RPC, so there's nothing to charge. Omitting
94
- // it on internal builders keeps the runtime shape aligned with the types.
95
- ...visibility ? {} : { x402: (config) => makeBuilder(kind, { ...state, x402: config }, visibility) }
96
- };
97
- };
98
- const initLunora = {
99
- dataModel: () => {
100
- return {
101
- create: (_options) => {
102
- return {
103
- action: makeBuilder("action", { args: {}, middlewares: [] }),
104
- internalAction: makeBuilder("action", { args: {}, middlewares: [] }, "internal"),
105
- internalMutation: makeBuilder("mutation", { args: {}, middlewares: [] }, "internal"),
106
- internalQuery: makeBuilder("query", { args: {}, middlewares: [] }, "internal"),
107
- mutation: makeBuilder("mutation", { args: {}, middlewares: [] }),
108
- query: makeBuilder("query", { args: {}, middlewares: [] })
109
- };
110
- }
111
- };
112
- }
113
- };
114
-
115
- export { initLunora };