@contractspec/lib.contracts-runtime-server-rest 2.0.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.
@@ -0,0 +1,129 @@
1
+ // src/rest-generic.ts
2
+ import { defaultRestPath } from "@contractspec/lib.contracts-spec/jsonschema";
3
+ function corsHeaders(opt) {
4
+ const h = {};
5
+ const origin = typeof opt === "object" ? opt.origin ?? "*" : "*";
6
+ h["access-control-allow-origin"] = origin;
7
+ h["vary"] = "Origin";
8
+ if (typeof opt === "object") {
9
+ if (opt.methods)
10
+ h["access-control-allow-methods"] = opt.methods.join(", ");
11
+ if (opt.headers)
12
+ h["access-control-allow-headers"] = opt.headers.join(", ");
13
+ if (opt.credentials)
14
+ h["access-control-allow-credentials"] = "true";
15
+ if (typeof opt.maxAge === "number") {
16
+ h["access-control-max-age"] = String(opt.maxAge);
17
+ }
18
+ } else {
19
+ h["access-control-allow-methods"] = "GET,POST,OPTIONS";
20
+ h["access-control-allow-headers"] = "content-type,x-idempotency-key,x-trace-id";
21
+ }
22
+ return h;
23
+ }
24
+ function joinPath(a, b) {
25
+ const left = (a ?? "").replace(/\/+$/g, "");
26
+ const right = b.replace(/^\/+/g, "");
27
+ return `${left}/${right}`.replace(/\/{2,}/g, "/");
28
+ }
29
+ function createFetchHandler(reg, ctxFactory, options) {
30
+ const opts = {
31
+ basePath: options?.basePath ?? "",
32
+ cors: options?.cors ?? false,
33
+ prettyJson: options?.prettyJson ?? false,
34
+ onError: options?.onError
35
+ };
36
+ const routes = reg.list().map((spec) => ({
37
+ method: spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST"),
38
+ path: joinPath(opts.basePath, spec.transport?.rest?.path ?? defaultRestPath(spec.meta.key, spec.meta.version)),
39
+ name: spec.meta.key,
40
+ version: spec.meta.version
41
+ }));
42
+ const routeTable = new Map;
43
+ for (const r of routes)
44
+ routeTable.set(`${r.method} ${r.path}`, r);
45
+ const makeJson = (status, data, extraHeaders) => {
46
+ const body = opts.prettyJson ? JSON.stringify(data, null, opts.prettyJson) : JSON.stringify(data);
47
+ const base = {
48
+ "content-type": "application/json; charset=utf-8"
49
+ };
50
+ return new Response(body, {
51
+ status,
52
+ headers: extraHeaders ? { ...base, ...extraHeaders } : base
53
+ });
54
+ };
55
+ return async function handle(req) {
56
+ const url = new URL(req.url);
57
+ const key = `${req.method.toUpperCase()} ${url.pathname}`;
58
+ if (opts.cors && req.method.toUpperCase() === "OPTIONS") {
59
+ const h = corsHeaders(opts.cors === true ? {} : opts.cors);
60
+ return new Response(null, {
61
+ status: 204,
62
+ headers: { ...h, "content-length": "0" }
63
+ });
64
+ }
65
+ const route = routeTable.get(key);
66
+ if (!route) {
67
+ const headers = {};
68
+ if (opts.cors)
69
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
70
+ return makeJson(404, { error: "NotFound", path: url.pathname }, headers);
71
+ }
72
+ try {
73
+ let input = {};
74
+ if (route.method === "GET") {
75
+ if (url.searchParams.has("input")) {
76
+ const raw = url.searchParams.get("input");
77
+ input = raw ? JSON.parse(raw) : {};
78
+ } else {
79
+ const obj = {};
80
+ for (const [k, v] of url.searchParams.entries())
81
+ obj[k] = v;
82
+ input = obj;
83
+ }
84
+ } else {
85
+ const contentType = req.headers.get("content-type") || "";
86
+ if (contentType.includes("application/json")) {
87
+ input = await req.json();
88
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
89
+ const form = await req.formData();
90
+ input = Object.fromEntries(form.entries());
91
+ } else if (!contentType) {
92
+ input = {};
93
+ } else {
94
+ return makeJson(415, { error: "UnsupportedMediaType", contentType });
95
+ }
96
+ }
97
+ const ctx = ctxFactory(req);
98
+ const result = await reg.execute(route.name, route.version, input, ctx);
99
+ const headers = {};
100
+ if (opts.cors)
101
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
102
+ return makeJson(200, result, headers);
103
+ } catch (err) {
104
+ if (opts.onError) {
105
+ const mapped = opts.onError(err);
106
+ const headers2 = {};
107
+ if (opts.cors)
108
+ Object.assign(headers2, corsHeaders(opts.cors === true ? {} : opts.cors));
109
+ return makeJson(mapped.status, mapped.body, headers2);
110
+ }
111
+ const headers = {};
112
+ if (opts.cors)
113
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
114
+ if (err?.issues) {
115
+ return makeJson(400, {
116
+ error: "ValidationError",
117
+ issues: err.issues
118
+ }, headers);
119
+ }
120
+ if (typeof err?.message === "string" && err.message.startsWith("PolicyDenied")) {
121
+ return makeJson(403, { error: "PolicyDenied" }, headers);
122
+ }
123
+ return makeJson(500, { error: "InternalError" }, headers);
124
+ }
125
+ };
126
+ }
127
+ export {
128
+ createFetchHandler
129
+ };
@@ -0,0 +1,137 @@
1
+ // src/rest-generic.ts
2
+ import { defaultRestPath } from "@contractspec/lib.contracts-spec/jsonschema";
3
+ function corsHeaders(opt) {
4
+ const h = {};
5
+ const origin = typeof opt === "object" ? opt.origin ?? "*" : "*";
6
+ h["access-control-allow-origin"] = origin;
7
+ h["vary"] = "Origin";
8
+ if (typeof opt === "object") {
9
+ if (opt.methods)
10
+ h["access-control-allow-methods"] = opt.methods.join(", ");
11
+ if (opt.headers)
12
+ h["access-control-allow-headers"] = opt.headers.join(", ");
13
+ if (opt.credentials)
14
+ h["access-control-allow-credentials"] = "true";
15
+ if (typeof opt.maxAge === "number") {
16
+ h["access-control-max-age"] = String(opt.maxAge);
17
+ }
18
+ } else {
19
+ h["access-control-allow-methods"] = "GET,POST,OPTIONS";
20
+ h["access-control-allow-headers"] = "content-type,x-idempotency-key,x-trace-id";
21
+ }
22
+ return h;
23
+ }
24
+ function joinPath(a, b) {
25
+ const left = (a ?? "").replace(/\/+$/g, "");
26
+ const right = b.replace(/^\/+/g, "");
27
+ return `${left}/${right}`.replace(/\/{2,}/g, "/");
28
+ }
29
+ function createFetchHandler(reg, ctxFactory, options) {
30
+ const opts = {
31
+ basePath: options?.basePath ?? "",
32
+ cors: options?.cors ?? false,
33
+ prettyJson: options?.prettyJson ?? false,
34
+ onError: options?.onError
35
+ };
36
+ const routes = reg.list().map((spec) => ({
37
+ method: spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST"),
38
+ path: joinPath(opts.basePath, spec.transport?.rest?.path ?? defaultRestPath(spec.meta.key, spec.meta.version)),
39
+ name: spec.meta.key,
40
+ version: spec.meta.version
41
+ }));
42
+ const routeTable = new Map;
43
+ for (const r of routes)
44
+ routeTable.set(`${r.method} ${r.path}`, r);
45
+ const makeJson = (status, data, extraHeaders) => {
46
+ const body = opts.prettyJson ? JSON.stringify(data, null, opts.prettyJson) : JSON.stringify(data);
47
+ const base = {
48
+ "content-type": "application/json; charset=utf-8"
49
+ };
50
+ return new Response(body, {
51
+ status,
52
+ headers: extraHeaders ? { ...base, ...extraHeaders } : base
53
+ });
54
+ };
55
+ return async function handle(req) {
56
+ const url = new URL(req.url);
57
+ const key = `${req.method.toUpperCase()} ${url.pathname}`;
58
+ if (opts.cors && req.method.toUpperCase() === "OPTIONS") {
59
+ const h = corsHeaders(opts.cors === true ? {} : opts.cors);
60
+ return new Response(null, {
61
+ status: 204,
62
+ headers: { ...h, "content-length": "0" }
63
+ });
64
+ }
65
+ const route = routeTable.get(key);
66
+ if (!route) {
67
+ const headers = {};
68
+ if (opts.cors)
69
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
70
+ return makeJson(404, { error: "NotFound", path: url.pathname }, headers);
71
+ }
72
+ try {
73
+ let input = {};
74
+ if (route.method === "GET") {
75
+ if (url.searchParams.has("input")) {
76
+ const raw = url.searchParams.get("input");
77
+ input = raw ? JSON.parse(raw) : {};
78
+ } else {
79
+ const obj = {};
80
+ for (const [k, v] of url.searchParams.entries())
81
+ obj[k] = v;
82
+ input = obj;
83
+ }
84
+ } else {
85
+ const contentType = req.headers.get("content-type") || "";
86
+ if (contentType.includes("application/json")) {
87
+ input = await req.json();
88
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
89
+ const form = await req.formData();
90
+ input = Object.fromEntries(form.entries());
91
+ } else if (!contentType) {
92
+ input = {};
93
+ } else {
94
+ return makeJson(415, { error: "UnsupportedMediaType", contentType });
95
+ }
96
+ }
97
+ const ctx = ctxFactory(req);
98
+ const result = await reg.execute(route.name, route.version, input, ctx);
99
+ const headers = {};
100
+ if (opts.cors)
101
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
102
+ return makeJson(200, result, headers);
103
+ } catch (err) {
104
+ if (opts.onError) {
105
+ const mapped = opts.onError(err);
106
+ const headers2 = {};
107
+ if (opts.cors)
108
+ Object.assign(headers2, corsHeaders(opts.cors === true ? {} : opts.cors));
109
+ return makeJson(mapped.status, mapped.body, headers2);
110
+ }
111
+ const headers = {};
112
+ if (opts.cors)
113
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
114
+ if (err?.issues) {
115
+ return makeJson(400, {
116
+ error: "ValidationError",
117
+ issues: err.issues
118
+ }, headers);
119
+ }
120
+ if (typeof err?.message === "string" && err.message.startsWith("PolicyDenied")) {
121
+ return makeJson(403, { error: "PolicyDenied" }, headers);
122
+ }
123
+ return makeJson(500, { error: "InternalError" }, headers);
124
+ }
125
+ };
126
+ }
127
+
128
+ // src/rest-next-app.ts
129
+ function makeNextAppHandler(reg, ctxFactory, options) {
130
+ const handler = createFetchHandler(reg, ctxFactory, options);
131
+ return async function requestHandler(req) {
132
+ return handler(req);
133
+ };
134
+ }
135
+ export {
136
+ makeNextAppHandler
137
+ };
@@ -0,0 +1,148 @@
1
+ // src/rest-generic.ts
2
+ import { defaultRestPath } from "@contractspec/lib.contracts-spec/jsonschema";
3
+ function corsHeaders(opt) {
4
+ const h = {};
5
+ const origin = typeof opt === "object" ? opt.origin ?? "*" : "*";
6
+ h["access-control-allow-origin"] = origin;
7
+ h["vary"] = "Origin";
8
+ if (typeof opt === "object") {
9
+ if (opt.methods)
10
+ h["access-control-allow-methods"] = opt.methods.join(", ");
11
+ if (opt.headers)
12
+ h["access-control-allow-headers"] = opt.headers.join(", ");
13
+ if (opt.credentials)
14
+ h["access-control-allow-credentials"] = "true";
15
+ if (typeof opt.maxAge === "number") {
16
+ h["access-control-max-age"] = String(opt.maxAge);
17
+ }
18
+ } else {
19
+ h["access-control-allow-methods"] = "GET,POST,OPTIONS";
20
+ h["access-control-allow-headers"] = "content-type,x-idempotency-key,x-trace-id";
21
+ }
22
+ return h;
23
+ }
24
+ function joinPath(a, b) {
25
+ const left = (a ?? "").replace(/\/+$/g, "");
26
+ const right = b.replace(/^\/+/g, "");
27
+ return `${left}/${right}`.replace(/\/{2,}/g, "/");
28
+ }
29
+ function createFetchHandler(reg, ctxFactory, options) {
30
+ const opts = {
31
+ basePath: options?.basePath ?? "",
32
+ cors: options?.cors ?? false,
33
+ prettyJson: options?.prettyJson ?? false,
34
+ onError: options?.onError
35
+ };
36
+ const routes = reg.list().map((spec) => ({
37
+ method: spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST"),
38
+ path: joinPath(opts.basePath, spec.transport?.rest?.path ?? defaultRestPath(spec.meta.key, spec.meta.version)),
39
+ name: spec.meta.key,
40
+ version: spec.meta.version
41
+ }));
42
+ const routeTable = new Map;
43
+ for (const r of routes)
44
+ routeTable.set(`${r.method} ${r.path}`, r);
45
+ const makeJson = (status, data, extraHeaders) => {
46
+ const body = opts.prettyJson ? JSON.stringify(data, null, opts.prettyJson) : JSON.stringify(data);
47
+ const base = {
48
+ "content-type": "application/json; charset=utf-8"
49
+ };
50
+ return new Response(body, {
51
+ status,
52
+ headers: extraHeaders ? { ...base, ...extraHeaders } : base
53
+ });
54
+ };
55
+ return async function handle(req) {
56
+ const url = new URL(req.url);
57
+ const key = `${req.method.toUpperCase()} ${url.pathname}`;
58
+ if (opts.cors && req.method.toUpperCase() === "OPTIONS") {
59
+ const h = corsHeaders(opts.cors === true ? {} : opts.cors);
60
+ return new Response(null, {
61
+ status: 204,
62
+ headers: { ...h, "content-length": "0" }
63
+ });
64
+ }
65
+ const route = routeTable.get(key);
66
+ if (!route) {
67
+ const headers = {};
68
+ if (opts.cors)
69
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
70
+ return makeJson(404, { error: "NotFound", path: url.pathname }, headers);
71
+ }
72
+ try {
73
+ let input = {};
74
+ if (route.method === "GET") {
75
+ if (url.searchParams.has("input")) {
76
+ const raw = url.searchParams.get("input");
77
+ input = raw ? JSON.parse(raw) : {};
78
+ } else {
79
+ const obj = {};
80
+ for (const [k, v] of url.searchParams.entries())
81
+ obj[k] = v;
82
+ input = obj;
83
+ }
84
+ } else {
85
+ const contentType = req.headers.get("content-type") || "";
86
+ if (contentType.includes("application/json")) {
87
+ input = await req.json();
88
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
89
+ const form = await req.formData();
90
+ input = Object.fromEntries(form.entries());
91
+ } else if (!contentType) {
92
+ input = {};
93
+ } else {
94
+ return makeJson(415, { error: "UnsupportedMediaType", contentType });
95
+ }
96
+ }
97
+ const ctx = ctxFactory(req);
98
+ const result = await reg.execute(route.name, route.version, input, ctx);
99
+ const headers = {};
100
+ if (opts.cors)
101
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
102
+ return makeJson(200, result, headers);
103
+ } catch (err) {
104
+ if (opts.onError) {
105
+ const mapped = opts.onError(err);
106
+ const headers2 = {};
107
+ if (opts.cors)
108
+ Object.assign(headers2, corsHeaders(opts.cors === true ? {} : opts.cors));
109
+ return makeJson(mapped.status, mapped.body, headers2);
110
+ }
111
+ const headers = {};
112
+ if (opts.cors)
113
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
114
+ if (err?.issues) {
115
+ return makeJson(400, {
116
+ error: "ValidationError",
117
+ issues: err.issues
118
+ }, headers);
119
+ }
120
+ if (typeof err?.message === "string" && err.message.startsWith("PolicyDenied")) {
121
+ return makeJson(403, { error: "PolicyDenied" }, headers);
122
+ }
123
+ return makeJson(500, { error: "InternalError" }, headers);
124
+ }
125
+ };
126
+ }
127
+
128
+ // src/rest-next-pages.ts
129
+ function makeNextPagesHandler(reg, ctxFactory, options) {
130
+ return async function handler(req, res) {
131
+ const url = `${req.headers["x-forwarded-proto"] ?? "http"}://${req.headers.host}${req.url}`;
132
+ const method = req.method?.toUpperCase() || "GET";
133
+ const request = new Request(url, {
134
+ method,
135
+ headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, String(v)])),
136
+ body: method === "POST" ? JSON.stringify(req.body ?? {}) : undefined
137
+ });
138
+ const perReqHandler = createFetchHandler(reg, () => ctxFactory(req), options);
139
+ const response = await perReqHandler(request);
140
+ res.status(response.status);
141
+ response.headers.forEach((v, k) => res.setHeader(k, v));
142
+ const text = await response.text();
143
+ res.send(text);
144
+ };
145
+ }
146
+ export {
147
+ makeNextPagesHandler
148
+ };
@@ -0,0 +1,35 @@
1
+ import type { Elysia } from 'elysia';
2
+ import { type RestOptions } from './rest-generic';
3
+ import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
4
+ import type { HandlerCtx } from '@contractspec/lib.contracts-spec/types';
5
+ export declare function elysiaPlugin(app: Elysia, reg: OperationSpecRegistry, ctxFactory: (c: {
6
+ request: Request;
7
+ store: unknown;
8
+ }) => HandlerCtx, options?: RestOptions): Elysia<"", {
9
+ decorator: {};
10
+ store: {};
11
+ derive: {};
12
+ resolve: {};
13
+ }, {
14
+ typebox: {};
15
+ error: {};
16
+ }, {
17
+ schema: {};
18
+ standaloneSchema: {};
19
+ macro: {};
20
+ macroFn: {};
21
+ parser: {};
22
+ response: {};
23
+ }, {}, {
24
+ derive: {};
25
+ resolve: {};
26
+ schema: {};
27
+ standaloneSchema: {};
28
+ response: {};
29
+ }, {
30
+ derive: {};
31
+ resolve: {};
32
+ schema: {};
33
+ standaloneSchema: {};
34
+ response: {};
35
+ }>;
@@ -0,0 +1,147 @@
1
+ // @bun
2
+ // src/rest-generic.ts
3
+ import { defaultRestPath } from "@contractspec/lib.contracts-spec/jsonschema";
4
+ function corsHeaders(opt) {
5
+ const h = {};
6
+ const origin = typeof opt === "object" ? opt.origin ?? "*" : "*";
7
+ h["access-control-allow-origin"] = origin;
8
+ h["vary"] = "Origin";
9
+ if (typeof opt === "object") {
10
+ if (opt.methods)
11
+ h["access-control-allow-methods"] = opt.methods.join(", ");
12
+ if (opt.headers)
13
+ h["access-control-allow-headers"] = opt.headers.join(", ");
14
+ if (opt.credentials)
15
+ h["access-control-allow-credentials"] = "true";
16
+ if (typeof opt.maxAge === "number") {
17
+ h["access-control-max-age"] = String(opt.maxAge);
18
+ }
19
+ } else {
20
+ h["access-control-allow-methods"] = "GET,POST,OPTIONS";
21
+ h["access-control-allow-headers"] = "content-type,x-idempotency-key,x-trace-id";
22
+ }
23
+ return h;
24
+ }
25
+ function joinPath(a, b) {
26
+ const left = (a ?? "").replace(/\/+$/g, "");
27
+ const right = b.replace(/^\/+/g, "");
28
+ return `${left}/${right}`.replace(/\/{2,}/g, "/");
29
+ }
30
+ function createFetchHandler(reg, ctxFactory, options) {
31
+ const opts = {
32
+ basePath: options?.basePath ?? "",
33
+ cors: options?.cors ?? false,
34
+ prettyJson: options?.prettyJson ?? false,
35
+ onError: options?.onError
36
+ };
37
+ const routes = reg.list().map((spec) => ({
38
+ method: spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST"),
39
+ path: joinPath(opts.basePath, spec.transport?.rest?.path ?? defaultRestPath(spec.meta.key, spec.meta.version)),
40
+ name: spec.meta.key,
41
+ version: spec.meta.version
42
+ }));
43
+ const routeTable = new Map;
44
+ for (const r of routes)
45
+ routeTable.set(`${r.method} ${r.path}`, r);
46
+ const makeJson = (status, data, extraHeaders) => {
47
+ const body = opts.prettyJson ? JSON.stringify(data, null, opts.prettyJson) : JSON.stringify(data);
48
+ const base = {
49
+ "content-type": "application/json; charset=utf-8"
50
+ };
51
+ return new Response(body, {
52
+ status,
53
+ headers: extraHeaders ? { ...base, ...extraHeaders } : base
54
+ });
55
+ };
56
+ return async function handle(req) {
57
+ const url = new URL(req.url);
58
+ const key = `${req.method.toUpperCase()} ${url.pathname}`;
59
+ if (opts.cors && req.method.toUpperCase() === "OPTIONS") {
60
+ const h = corsHeaders(opts.cors === true ? {} : opts.cors);
61
+ return new Response(null, {
62
+ status: 204,
63
+ headers: { ...h, "content-length": "0" }
64
+ });
65
+ }
66
+ const route = routeTable.get(key);
67
+ if (!route) {
68
+ const headers = {};
69
+ if (opts.cors)
70
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
71
+ return makeJson(404, { error: "NotFound", path: url.pathname }, headers);
72
+ }
73
+ try {
74
+ let input = {};
75
+ if (route.method === "GET") {
76
+ if (url.searchParams.has("input")) {
77
+ const raw = url.searchParams.get("input");
78
+ input = raw ? JSON.parse(raw) : {};
79
+ } else {
80
+ const obj = {};
81
+ for (const [k, v] of url.searchParams.entries())
82
+ obj[k] = v;
83
+ input = obj;
84
+ }
85
+ } else {
86
+ const contentType = req.headers.get("content-type") || "";
87
+ if (contentType.includes("application/json")) {
88
+ input = await req.json();
89
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
90
+ const form = await req.formData();
91
+ input = Object.fromEntries(form.entries());
92
+ } else if (!contentType) {
93
+ input = {};
94
+ } else {
95
+ return makeJson(415, { error: "UnsupportedMediaType", contentType });
96
+ }
97
+ }
98
+ const ctx = ctxFactory(req);
99
+ const result = await reg.execute(route.name, route.version, input, ctx);
100
+ const headers = {};
101
+ if (opts.cors)
102
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
103
+ return makeJson(200, result, headers);
104
+ } catch (err) {
105
+ if (opts.onError) {
106
+ const mapped = opts.onError(err);
107
+ const headers2 = {};
108
+ if (opts.cors)
109
+ Object.assign(headers2, corsHeaders(opts.cors === true ? {} : opts.cors));
110
+ return makeJson(mapped.status, mapped.body, headers2);
111
+ }
112
+ const headers = {};
113
+ if (opts.cors)
114
+ Object.assign(headers, corsHeaders(opts.cors === true ? {} : opts.cors));
115
+ if (err?.issues) {
116
+ return makeJson(400, {
117
+ error: "ValidationError",
118
+ issues: err.issues
119
+ }, headers);
120
+ }
121
+ if (typeof err?.message === "string" && err.message.startsWith("PolicyDenied")) {
122
+ return makeJson(403, { error: "PolicyDenied" }, headers);
123
+ }
124
+ return makeJson(500, { error: "InternalError" }, headers);
125
+ }
126
+ };
127
+ }
128
+
129
+ // src/rest-elysia.ts
130
+ function elysiaPlugin(app, reg, ctxFactory, options) {
131
+ const handler = createFetchHandler(reg, (req) => ctxFactory({
132
+ request: req,
133
+ store: app.store
134
+ }), options);
135
+ for (const spec of reg.list()) {
136
+ const method = spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST");
137
+ const path = (options?.basePath ?? "") + (spec.transport?.rest?.path ?? `/${spec.meta.key.replace(/\./g, "/")}/v${spec.meta.version}`);
138
+ app[method.toLowerCase()](path, ({ request }) => handler(request));
139
+ }
140
+ if (options?.cors) {
141
+ app.options("*", ({ request }) => handler(request));
142
+ }
143
+ return app;
144
+ }
145
+ export {
146
+ elysiaPlugin
147
+ };
@@ -0,0 +1,7 @@
1
+ import type { Request as ExpressReq, Router } from 'express';
2
+ import { type RestOptions } from './rest-generic';
3
+ import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
4
+ import type { HandlerCtx } from '@contractspec/lib.contracts-spec/types';
5
+ export declare function expressRouter(express: {
6
+ Router: () => Router;
7
+ }, reg: OperationSpecRegistry, ctxFactory: (req: ExpressReq) => HandlerCtx, options?: RestOptions): Router;