@epoch32/visage 1.1.3 → 1.1.4
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/dist/api/index.js +55 -4085
- package/dist/charts/index.js +48 -2806
- package/dist/devtools/index.js +18 -4771
- package/dist/dom/index.js +32 -1639
- package/dist/form/index.js +13 -1498
- package/dist/index-061jf06a.js +848 -0
- package/dist/index-0madxn60.js +442 -0
- package/dist/index-1xtrtz1t.js +256 -0
- package/dist/index-2zz0g2n2.js +212 -0
- package/dist/index-5s255b3w.js +303 -0
- package/dist/index-6kfe1nr4.js +1179 -0
- package/dist/index-8czhpea8.js +338 -0
- package/dist/index-8x8xz80v.js +727 -0
- package/dist/index-921pn75b.js +0 -0
- package/dist/index-965ryh36.js +537 -0
- package/dist/index-9fvarkrx.js +98 -0
- package/dist/index-a4xns6cc.js +219 -0
- package/dist/index-b5wc752a.js +1696 -0
- package/dist/index-df5e614s.js +334 -0
- package/dist/index-dyvgtp43.js +1094 -0
- package/dist/index-grhdtcb5.js +234 -0
- package/dist/index-h6tjqk6e.js +158 -0
- package/dist/index-js199er4.js +101 -0
- package/dist/index-x2rs3vab.js +124 -0
- package/dist/index-y9bq7h9g.js +1162 -0
- package/dist/index-zr6cvset.js +2092 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +458 -11920
- package/dist/jsx-runtime/index.js +13 -1719
- package/dist/query/index.js +47 -2505
- package/dist/router/index.js +25 -1945
- package/dist/rpc/index.js +39 -3567
- package/dist/server/index.js +11 -1256
- package/dist/signals/index.js +52 -1160
- package/dist/style/index.js +77 -2429
- package/dist/template/index.js +7 -1232
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +150 -4249
- package/dist/ui/tabs.d.ts +23 -1
- package/dist/virtual/index.js +12 -2044
- package/dist/vite/index.js +5 -744
- package/package.json +2 -2
- package/src/dom/render.ts +20 -7
- package/src/index.ts +2 -0
- package/src/signals/computed.ts +19 -12
- package/src/signals/effect.ts +7 -7
- package/src/signals/signal.ts +16 -12
- package/src/signals/system.ts +50 -21
- package/src/ui/index.ts +2 -0
- package/src/ui/tabs.tsx +211 -10
- package/src/ui/utils.ts +1 -3
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERROR_CODE_TO_STATUS2,
|
|
3
|
+
RPCError2,
|
|
4
|
+
matchesRulePattern
|
|
5
|
+
} from "./index-965ryh36.js";
|
|
6
|
+
import {
|
|
7
|
+
validateWithSchema2
|
|
8
|
+
} from "./index-2zz0g2n2.js";
|
|
9
|
+
import {
|
|
10
|
+
createMutation2,
|
|
11
|
+
createQuery2
|
|
12
|
+
} from "./index-y9bq7h9g.js";
|
|
13
|
+
// src/api/client.ts
|
|
14
|
+
async function resolveHeaders(headers, fullPath = "") {
|
|
15
|
+
if (!headers)
|
|
16
|
+
return {};
|
|
17
|
+
if (typeof headers === "function") {
|
|
18
|
+
return await headers();
|
|
19
|
+
}
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
22
|
+
if (typeof val === "object" || typeof val === "function") {
|
|
23
|
+
if (matchesRulePattern(key, fullPath, fullPath)) {
|
|
24
|
+
const resolved = typeof val === "function" ? await val() : val;
|
|
25
|
+
if (resolved && typeof resolved === "object") {
|
|
26
|
+
Object.assign(result, resolved);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else if (typeof val === "string") {
|
|
30
|
+
result[key] = val;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
function buildUrl(baseUrl, pathSegments, queryParams) {
|
|
36
|
+
const cleanBase = baseUrl.replace(/\/+$/, "");
|
|
37
|
+
const cleanPath = pathSegments.filter(Boolean).join("/");
|
|
38
|
+
let url = cleanPath ? `${cleanBase}/${cleanPath}` : cleanBase;
|
|
39
|
+
if (queryParams !== undefined && queryParams !== null) {
|
|
40
|
+
if (typeof queryParams === "object") {
|
|
41
|
+
const params = new URLSearchParams;
|
|
42
|
+
for (const [k, v] of Object.entries(queryParams)) {
|
|
43
|
+
if (v !== undefined && v !== null) {
|
|
44
|
+
if (typeof v === "object") {
|
|
45
|
+
params.set(k, JSON.stringify(v));
|
|
46
|
+
} else {
|
|
47
|
+
params.set(k, String(v));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const qs = params.toString();
|
|
52
|
+
if (qs) {
|
|
53
|
+
url += `${url.includes("?") ? "&" : "?"}${qs}`;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
url += `${url.includes("?") ? "&" : "?"}input=${encodeURIComponent(String(queryParams))}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
function createApiProxy(options, path = []) {
|
|
62
|
+
const handlerFn = (...args) => {
|
|
63
|
+
if (args.length > 0) {
|
|
64
|
+
return createApiProxy(options, [...path, String(args[0])]);
|
|
65
|
+
}
|
|
66
|
+
return createApiProxy(options, path);
|
|
67
|
+
};
|
|
68
|
+
return new Proxy(handlerFn, {
|
|
69
|
+
get(_target, prop) {
|
|
70
|
+
if (prop === "then")
|
|
71
|
+
return;
|
|
72
|
+
const fullPath = path.join("/");
|
|
73
|
+
if (prop === "get") {
|
|
74
|
+
return async (input, callOpts) => {
|
|
75
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
76
|
+
const url = buildUrl(options.baseUrl, path, input);
|
|
77
|
+
const dynamicHeaders = await resolveHeaders(options.headers, fullPath);
|
|
78
|
+
const headers = {
|
|
79
|
+
Accept: "application/json",
|
|
80
|
+
...dynamicHeaders,
|
|
81
|
+
...callOpts?.headers
|
|
82
|
+
};
|
|
83
|
+
const devtools = typeof window !== "undefined" ? window.__VISAGE_DEVTOOLS__ : null;
|
|
84
|
+
const logId = devtools?.logRpcCall({
|
|
85
|
+
id: `api_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
86
|
+
path: fullPath,
|
|
87
|
+
type: "query",
|
|
88
|
+
input
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetchFn(url, {
|
|
92
|
+
method: "GET",
|
|
93
|
+
headers,
|
|
94
|
+
signal: callOpts?.signal
|
|
95
|
+
});
|
|
96
|
+
const contentType = res.headers.get("Content-Type") || "";
|
|
97
|
+
if (contentType.includes("application/json")) {
|
|
98
|
+
const body = await res.json();
|
|
99
|
+
if (body && typeof body === "object" && "ok" in body) {
|
|
100
|
+
if (!body.ok) {
|
|
101
|
+
devtools?.updateRpcCall(logId, { status: "error", error: body.error });
|
|
102
|
+
throw new RPCError2({
|
|
103
|
+
code: body.error?.code || "INTERNAL_SERVER_ERROR",
|
|
104
|
+
message: body.error?.message || "Request failed",
|
|
105
|
+
issues: body.error?.issues
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
devtools?.updateRpcCall(logId, { status: "success", response: body.data });
|
|
109
|
+
return body.data;
|
|
110
|
+
}
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
113
|
+
}
|
|
114
|
+
devtools?.updateRpcCall(logId, { status: "success", response: body });
|
|
115
|
+
return body;
|
|
116
|
+
}
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
119
|
+
}
|
|
120
|
+
const text = await res.text();
|
|
121
|
+
devtools?.updateRpcCall(logId, { status: "success", response: text });
|
|
122
|
+
return text;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
devtools?.updateRpcCall(logId, { status: "error", error: err });
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (prop === "post" || prop === "put" || prop === "patch" || prop === "delete" || prop === "del") {
|
|
130
|
+
const httpMethod = prop === "del" ? "DELETE" : prop.toUpperCase();
|
|
131
|
+
return async (body, callOpts) => {
|
|
132
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
133
|
+
const url = buildUrl(options.baseUrl, path);
|
|
134
|
+
const dynamicHeaders = await resolveHeaders(options.headers, fullPath);
|
|
135
|
+
const headers = {
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
Accept: "application/json",
|
|
138
|
+
...dynamicHeaders,
|
|
139
|
+
...callOpts?.headers
|
|
140
|
+
};
|
|
141
|
+
const devtools = typeof window !== "undefined" ? window.__VISAGE_DEVTOOLS__ : null;
|
|
142
|
+
const logId = devtools?.logRpcCall({
|
|
143
|
+
id: `api_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
144
|
+
path: fullPath,
|
|
145
|
+
type: "mutation",
|
|
146
|
+
input: body
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetchFn(url, {
|
|
150
|
+
method: httpMethod,
|
|
151
|
+
headers,
|
|
152
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
153
|
+
signal: callOpts?.signal
|
|
154
|
+
});
|
|
155
|
+
const contentType = res.headers.get("Content-Type") || "";
|
|
156
|
+
if (contentType.includes("application/json")) {
|
|
157
|
+
const resBody = await res.json();
|
|
158
|
+
if (resBody && typeof resBody === "object" && "ok" in resBody) {
|
|
159
|
+
if (!resBody.ok) {
|
|
160
|
+
devtools?.updateRpcCall(logId, { status: "error", error: resBody.error });
|
|
161
|
+
throw new RPCError2({
|
|
162
|
+
code: resBody.error?.code || "INTERNAL_SERVER_ERROR",
|
|
163
|
+
message: resBody.error?.message || "Request failed",
|
|
164
|
+
issues: resBody.error?.issues
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
devtools?.updateRpcCall(logId, { status: "success", response: resBody.data });
|
|
168
|
+
return resBody.data;
|
|
169
|
+
}
|
|
170
|
+
if (!res.ok) {
|
|
171
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
172
|
+
}
|
|
173
|
+
devtools?.updateRpcCall(logId, { status: "success", response: resBody });
|
|
174
|
+
return resBody;
|
|
175
|
+
}
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
178
|
+
}
|
|
179
|
+
const text = await res.text();
|
|
180
|
+
devtools?.updateRpcCall(logId, { status: "success", response: text });
|
|
181
|
+
return text;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
devtools?.updateRpcCall(logId, { status: "error", error: err });
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (prop === "subscribe") {
|
|
189
|
+
return (input, callbacks) => {
|
|
190
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
191
|
+
const url = buildUrl(options.baseUrl, path, input);
|
|
192
|
+
const controller = new AbortController;
|
|
193
|
+
const devtools = typeof window !== "undefined" ? window.__VISAGE_DEVTOOLS__ : null;
|
|
194
|
+
const logId = devtools?.logRpcCall({
|
|
195
|
+
id: `api_sse_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
196
|
+
path: fullPath,
|
|
197
|
+
type: "subscription",
|
|
198
|
+
input
|
|
199
|
+
});
|
|
200
|
+
(async () => {
|
|
201
|
+
try {
|
|
202
|
+
const dynamicHeaders = await resolveHeaders(options.headers, fullPath);
|
|
203
|
+
const headers = {
|
|
204
|
+
Accept: "text/event-stream",
|
|
205
|
+
...dynamicHeaders
|
|
206
|
+
};
|
|
207
|
+
const res = await fetchFn(url, {
|
|
208
|
+
method: "GET",
|
|
209
|
+
headers,
|
|
210
|
+
signal: controller.signal
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok || !res.body) {
|
|
213
|
+
throw new Error(`SSE stream failed with status ${res.status}`);
|
|
214
|
+
}
|
|
215
|
+
const reader = res.body.getReader();
|
|
216
|
+
const decoder = new TextDecoder;
|
|
217
|
+
let buffer = "";
|
|
218
|
+
while (true) {
|
|
219
|
+
const { done, value } = await reader.read();
|
|
220
|
+
if (done)
|
|
221
|
+
break;
|
|
222
|
+
buffer += decoder.decode(value, { stream: true });
|
|
223
|
+
const lines = buffer.split(`
|
|
224
|
+
|
|
225
|
+
`);
|
|
226
|
+
buffer = lines.pop() ?? "";
|
|
227
|
+
for (const line of lines) {
|
|
228
|
+
const trimmed = line.trim();
|
|
229
|
+
if (trimmed.startsWith("data:")) {
|
|
230
|
+
const jsonStr = trimmed.slice(5).trim();
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(jsonStr);
|
|
233
|
+
devtools?.logRpcEvent(logId, parsed);
|
|
234
|
+
callbacks.onData(parsed);
|
|
235
|
+
} catch (parseErr) {
|
|
236
|
+
devtools?.updateRpcCall(logId, { status: "error", error: parseErr });
|
|
237
|
+
callbacks.onError?.(parseErr);
|
|
238
|
+
}
|
|
239
|
+
} else if (trimmed.startsWith("event: error")) {
|
|
240
|
+
devtools?.updateRpcCall(logId, { status: "error" });
|
|
241
|
+
callbacks.onError?.(new Error("Server SSE error"));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
callbacks.onComplete?.();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (!controller.signal.aborted) {
|
|
248
|
+
callbacks.onError?.(err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
return {
|
|
253
|
+
unsubscribe: () => {
|
|
254
|
+
controller.abort();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (prop === "queryKey") {
|
|
260
|
+
return (input) => {
|
|
261
|
+
const resolved = typeof input === "function" ? input() : input;
|
|
262
|
+
const key = [...path];
|
|
263
|
+
if (resolved !== undefined)
|
|
264
|
+
key.push(resolved);
|
|
265
|
+
return key;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (prop === "queryOptions") {
|
|
269
|
+
return (input, extra) => {
|
|
270
|
+
const resolved = typeof input === "function" ? input() : input;
|
|
271
|
+
const key = [...path];
|
|
272
|
+
if (resolved !== undefined)
|
|
273
|
+
key.push(resolved);
|
|
274
|
+
return {
|
|
275
|
+
queryKey: key,
|
|
276
|
+
queryFn: ({ signal }) => {
|
|
277
|
+
const proxy = createApiProxy(options, path);
|
|
278
|
+
return proxy.get(resolved, { signal });
|
|
279
|
+
},
|
|
280
|
+
...extra
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (prop === "createQuery" || prop === "useQuery") {
|
|
285
|
+
return (input, extra) => {
|
|
286
|
+
const proxy = createApiProxy(options, path);
|
|
287
|
+
return createQuery2(() => proxy.queryOptions(input, extra));
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (prop === "mutationKey") {
|
|
291
|
+
return () => [...path];
|
|
292
|
+
}
|
|
293
|
+
if (prop === "mutationOptions") {
|
|
294
|
+
return (extra) => {
|
|
295
|
+
return {
|
|
296
|
+
mutationKey: [...path],
|
|
297
|
+
mutationFn: (body) => {
|
|
298
|
+
const proxy = createApiProxy(options, path);
|
|
299
|
+
return proxy.post(body);
|
|
300
|
+
},
|
|
301
|
+
...extra
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (prop === "createMutation" || prop === "useMutation") {
|
|
306
|
+
return (extra) => {
|
|
307
|
+
const proxy = createApiProxy(options, path);
|
|
308
|
+
return createMutation2(proxy.mutationOptions(extra));
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return createApiProxy(options, [...path, prop]);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function createApiClient2(options) {
|
|
316
|
+
return createApiProxy(options, []);
|
|
317
|
+
}
|
|
318
|
+
// src/api/pragma.ts
|
|
319
|
+
var PRAGMA_REGEX = /(?:\/\/\s*@visage-middleware\s+([^\r\n]+))|(?:\/\*\s*@visage-middleware\s+([\s\S]*?)\*\/)/i;
|
|
320
|
+
function parsePragma2(sourceCode) {
|
|
321
|
+
if (!sourceCode || typeof sourceCode !== "string") {
|
|
322
|
+
return { cascade: true, match: ["*"], exclude: [], priority: 0 };
|
|
323
|
+
}
|
|
324
|
+
const match = sourceCode.match(PRAGMA_REGEX);
|
|
325
|
+
if (!match) {
|
|
326
|
+
return { cascade: true, match: ["*"], exclude: [], priority: 0 };
|
|
327
|
+
}
|
|
328
|
+
const rawDirective = (match[1] || match[2] || "").trim();
|
|
329
|
+
const pragma = {
|
|
330
|
+
cascade: true,
|
|
331
|
+
match: ["*"],
|
|
332
|
+
exclude: [],
|
|
333
|
+
priority: 0
|
|
334
|
+
};
|
|
335
|
+
const pairs = rawDirective.split(/,(?![^[]*\])/);
|
|
336
|
+
for (const pair of pairs) {
|
|
337
|
+
const colonIdx = pair.indexOf(":");
|
|
338
|
+
if (colonIdx === -1)
|
|
339
|
+
continue;
|
|
340
|
+
const key = pair.slice(0, colonIdx).trim().toLowerCase();
|
|
341
|
+
const valStr = pair.slice(colonIdx + 1).trim();
|
|
342
|
+
if (key === "cascade") {
|
|
343
|
+
pragma.cascade = valStr === "true" || valStr === "1";
|
|
344
|
+
} else if (key === "priority") {
|
|
345
|
+
const num = Number.parseInt(valStr, 10);
|
|
346
|
+
if (!Number.isNaN(num))
|
|
347
|
+
pragma.priority = num;
|
|
348
|
+
} else if (key === "match" || key === "exclude") {
|
|
349
|
+
let items = [];
|
|
350
|
+
if (valStr.startsWith("[") && valStr.endsWith("]")) {
|
|
351
|
+
try {
|
|
352
|
+
items = JSON.parse(valStr.replace(/'/g, '"'));
|
|
353
|
+
} catch {
|
|
354
|
+
items = valStr.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
items = [valStr.replace(/^['"]|['"]$/g, "")];
|
|
358
|
+
}
|
|
359
|
+
if (key === "match")
|
|
360
|
+
pragma.match = items;
|
|
361
|
+
if (key === "exclude")
|
|
362
|
+
pragma.exclude = items;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return pragma;
|
|
366
|
+
}
|
|
367
|
+
function globToRegExp2(pattern) {
|
|
368
|
+
const normalized = pattern.trim().replace(/^\/+|\/+$/g, "");
|
|
369
|
+
if (normalized === "*" || normalized === "**" || normalized === "") {
|
|
370
|
+
return /^.*$/;
|
|
371
|
+
}
|
|
372
|
+
const escaped = normalized.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLE_STAR___").replace(/\*/g, "[^/]+").replace(/___DOUBLE_STAR___/g, ".*");
|
|
373
|
+
return new RegExp(`^(?:/)?${escaped}(?:/)?$`);
|
|
374
|
+
}
|
|
375
|
+
function matchesAnyPattern2(routePath, patterns) {
|
|
376
|
+
if (!patterns || patterns.length === 0)
|
|
377
|
+
return false;
|
|
378
|
+
const cleanPath = routePath.replace(/^\/+|\/+$/g, "");
|
|
379
|
+
for (const pat of patterns) {
|
|
380
|
+
const cleanPat = pat.replace(/^\/+|\/+$/g, "");
|
|
381
|
+
if (cleanPat === "*" || cleanPat === "**")
|
|
382
|
+
return true;
|
|
383
|
+
if (cleanPat === cleanPath)
|
|
384
|
+
return true;
|
|
385
|
+
if (globToRegExp2(cleanPat).test(cleanPath))
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
function shouldApplyMiddleware2(pragma, routePath, routeDir, middlewareDir) {
|
|
391
|
+
const normRouteDir = routeDir.replace(/^\/+|\/+$/g, "");
|
|
392
|
+
const normMwDir = middlewareDir.replace(/^\/+|\/+$/g, "");
|
|
393
|
+
const isDirectDir = normRouteDir === normMwDir;
|
|
394
|
+
const isSubDir = normMwDir === "" ? normRouteDir !== "" : normRouteDir.startsWith(`${normMwDir}/`);
|
|
395
|
+
if (!isDirectDir && !isSubDir) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
const isCascade = pragma.cascade ?? true;
|
|
399
|
+
if (isSubDir && !isCascade) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
let relativePath = routePath.replace(/^\/+|\/+$/g, "");
|
|
403
|
+
if (normMwDir && relativePath.startsWith(normMwDir)) {
|
|
404
|
+
relativePath = relativePath.slice(normMwDir.length).replace(/^\/+/, "");
|
|
405
|
+
}
|
|
406
|
+
if (pragma.exclude && pragma.exclude.length > 0) {
|
|
407
|
+
if (matchesAnyPattern2(routePath, pragma.exclude) || matchesAnyPattern2(relativePath, pragma.exclude)) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (!pragma.match || pragma.match.length === 0 || pragma.match.includes("*")) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
return matchesAnyPattern2(routePath, pragma.match) || matchesAnyPattern2(relativePath, pragma.match);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/api/file-router.ts
|
|
418
|
+
function compileRoutePattern2(path) {
|
|
419
|
+
const paramNames = [];
|
|
420
|
+
const normalized = path.replace(/\/+$/, "") || "/";
|
|
421
|
+
const regexStr = normalized.replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {
|
|
422
|
+
paramNames.push(name);
|
|
423
|
+
return "([^/]+)";
|
|
424
|
+
}).replace(/\*([a-zA-Z0-9_]+)/g, (_, name) => {
|
|
425
|
+
paramNames.push(name);
|
|
426
|
+
return "(.*)";
|
|
427
|
+
});
|
|
428
|
+
const pattern = new RegExp(`^${regexStr}(?:/)?$`);
|
|
429
|
+
return { pattern, paramNames };
|
|
430
|
+
}
|
|
431
|
+
function normalizeFilePath2(rawPath) {
|
|
432
|
+
const cleaned = rawPath.replace(/^\.\//, "").replace(/\.(ts|tsx|js|jsx)$/, "").replace(/^(src\/)?(routes|api)\//, "");
|
|
433
|
+
const parts = cleaned.split("/").filter(Boolean);
|
|
434
|
+
const fileName = parts.pop() || "";
|
|
435
|
+
const isMiddleware = fileName === "_middleware";
|
|
436
|
+
const dirParts = [...parts];
|
|
437
|
+
const directory = `/${dirParts.join("/")}`.replace(/\/+$/, "") || "/";
|
|
438
|
+
if (isMiddleware) {
|
|
439
|
+
return {
|
|
440
|
+
isMiddleware: true,
|
|
441
|
+
routePath: directory,
|
|
442
|
+
directory
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (fileName !== "index") {
|
|
446
|
+
const transformedFile = fileName.replace(/\[\.\.\.([a-zA-Z0-9_]+)\]/g, "*$1").replace(/\[([a-zA-Z0-9_]+)\]/g, ":$1");
|
|
447
|
+
parts.push(transformedFile);
|
|
448
|
+
}
|
|
449
|
+
const routePath = `/${parts.join("/")}`.replace(/\/+$/, "") || "/";
|
|
450
|
+
return {
|
|
451
|
+
isMiddleware: false,
|
|
452
|
+
routePath,
|
|
453
|
+
directory
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function buildFileRouter2(files, sourceMap = {}) {
|
|
457
|
+
const middlewaresByDir = [];
|
|
458
|
+
const rawRoutes = [];
|
|
459
|
+
for (const [filePath, mod] of Object.entries(files)) {
|
|
460
|
+
const { isMiddleware, routePath, directory } = normalizeFilePath2(filePath);
|
|
461
|
+
const sourceCode = sourceMap[filePath] || "";
|
|
462
|
+
const pragma = mod.pragma || parsePragma2(sourceCode);
|
|
463
|
+
if (isMiddleware) {
|
|
464
|
+
const mwFn = typeof mod === "function" ? mod : mod.middleware || mod.default;
|
|
465
|
+
if (typeof mwFn === "function") {
|
|
466
|
+
middlewaresByDir.push({
|
|
467
|
+
fn: mwFn,
|
|
468
|
+
pragma,
|
|
469
|
+
filePath,
|
|
470
|
+
directory
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
rawRoutes.push({
|
|
475
|
+
filePath,
|
|
476
|
+
routePath,
|
|
477
|
+
directory,
|
|
478
|
+
module: mod
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
middlewaresByDir.sort((a, b) => {
|
|
483
|
+
const dirA = a.directory || "/";
|
|
484
|
+
const dirB = b.directory || "/";
|
|
485
|
+
const depthA = dirA.split("/").filter(Boolean).length;
|
|
486
|
+
const depthB = dirB.split("/").filter(Boolean).length;
|
|
487
|
+
if (depthA !== depthB)
|
|
488
|
+
return depthA - depthB;
|
|
489
|
+
return (b.pragma.priority ?? 0) - (a.pragma.priority ?? 0);
|
|
490
|
+
});
|
|
491
|
+
const routes = [];
|
|
492
|
+
for (const raw of rawRoutes) {
|
|
493
|
+
const applicableMiddlewares = middlewaresByDir.filter((mw) => shouldApplyMiddleware2(mw.pragma, raw.routePath, raw.directory, mw.directory || "/"));
|
|
494
|
+
const { pattern, paramNames } = compileRoutePattern2(raw.routePath);
|
|
495
|
+
const procedures = {};
|
|
496
|
+
const mod = raw.module;
|
|
497
|
+
if (mod.default && typeof mod.default === "object" && "type" in mod.default) {
|
|
498
|
+
procedures.default = mod.default;
|
|
499
|
+
}
|
|
500
|
+
for (const [key, val] of Object.entries(mod)) {
|
|
501
|
+
if (val && typeof val === "object" && "type" in val && "options" in val) {
|
|
502
|
+
procedures[key] = val;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const handler = typeof mod.default === "function" && !("type" in mod.default) ? mod.default : undefined;
|
|
506
|
+
routes.push({
|
|
507
|
+
pattern,
|
|
508
|
+
paramNames,
|
|
509
|
+
routePath: raw.routePath,
|
|
510
|
+
filePath: raw.filePath,
|
|
511
|
+
directory: raw.directory,
|
|
512
|
+
procedures,
|
|
513
|
+
middlewares: applicableMiddlewares,
|
|
514
|
+
handler
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
routes.sort((a, b) => {
|
|
518
|
+
const isParamA = a.routePath.includes(":") || a.routePath.includes("*");
|
|
519
|
+
const isParamB = b.routePath.includes(":") || b.routePath.includes("*");
|
|
520
|
+
if (isParamA !== isParamB)
|
|
521
|
+
return isParamA ? 1 : -1;
|
|
522
|
+
return b.routePath.length - a.routePath.length;
|
|
523
|
+
});
|
|
524
|
+
return routes;
|
|
525
|
+
}
|
|
526
|
+
// src/api/middleware.ts
|
|
527
|
+
async function executeMiddlewareChain2(middlewares, req, url, params, initialCtx = {}) {
|
|
528
|
+
let currentCtx = { ...initialCtx };
|
|
529
|
+
let earlyResponse;
|
|
530
|
+
let index = 0;
|
|
531
|
+
const runner = async () => {
|
|
532
|
+
if (index >= middlewares.length) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const currentMw = middlewares[index++];
|
|
536
|
+
const next = async (enriched) => {
|
|
537
|
+
if (enriched && typeof enriched === "object") {
|
|
538
|
+
currentCtx = { ...currentCtx, ...enriched };
|
|
539
|
+
}
|
|
540
|
+
await runner();
|
|
541
|
+
return currentCtx;
|
|
542
|
+
};
|
|
543
|
+
const result = await currentMw({
|
|
544
|
+
req,
|
|
545
|
+
ctx: currentCtx,
|
|
546
|
+
url,
|
|
547
|
+
params,
|
|
548
|
+
next
|
|
549
|
+
});
|
|
550
|
+
if (result instanceof Response) {
|
|
551
|
+
earlyResponse = result;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
await runner();
|
|
555
|
+
return {
|
|
556
|
+
response: earlyResponse,
|
|
557
|
+
ctx: currentCtx
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/api/handler.ts
|
|
562
|
+
function handleCors(req, config) {
|
|
563
|
+
if (!config)
|
|
564
|
+
return null;
|
|
565
|
+
const origin = req.headers.get("Origin") || "*";
|
|
566
|
+
const isAllowed = !config.origin || config.origin === "*" || typeof config.origin === "function" && config.origin(origin) || Array.isArray(config.origin) && config.origin.includes(origin) || config.origin === origin;
|
|
567
|
+
if (req.method === "OPTIONS") {
|
|
568
|
+
const headers = new Headers;
|
|
569
|
+
if (isAllowed) {
|
|
570
|
+
headers.set("Access-Control-Allow-Origin", origin);
|
|
571
|
+
}
|
|
572
|
+
headers.set("Access-Control-Allow-Methods", (config.methods || ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]).join(", "));
|
|
573
|
+
headers.set("Access-Control-Allow-Headers", (config.allowedHeaders || ["Content-Type", "Authorization", "x-visage-rpc"]).join(", "));
|
|
574
|
+
if (config.credentials) {
|
|
575
|
+
headers.set("Access-Control-Allow-Credentials", "true");
|
|
576
|
+
}
|
|
577
|
+
if (config.maxAge) {
|
|
578
|
+
headers.set("Access-Control-Max-Age", config.maxAge.toString());
|
|
579
|
+
}
|
|
580
|
+
return new Response(null, { status: 204, headers });
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
function applyCorsHeaders(res, req, config) {
|
|
585
|
+
if (!config)
|
|
586
|
+
return res;
|
|
587
|
+
const origin = req.headers.get("Origin") || "*";
|
|
588
|
+
res.headers.set("Access-Control-Allow-Origin", origin);
|
|
589
|
+
if (config.credentials) {
|
|
590
|
+
res.headers.set("Access-Control-Allow-Credentials", "true");
|
|
591
|
+
}
|
|
592
|
+
return res;
|
|
593
|
+
}
|
|
594
|
+
function createApiHandler2(routes, config = {}) {
|
|
595
|
+
const prefix = config.prefix ? `/${config.prefix.replace(/^\/+|\/+$/g, "")}` : "";
|
|
596
|
+
return async (req) => {
|
|
597
|
+
const corsPreflight = handleCors(req, config.cors);
|
|
598
|
+
if (corsPreflight)
|
|
599
|
+
return corsPreflight;
|
|
600
|
+
const url = new URL(req.url);
|
|
601
|
+
let pathname = url.pathname;
|
|
602
|
+
if (prefix && pathname.startsWith(prefix)) {
|
|
603
|
+
pathname = pathname.slice(prefix.length) || "/";
|
|
604
|
+
}
|
|
605
|
+
let matchedRoute = null;
|
|
606
|
+
const pathParams = {};
|
|
607
|
+
for (const route of routes) {
|
|
608
|
+
const match = pathname.match(route.pattern);
|
|
609
|
+
if (match) {
|
|
610
|
+
matchedRoute = route;
|
|
611
|
+
route.paramNames.forEach((name, i) => {
|
|
612
|
+
pathParams[name] = decodeURIComponent(match[i + 1] || "");
|
|
613
|
+
});
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (!matchedRoute) {
|
|
618
|
+
return applyCorsHeaders(new Response(JSON.stringify({
|
|
619
|
+
ok: false,
|
|
620
|
+
error: { code: "NOT_FOUND", message: `Route not found: ${pathname}` }
|
|
621
|
+
}), { status: 404, headers: { "Content-Type": "application/json" } }), req, config.cors);
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
const method = req.method.toUpperCase();
|
|
625
|
+
let procedure = matchedRoute.procedures.default;
|
|
626
|
+
if (!procedure) {
|
|
627
|
+
if (method === "GET") {
|
|
628
|
+
procedure = matchedRoute.procedures.GET || matchedRoute.procedures.get;
|
|
629
|
+
} else if (method === "POST") {
|
|
630
|
+
procedure = matchedRoute.procedures.POST || matchedRoute.procedures.post;
|
|
631
|
+
} else if (method === "PUT") {
|
|
632
|
+
procedure = matchedRoute.procedures.PUT || matchedRoute.procedures.put;
|
|
633
|
+
} else if (method === "DELETE") {
|
|
634
|
+
procedure = matchedRoute.procedures.DELETE || matchedRoute.procedures.del || matchedRoute.procedures.delete;
|
|
635
|
+
} else if (method === "PATCH") {
|
|
636
|
+
procedure = matchedRoute.procedures.PATCH || matchedRoute.procedures.patch;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const mwFns = [
|
|
640
|
+
...matchedRoute.middlewares.map((m) => m.fn),
|
|
641
|
+
...procedure?.middlewares || []
|
|
642
|
+
];
|
|
643
|
+
const { response: earlyResponse, ctx } = await executeMiddlewareChain2(mwFns, req, url, pathParams);
|
|
644
|
+
if (earlyResponse) {
|
|
645
|
+
return applyCorsHeaders(earlyResponse, req, config.cors);
|
|
646
|
+
}
|
|
647
|
+
if (matchedRoute.handler) {
|
|
648
|
+
const res = await matchedRoute.handler(req, ctx);
|
|
649
|
+
return applyCorsHeaders(res, req, config.cors);
|
|
650
|
+
}
|
|
651
|
+
if (!procedure) {
|
|
652
|
+
return applyCorsHeaders(new Response(JSON.stringify({
|
|
653
|
+
ok: false,
|
|
654
|
+
error: {
|
|
655
|
+
code: "METHOD_NOT_ALLOWED",
|
|
656
|
+
message: `Method ${method} not allowed on ${pathname}`
|
|
657
|
+
}
|
|
658
|
+
}), { status: 405, headers: { "Content-Type": "application/json" } }), req, config.cors);
|
|
659
|
+
}
|
|
660
|
+
let rawInput = {};
|
|
661
|
+
if (method === "GET") {
|
|
662
|
+
const inputQuery = url.searchParams.get("input");
|
|
663
|
+
if (inputQuery) {
|
|
664
|
+
try {
|
|
665
|
+
rawInput = JSON.parse(inputQuery);
|
|
666
|
+
} catch {
|
|
667
|
+
rawInput = inputQuery;
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
const spObj = {};
|
|
671
|
+
url.searchParams.forEach((val, key) => {
|
|
672
|
+
spObj[key] = val;
|
|
673
|
+
});
|
|
674
|
+
if (Object.keys(spObj).length > 0) {
|
|
675
|
+
rawInput = spObj;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
const contentType = req.headers.get("Content-Type") || "";
|
|
680
|
+
if (contentType.includes("application/json")) {
|
|
681
|
+
try {
|
|
682
|
+
rawInput = await req.json();
|
|
683
|
+
} catch {
|
|
684
|
+
rawInput = {};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (typeof rawInput === "object" && rawInput !== null) {
|
|
689
|
+
rawInput = { ...pathParams, ...rawInput };
|
|
690
|
+
}
|
|
691
|
+
let validatedInput = rawInput;
|
|
692
|
+
if (procedure.options.input) {
|
|
693
|
+
const validation = await validateWithSchema2(procedure.options.input, rawInput);
|
|
694
|
+
if (!validation.success) {
|
|
695
|
+
return applyCorsHeaders(new Response(JSON.stringify({
|
|
696
|
+
ok: false,
|
|
697
|
+
error: {
|
|
698
|
+
code: "BAD_REQUEST",
|
|
699
|
+
message: "Validation Error",
|
|
700
|
+
issues: validation.issues
|
|
701
|
+
}
|
|
702
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } }), req, config.cors);
|
|
703
|
+
}
|
|
704
|
+
validatedInput = validation.data;
|
|
705
|
+
}
|
|
706
|
+
if (procedure.type === "subscription") {
|
|
707
|
+
const generator = procedure.options.resolve({
|
|
708
|
+
input: validatedInput,
|
|
709
|
+
ctx,
|
|
710
|
+
req,
|
|
711
|
+
params: pathParams
|
|
712
|
+
});
|
|
713
|
+
const stream = new ReadableStream({
|
|
714
|
+
async start(controller) {
|
|
715
|
+
const encoder = new TextEncoder;
|
|
716
|
+
try {
|
|
717
|
+
for await (const chunk of generator) {
|
|
718
|
+
const payload = `data: ${JSON.stringify(chunk)}
|
|
719
|
+
|
|
720
|
+
`;
|
|
721
|
+
controller.enqueue(encoder.encode(payload));
|
|
722
|
+
}
|
|
723
|
+
controller.close();
|
|
724
|
+
} catch (err) {
|
|
725
|
+
controller.error(err);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
const sseRes = new Response(stream, {
|
|
730
|
+
headers: {
|
|
731
|
+
"Content-Type": "text/event-stream",
|
|
732
|
+
"Cache-Control": "no-cache",
|
|
733
|
+
Connection: "keep-alive"
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
return applyCorsHeaders(sseRes, req, config.cors);
|
|
737
|
+
}
|
|
738
|
+
const result = await procedure.options.resolve({
|
|
739
|
+
input: validatedInput,
|
|
740
|
+
ctx,
|
|
741
|
+
req,
|
|
742
|
+
params: pathParams
|
|
743
|
+
});
|
|
744
|
+
const successRes = new Response(JSON.stringify({ ok: true, data: result }), {
|
|
745
|
+
status: 200,
|
|
746
|
+
headers: { "Content-Type": "application/json" }
|
|
747
|
+
});
|
|
748
|
+
return applyCorsHeaders(successRes, req, config.cors);
|
|
749
|
+
} catch (err) {
|
|
750
|
+
if (config.onError) {
|
|
751
|
+
const customRes = await config.onError(err, req);
|
|
752
|
+
return applyCorsHeaders(customRes, req, config.cors);
|
|
753
|
+
}
|
|
754
|
+
const statusCode = err instanceof RPCError2 ? err.statusCode : ERROR_CODE_TO_STATUS2[err?.code] || 500;
|
|
755
|
+
const code = err instanceof RPCError2 ? err.code : "INTERNAL_SERVER_ERROR";
|
|
756
|
+
const message = err?.message || "Internal Server Error";
|
|
757
|
+
const errRes = new Response(JSON.stringify({ ok: false, error: { code, message } }), {
|
|
758
|
+
status: statusCode,
|
|
759
|
+
headers: { "Content-Type": "application/json" }
|
|
760
|
+
});
|
|
761
|
+
return applyCorsHeaders(errRes, req, config.cors);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function createApi2(files, sourceMapOrConfig, maybeConfig) {
|
|
766
|
+
const sourceMap = sourceMapOrConfig && !("prefix" in sourceMapOrConfig) && !("cors" in sourceMapOrConfig) ? sourceMapOrConfig : {};
|
|
767
|
+
const config = maybeConfig || (sourceMapOrConfig && (("prefix" in sourceMapOrConfig) || ("cors" in sourceMapOrConfig)) ? sourceMapOrConfig : {});
|
|
768
|
+
const routes = buildFileRouter2(files, sourceMap);
|
|
769
|
+
const fetchHandler = createApiHandler2(routes, config);
|
|
770
|
+
return {
|
|
771
|
+
routes,
|
|
772
|
+
fetch: fetchHandler
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
// src/api/procedure.ts
|
|
776
|
+
function query2(options) {
|
|
777
|
+
return {
|
|
778
|
+
type: "query",
|
|
779
|
+
options
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function mutation2(options) {
|
|
783
|
+
return {
|
|
784
|
+
type: "mutation",
|
|
785
|
+
options
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function subscription2(options) {
|
|
789
|
+
return {
|
|
790
|
+
type: "subscription",
|
|
791
|
+
options: {
|
|
792
|
+
...options,
|
|
793
|
+
resolve: options.resolve
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function defineMiddleware2(fn, pragma = { cascade: true, match: ["*"] }) {
|
|
798
|
+
return {
|
|
799
|
+
fn,
|
|
800
|
+
pragma
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
class ProcedureBuilder2 {
|
|
805
|
+
_middlewares = [];
|
|
806
|
+
_inputSchema;
|
|
807
|
+
constructor(middlewares = [], inputSchema) {
|
|
808
|
+
this._middlewares = middlewares;
|
|
809
|
+
this._inputSchema = inputSchema;
|
|
810
|
+
}
|
|
811
|
+
use(middleware) {
|
|
812
|
+
return new ProcedureBuilder2([...this._middlewares, middleware], this._inputSchema);
|
|
813
|
+
}
|
|
814
|
+
input(schema) {
|
|
815
|
+
return new ProcedureBuilder2(this._middlewares, schema);
|
|
816
|
+
}
|
|
817
|
+
query(resolve) {
|
|
818
|
+
return {
|
|
819
|
+
type: "query",
|
|
820
|
+
options: {
|
|
821
|
+
input: this._inputSchema,
|
|
822
|
+
resolve
|
|
823
|
+
},
|
|
824
|
+
middlewares: this._middlewares
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
mutation(resolve) {
|
|
828
|
+
return {
|
|
829
|
+
type: "mutation",
|
|
830
|
+
options: {
|
|
831
|
+
input: this._inputSchema,
|
|
832
|
+
resolve
|
|
833
|
+
},
|
|
834
|
+
middlewares: this._middlewares
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
subscription(resolve) {
|
|
838
|
+
return {
|
|
839
|
+
type: "subscription",
|
|
840
|
+
options: {
|
|
841
|
+
resolve
|
|
842
|
+
},
|
|
843
|
+
middlewares: this._middlewares
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
var procedure2 = new ProcedureBuilder2;
|
|
848
|
+
export { createApiClient2, parsePragma2, globToRegExp2, matchesAnyPattern2, shouldApplyMiddleware2, compileRoutePattern2, normalizeFilePath2, buildFileRouter2, executeMiddlewareChain2, createApiHandler2, createApi2, query2, mutation2, subscription2, defineMiddleware2, ProcedureBuilder2, procedure2 };
|