@zmdb/web 1.0.0-beta.1

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 (179) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +49 -0
  3. package/dist/app/bridge.d.ts +12 -0
  4. package/dist/app/bridge.d.ts.map +1 -0
  5. package/dist/app/bridge.js +16 -0
  6. package/dist/app/bridge.js.map +1 -0
  7. package/dist/app/index.d.ts +23 -0
  8. package/dist/app/index.d.ts.map +1 -0
  9. package/dist/app/index.js +47 -0
  10. package/dist/app/index.js.map +1 -0
  11. package/dist/bench/index.d.ts +68 -0
  12. package/dist/bench/index.d.ts.map +1 -0
  13. package/dist/bench/index.js +250 -0
  14. package/dist/bench/index.js.map +1 -0
  15. package/dist/compression/index.d.ts +13 -0
  16. package/dist/compression/index.d.ts.map +1 -0
  17. package/dist/compression/index.js +211 -0
  18. package/dist/compression/index.js.map +1 -0
  19. package/dist/context/index.d.ts +74 -0
  20. package/dist/context/index.d.ts.map +1 -0
  21. package/dist/context/index.js +118 -0
  22. package/dist/context/index.js.map +1 -0
  23. package/dist/contract/__fixtures__/http-client.generated.d.ts +70 -0
  24. package/dist/contract/__fixtures__/http-client.generated.d.ts.map +1 -0
  25. package/dist/contract/__fixtures__/http-client.generated.js +86 -0
  26. package/dist/contract/__fixtures__/http-client.generated.js.map +1 -0
  27. package/dist/contract/__fixtures__/http-convergence.d.ts +396 -0
  28. package/dist/contract/__fixtures__/http-convergence.d.ts.map +1 -0
  29. package/dist/contract/__fixtures__/http-convergence.js +425 -0
  30. package/dist/contract/__fixtures__/http-convergence.js.map +1 -0
  31. package/dist/contract/compiler/client.d.ts +12 -0
  32. package/dist/contract/compiler/client.d.ts.map +1 -0
  33. package/dist/contract/compiler/client.js +1293 -0
  34. package/dist/contract/compiler/client.js.map +1 -0
  35. package/dist/contract/compiler/index.d.ts +18 -0
  36. package/dist/contract/compiler/index.d.ts.map +1 -0
  37. package/dist/contract/compiler/index.js +1058 -0
  38. package/dist/contract/compiler/index.js.map +1 -0
  39. package/dist/contract/index.d.ts +237 -0
  40. package/dist/contract/index.d.ts.map +1 -0
  41. package/dist/contract/index.js +22 -0
  42. package/dist/contract/index.js.map +1 -0
  43. package/dist/csrf/index.d.ts +15 -0
  44. package/dist/csrf/index.d.ts.map +1 -0
  45. package/dist/csrf/index.js +218 -0
  46. package/dist/csrf/index.js.map +1 -0
  47. package/dist/data/index.d.ts +35 -0
  48. package/dist/data/index.d.ts.map +1 -0
  49. package/dist/data/index.js +58 -0
  50. package/dist/data/index.js.map +1 -0
  51. package/dist/devtools/index.d.ts +74 -0
  52. package/dist/devtools/index.d.ts.map +1 -0
  53. package/dist/devtools/index.js +552 -0
  54. package/dist/devtools/index.js.map +1 -0
  55. package/dist/dto-pipes/index.d.ts +45 -0
  56. package/dist/dto-pipes/index.d.ts.map +1 -0
  57. package/dist/dto-pipes/index.js +78 -0
  58. package/dist/dto-pipes/index.js.map +1 -0
  59. package/dist/gateways/index.d.ts +31 -0
  60. package/dist/gateways/index.d.ts.map +1 -0
  61. package/dist/gateways/index.js +138 -0
  62. package/dist/gateways/index.js.map +1 -0
  63. package/dist/health/index.d.ts +20 -0
  64. package/dist/health/index.d.ts.map +1 -0
  65. package/dist/health/index.js +148 -0
  66. package/dist/health/index.js.map +1 -0
  67. package/dist/http-token.d.ts +2 -0
  68. package/dist/http-token.d.ts.map +1 -0
  69. package/dist/http-token.js +21 -0
  70. package/dist/http-token.js.map +1 -0
  71. package/dist/index.d.ts +18 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +40 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/integrations/index.d.ts +11 -0
  76. package/dist/integrations/index.d.ts.map +1 -0
  77. package/dist/integrations/index.js +39 -0
  78. package/dist/integrations/index.js.map +1 -0
  79. package/dist/middleware/errors.d.ts +14 -0
  80. package/dist/middleware/errors.d.ts.map +1 -0
  81. package/dist/middleware/errors.js +18 -0
  82. package/dist/middleware/errors.js.map +1 -0
  83. package/dist/middleware/index.d.ts +46 -0
  84. package/dist/middleware/index.d.ts.map +1 -0
  85. package/dist/middleware/index.js +64 -0
  86. package/dist/middleware/index.js.map +1 -0
  87. package/dist/modules/__fixtures__/large-graph.d.ts +239 -0
  88. package/dist/modules/__fixtures__/large-graph.d.ts.map +1 -0
  89. package/dist/modules/__fixtures__/large-graph.js +483 -0
  90. package/dist/modules/__fixtures__/large-graph.js.map +1 -0
  91. package/dist/openapi/__fixtures__/entities.d.ts +8 -0
  92. package/dist/openapi/__fixtures__/entities.d.ts.map +1 -0
  93. package/dist/openapi/__fixtures__/entities.js +4 -0
  94. package/dist/openapi/__fixtures__/entities.js.map +1 -0
  95. package/dist/openapi/__fixtures__/openapi-tools.fixture.d.ts +38 -0
  96. package/dist/openapi/__fixtures__/openapi-tools.fixture.d.ts.map +1 -0
  97. package/dist/openapi/__fixtures__/openapi-tools.fixture.js +172 -0
  98. package/dist/openapi/__fixtures__/openapi-tools.fixture.js.map +1 -0
  99. package/dist/openapi/__fixtures__/route-schemas.d.ts +2 -0
  100. package/dist/openapi/__fixtures__/route-schemas.d.ts.map +1 -0
  101. package/dist/openapi/__fixtures__/route-schemas.js +17 -0
  102. package/dist/openapi/__fixtures__/route-schemas.js.map +1 -0
  103. package/dist/openapi/index.d.ts +61 -0
  104. package/dist/openapi/index.d.ts.map +1 -0
  105. package/dist/openapi/index.js +314 -0
  106. package/dist/openapi/index.js.map +1 -0
  107. package/dist/pipeline/guards.d.ts +9 -0
  108. package/dist/pipeline/guards.d.ts.map +1 -0
  109. package/dist/pipeline/guards.js +11 -0
  110. package/dist/pipeline/guards.js.map +1 -0
  111. package/dist/pipeline/index.d.ts +141 -0
  112. package/dist/pipeline/index.d.ts.map +1 -0
  113. package/dist/pipeline/index.js +1624 -0
  114. package/dist/pipeline/index.js.map +1 -0
  115. package/dist/routing/index.d.ts +40 -0
  116. package/dist/routing/index.d.ts.map +1 -0
  117. package/dist/routing/index.js +162 -0
  118. package/dist/routing/index.js.map +1 -0
  119. package/dist/static/index.d.ts +14 -0
  120. package/dist/static/index.d.ts.map +1 -0
  121. package/dist/static/index.js +348 -0
  122. package/dist/static/index.js.map +1 -0
  123. package/dist/testing/index.d.ts +20 -0
  124. package/dist/testing/index.d.ts.map +1 -0
  125. package/dist/testing/index.js +32 -0
  126. package/dist/testing/index.js.map +1 -0
  127. package/dist/traced-router.d.ts +5 -0
  128. package/dist/traced-router.d.ts.map +1 -0
  129. package/dist/traced-router.js +6 -0
  130. package/dist/traced-router.js.map +1 -0
  131. package/dist/upload/index.d.ts +28 -0
  132. package/dist/upload/index.d.ts.map +1 -0
  133. package/dist/upload/index.js +410 -0
  134. package/dist/upload/index.js.map +1 -0
  135. package/dist/versioning/index.d.ts +35 -0
  136. package/dist/versioning/index.d.ts.map +1 -0
  137. package/dist/versioning/index.js +66 -0
  138. package/dist/versioning/index.js.map +1 -0
  139. package/dist/versioning/runtime.d.ts +5 -0
  140. package/dist/versioning/runtime.d.ts.map +1 -0
  141. package/dist/versioning/runtime.js +34 -0
  142. package/dist/versioning/runtime.js.map +1 -0
  143. package/package.json +143 -0
  144. package/src/app/bridge.ts +39 -0
  145. package/src/app/index.ts +76 -0
  146. package/src/bench/index.ts +329 -0
  147. package/src/compression/index.ts +255 -0
  148. package/src/context/index.ts +181 -0
  149. package/src/contract/__fixtures__/http-client.generated.ts +73 -0
  150. package/src/contract/__fixtures__/http-client.generated.ts.map +1 -0
  151. package/src/contract/__fixtures__/http-convergence.ts +564 -0
  152. package/src/contract/compiler/client.ts +1518 -0
  153. package/src/contract/compiler/index.ts +1457 -0
  154. package/src/contract/index.ts +266 -0
  155. package/src/csrf/index.ts +264 -0
  156. package/src/data/index.ts +65 -0
  157. package/src/devtools/index.ts +727 -0
  158. package/src/dto-pipes/index.ts +93 -0
  159. package/src/gateways/index.ts +180 -0
  160. package/src/health/index.ts +198 -0
  161. package/src/http-token.ts +22 -0
  162. package/src/index.ts +167 -0
  163. package/src/integrations/index.ts +50 -0
  164. package/src/middleware/errors.ts +17 -0
  165. package/src/middleware/index.ts +108 -0
  166. package/src/modules/__fixtures__/large-graph.ts +540 -0
  167. package/src/openapi/__fixtures__/entities.ts +19 -0
  168. package/src/openapi/__fixtures__/openapi-tools.fixture.ts +212 -0
  169. package/src/openapi/__fixtures__/route-schemas.ts +27 -0
  170. package/src/openapi/index.ts +483 -0
  171. package/src/pipeline/guards.ts +23 -0
  172. package/src/pipeline/index.ts +2041 -0
  173. package/src/routing/index.ts +201 -0
  174. package/src/static/index.ts +443 -0
  175. package/src/testing/index.ts +58 -0
  176. package/src/traced-router.ts +8 -0
  177. package/src/upload/index.ts +489 -0
  178. package/src/versioning/index.ts +106 -0
  179. package/src/versioning/runtime.ts +38 -0
