@loopbacktools/server 0.0.0-stage → 0.4.0

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -2
  3. package/dist/package.json +57 -0
  4. package/dist/src/domain/ask.d.ts +78 -0
  5. package/dist/src/domain/ask.js +161 -0
  6. package/dist/src/domain/errors.d.ts +70 -0
  7. package/dist/src/domain/errors.js +57 -0
  8. package/dist/src/generated/app.gen.d.ts +2311 -0
  9. package/dist/src/generated/app.gen.js +400 -0
  10. package/dist/src/generated/runtime.gen.d.ts +357 -0
  11. package/dist/src/generated/runtime.gen.js +298 -0
  12. package/dist/src/generated/schemas.gen.d.ts +6416 -0
  13. package/dist/src/generated/schemas.gen.js +306 -0
  14. package/dist/src/generated/wire-contract.gen.d.ts +77 -0
  15. package/dist/src/generated/wire-contract.gen.js +3 -0
  16. package/dist/src/index.d.ts +11 -0
  17. package/dist/src/index.js +8 -0
  18. package/dist/src/loopback.d.ts +27 -0
  19. package/dist/src/loopback.js +16 -0
  20. package/dist/src/space/arguments.d.ts +8 -0
  21. package/dist/src/space/arguments.js +52 -0
  22. package/dist/src/space/migrations.d.ts +10 -0
  23. package/dist/src/space/migrations.js +106 -0
  24. package/dist/src/space/object.d.ts +30 -0
  25. package/dist/src/space/object.js +169 -0
  26. package/dist/src/space/operations.d.ts +149 -0
  27. package/dist/src/space/operations.js +568 -0
  28. package/dist/src/space/read.d.ts +8 -0
  29. package/dist/src/space/read.js +33 -0
  30. package/dist/src/space/rpc.d.ts +61 -0
  31. package/dist/src/space/rpc.js +96 -0
  32. package/dist/src/space/services.d.ts +41 -0
  33. package/dist/src/space/services.js +58 -0
  34. package/dist/src/space/store.d.ts +87 -0
  35. package/dist/src/space/store.js +276 -0
  36. package/dist/src/version.d.ts +2 -0
  37. package/dist/src/version.js +3 -0
  38. package/dist/src/worker/env.d.ts +19 -0
  39. package/dist/src/worker/env.js +3 -0
  40. package/dist/src/worker/files.d.ts +21 -0
  41. package/dist/src/worker/files.js +42 -0
  42. package/dist/src/worker/gate.d.ts +55 -0
  43. package/dist/src/worker/gate.js +83 -0
  44. package/dist/src/worker/http.d.ts +2092 -0
  45. package/dist/src/worker/http.js +291 -0
  46. package/dist/src/worker/jwt.d.ts +49 -0
  47. package/dist/src/worker/jwt.js +142 -0
  48. package/dist/src/worker/mcp.d.ts +15 -0
  49. package/dist/src/worker/mcp.js +401 -0
  50. package/dist/src/worker/run.d.ts +45 -0
  51. package/dist/src/worker/run.js +71 -0
  52. package/dist/src/worker/spaces.d.ts +24 -0
  53. package/dist/src/worker/spaces.js +64 -0
  54. package/package.json +51 -3
