@marifold/service 0.60.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.
- package/LICENSE +674 -0
- package/dist/MarifoldService.d.ts +43 -0
- package/dist/MarifoldService.d.ts.map +1 -0
- package/dist/MarifoldService.js +851 -0
- package/dist/MarifoldService.js.map +1 -0
- package/dist/ProfileRoutes.d.ts +12 -0
- package/dist/ProfileRoutes.d.ts.map +1 -0
- package/dist/ProfileRoutes.js +288 -0
- package/dist/ProfileRoutes.js.map +1 -0
- package/dist/RunRoutes.d.ts +10 -0
- package/dist/RunRoutes.d.ts.map +1 -0
- package/dist/RunRoutes.js +123 -0
- package/dist/RunRoutes.js.map +1 -0
- package/dist/Security.d.ts +27 -0
- package/dist/Security.d.ts.map +1 -0
- package/dist/Security.js +231 -0
- package/dist/Security.js.map +1 -0
- package/dist/Sse.d.ts +17 -0
- package/dist/Sse.d.ts.map +1 -0
- package/dist/Sse.js +37 -0
- package/dist/Sse.js.map +1 -0
- package/dist/StaticRoutes.d.ts +16 -0
- package/dist/StaticRoutes.d.ts.map +1 -0
- package/dist/StaticRoutes.js +145 -0
- package/dist/StaticRoutes.js.map +1 -0
- package/dist/Validation.d.ts +19 -0
- package/dist/Validation.d.ts.map +1 -0
- package/dist/Validation.js +125 -0
- package/dist/Validation.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/web/assets/index-Cwd6L0Gh.js +31 -0
- package/dist/web/assets/index-FYW6vjJ9.css +1 -0
- package/dist/web/assets/officeAttachments-DGKi_KQx.js +17 -0
- package/dist/web/index.html +18 -0
- package/package.json +28 -0
package/dist/Security.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveSecurityOptions = resolveSecurityOptions;
|
|
37
|
+
exports.registerSecurity = registerSecurity;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
39
|
+
const net_1 = require("net");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const core_1 = require("@marifold/core");
|
|
42
|
+
function resolveSecurityOptions(config, overrides = {}) {
|
|
43
|
+
const configured = config?.tokenEnv ? process.env[config.tokenEnv] : config?.token;
|
|
44
|
+
const token = overrides.token ?? configured;
|
|
45
|
+
return {
|
|
46
|
+
...(token ? { token } : {}),
|
|
47
|
+
corsOrigins: overrides.corsOrigins ?? config?.corsOrigins ?? [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const CORS_METHODS = 'GET,POST,PATCH,DELETE,OPTIONS';
|
|
51
|
+
const CORS_HEADERS = 'authorization, content-type, last-event-id';
|
|
52
|
+
const CORS_MAX_AGE = '600';
|
|
53
|
+
/** Host values a default loopback-bound service legitimately sees. */
|
|
54
|
+
const LOOPBACK_HOST = /^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i;
|
|
55
|
+
/** Native EventSource cannot set headers, so the runs event stream may carry
|
|
56
|
+
* the token as ?access_token=. Fetch-based SSE with the header is preferred. */
|
|
57
|
+
const QUERY_TOKEN_PATHS = /^\/v1\/runs\/[^/]+\/events(\?|$)/;
|
|
58
|
+
/**
|
|
59
|
+
* One onRequest hook enforcing, in order: the browser origin allowlist (with
|
|
60
|
+
* preflight short-circuit), a bind-scope Host check, and optional bearer auth.
|
|
61
|
+
* Runs before route matching, so unknown routes are protected too.
|
|
62
|
+
*/
|
|
63
|
+
function registerSecurity(server, options) {
|
|
64
|
+
server.addHook('onRequest', async (request, reply) => {
|
|
65
|
+
if (options.access === 'private' && !isPrivateNetworkAddress(request.ip)) {
|
|
66
|
+
throw core_1.MarifoldError.networkForbidden(request.ip);
|
|
67
|
+
}
|
|
68
|
+
const origin = request.headers.origin;
|
|
69
|
+
if (typeof origin === 'string' && origin !== '' && !isSameAllowedOrigin(origin, request.headers.host, options)) {
|
|
70
|
+
if (!options.corsOrigins.includes(origin))
|
|
71
|
+
throw core_1.MarifoldError.originForbidden(origin);
|
|
72
|
+
reply.header('Access-Control-Allow-Origin', origin);
|
|
73
|
+
reply.header('Vary', 'Origin');
|
|
74
|
+
if (request.method === 'OPTIONS') {
|
|
75
|
+
await reply
|
|
76
|
+
.status(204)
|
|
77
|
+
.header('Access-Control-Allow-Methods', CORS_METHODS)
|
|
78
|
+
.header('Access-Control-Allow-Headers', CORS_HEADERS)
|
|
79
|
+
.header('Access-Control-Max-Age', CORS_MAX_AGE)
|
|
80
|
+
.send();
|
|
81
|
+
return reply;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const host = request.headers.host;
|
|
85
|
+
if (typeof host === 'string' && host !== '' && !isAllowedHost(host, options)) {
|
|
86
|
+
throw core_1.MarifoldError.originForbidden(host);
|
|
87
|
+
}
|
|
88
|
+
if (!options.token)
|
|
89
|
+
return;
|
|
90
|
+
// Only the stateful API is token-gated: the static Web UI shell (and
|
|
91
|
+
// /health) must stay reachable — the shell carries no secrets, and every
|
|
92
|
+
// data route is versioned under /v1. Gate on the NORMALIZED pathname (not
|
|
93
|
+
// a raw-URL prefix) so variants like //v1/x or /v1/../v1/x can't slip past
|
|
94
|
+
// the check regardless of how strictly the router matches them.
|
|
95
|
+
if (!isApiPath(request.url))
|
|
96
|
+
return;
|
|
97
|
+
const presented = extractToken(request);
|
|
98
|
+
if (!presented || !timingSafeEqualString(presented, options.token)) {
|
|
99
|
+
throw core_1.MarifoldError.unauthorized();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** True when the request targets the token-gated /v1 API, judged on the
|
|
104
|
+
* normalized decoded pathname. Undecodable or suspicious paths are treated as
|
|
105
|
+
* API (gated) — fail closed. Note: deliberately NOT `new URL(url, base)` — a
|
|
106
|
+
* raw path like `//v1/x` would parse as protocol-relative (host `v1`), hiding
|
|
107
|
+
* the very prefix this check exists to see. */
|
|
108
|
+
function isApiPath(url) {
|
|
109
|
+
let pathname = url.split(/[?#]/, 1)[0];
|
|
110
|
+
try {
|
|
111
|
+
pathname = decodeURIComponent(pathname);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (!pathname.startsWith('/') || pathname.includes('\0') || pathname.includes('\\'))
|
|
117
|
+
return true;
|
|
118
|
+
// posix.normalize resolves ./.. segments; a leading `//` is preserved by
|
|
119
|
+
// POSIX rules, so collapse leading slashes explicitly.
|
|
120
|
+
const normalized = path.posix.normalize(pathname).replace(/^\/+/, '/');
|
|
121
|
+
return normalized === '/v1' || normalized.startsWith('/v1/');
|
|
122
|
+
}
|
|
123
|
+
/** The service-hosted app is same-origin whether it was reached through the
|
|
124
|
+
* default loopback bind or an explicitly authenticated remote bind. */
|
|
125
|
+
function isSameAllowedOrigin(origin, host, options) {
|
|
126
|
+
if (!host || !isAllowedHost(host, options))
|
|
127
|
+
return false;
|
|
128
|
+
return origin === `http://${host}` || origin === `https://${host}`;
|
|
129
|
+
}
|
|
130
|
+
function isAllowedHost(host, options) {
|
|
131
|
+
if (options.access === 'loopback')
|
|
132
|
+
return LOOPBACK_HOST.test(host);
|
|
133
|
+
const hostname = hostnameFromHeader(host);
|
|
134
|
+
if (!hostname)
|
|
135
|
+
return false;
|
|
136
|
+
const normalized = hostname.toLowerCase();
|
|
137
|
+
return normalized === options.boundHost.toLowerCase()
|
|
138
|
+
|| isPrivateNetworkAddress(normalized)
|
|
139
|
+
|| !normalized.includes('.')
|
|
140
|
+
|| normalized.endsWith('.local')
|
|
141
|
+
|| normalized.endsWith('.ts.net');
|
|
142
|
+
}
|
|
143
|
+
function hostnameFromHeader(host) {
|
|
144
|
+
try {
|
|
145
|
+
const hostname = new URL(`http://${host}`).hostname;
|
|
146
|
+
return hostname.startsWith('[') && hostname.endsWith(']')
|
|
147
|
+
? hostname.slice(1, -1)
|
|
148
|
+
: hostname;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** True for directly connected client ranges intended by private access:
|
|
155
|
+
* loopback, RFC 1918 LANs, IPv4/IPv6 link-local, IPv6 ULA, and the shared
|
|
156
|
+
* 100.64/10 range used by Tailscale. X-Forwarded-For is intentionally ignored;
|
|
157
|
+
* Fastify's direct socket peer is the security boundary. */
|
|
158
|
+
function isPrivateNetworkAddress(address) {
|
|
159
|
+
const bytes = parseIpAddress(address);
|
|
160
|
+
if (!bytes)
|
|
161
|
+
return false;
|
|
162
|
+
if (bytes.length === 4)
|
|
163
|
+
return isPrivateIpv4(bytes);
|
|
164
|
+
const mapped = bytes.slice(0, 10).every(byte => byte === 0)
|
|
165
|
+
&& bytes[10] === 0xff
|
|
166
|
+
&& bytes[11] === 0xff;
|
|
167
|
+
if (mapped)
|
|
168
|
+
return isPrivateIpv4(bytes.slice(12));
|
|
169
|
+
const loopback = bytes.slice(0, 15).every(byte => byte === 0) && bytes[15] === 1;
|
|
170
|
+
const uniqueLocal = (bytes[0] & 0xfe) === 0xfc;
|
|
171
|
+
const linkLocal = bytes[0] === 0xfe && (bytes[1] & 0xc0) === 0x80;
|
|
172
|
+
return loopback || uniqueLocal || linkLocal;
|
|
173
|
+
}
|
|
174
|
+
function isPrivateIpv4(bytes) {
|
|
175
|
+
return bytes[0] === 10
|
|
176
|
+
|| bytes[0] === 127
|
|
177
|
+
|| (bytes[0] === 169 && bytes[1] === 254)
|
|
178
|
+
|| (bytes[0] === 172 && bytes[1] >= 16 && bytes[1] <= 31)
|
|
179
|
+
|| (bytes[0] === 192 && bytes[1] === 168)
|
|
180
|
+
|| (bytes[0] === 100 && bytes[1] >= 64 && bytes[1] <= 127);
|
|
181
|
+
}
|
|
182
|
+
function parseIpAddress(rawAddress) {
|
|
183
|
+
const address = rawAddress.split('%', 1)[0];
|
|
184
|
+
const version = (0, net_1.isIP)(address);
|
|
185
|
+
if (version === 4)
|
|
186
|
+
return address.split('.').map(part => Number(part));
|
|
187
|
+
if (version !== 6)
|
|
188
|
+
return undefined;
|
|
189
|
+
let ipv6 = address.toLowerCase();
|
|
190
|
+
if (ipv6.includes('.')) {
|
|
191
|
+
const lastColon = ipv6.lastIndexOf(':');
|
|
192
|
+
const ipv4 = parseIpAddress(ipv6.slice(lastColon + 1));
|
|
193
|
+
if (!ipv4 || ipv4.length !== 4)
|
|
194
|
+
return undefined;
|
|
195
|
+
ipv6 = `${ipv6.slice(0, lastColon)}:${((ipv4[0] << 8) | ipv4[1]).toString(16)}:${((ipv4[2] << 8) | ipv4[3]).toString(16)}`;
|
|
196
|
+
}
|
|
197
|
+
const halves = ipv6.split('::');
|
|
198
|
+
if (halves.length > 2)
|
|
199
|
+
return undefined;
|
|
200
|
+
const left = halves[0] ? halves[0].split(':') : [];
|
|
201
|
+
const right = halves.length === 2 && halves[1] ? halves[1].split(':') : [];
|
|
202
|
+
const missing = 8 - left.length - right.length;
|
|
203
|
+
if (missing < 0 || (halves.length === 1 && missing !== 0))
|
|
204
|
+
return undefined;
|
|
205
|
+
const words = [...left, ...Array(missing).fill('0'), ...right].map(word => Number.parseInt(word, 16));
|
|
206
|
+
if (words.length !== 8 || words.some(word => !Number.isInteger(word) || word < 0 || word > 0xffff)) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
return words.flatMap(word => [word >> 8, word & 0xff]);
|
|
210
|
+
}
|
|
211
|
+
function extractToken(request) {
|
|
212
|
+
const header = request.headers.authorization;
|
|
213
|
+
if (typeof header === 'string' && header.startsWith('Bearer ')) {
|
|
214
|
+
const token = header.slice('Bearer '.length).trim();
|
|
215
|
+
if (token)
|
|
216
|
+
return token;
|
|
217
|
+
}
|
|
218
|
+
if (request.method === 'GET' && QUERY_TOKEN_PATHS.test(request.url)) {
|
|
219
|
+
const url = new URL(request.url, 'http://loopback');
|
|
220
|
+
return url.searchParams.get('access_token') ?? undefined;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
function timingSafeEqualString(a, b) {
|
|
225
|
+
const left = Buffer.from(a);
|
|
226
|
+
const right = Buffer.from(b);
|
|
227
|
+
if (left.length !== right.length)
|
|
228
|
+
return false;
|
|
229
|
+
return crypto.timingSafeEqual(left, right);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=Security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Security.js","sourceRoot":"","sources":["../src/Security.ts"],"names":[],"mappings":"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"}
|
package/dist/Sse.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FastifyReply } from 'fastify';
|
|
2
|
+
/** Response headers for a server-sent-event stream over a hijacked reply. */
|
|
3
|
+
export declare const SSE_HEADERS: {
|
|
4
|
+
readonly 'Content-Type': 'text/event-stream; charset=utf-8';
|
|
5
|
+
readonly 'Cache-Control': 'no-cache, no-transform';
|
|
6
|
+
readonly Connection: 'keep-alive';
|
|
7
|
+
readonly 'X-Accel-Buffering': 'no';
|
|
8
|
+
};
|
|
9
|
+
/** Write one SSE frame. `id` (when given) becomes the frame's `id:` field so
|
|
10
|
+
* EventSource reconnects report it back via the Last-Event-ID header. */
|
|
11
|
+
export declare function writeSse(reply: FastifyReply, event: string, data: unknown, id?: number): void;
|
|
12
|
+
/** Suggest a client reconnect delay (EventSource honors `retry:`). */
|
|
13
|
+
export declare function writeSseRetry(reply: FastifyReply, delayMs: number): void;
|
|
14
|
+
/** Periodic comment frames so idle streams keep proxies and clients from
|
|
15
|
+
* timing out the connection. Returns the stop function; call it on close. */
|
|
16
|
+
export declare function startSseHeartbeat(reply: FastifyReply, intervalMs?: number): () => void;
|
|
17
|
+
//# sourceMappingURL=Sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sse.d.ts","sourceRoot":"","sources":["../src/Sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,6EAA6E;AAC7E,eAAO,MAAM,WAAW;aACtB,cAAc,EAAE,kCAAkC;aAClD,eAAe,EAAE,wBAAwB;aACzC,UAAU,EAAE,YAAY;aAExB,mBAAmB,EAAE,IAAI;CACjB,CAAC;AAIX;yEACyE;AACzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAI7F;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAExE;AAED;6EAC6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAwB,GAAG,MAAM,IAAI,CAMrG"}
|
package/dist/Sse.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SSE_HEADERS = void 0;
|
|
4
|
+
exports.writeSse = writeSse;
|
|
5
|
+
exports.writeSseRetry = writeSseRetry;
|
|
6
|
+
exports.startSseHeartbeat = startSseHeartbeat;
|
|
7
|
+
/** Response headers for a server-sent-event stream over a hijacked reply. */
|
|
8
|
+
exports.SSE_HEADERS = {
|
|
9
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
10
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
11
|
+
Connection: 'keep-alive',
|
|
12
|
+
// Tells buffering reverse proxies (nginx) to pass frames through untouched.
|
|
13
|
+
'X-Accel-Buffering': 'no',
|
|
14
|
+
};
|
|
15
|
+
const HEARTBEAT_INTERVAL_MS = 15_000;
|
|
16
|
+
/** Write one SSE frame. `id` (when given) becomes the frame's `id:` field so
|
|
17
|
+
* EventSource reconnects report it back via the Last-Event-ID header. */
|
|
18
|
+
function writeSse(reply, event, data, id) {
|
|
19
|
+
if (id !== undefined)
|
|
20
|
+
reply.raw.write(`id: ${id}\n`);
|
|
21
|
+
reply.raw.write(`event: ${event}\n`);
|
|
22
|
+
reply.raw.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
23
|
+
}
|
|
24
|
+
/** Suggest a client reconnect delay (EventSource honors `retry:`). */
|
|
25
|
+
function writeSseRetry(reply, delayMs) {
|
|
26
|
+
reply.raw.write(`retry: ${delayMs}\n\n`);
|
|
27
|
+
}
|
|
28
|
+
/** Periodic comment frames so idle streams keep proxies and clients from
|
|
29
|
+
* timing out the connection. Returns the stop function; call it on close. */
|
|
30
|
+
function startSseHeartbeat(reply, intervalMs = HEARTBEAT_INTERVAL_MS) {
|
|
31
|
+
const timer = setInterval(() => {
|
|
32
|
+
reply.raw.write(': ping\n\n');
|
|
33
|
+
}, intervalMs);
|
|
34
|
+
timer.unref?.();
|
|
35
|
+
return () => clearInterval(timer);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=Sse.js.map
|
package/dist/Sse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sse.js","sourceRoot":"","sources":["../src/Sse.ts"],"names":[],"mappings":";;;;;;AAEA,6EAA6E;AAChE,QAAA,WAAW,GAAG;IACzB,cAAc,EAAE,kCAAkC;IAClD,eAAe,EAAE,wBAAwB;IACzC,UAAU,EAAE,YAAY;IACxB,4EAA4E;IAC5E,mBAAmB,EAAE,IAAI;CACjB,CAAC;AAEX,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAErC;yEACyE;AACzE,kBAAyB,KAAmB,EAAE,KAAa,EAAE,IAAa,EAAE,EAAW;IACrF,IAAI,EAAE,KAAK,SAAS;QAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC;IACrC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACvD,CAAC;AAED,sEAAsE;AACtE,uBAA8B,KAAmB,EAAE,OAAe;IAChE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,OAAO,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED;6EAC6E;AAC7E,2BAAkC,KAAmB,EAAE,UAAU,GAAG,qBAAqB;IACvF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAChC,CAAC,EAAE,UAAU,CAAC,CAAC;IACf,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAChB,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
/** Resolve the Web bundle staged beside the compiled service entrypoints. */
|
|
3
|
+
export declare function resolveBundledWebDir(moduleDir?: string): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Host a built single-page Web UI from `webDir` (hand-rolled on purpose —
|
|
6
|
+
* zero-plugin house style, and the SPA/JSON-404 coordination is easier owned
|
|
7
|
+
* than configured):
|
|
8
|
+
*
|
|
9
|
+
* - `/v1/*` and `/health` fall through to the JSON 404 handler untouched.
|
|
10
|
+
* - Real files stream with their content type; Vite's content-hashed
|
|
11
|
+
* `/assets/*` get immutable caching, everything else no-cache.
|
|
12
|
+
* - Extensionless misses serve index.html (SPA fallback for clean deep links).
|
|
13
|
+
* - Paths resolving outside `webDir` are rejected (traversal guard).
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerStaticRoutes(server: FastifyInstance, webDir: string): void;
|
|
16
|
+
//# sourceMappingURL=StaticRoutes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StaticRoutes.d.ts","sourceRoot":"","sources":["../src/StaticRoutes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAqBxD,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,SAAS,SAAY,GAAG,MAAM,GAAG,SAAS,CAO9E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAuBlF"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveBundledWebDir = resolveBundledWebDir;
|
|
37
|
+
exports.registerStaticRoutes = registerStaticRoutes;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const CONTENT_TYPES = {
|
|
41
|
+
'.html': 'text/html; charset=utf-8',
|
|
42
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
43
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
44
|
+
'.css': 'text/css; charset=utf-8',
|
|
45
|
+
'.json': 'application/json; charset=utf-8',
|
|
46
|
+
'.map': 'application/json; charset=utf-8',
|
|
47
|
+
'.svg': 'image/svg+xml',
|
|
48
|
+
'.png': 'image/png',
|
|
49
|
+
'.jpg': 'image/jpeg',
|
|
50
|
+
'.jpeg': 'image/jpeg',
|
|
51
|
+
'.webp': 'image/webp',
|
|
52
|
+
'.gif': 'image/gif',
|
|
53
|
+
'.ico': 'image/x-icon',
|
|
54
|
+
'.woff': 'font/woff',
|
|
55
|
+
'.woff2': 'font/woff2',
|
|
56
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
57
|
+
};
|
|
58
|
+
/** Resolve the Web bundle staged beside the compiled service entrypoints. */
|
|
59
|
+
function resolveBundledWebDir(moduleDir = __dirname) {
|
|
60
|
+
const candidate = path.join(moduleDir, 'web');
|
|
61
|
+
try {
|
|
62
|
+
return fs.statSync(path.join(candidate, 'index.html')).isFile() ? candidate : undefined;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Host a built single-page Web UI from `webDir` (hand-rolled on purpose —
|
|
70
|
+
* zero-plugin house style, and the SPA/JSON-404 coordination is easier owned
|
|
71
|
+
* than configured):
|
|
72
|
+
*
|
|
73
|
+
* - `/v1/*` and `/health` fall through to the JSON 404 handler untouched.
|
|
74
|
+
* - Real files stream with their content type; Vite's content-hashed
|
|
75
|
+
* `/assets/*` get immutable caching, everything else no-cache.
|
|
76
|
+
* - Extensionless misses serve index.html (SPA fallback for clean deep links).
|
|
77
|
+
* - Paths resolving outside `webDir` are rejected (traversal guard).
|
|
78
|
+
*/
|
|
79
|
+
function registerStaticRoutes(server, webDir) {
|
|
80
|
+
const root = path.resolve(webDir);
|
|
81
|
+
server.get('/*', async (request, reply) => {
|
|
82
|
+
const pathname = decodePathname(request.url);
|
|
83
|
+
if (pathname === undefined)
|
|
84
|
+
return notFound(reply);
|
|
85
|
+
if (pathname === '/health' || pathname === '/v1' || pathname.startsWith('/v1/')) {
|
|
86
|
+
return notFound(reply);
|
|
87
|
+
}
|
|
88
|
+
const resolved = path.resolve(root, `.${path.posix.normalize(pathname)}`);
|
|
89
|
+
if (resolved !== root && !resolved.startsWith(root + path.sep))
|
|
90
|
+
return notFound(reply);
|
|
91
|
+
const filePath = await resolveFile(resolved);
|
|
92
|
+
if (filePath)
|
|
93
|
+
return sendFile(reply, filePath, pathname);
|
|
94
|
+
// SPA fallback: routes without an extension are app views.
|
|
95
|
+
if (path.posix.extname(pathname) === '') {
|
|
96
|
+
const index = await resolveFile(path.join(root, 'index.html'));
|
|
97
|
+
if (index)
|
|
98
|
+
return sendFile(reply, index, '/index.html');
|
|
99
|
+
}
|
|
100
|
+
return notFound(reply);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function decodePathname(url) {
|
|
104
|
+
try {
|
|
105
|
+
const pathname = new URL(url, 'http://loopback').pathname;
|
|
106
|
+
const decoded = decodeURIComponent(pathname);
|
|
107
|
+
// Encoded separators or null bytes have no legitimate use here.
|
|
108
|
+
if (decoded.includes('\0') || decoded.includes('\\'))
|
|
109
|
+
return undefined;
|
|
110
|
+
return decoded;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function resolveFile(candidate) {
|
|
117
|
+
try {
|
|
118
|
+
const stat = await fs.promises.stat(candidate);
|
|
119
|
+
if (stat.isFile())
|
|
120
|
+
return candidate;
|
|
121
|
+
if (stat.isDirectory()) {
|
|
122
|
+
const index = path.join(candidate, 'index.html');
|
|
123
|
+
const indexStat = await fs.promises.stat(index).catch(() => undefined);
|
|
124
|
+
if (indexStat?.isFile())
|
|
125
|
+
return index;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Missing — the caller decides between SPA fallback and 404.
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
function sendFile(reply, filePath, pathname) {
|
|
134
|
+
const type = CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';
|
|
135
|
+
const immutable = pathname.startsWith('/assets/');
|
|
136
|
+
return reply
|
|
137
|
+
.header('content-type', type)
|
|
138
|
+
.header('cache-control', immutable ? 'public, max-age=31536000, immutable' : 'no-cache')
|
|
139
|
+
.send(fs.createReadStream(filePath));
|
|
140
|
+
}
|
|
141
|
+
function notFound(reply) {
|
|
142
|
+
reply.callNotFound();
|
|
143
|
+
return reply;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=StaticRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StaticRoutes.js","sourceRoot":"","sources":["../src/StaticRoutes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ImageInput, RunFileInput } from '@marifold/core';
|
|
2
|
+
export type JsonObject = Record<string, unknown>;
|
|
3
|
+
export declare function objectBody(value: unknown): JsonObject;
|
|
4
|
+
export declare function requiredString(value: unknown, label: string): string;
|
|
5
|
+
export declare function stringValue(value: unknown, label: string): string;
|
|
6
|
+
export declare function optionalStringField<Key extends string>(key: Key, value: unknown): Record<Key, string> | Record<string, never>;
|
|
7
|
+
export declare function optionalBooleanField<Key extends string>(key: Key, value: unknown): Record<Key, boolean> | Record<string, never>;
|
|
8
|
+
export declare function optionalPositiveIntegerField<Key extends string>(key: Key, value: unknown): Record<Key, number> | Record<string, never>;
|
|
9
|
+
export declare function optionalNonNegativeIntegerField<Key extends string>(key: Key, value: unknown): Record<Key, number> | Record<string, never>;
|
|
10
|
+
export declare function stringArray(value: unknown, label: string): string[];
|
|
11
|
+
export declare function optionalImagesField(value: unknown): {
|
|
12
|
+
images: ImageInput[];
|
|
13
|
+
} | Record<string, never>;
|
|
14
|
+
/** Binary run inputs are base64 JSON because the service remains a compact
|
|
15
|
+
* loopback API. Core stages them into the run's read-only input directory. */
|
|
16
|
+
export declare function optionalRunFilesField(value: unknown): {
|
|
17
|
+
files: RunFileInput[];
|
|
18
|
+
} | Record<string, never>;
|
|
19
|
+
//# sourceMappingURL=Validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Validation.d.ts","sourceRoot":"","sources":["../src/Validation.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIV,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAGrD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAG7H;AAED,wBAAgB,oBAAoB,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAI/H;AAED,wBAAgB,4BAA4B,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAMtI;AAED,wBAAgB,+BAA+B,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAMzI;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGnE;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,MAAM,EAAE,UAAU,EAAE,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAoBpG;AAED;8EAC8E;AAC9E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAiCvG"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.objectBody = objectBody;
|
|
4
|
+
exports.requiredString = requiredString;
|
|
5
|
+
exports.stringValue = stringValue;
|
|
6
|
+
exports.optionalStringField = optionalStringField;
|
|
7
|
+
exports.optionalBooleanField = optionalBooleanField;
|
|
8
|
+
exports.optionalPositiveIntegerField = optionalPositiveIntegerField;
|
|
9
|
+
exports.optionalNonNegativeIntegerField = optionalNonNegativeIntegerField;
|
|
10
|
+
exports.stringArray = stringArray;
|
|
11
|
+
exports.optionalImagesField = optionalImagesField;
|
|
12
|
+
exports.optionalRunFilesField = optionalRunFilesField;
|
|
13
|
+
const core_1 = require("@marifold/core");
|
|
14
|
+
function objectBody(value) {
|
|
15
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value))
|
|
16
|
+
return value;
|
|
17
|
+
throw core_1.MarifoldError.configInvalid('Expected a JSON object request body.');
|
|
18
|
+
}
|
|
19
|
+
function requiredString(value, label) {
|
|
20
|
+
const text = stringValue(value, label);
|
|
21
|
+
if (!text.trim())
|
|
22
|
+
throw core_1.MarifoldError.configInvalid(`${label} cannot be empty.`);
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
function stringValue(value, label) {
|
|
26
|
+
if (typeof value !== 'string')
|
|
27
|
+
throw core_1.MarifoldError.configInvalid(`${label} must be a string.`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function optionalStringField(key, value) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return {};
|
|
33
|
+
return { [key]: stringValue(value, key) };
|
|
34
|
+
}
|
|
35
|
+
function optionalBooleanField(key, value) {
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
return {};
|
|
38
|
+
if (typeof value !== 'boolean')
|
|
39
|
+
throw core_1.MarifoldError.configInvalid(`${key} must be a boolean.`);
|
|
40
|
+
return { [key]: value };
|
|
41
|
+
}
|
|
42
|
+
function optionalPositiveIntegerField(key, value) {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return {};
|
|
45
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
|
|
46
|
+
throw core_1.MarifoldError.configInvalid(`${key} must be a positive integer.`);
|
|
47
|
+
}
|
|
48
|
+
return { [key]: value };
|
|
49
|
+
}
|
|
50
|
+
function optionalNonNegativeIntegerField(key, value) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
return {};
|
|
53
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
54
|
+
throw core_1.MarifoldError.configInvalid(`${key} must be a non-negative integer.`);
|
|
55
|
+
}
|
|
56
|
+
return { [key]: value };
|
|
57
|
+
}
|
|
58
|
+
function stringArray(value, label) {
|
|
59
|
+
if (!Array.isArray(value))
|
|
60
|
+
throw core_1.MarifoldError.configInvalid(`${label} must be an array of strings.`);
|
|
61
|
+
return value.map((item, index) => stringValue(item, `${label}[${index}]`));
|
|
62
|
+
}
|
|
63
|
+
// App clients send images as base64 payloads ({data, mediaType}) or URLs;
|
|
64
|
+
// local file paths are not accepted over the service boundary.
|
|
65
|
+
function optionalImagesField(value) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return {};
|
|
68
|
+
if (!Array.isArray(value))
|
|
69
|
+
throw core_1.MarifoldError.configInvalid('Expected images to be an array.');
|
|
70
|
+
const images = value.map((item, index) => {
|
|
71
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
72
|
+
throw core_1.MarifoldError.configInvalid(`Expected images[${index}] to be an object.`);
|
|
73
|
+
}
|
|
74
|
+
const image = item;
|
|
75
|
+
const data = typeof image.data === 'string' && image.data ? image.data : undefined;
|
|
76
|
+
const url = typeof image.url === 'string' && image.url ? image.url : undefined;
|
|
77
|
+
if ((data === undefined) === (url === undefined)) {
|
|
78
|
+
throw core_1.MarifoldError.configInvalid(`Expected images[${index}] to set exactly one of data or url.`);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...(data !== undefined ? { data } : {}),
|
|
82
|
+
...(url !== undefined ? { url } : {}),
|
|
83
|
+
...(typeof image.mediaType === 'string' && image.mediaType ? { mediaType: image.mediaType } : {}),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return { images };
|
|
87
|
+
}
|
|
88
|
+
/** Binary run inputs are base64 JSON because the service remains a compact
|
|
89
|
+
* loopback API. Core stages them into the run's read-only input directory. */
|
|
90
|
+
function optionalRunFilesField(value) {
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
return {};
|
|
93
|
+
if (!Array.isArray(value))
|
|
94
|
+
throw core_1.MarifoldError.configInvalid('Expected files to be an array.');
|
|
95
|
+
let total = 0;
|
|
96
|
+
const files = value.map((item, index) => {
|
|
97
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
98
|
+
throw core_1.MarifoldError.configInvalid(`Expected files[${index}] to be an object.`);
|
|
99
|
+
}
|
|
100
|
+
const file = item;
|
|
101
|
+
const name = requiredString(file.name, `files[${index}].name`);
|
|
102
|
+
const mediaType = requiredString(file.mediaType, `files[${index}].mediaType`);
|
|
103
|
+
const data = requiredString(file.data, `files[${index}].data`);
|
|
104
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(data) || data.length % 4 === 1) {
|
|
105
|
+
throw core_1.MarifoldError.configInvalid(`files[${index}].data must be base64.`);
|
|
106
|
+
}
|
|
107
|
+
const size = Buffer.from(data, 'base64').length;
|
|
108
|
+
if (size === 0)
|
|
109
|
+
throw core_1.MarifoldError.configInvalid(`files[${index}].data cannot be empty.`);
|
|
110
|
+
total += size;
|
|
111
|
+
if (total > core_1.MAX_RUN_INPUT_BYTES) {
|
|
112
|
+
throw core_1.MarifoldError.configInvalid(`Run files exceed ${core_1.MAX_RUN_INPUT_BYTES / (1024 * 1024)} MiB.`);
|
|
113
|
+
}
|
|
114
|
+
let inspectionText;
|
|
115
|
+
if (file.inspectionText !== undefined) {
|
|
116
|
+
inspectionText = stringValue(file.inspectionText, `files[${index}].inspectionText`);
|
|
117
|
+
if (Buffer.byteLength(inspectionText, 'utf8') > core_1.MAX_RUN_INSPECTION_TEXT_BYTES) {
|
|
118
|
+
throw core_1.MarifoldError.configInvalid(`files[${index}].inspectionText exceeds ${core_1.MAX_RUN_INSPECTION_TEXT_BYTES / 1024} KiB.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { name, mediaType, data, ...(inspectionText !== undefined ? { inspectionText } : {}) };
|
|
122
|
+
});
|
|
123
|
+
return { files };
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=Validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Validation.js","sourceRoot":"","sources":["../src/Validation.ts"],"names":[],"mappings":"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"}
|