@@ -0,0 +1,50 @@
1
+ // Framework-agnostic endpoint adapter — see ./SPEC.md.
2
+
3
+ import { claimsValidationIssues, ValidationError, validationIssuesOf } from '@zmdb/validator';
4
+
5
+ export interface Handler<In, Out> {
6
+ validate: (raw: unknown) => In;
7
+ handle: (input: In) => Promise<Out>;
8
+ serialize?: (out: Out) => string;
9
+ }
10
+
11
+ export interface EndpointResult {
12
+ status: number;
13
+ body: string;
14
+ }
15
+
16
+ export function makeEndpoint<In, Out>(h: Handler<In, Out>): (raw: unknown) => Promise<EndpointResult> {
17
+ const serialize = h.serialize ?? ((o: Out) => JSON.stringify(o));
18
+ return async (raw: unknown) => {
19
+ let input: In;
20
+ try {
21
+ input = h.validate(raw);
22
+ } catch (err) {
23
+ const error = err instanceof Error ? err.message : 'invalid input';
24
+ const issues = validationIssuesOf(err);
25
+ return { status: 400, body: JSON.stringify(issues ? { error, issues } : { error }) };
26
+ }
27
+ try {
28
+ const out = await h.handle(input);
29
+ return { status: 200, body: serialize(out) };
30
+ } catch (err) {
31
+ // A validation error from the *handler* is still the caller's fault — a write that
32
+ // failed its own schema check on the way to the driver — so it is a 400 and not a 500.
33
+ // Anything else is rethrown untouched: this adapter has no business deciding what an
34
+ // unexpected exception means.
35
+ if (err instanceof ValidationError || claimsValidationIssues(err)) {
36
+ const error = err instanceof Error ? err.message : 'invalid input';
37
+ const issues = validationIssuesOf(err);
38
+ return { status: 400, body: JSON.stringify(issues ? { error, issues } : { error }) };
39
+ }
40
+ throw err;
41
+ }
42
+ };
43
+ }
44
+
45
+ // --- Thin per-framework adapters (optional; no hard deps) ---
46
+ // Hono: app.post('/x', async (c) => { const r = await ep(await c.req.json()); return c.body(r.body, r.status); })
47
+ // Express: app.post('/x', async (req,res) => { const r = await ep(req.body); res.status(r.status).send(r.body); })
48
+ // tRPC: procedure.input(z.unknown()).mutation(({input}) => ep(input))
49
+ // NestJS: @Post() async create(@Body() b){ return ep(b); }
50
+ // Each is a 1–2 line wrapper over makeEndpoint — see docs/framework-integrations.
@@ -0,0 +1,17 @@
1
+ /** An error carrying an HTTP status, thrown when a middleware chain short-circuits. */
2
+ export class ChainError extends Error {
3
+ readonly status: number;
4
+ constructor(status: number, message: string) {
5
+ super(message);
6
+ this.name = 'ChainError';
7
+ this.status = status;
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Internal boundary error whose status is already the framework's decision.
13
+ *
14
+ * Ordinary user-thrown pipe errors still become ChainError(400). Built-in
15
+ * boundary parsers use this subclass when 400 and 413 have distinct meanings.
16
+ */
17
+ export class BoundaryStatusError extends ChainError {}
@@ -0,0 +1,108 @@
1
+ // @zmdb/web — guards, pipes, interceptors & exception filters (epic #287, spec
2
+ // ./SPEC.md). A Chain composes middleware around a handler in deterministic
3
+ // order. Static composition, no reflection, no `as` on the consumer surface.
4
+
5
+ import type { Ctx, QueryValues } from '../context/index.js';
6
+ import type { WebResponse } from '../pipeline/index.js';
7
+ import { BoundaryStatusError, ChainError } from './errors.js';
8
+
9
+ export { ChainError } from './errors.js';
10
+
11
+ export type AnyCtx = Ctx<Record<string, string>, unknown, QueryValues>;
12
+
13
+ /** Authorization gate: a `false` short-circuits the request (403). */
14
+ export interface Guard {
15
+ canActivate(ctx: AnyCtx): boolean | Promise<boolean>;
16
+ }
17
+
18
+ /** A guard that declares the OpenAPI security scheme and scopes it enforces. */
19
+ export interface SecurityAwareGuard extends Guard {
20
+ readonly enforces: { readonly scheme: string; readonly scopes: readonly string[] };
21
+ }
22
+
23
+ /** Transform/validate a value; ordinary throws yield 400. */
24
+ export interface Pipe<In = unknown, Out = unknown> {
25
+ transform(value: In, ctx: AnyCtx): Out | Promise<Out>;
26
+ }
27
+
28
+ /** Wrap the rest of the chain (handler) with pre/post behavior. */
29
+ export interface Interceptor {
30
+ intercept(ctx: AnyCtx, next: () => Promise<unknown>): Promise<unknown>;
31
+ }
32
+
33
+ /** Map a thrown error to a response. */
34
+ export interface ExceptionFilter {
35
+ catch(error: unknown, ctx: AnyCtx): WebResponse | undefined;
36
+ }
37
+
38
+ /** A composed middleware chain for a route. */
39
+ export interface Chain {
40
+ readonly guards: readonly Guard[];
41
+ readonly pipes: readonly Pipe[];
42
+ readonly interceptors: readonly Interceptor[];
43
+ readonly filters: readonly ExceptionFilter[];
44
+ }
45
+
46
+ /** A handler invoked with the (piped) ctx. */
47
+ export type ChainHandler = (ctx: AnyCtx) => unknown;
48
+
49
+ /**
50
+ * Run a middleware chain around `handler` for `ctx`. Order: guards → pipes (fold
51
+ * the body) → interceptors (nested) → handler. A guard returning false throws
52
+ * ChainError(403); an ordinary throwing pipe throws ChainError(400), while a
53
+ * built-in boundary pipe can preserve its framework-selected status; a thrown
54
+ * handler is offered to the exception filters — a matching filter's response is
55
+ * returned, otherwise the error rethrows for the pipeline to serialize.
56
+ */
57
+ export async function runChain(chain: Chain, ctx: AnyCtx, handler: ChainHandler): Promise<unknown> {
58
+ // 1) guards
59
+ for (const guard of chain.guards) {
60
+ const allowed = await guard.canActivate(ctx);
61
+ if (!allowed) {
62
+ throw new ChainError(403, 'forbidden');
63
+ }
64
+ }
65
+
66
+ // 2) pipes — fold over the body, producing a new ctx the handler sees.
67
+ let body = ctx.body;
68
+ for (const pipe of chain.pipes) {
69
+ try {
70
+ body = await pipe.transform(body, ctx);
71
+ } catch (error) {
72
+ if (error instanceof BoundaryStatusError) {
73
+ throw error;
74
+ }
75
+ throw new ChainError(400, messageOf(error));
76
+ }
77
+ }
78
+ const pipedCtx: AnyCtx = { ...ctx, body };
79
+
80
+ // 3) interceptors — nest right-to-left so the first listed runs outermost.
81
+ const invokeHandler = (): Promise<unknown> => Promise.resolve(handler(pipedCtx));
82
+ let next = invokeHandler;
83
+ for (let i = chain.interceptors.length - 1; i >= 0; i -= 1) {
84
+ const interceptor = chain.interceptors[i];
85
+ if (interceptor === undefined) {
86
+ continue;
87
+ }
88
+ const downstream = next;
89
+ next = (): Promise<unknown> => interceptor.intercept(pipedCtx, downstream);
90
+ }
91
+
92
+ // 4) handler (via the interceptor chain); 5) exception filters on throw.
93
+ try {
94
+ return await next();
95
+ } catch (error) {
96
+ for (const filter of chain.filters) {
97
+ const response = filter.catch(error, pipedCtx);
98
+ if (response !== undefined) {
99
+ return response;
100
+ }
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ function messageOf(error: unknown): string {
107
+ return error instanceof Error ? error.message : String(error);
108
+ }