@@ -0,0 +1,400 @@
1
+ // GENERATED by typespec-hono from the TypeSpec service definition. DO NOT EDIT.
2
+ // Regenerate with: pnpm generate
3
+ import { zValidator } from "@hono/zod-validator";
4
+ import { Hono } from "hono";
5
+ import { parse as parseJson } from "secure-json-parse";
6
+ import { z } from "zod";
7
+ import { UndeclaredStatusError, headersOf, servedBody, servedBytes } from "./runtime.gen.js";
8
+ import { Asks_listQuery, Asks_readPath, Asks_replyPath, Files_readPath, Uploads_storeHeader, Uploads_storePath, askPageSchema, askSchema, eventTicketSchema, problemSchema, replySchema, snapshotSchema, uploadReceiptSchema, whoamiSchema, } from "./schemas.gen.js";
9
+ /**
10
+ * Parse, and treat input nested deeper than this runtime's stack as a refusal rather than a crash.
11
+ *
12
+ * **`safeParse` not throwing is the contract everything here rests on, and a deep enough value
13
+ * breaks it.** Zod recurses once per level, so a recursive model meets a `RangeError` that is not a
14
+ * validation failure and escapes to `app.onError` indistinguishable from a bug in the application's
15
+ * own code. Measured on `workerd`: a 40.5 KB body, about 1,925 levels, which Ajv on the published
16
+ * document calls VALID.
17
+ *
18
+ * It cannot be served, so it is refused - through `deps.invalid` like every other bad request, with
19
+ * an issue that says which limit was met rather than a generic failure. Anything that is not a
20
+ * `RangeError` is rethrown: a bug here must still look like one.
21
+ */
22
+ function parsed(schema, value) {
23
+ try {
24
+ return schema.safeParse(value);
25
+ }
26
+ catch (error) {
27
+ if (!(error instanceof RangeError))
28
+ throw error;
29
+ return {
30
+ success: false,
31
+ error: new z.ZodError([
32
+ {
33
+ code: "custom",
34
+ path: [],
35
+ message: "input is nested too deeply for this runtime to validate",
36
+ },
37
+ ]),
38
+ };
39
+ }
40
+ }
41
+ /** Parse synchronously: nothing emitted here is async, and the async path costs 3.4x. */
42
+ const SYNC = { validationFunction: (schema, value) => parsed(schema, value) };
43
+ /**
44
+ * A body that is not what its content type claims.
45
+ *
46
+ * **BOTH readers can fail.** Hono's own validator catches in both branches and throws
47
+ * `HTTPException` - which is what escapes the app's envelope. Catching here is what lets the failure
48
+ * be reported like any other. Measured: an unreadable multipart body answered 500 while the JSON case
49
+ * answered 400, from the same missing `try`.
50
+ */
51
+ const UNREADABLE = Symbol("a body that is not what its content type claims");
52
+ /**
53
+ * Reads a body in the form its target names.
54
+ *
55
+ * **JSON naming the object prototype is unreadable.** Zod strips a `__proto__` key unread
56
+ * (colinhacks/zod#6386), so `Record<boolean>` accepted `{"__proto__": 1}`, which the document
57
+ * refuses. Fastify and hapi refuse such a body by default, at the parser, through
58
+ * `secure-json-parse` - a `__proto__` key, or `constructor.prototype`, anywhere in it - and so
59
+ * does this reader. A body the document would accept is refused with it, as theirs is.
60
+ */
61
+ async function readBody(c, target, parts = []) {
62
+ try {
63
+ if (target === "form")
64
+ return await readForm(c, parts);
65
+ // A text body IS the text: it is validated as the string the caller sent, not parsed first.
66
+ if (target === "text")
67
+ return await c.req.text();
68
+ return parseJson(await c.req.text());
69
+ }
70
+ catch {
71
+ return UNREADABLE;
72
+ }
73
+ }
74
+ /**
75
+ * **A form, each part the document reads from text handed over as its text.** A client may send such
76
+ * a part with a filename - `curl -F 'r=@data.json;type=application/json'` - and `parseBody` then
77
+ * hands it over as a file, which the part's schema, reading text, refused: the part's media type says
78
+ * what it is, not the filename. A file part and raw bytes are left as they arrive.
79
+ *
80
+ * **A JSON part naming the object prototype is unreadable**, as a JSON body is - see `readBody`. Its
81
+ * JSON is parsed by the part's schema, which `secure-json-parse` never saw, so `{"__proto__": 1}`
82
+ * reached a `Record<boolean>` part and was accepted, the key dropped. A part that may be text or
83
+ * JSON is read as either by its schema, and its text stays text.
84
+ */
85
+ async function readForm(c, parts) {
86
+ const form = await c.req.parseBody({ all: true });
87
+ for (const [name, json] of parts) {
88
+ const value = form[name];
89
+ if (value === undefined)
90
+ continue;
91
+ const texts = await Promise.all((Array.isArray(value) ? value : [value]).map((each) => typeof each === "string" ? each : each.text()));
92
+ if (json && texts.some(namesPrototype))
93
+ return UNREADABLE;
94
+ form[name] = Array.isArray(value) ? texts : (texts[0] ?? "");
95
+ }
96
+ return form;
97
+ }
98
+ /** JSON that `secure-json-parse` refuses where `JSON.parse` does not: the prototype named. */
99
+ function namesPrototype(text) {
100
+ try {
101
+ JSON.parse(text);
102
+ }
103
+ catch {
104
+ // No JSON at all, which the part's schema refuses as such.
105
+ return false;
106
+ }
107
+ try {
108
+ parseJson(text);
109
+ return false;
110
+ }
111
+ catch {
112
+ return true;
113
+ }
114
+ }
115
+ /**
116
+ * The failure an unreadable body produces, in the shape every other rejection has.
117
+ *
118
+ * Parsing `undefined` against the body's own schema is what makes a real Zod failure, so
119
+ * `deps.invalid` receives what it receives for a failed parse rather than a special case it has to
120
+ * know about. A schema that somehow accepts `undefined` still fails: a body that could not be read
121
+ * is not a body that validated.
122
+ */
123
+ function unreadableResult(schema) {
124
+ const result = parsed(schema, undefined);
125
+ return result.success ? { success: false } : result;
126
+ }
127
+ /**
128
+ * Which reader parses the media type the request actually carries.
129
+ *
130
+ * A route may declare several request media types needing different parsers, and which one applies is
131
+ * decided by the caller's `Content-Type` when the request arrives - not when this file was
132
+ * generated. Parameters (`; charset=utf-8`, `; boundary=...`) are not part of the match, which
133
+ * matters because a multipart request always carries a boundary.
134
+ *
135
+ * **A `Content-Type` matching nothing declared is REFUSED, and used to be parsed anyway.** It fell
136
+ * through to the first branch, and the comment here claimed the body would then "simply fail to
137
+ * parse" - which is not what happened: `c.req.json()` reads the bytes whatever the header says, so a
138
+ * route publishing `application/json` alone answered 200 to the same body sent as `text/plain`, as
139
+ * `multipart/form-data`, and with no `Content-Type` at all. The document says which media types an
140
+ * operation accepts, and a server that accepts others is not serving that document.
141
+ *
142
+ * `undefined` means "nothing declared covers this", and the caller refuses it through
143
+ * `deps.invalid` like any other bad request. No status is invented that the document does not
144
+ * describe, which is also what `@typespec/http-server-js` does with the identical case.
145
+ */
146
+ function bodyBranch(c, branches) {
147
+ const declared = (c.req.header("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
148
+ if (declared === "")
149
+ return undefined;
150
+ /**
151
+ * A declared media RANGE matches the types inside it, which a string comparison cannot do.
152
+ * `@typespec/http` reports a bare `string`-typed `contentType` as the wildcard range, and an author
153
+ * may write a subtype wildcard such as text-slash-star outright. Comparing the range TEXT refuses
154
+ * every real request, because no caller sends the range itself as its `Content-Type`. This is the
155
+ * request-side counterpart of `mediaTypeWithin` in the runtime module, inlined because the branch
156
+ * list is emitted here.
157
+ */
158
+ const covers = (range) => {
159
+ if (range === "*/*")
160
+ return true;
161
+ if (!range.endsWith("/*"))
162
+ return false;
163
+ const slash = declared.indexOf("/");
164
+ return slash > 0 && declared.slice(0, slash) === range.slice(0, -2);
165
+ };
166
+ return branches.find(([mediaType]) => {
167
+ const lowered = mediaType.toLowerCase();
168
+ return lowered === declared || covers(lowered);
169
+ });
170
+ }
171
+ /**
172
+ * The failure an undeclared media type produces, in the shape every other rejection has.
173
+ *
174
+ * Reported as an issue on the body rather than a bare `{ success: false }`, so an application's
175
+ * `invalid` hook can tell a caller WHICH thing was wrong - the same reason `unreadableResult` parses
176
+ * `undefined` rather than inventing a result.
177
+ */
178
+ function unsupportedMediaTypeResult(branches) {
179
+ return {
180
+ success: false,
181
+ error: new z.ZodError([
182
+ {
183
+ code: "custom",
184
+ path: ["content-type"],
185
+ message: `this operation accepts ${branches.map(([type]) => type).join(", ")}`,
186
+ },
187
+ ]),
188
+ };
189
+ }
190
+ /**
191
+ * Validate a REQUIRED request body, whatever media type it arrives as.
192
+ *
193
+ * The validated value is published under `"json"` whichever reader produced it: `ValidationTargets`
194
+ * is a closed union in Hono, so there is no name to coin for "the body", and using one slot is what
195
+ * makes every route the same shape downstream.
196
+ */
197
+ function validateBody(schema, invalid, branches) {
198
+ return async (c, next) => {
199
+ /**
200
+ * Annotated rather than inlined, and that is what removes a CAST from this file. `Context` is
201
+ * invariant in its environment and in its `Input`, so passing the outer `c` to a hook typed
202
+ * against the route's own input needs either an annotation here or an `as never` there.
203
+ */
204
+ const parse = async (ctx, proceed) => {
205
+ const branch = bodyBranch(ctx, branches);
206
+ const reading = branch?.[2] ?? schema;
207
+ const raw = branch === undefined ? UNREADABLE : await readBody(ctx, branch[1], branch[3]);
208
+ const result = branch === undefined
209
+ ? unsupportedMediaTypeResult(branches)
210
+ : raw === UNREADABLE
211
+ ? unreadableResult(reading)
212
+ : parsed(reading, raw);
213
+ const response = invalid(result, ctx);
214
+ if (response !== undefined)
215
+ return response;
216
+ if (!result.success)
217
+ return ctx.json(result, 400);
218
+ ctx.req.addValidatedData("json", ("data" in result ? result.data : undefined) ?? {});
219
+ await proceed();
220
+ return undefined;
221
+ };
222
+ return parse(c, next);
223
+ };
224
+ }
225
+ /** Mount every operation the service declares. */
226
+ export function registerRoutes(app, handlersFor, deps) {
227
+ const contexts = new WeakMap();
228
+ const contextFor = (authentication) => async (c, next) => {
229
+ const ctx = deps.context(c, authentication);
230
+ if (ctx === null)
231
+ return deps.noContext(c);
232
+ contexts.set(c, { value: ctx });
233
+ await next();
234
+ return undefined;
235
+ };
236
+ const contextOf = (c) => {
237
+ const held = contexts.get(c);
238
+ if (held === undefined)
239
+ throw new Error("typespec-hono: no caller context was established for this request");
240
+ return held.value;
241
+ };
242
+ const asksRoutes = new Hono()
243
+ .get("/", deps.authorize([{ "BearerAuth": [] }]), zValidator("query", Asks_listQuery, deps.invalid, SYNC), contextFor("required"), async (c) => {
244
+ const result = await handlersFor(c).Asks_list(contextOf(c), {
245
+ ...c.req.valid("query"),
246
+ });
247
+ switch (result.status) {
248
+ case 200: {
249
+ return c.json(servedBody(askPageSchema, result.body, "Asks_list", 200), 200);
250
+ }
251
+ case 401: {
252
+ return c.json(servedBody(problemSchema, result.body, "Asks_list", 401), 401);
253
+ }
254
+ default:
255
+ throw new UndeclaredStatusError("Asks_list", result);
256
+ }
257
+ })
258
+ .get("/:id", deps.authorize([{ "BearerAuth": [] }]), zValidator("param", Asks_readPath, deps.invalid, SYNC), contextFor("required"), async (c) => {
259
+ const result = await handlersFor(c).Asks_read(contextOf(c), {
260
+ ...c.req.valid("param"),
261
+ });
262
+ switch (result.status) {
263
+ case 200: {
264
+ return c.json(servedBody(askSchema, result.body, "Asks_read", 200), 200);
265
+ }
266
+ case 401: {
267
+ return c.json(servedBody(problemSchema, result.body, "Asks_read", 401), 401);
268
+ }
269
+ case 404: {
270
+ return c.json(servedBody(problemSchema, result.body, "Asks_read", 404), 404);
271
+ }
272
+ default:
273
+ throw new UndeclaredStatusError("Asks_read", result);
274
+ }
275
+ })
276
+ .post("/:id/reply", deps.authorize([{ "BearerAuth": [] }]), zValidator("param", Asks_replyPath, deps.invalid, SYNC), validateBody(replySchema, deps.invalid, [
277
+ ["application/json", "json"],
278
+ ]), contextFor("required"), async (c) => {
279
+ const result = await handlersFor(c).Asks_reply(contextOf(c), {
280
+ ...c.req.valid("param"),
281
+ reply: c.req.valid("json"),
282
+ });
283
+ switch (result.status) {
284
+ case 200: {
285
+ return c.json(servedBody(askSchema, result.body, "Asks_reply", 200), 200);
286
+ }
287
+ case 401: {
288
+ return c.json(servedBody(problemSchema, result.body, "Asks_reply", 401), 401);
289
+ }
290
+ case 404: {
291
+ return c.json(servedBody(problemSchema, result.body, "Asks_reply", 404), 404);
292
+ }
293
+ case 409: {
294
+ return c.json(servedBody(problemSchema, result.body, "Asks_reply", 409), 409);
295
+ }
296
+ case 422: {
297
+ return c.json(servedBody(problemSchema, result.body, "Asks_reply", 422), 422);
298
+ }
299
+ default:
300
+ throw new UndeclaredStatusError("Asks_reply", result);
301
+ }
302
+ });
303
+ const basePathRoutes = new Hono()
304
+ .get("/whoami", deps.authorize([{ "BearerAuth": [] }]), contextFor("required"), async (c) => {
305
+ const result = await handlersFor(c).Session_whoami(contextOf(c), {});
306
+ switch (result.status) {
307
+ case 200: {
308
+ return c.json(servedBody(whoamiSchema, result.body, "Session_whoami", 200), 200);
309
+ }
310
+ case 401: {
311
+ return c.json(servedBody(problemSchema, result.body, "Session_whoami", 401), 401);
312
+ }
313
+ default:
314
+ throw new UndeclaredStatusError("Session_whoami", result);
315
+ }
316
+ })
317
+ .post("/events/tickets", deps.authorize([{ "BearerAuth": [] }]), contextFor("required"), async (c) => {
318
+ const result = await handlersFor(c).EventTickets_issue(contextOf(c), {});
319
+ switch (result.status) {
320
+ case 201: {
321
+ return c.json(servedBody(eventTicketSchema, result.body, "EventTickets_issue", 201), 201);
322
+ }
323
+ case 401: {
324
+ return c.json(servedBody(problemSchema, result.body, "EventTickets_issue", 401), 401);
325
+ }
326
+ default:
327
+ throw new UndeclaredStatusError("EventTickets_issue", result);
328
+ }
329
+ })
330
+ .get("/snapshot", deps.authorize([{ "BearerAuth": [] }]), contextFor("required"), async (c) => {
331
+ const result = await handlersFor(c).Snapshots_read(contextOf(c), {});
332
+ switch (result.status) {
333
+ case 200: {
334
+ return c.json(servedBody(snapshotSchema, result.body, "Snapshots_read", 200), 200);
335
+ }
336
+ case 401: {
337
+ return c.json(servedBody(problemSchema, result.body, "Snapshots_read", 401), 401);
338
+ }
339
+ default:
340
+ throw new UndeclaredStatusError("Snapshots_read", result);
341
+ }
342
+ })
343
+ .get("/files/:uploadId", deps.authorize([{ "BearerAuth": [] }]), zValidator("param", Files_readPath, deps.invalid, SYNC), contextFor("required"), async (c) => {
344
+ const result = await handlersFor(c).Files_read(contextOf(c), {
345
+ ...c.req.valid("param"),
346
+ });
347
+ switch (result.status) {
348
+ case 200: {
349
+ switch (result.contentType) {
350
+ case "image/png":
351
+ return c.body(servedBytes(result.body), 200, headersOf({ "Content-Type": result.contentType }));
352
+ case "image/jpeg":
353
+ return c.body(servedBytes(result.body), 200, headersOf({ "Content-Type": result.contentType }));
354
+ case "image/webp":
355
+ return c.body(servedBytes(result.body), 200, headersOf({ "Content-Type": result.contentType }));
356
+ case "image/gif":
357
+ return c.body(servedBytes(result.body), 200, headersOf({ "Content-Type": result.contentType }));
358
+ }
359
+ throw new UndeclaredStatusError("Files_read", result);
360
+ }
361
+ case 401: {
362
+ return c.json(servedBody(problemSchema, result.body, "Files_read", 401), 401);
363
+ }
364
+ case 404: {
365
+ return c.json(servedBody(problemSchema, result.body, "Files_read", 404), 404);
366
+ }
367
+ default:
368
+ throw new UndeclaredStatusError("Files_read", result);
369
+ }
370
+ })
371
+ .put("/uploads/:spaceId/:token", zValidator("param", Uploads_storePath, deps.invalid, SYNC), zValidator("header", Uploads_storeHeader, deps.invalid, SYNC), contextFor("none"), async (c) => {
372
+ const result = await handlersFor(c).Uploads_store(contextOf(c), {
373
+ ...c.req.valid("param"),
374
+ ...c.req.valid("header"),
375
+ body: c.req.raw.body,
376
+ });
377
+ switch (result.status) {
378
+ case 201: {
379
+ return c.json(servedBody(uploadReceiptSchema, result.body, "Uploads_store", 201), 201);
380
+ }
381
+ case 404: {
382
+ return c.json(servedBody(problemSchema, result.body, "Uploads_store", 404), 404);
383
+ }
384
+ case 410: {
385
+ return c.json(servedBody(problemSchema, result.body, "Uploads_store", 410), 410);
386
+ }
387
+ case 413: {
388
+ return c.json(servedBody(problemSchema, result.body, "Uploads_store", 413), 413);
389
+ }
390
+ case 422: {
391
+ return c.json(servedBody(problemSchema, result.body, "Uploads_store", 422), 422);
392
+ }
393
+ default:
394
+ throw new UndeclaredStatusError("Uploads_store", result);
395
+ }
396
+ })
397
+ .route("/asks", asksRoutes);
398
+ return app
399
+ .route("/api/v1", basePathRoutes);
400
+ }