@boon4681/giri 0.0.2 → 0.0.3
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/adapters/hono.d.ts +25 -4
- package/dist/adapters/hono.js +171 -8
- package/dist/adapters/hono.js.map +1 -1
- package/dist/cli.js +862 -234
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +24 -4
- package/dist/index.js +649 -122
- package/dist/index.js.map +1 -1
- package/dist/{types-DkrKD1S4.d.ts → types-BvRph0mx.d.ts} +92 -2
- package/dist/validators/valibot.d.ts +1 -1
- package/dist/validators/valibot.js.map +1 -1
- package/dist/validators/zod.d.ts +1 -1
- package/dist/validators/zod.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -45,12 +45,18 @@ var init_es5 = __esm({
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// src/generator/schema/program.ts
|
|
48
|
-
function
|
|
48
|
+
function leanOptions(options) {
|
|
49
|
+
return {
|
|
50
|
+
...options,
|
|
51
|
+
types: []
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function createSchemaProgram(paths, routeFiles, programOptions = {}) {
|
|
49
55
|
let options = { ...DEFAULT_OPTIONS };
|
|
50
|
-
const configPath =
|
|
56
|
+
const configPath = import_typescript2.default.findConfigFile(paths.cwd, import_typescript2.default.sys.fileExists, "tsconfig.json");
|
|
51
57
|
if (configPath) {
|
|
52
|
-
const parsed =
|
|
53
|
-
...
|
|
58
|
+
const parsed = import_typescript2.default.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
59
|
+
...import_typescript2.default.sys,
|
|
54
60
|
onUnRecoverableConfigFileDiagnostic: () => {
|
|
55
61
|
}
|
|
56
62
|
});
|
|
@@ -58,17 +64,20 @@ function createSchemaProgram(paths, routeFiles) {
|
|
|
58
64
|
options = { ...parsed.options, noEmit: true };
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
|
-
|
|
67
|
+
if (programOptions.lean) {
|
|
68
|
+
options = leanOptions(options);
|
|
69
|
+
}
|
|
70
|
+
return import_typescript2.default.createProgram(routeFiles, options);
|
|
62
71
|
}
|
|
63
|
-
var
|
|
72
|
+
var import_typescript2, DEFAULT_OPTIONS;
|
|
64
73
|
var init_program = __esm({
|
|
65
74
|
"src/generator/schema/program.ts"() {
|
|
66
75
|
"use strict";
|
|
67
|
-
|
|
76
|
+
import_typescript2 = __toESM(require("typescript"));
|
|
68
77
|
DEFAULT_OPTIONS = {
|
|
69
|
-
target:
|
|
70
|
-
module:
|
|
71
|
-
moduleResolution:
|
|
78
|
+
target: import_typescript2.default.ScriptTarget.ES2022,
|
|
79
|
+
module: import_typescript2.default.ModuleKind.NodeNext,
|
|
80
|
+
moduleResolution: import_typescript2.default.ModuleResolutionKind.NodeNext,
|
|
72
81
|
strict: true,
|
|
73
82
|
skipLibCheck: true,
|
|
74
83
|
noEmit: true
|
|
@@ -102,7 +111,7 @@ function literalValuesOf(types) {
|
|
|
102
111
|
for (const member of types) {
|
|
103
112
|
if (member.isStringLiteral() || member.isNumberLiteral()) {
|
|
104
113
|
values.push(member.value);
|
|
105
|
-
} else if (member.flags &
|
|
114
|
+
} else if (member.flags & import_typescript3.default.TypeFlags.BooleanLiteral) {
|
|
106
115
|
values.push(intrinsicName(member) === "true");
|
|
107
116
|
} else {
|
|
108
117
|
return void 0;
|
|
@@ -111,7 +120,7 @@ function literalValuesOf(types) {
|
|
|
111
120
|
return values;
|
|
112
121
|
}
|
|
113
122
|
function walkUnion(type, ctx) {
|
|
114
|
-
const flag =
|
|
123
|
+
const flag = import_typescript3.default.TypeFlags.Undefined | import_typescript3.default.TypeFlags.Void | import_typescript3.default.TypeFlags.Never;
|
|
115
124
|
const members = type.types.filter((member) => !(member.flags & flag));
|
|
116
125
|
if (members.length === 1) {
|
|
117
126
|
return walkType(members[0], ctx);
|
|
@@ -124,13 +133,13 @@ function walkUnion(type, ctx) {
|
|
|
124
133
|
}
|
|
125
134
|
function buildObjectSchema(type, ctx) {
|
|
126
135
|
const { checker } = ctx;
|
|
127
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
136
|
+
const indexInfo = checker.getIndexInfoOfType(type, import_typescript3.default.IndexKind.String) ?? checker.getIndexInfoOfType(type, import_typescript3.default.IndexKind.Number);
|
|
128
137
|
const properties = {};
|
|
129
138
|
const required = [];
|
|
130
139
|
for (const symbol of checker.getPropertiesOfType(type)) {
|
|
131
140
|
const name = symbol.getName();
|
|
132
141
|
const propType = checker.getTypeOfSymbolAtLocation(symbol, ctx.location);
|
|
133
|
-
const optional = Boolean(symbol.getFlags() &
|
|
142
|
+
const optional = Boolean(symbol.getFlags() & import_typescript3.default.SymbolFlags.Optional) || Boolean(propType.flags & import_typescript3.default.TypeFlags.Union && propType.types.some((t) => t.flags & import_typescript3.default.TypeFlags.Undefined));
|
|
134
143
|
properties[name] = walkType(propType, ctx);
|
|
135
144
|
if (!optional) {
|
|
136
145
|
required.push(name);
|
|
@@ -189,16 +198,16 @@ function walkObject(type, ctx) {
|
|
|
189
198
|
}
|
|
190
199
|
function walkType(type, ctx) {
|
|
191
200
|
const flags = type.flags;
|
|
192
|
-
if (flags & (
|
|
201
|
+
if (flags & (import_typescript3.default.TypeFlags.Any | import_typescript3.default.TypeFlags.Unknown)) {
|
|
193
202
|
return {};
|
|
194
203
|
}
|
|
195
|
-
if (flags &
|
|
204
|
+
if (flags & import_typescript3.default.TypeFlags.Null) {
|
|
196
205
|
return { type: "null" };
|
|
197
206
|
}
|
|
198
|
-
if (flags & (
|
|
207
|
+
if (flags & (import_typescript3.default.TypeFlags.Undefined | import_typescript3.default.TypeFlags.Void)) {
|
|
199
208
|
return {};
|
|
200
209
|
}
|
|
201
|
-
if (flags & (
|
|
210
|
+
if (flags & (import_typescript3.default.TypeFlags.BigInt | import_typescript3.default.TypeFlags.BigIntLiteral)) {
|
|
202
211
|
ctx.warnings.push("bigint is not JSON-serializable (JSON.stringify throws); documented as string.");
|
|
203
212
|
return { type: "string" };
|
|
204
213
|
}
|
|
@@ -208,45 +217,45 @@ function walkType(type, ctx) {
|
|
|
208
217
|
if (type.isNumberLiteral()) {
|
|
209
218
|
return { type: "number", const: type.value };
|
|
210
219
|
}
|
|
211
|
-
if (flags &
|
|
220
|
+
if (flags & import_typescript3.default.TypeFlags.BooleanLiteral) {
|
|
212
221
|
return { type: "boolean", const: intrinsicName(type) === "true" };
|
|
213
222
|
}
|
|
214
|
-
if (flags &
|
|
223
|
+
if (flags & import_typescript3.default.TypeFlags.String) {
|
|
215
224
|
return { type: "string" };
|
|
216
225
|
}
|
|
217
|
-
if (flags &
|
|
226
|
+
if (flags & import_typescript3.default.TypeFlags.Number) {
|
|
218
227
|
return { type: "number" };
|
|
219
228
|
}
|
|
220
|
-
if (flags &
|
|
229
|
+
if (flags & import_typescript3.default.TypeFlags.Boolean) {
|
|
221
230
|
return { type: "boolean" };
|
|
222
231
|
}
|
|
223
232
|
if (type.isUnion()) {
|
|
224
233
|
return walkUnion(type, ctx);
|
|
225
234
|
}
|
|
226
|
-
if (flags &
|
|
235
|
+
if (flags & import_typescript3.default.TypeFlags.Object || type.isIntersection()) {
|
|
227
236
|
return walkObject(type, ctx);
|
|
228
237
|
}
|
|
229
238
|
return {};
|
|
230
239
|
}
|
|
231
|
-
var
|
|
240
|
+
var import_typescript3;
|
|
232
241
|
var init_json_schema = __esm({
|
|
233
242
|
"src/generator/schema/json-schema.ts"() {
|
|
234
243
|
"use strict";
|
|
235
|
-
|
|
244
|
+
import_typescript3 = __toESM(require("typescript"));
|
|
236
245
|
}
|
|
237
246
|
});
|
|
238
247
|
|
|
239
248
|
// src/generator/schema/responses.ts
|
|
240
249
|
function findHandleFunction(source) {
|
|
241
250
|
let found;
|
|
242
|
-
const isExported = (node) =>
|
|
251
|
+
const isExported = (node) => import_typescript4.default.canHaveModifiers(node) && (import_typescript4.default.getModifiers(node)?.some((m) => m.kind === import_typescript4.default.SyntaxKind.ExportKeyword) ?? false);
|
|
243
252
|
for (const statement of source.statements) {
|
|
244
|
-
if (
|
|
253
|
+
if (import_typescript4.default.isFunctionDeclaration(statement) && statement.name?.text === "handle" && isExported(statement)) {
|
|
245
254
|
found = statement;
|
|
246
255
|
}
|
|
247
|
-
if (
|
|
256
|
+
if (import_typescript4.default.isVariableStatement(statement) && isExported(statement)) {
|
|
248
257
|
for (const declaration of statement.declarationList.declarations) {
|
|
249
|
-
if (
|
|
258
|
+
if (import_typescript4.default.isIdentifier(declaration.name) && declaration.name.text === "handle" && declaration.initializer && (import_typescript4.default.isArrowFunction(declaration.initializer) || import_typescript4.default.isFunctionExpression(declaration.initializer))) {
|
|
250
259
|
found = declaration.initializer;
|
|
251
260
|
}
|
|
252
261
|
}
|
|
@@ -255,7 +264,7 @@ function findHandleFunction(source) {
|
|
|
255
264
|
return found;
|
|
256
265
|
}
|
|
257
266
|
function collectReturnExpressions(fn) {
|
|
258
|
-
if (
|
|
267
|
+
if (import_typescript4.default.isArrowFunction(fn) && !import_typescript4.default.isBlock(fn.body)) {
|
|
259
268
|
return [fn.body];
|
|
260
269
|
}
|
|
261
270
|
if (!fn.body) {
|
|
@@ -263,15 +272,15 @@ function collectReturnExpressions(fn) {
|
|
|
263
272
|
}
|
|
264
273
|
const expressions = [];
|
|
265
274
|
const visit = (node) => {
|
|
266
|
-
if (
|
|
275
|
+
if (import_typescript4.default.isFunctionDeclaration(node) || import_typescript4.default.isFunctionExpression(node) || import_typescript4.default.isArrowFunction(node)) {
|
|
267
276
|
return;
|
|
268
277
|
}
|
|
269
|
-
if (
|
|
278
|
+
if (import_typescript4.default.isReturnStatement(node) && node.expression) {
|
|
270
279
|
expressions.push(node.expression);
|
|
271
280
|
}
|
|
272
|
-
|
|
281
|
+
import_typescript4.default.forEachChild(node, visit);
|
|
273
282
|
};
|
|
274
|
-
|
|
283
|
+
import_typescript4.default.forEachChild(fn.body, visit);
|
|
275
284
|
return expressions;
|
|
276
285
|
}
|
|
277
286
|
function propertyType(checker, type, name, location) {
|
|
@@ -283,15 +292,21 @@ function isTypedResponse2(checker, type) {
|
|
|
283
292
|
checker.getPropertyOfType(type, "data") && checker.getPropertyOfType(type, "status") && checker.getPropertyOfType(type, "format")
|
|
284
293
|
);
|
|
285
294
|
}
|
|
286
|
-
function
|
|
287
|
-
|
|
295
|
+
function firstParameterName(fn) {
|
|
296
|
+
const [first] = fn.parameters;
|
|
297
|
+
return first && import_typescript4.default.isIdentifier(first.name) ? first.name.text : void 0;
|
|
298
|
+
}
|
|
299
|
+
function readFromCall(checker, expression, contextName) {
|
|
300
|
+
if (!import_typescript4.default.isCallExpression(expression) || !import_typescript4.default.isPropertyAccessExpression(expression.expression)) {
|
|
288
301
|
return void 0;
|
|
289
302
|
}
|
|
290
303
|
const method = expression.expression.name.text;
|
|
291
304
|
if (method !== "json" && method !== "text") {
|
|
292
305
|
return void 0;
|
|
293
306
|
}
|
|
294
|
-
|
|
307
|
+
const target = expression.expression.expression;
|
|
308
|
+
const directContextCall = contextName && import_typescript4.default.isIdentifier(target) && target.text === contextName;
|
|
309
|
+
if (!directContextCall && !isTypedResponse2(checker, checker.getTypeAtLocation(expression))) {
|
|
295
310
|
return void 0;
|
|
296
311
|
}
|
|
297
312
|
const [dataArg, statusArg] = expression.arguments;
|
|
@@ -331,6 +346,7 @@ function extractRouteResponses(program, file) {
|
|
|
331
346
|
return result;
|
|
332
347
|
}
|
|
333
348
|
const ctx = createWalkContext(checker, fn);
|
|
349
|
+
const contextName = firstParameterName(fn);
|
|
334
350
|
const byStatus = /* @__PURE__ */ new Map();
|
|
335
351
|
const record = (hit) => {
|
|
336
352
|
const schema = walkType(hit.data, ctx);
|
|
@@ -339,7 +355,7 @@ function extractRouteResponses(program, file) {
|
|
|
339
355
|
byStatus.set(hit.status, bucket);
|
|
340
356
|
};
|
|
341
357
|
for (const expression of collectReturnExpressions(fn)) {
|
|
342
|
-
const fromCall = readFromCall(checker, expression);
|
|
358
|
+
const fromCall = readFromCall(checker, expression, contextName);
|
|
343
359
|
if (fromCall) {
|
|
344
360
|
record(fromCall);
|
|
345
361
|
continue;
|
|
@@ -365,11 +381,11 @@ function extractRouteResponses(program, file) {
|
|
|
365
381
|
result.$defs = ctx.defs;
|
|
366
382
|
return result;
|
|
367
383
|
}
|
|
368
|
-
var
|
|
384
|
+
var import_typescript4;
|
|
369
385
|
var init_responses = __esm({
|
|
370
386
|
"src/generator/schema/responses.ts"() {
|
|
371
387
|
"use strict";
|
|
372
|
-
|
|
388
|
+
import_typescript4 = __toESM(require("typescript"));
|
|
373
389
|
init_json_schema();
|
|
374
390
|
}
|
|
375
391
|
});
|
|
@@ -425,6 +441,21 @@ var bodySchemaBrand = /* @__PURE__ */ Symbol.for("giri.body-schema");
|
|
|
425
441
|
|
|
426
442
|
// src/context.ts
|
|
427
443
|
var BODYLESS_STATUS = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
444
|
+
var pendingResponseBrand = /* @__PURE__ */ Symbol("giri.pending-response");
|
|
445
|
+
function getPending(context) {
|
|
446
|
+
return context[pendingResponseBrand];
|
|
447
|
+
}
|
|
448
|
+
var unsupportedCookieJar = {
|
|
449
|
+
get: cookiesUnsupported,
|
|
450
|
+
all: cookiesUnsupported,
|
|
451
|
+
set: cookiesUnsupported,
|
|
452
|
+
delete: cookiesUnsupported,
|
|
453
|
+
getSigned: cookiesUnsupported,
|
|
454
|
+
setSigned: cookiesUnsupported
|
|
455
|
+
};
|
|
456
|
+
function cookiesUnsupported() {
|
|
457
|
+
throw new Error("The active adapter does not support cookies.");
|
|
458
|
+
}
|
|
428
459
|
function createTypedResponse(data, status, format, headers) {
|
|
429
460
|
return {
|
|
430
461
|
[typedResponseBrand]: { data, status, format },
|
|
@@ -441,6 +472,13 @@ function createContext(options) {
|
|
|
441
472
|
const url = new URL(options.request.url);
|
|
442
473
|
const store = /* @__PURE__ */ new Map();
|
|
443
474
|
const validated = options.validated ?? {};
|
|
475
|
+
const pending = { headers: new Headers() };
|
|
476
|
+
const defaultStatus = () => pending.status ?? 200;
|
|
477
|
+
const cookies = options.cookies ? options.cookies({
|
|
478
|
+
request: options.request,
|
|
479
|
+
append: (header) => pending.headers.append("set-cookie", header),
|
|
480
|
+
secret: options.cookieSecret
|
|
481
|
+
}) : unsupportedCookieJar;
|
|
444
482
|
const context = {
|
|
445
483
|
params: options.params ?? {},
|
|
446
484
|
app: options.app ?? {},
|
|
@@ -458,16 +496,45 @@ function createContext(options) {
|
|
|
458
496
|
throw new Error(`No validated ${String(key)} data is available for this route.`);
|
|
459
497
|
}
|
|
460
498
|
return validated[key];
|
|
461
|
-
}
|
|
499
|
+
},
|
|
500
|
+
cookie: (name) => cookies.get(name),
|
|
501
|
+
cookies: () => cookies.all(),
|
|
502
|
+
signedCookie: (name) => cookies.getSigned(name)
|
|
462
503
|
},
|
|
463
504
|
set: (key, value) => {
|
|
464
505
|
store.set(key, value);
|
|
465
506
|
},
|
|
466
507
|
get: (key) => store.get(key),
|
|
467
|
-
json: (data, status
|
|
468
|
-
text: (text, status
|
|
508
|
+
json: (data, status, headers) => createTypedResponse(data, status ?? defaultStatus(), "json", headers),
|
|
509
|
+
text: (text, status, headers) => createTypedResponse(text, status ?? defaultStatus(), "text", headers),
|
|
510
|
+
html: (html, status, headers) => createTypedResponse(html, status ?? defaultStatus(), "html", headers),
|
|
511
|
+
body: (data, status, headers) => new Response(data, { status: status ?? defaultStatus(), headers }),
|
|
512
|
+
newResponse: (data, status, headers) => new Response(data, { status: status ?? defaultStatus(), headers }),
|
|
513
|
+
redirect: (location, status) => new Response(null, { status: status ?? 302, headers: { Location: location } }),
|
|
514
|
+
notFound: () => new Response("404 Not Found", { status: 404 }),
|
|
515
|
+
header: (name, value, options2) => {
|
|
516
|
+
if (value === void 0) {
|
|
517
|
+
pending.headers.delete(name);
|
|
518
|
+
} else if (options2?.append) {
|
|
519
|
+
pending.headers.append(name, value);
|
|
520
|
+
} else {
|
|
521
|
+
pending.headers.set(name, value);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
status: (code) => {
|
|
525
|
+
pending.status = code;
|
|
526
|
+
},
|
|
527
|
+
cookie: (name, value, options2) => {
|
|
528
|
+
if (value === null) {
|
|
529
|
+
cookies.delete(name, options2);
|
|
530
|
+
} else {
|
|
531
|
+
cookies.set(name, value, options2);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
signedCookie: (name, value, options2) => cookies.setSigned(name, value, options2)
|
|
469
535
|
};
|
|
470
536
|
context[nativeContextBrand] = options.native;
|
|
537
|
+
context[pendingResponseBrand] = pending;
|
|
471
538
|
return context;
|
|
472
539
|
}
|
|
473
540
|
function typedResponseToResponse(response) {
|
|
@@ -478,14 +545,41 @@ function typedResponseToResponse(response) {
|
|
|
478
545
|
if (response.format === "text" && !headers.has("content-type")) {
|
|
479
546
|
headers.set("content-type", "text/plain; charset=utf-8");
|
|
480
547
|
}
|
|
548
|
+
if (response.format === "html" && !headers.has("content-type")) {
|
|
549
|
+
headers.set("content-type", "text/html; charset=utf-8");
|
|
550
|
+
}
|
|
481
551
|
const body = BODYLESS_STATUS.has(response.status) ? null : response.format === "json" ? JSON.stringify(response.data) : String(response.data);
|
|
482
552
|
return new Response(body, {
|
|
483
553
|
status: response.status,
|
|
484
554
|
headers
|
|
485
555
|
});
|
|
486
556
|
}
|
|
487
|
-
function toResponse(response) {
|
|
488
|
-
|
|
557
|
+
function toResponse(response, context) {
|
|
558
|
+
const base = isTypedResponse(response) ? typedResponseToResponse(response) : response;
|
|
559
|
+
const pending = context ? getPending(context) : void 0;
|
|
560
|
+
if (!pending) {
|
|
561
|
+
return base;
|
|
562
|
+
}
|
|
563
|
+
let hasPending = false;
|
|
564
|
+
pending.headers.forEach(() => {
|
|
565
|
+
hasPending = true;
|
|
566
|
+
});
|
|
567
|
+
if (!hasPending) {
|
|
568
|
+
return base;
|
|
569
|
+
}
|
|
570
|
+
const headers = new Headers(base.headers);
|
|
571
|
+
pending.headers.forEach((value, key) => {
|
|
572
|
+
if (key === "set-cookie") {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (!headers.has(key)) {
|
|
576
|
+
headers.set(key, value);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
for (const cookie of pending.headers.getSetCookie?.() ?? []) {
|
|
580
|
+
headers.append("set-cookie", cookie);
|
|
581
|
+
}
|
|
582
|
+
return new Response(base.body, { status: base.status, statusText: base.statusText, headers });
|
|
489
583
|
}
|
|
490
584
|
async function composeMiddleware(middleware, handle, context) {
|
|
491
585
|
let index = -1;
|
|
@@ -686,7 +780,8 @@ var configSchema = import_typebox.Type.Object({
|
|
|
686
780
|
port: import_typebox.Type.Optional(import_typebox.Type.Number()),
|
|
687
781
|
hostname: import_typebox.Type.Optional(import_typebox.Type.String())
|
|
688
782
|
}, { additionalProperties: false })),
|
|
689
|
-
errorSchema: import_typebox.Type.Optional(import_typebox.Type.Any())
|
|
783
|
+
errorSchema: import_typebox.Type.Optional(import_typebox.Type.Any()),
|
|
784
|
+
cookieSecret: import_typebox.Type.Optional(import_typebox.Type.String())
|
|
690
785
|
}, { additionalProperties: false });
|
|
691
786
|
|
|
692
787
|
// src/loader/loader.ts
|
|
@@ -748,13 +843,18 @@ function methodFromFile(fileName) {
|
|
|
748
843
|
const stem = fileName.replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "").toLowerCase();
|
|
749
844
|
return METHOD_FROM_FILE.get(stem);
|
|
750
845
|
}
|
|
751
|
-
function sharedFileIn(dir) {
|
|
846
|
+
function sharedFileIn(dir, cache) {
|
|
847
|
+
if (cache?.has(dir)) {
|
|
848
|
+
return cache.get(dir);
|
|
849
|
+
}
|
|
752
850
|
for (const ext of ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]) {
|
|
753
851
|
const file = (0, import_node_path2.join)(dir, `+shared.${ext}`);
|
|
754
852
|
if ((0, import_node_fs2.existsSync)(file)) {
|
|
853
|
+
cache?.set(dir, file);
|
|
755
854
|
return file;
|
|
756
855
|
}
|
|
757
856
|
}
|
|
857
|
+
cache?.set(dir, void 0);
|
|
758
858
|
return void 0;
|
|
759
859
|
}
|
|
760
860
|
function physicalRouteSegments(routesDir, routeDir) {
|
|
@@ -823,7 +923,7 @@ async function scanRouteFolders(routesDir) {
|
|
|
823
923
|
function routeParamsForDir(routesDir, dir) {
|
|
824
924
|
return pathFromSegments(physicalRouteSegments(routesDir, dir)).params;
|
|
825
925
|
}
|
|
826
|
-
function sharedFilesForDir(routesDir, dir) {
|
|
926
|
+
function sharedFilesForDir(routesDir, dir, cache) {
|
|
827
927
|
const segments = physicalRouteSegments(routesDir, dir);
|
|
828
928
|
const dirs = [routesDir];
|
|
829
929
|
let current = routesDir;
|
|
@@ -831,7 +931,7 @@ function sharedFilesForDir(routesDir, dir) {
|
|
|
831
931
|
current = (0, import_node_path2.join)(current, segment);
|
|
832
932
|
dirs.push(current);
|
|
833
933
|
}
|
|
834
|
-
return dirs.map(sharedFileIn).filter((file) => Boolean(file));
|
|
934
|
+
return dirs.map((currentDir) => sharedFileIn(currentDir, cache)).filter((file) => Boolean(file));
|
|
835
935
|
}
|
|
836
936
|
async function scanRoutes(routesDir) {
|
|
837
937
|
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
@@ -843,6 +943,7 @@ async function scanRoutes(routesDir) {
|
|
|
843
943
|
onlyFiles: true
|
|
844
944
|
});
|
|
845
945
|
const routes = [];
|
|
946
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
846
947
|
for (const file of files) {
|
|
847
948
|
const method = methodFromFile((0, import_node_path2.basename)(file));
|
|
848
949
|
if (!method) {
|
|
@@ -858,7 +959,7 @@ async function scanRoutes(routesDir) {
|
|
|
858
959
|
routeDir,
|
|
859
960
|
routeSegments,
|
|
860
961
|
params,
|
|
861
|
-
sharedFiles: sharedFilesForDir(routesDir, routeDir)
|
|
962
|
+
sharedFiles: sharedFilesForDir(routesDir, routeDir, sharedCache)
|
|
862
963
|
});
|
|
863
964
|
}
|
|
864
965
|
return routes.sort((left, right) => {
|
|
@@ -871,9 +972,11 @@ async function scanRoutes(routesDir) {
|
|
|
871
972
|
}
|
|
872
973
|
|
|
873
974
|
// src/app.ts
|
|
874
|
-
function loadModule(file) {
|
|
975
|
+
function loadModule(file, force = true) {
|
|
875
976
|
const resolved = require.resolve(file);
|
|
876
|
-
|
|
977
|
+
if (force) {
|
|
978
|
+
delete require.cache[resolved];
|
|
979
|
+
}
|
|
877
980
|
return require(resolved);
|
|
878
981
|
}
|
|
879
982
|
function interopDefault(value) {
|
|
@@ -935,20 +1038,13 @@ function resolveAliasTarget(cwd, target, capture = "") {
|
|
|
935
1038
|
}
|
|
936
1039
|
function matchAlias(request, key) {
|
|
937
1040
|
if (key.includes("*")) {
|
|
938
|
-
const [
|
|
939
|
-
if (request.startsWith(
|
|
940
|
-
return request.slice(
|
|
1041
|
+
const [prefix, suffix = ""] = key.split("*");
|
|
1042
|
+
if (request.startsWith(prefix) && request.endsWith(suffix)) {
|
|
1043
|
+
return request.slice(prefix.length, request.length - suffix.length);
|
|
941
1044
|
}
|
|
942
1045
|
return void 0;
|
|
943
1046
|
}
|
|
944
|
-
|
|
945
|
-
return "";
|
|
946
|
-
}
|
|
947
|
-
const prefix = `${key}/`;
|
|
948
|
-
if (request.startsWith(prefix)) {
|
|
949
|
-
return request.slice(prefix.length);
|
|
950
|
-
}
|
|
951
|
-
return void 0;
|
|
1047
|
+
return request === key ? "" : void 0;
|
|
952
1048
|
}
|
|
953
1049
|
function resolveAliasRequest(request, alias, cwd) {
|
|
954
1050
|
for (const [key, value] of Object.entries(alias ?? {})) {
|
|
@@ -1015,13 +1111,23 @@ async function buildGiriApp(config, options = {}) {
|
|
|
1015
1111
|
const { unregister } = await safeRegister();
|
|
1016
1112
|
const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
|
|
1017
1113
|
try {
|
|
1114
|
+
const dirty = options.dirty;
|
|
1115
|
+
const forceReload = dirty === void 0;
|
|
1116
|
+
const isDirty = (file) => forceReload || dirty.has(file);
|
|
1117
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
1118
|
+
const loadShared = (file) => {
|
|
1119
|
+
if (!sharedCache.has(file)) {
|
|
1120
|
+
sharedCache.set(file, loadModule(file, isDirty(file)));
|
|
1121
|
+
}
|
|
1122
|
+
return sharedCache.get(file);
|
|
1123
|
+
};
|
|
1018
1124
|
for (const route of routes) {
|
|
1019
|
-
const routeModule = loadModule(route.file);
|
|
1125
|
+
const routeModule = loadModule(route.file, isDirty(route.file));
|
|
1020
1126
|
if (typeof routeModule.handle !== "function") {
|
|
1021
1127
|
throw new Error(`${route.file} must export a named handle function.`);
|
|
1022
1128
|
}
|
|
1023
1129
|
const folderMiddleware = routeModule.config?.skipInherited ? [] : route.sharedFiles.flatMap(
|
|
1024
|
-
(file) => normalizeMiddleware(
|
|
1130
|
+
(file) => normalizeMiddleware(loadShared(file).middleware, file)
|
|
1025
1131
|
);
|
|
1026
1132
|
const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
|
|
1027
1133
|
config.adapter.register(app, {
|
|
@@ -1030,7 +1136,8 @@ async function buildGiriApp(config, options = {}) {
|
|
|
1030
1136
|
handle: routeModule.handle,
|
|
1031
1137
|
middleware: [...folderMiddleware, ...verbMiddleware],
|
|
1032
1138
|
input: routeInput(routeModule, route.file),
|
|
1033
|
-
services: options.services
|
|
1139
|
+
services: options.services,
|
|
1140
|
+
cookieSecret: config.cookieSecret
|
|
1034
1141
|
});
|
|
1035
1142
|
}
|
|
1036
1143
|
} finally {
|
|
@@ -1041,7 +1148,7 @@ async function buildGiriApp(config, options = {}) {
|
|
|
1041
1148
|
}
|
|
1042
1149
|
|
|
1043
1150
|
// src/generator/sync.ts
|
|
1044
|
-
var
|
|
1151
|
+
var import_node_fs7 = require("fs");
|
|
1045
1152
|
var import_promises3 = require("fs/promises");
|
|
1046
1153
|
var import_node_path11 = require("path");
|
|
1047
1154
|
|
|
@@ -1295,6 +1402,7 @@ function buildOpenApiDocument(paths, routes, data = {}) {
|
|
|
1295
1402
|
const documentPaths = {};
|
|
1296
1403
|
const schemas = {};
|
|
1297
1404
|
const securitySchemes = {};
|
|
1405
|
+
const tagOrder = [];
|
|
1298
1406
|
for (const route of routes) {
|
|
1299
1407
|
if (data.hiddenFiles?.has(route.file)) {
|
|
1300
1408
|
continue;
|
|
@@ -1302,10 +1410,32 @@ function buildOpenApiDocument(paths, routes, data = {}) {
|
|
|
1302
1410
|
const responses = data.responsesByFile?.get(route.file);
|
|
1303
1411
|
const input = data.inputsByFile?.get(route.file);
|
|
1304
1412
|
const security = data.securityByFile?.get(route.file);
|
|
1413
|
+
const meta = data.openapiByFile?.get(route.file);
|
|
1305
1414
|
for (const [name, schema] of Object.entries(responses?.$defs ?? {})) {
|
|
1306
1415
|
schemas[name] = rewriteRefs(schema);
|
|
1307
1416
|
}
|
|
1308
|
-
const operation = {
|
|
1417
|
+
const operation = {};
|
|
1418
|
+
if (meta?.tags && meta.tags.length > 0) {
|
|
1419
|
+
operation.tags = meta.tags;
|
|
1420
|
+
for (const tag of meta.tags) {
|
|
1421
|
+
if (!tagOrder.includes(tag)) {
|
|
1422
|
+
tagOrder.push(tag);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
if (meta?.summary) {
|
|
1427
|
+
operation.summary = meta.summary;
|
|
1428
|
+
}
|
|
1429
|
+
if (meta?.description) {
|
|
1430
|
+
operation.description = meta.description;
|
|
1431
|
+
}
|
|
1432
|
+
if (meta?.operationId) {
|
|
1433
|
+
operation.operationId = meta.operationId;
|
|
1434
|
+
}
|
|
1435
|
+
if (meta?.deprecated) {
|
|
1436
|
+
operation.deprecated = true;
|
|
1437
|
+
}
|
|
1438
|
+
operation.responses = buildResponses(responses?.responses ?? []);
|
|
1309
1439
|
const parameters = [...pathParameters(route), ...queryParameters(input?.query)];
|
|
1310
1440
|
if (parameters.length > 0) {
|
|
1311
1441
|
operation.parameters = parameters;
|
|
@@ -1337,6 +1467,9 @@ function buildOpenApiDocument(paths, routes, data = {}) {
|
|
|
1337
1467
|
info: readProjectInfo(paths.cwd),
|
|
1338
1468
|
paths: documentPaths
|
|
1339
1469
|
};
|
|
1470
|
+
if (tagOrder.length > 0) {
|
|
1471
|
+
document.tags = tagOrder.map((name) => ({ name }));
|
|
1472
|
+
}
|
|
1340
1473
|
const components = {};
|
|
1341
1474
|
if (Object.keys(schemas).length > 0) {
|
|
1342
1475
|
components.schemas = schemas;
|
|
@@ -1368,14 +1501,29 @@ function paramsType(params) {
|
|
|
1368
1501
|
${fields}
|
|
1369
1502
|
}`;
|
|
1370
1503
|
}
|
|
1371
|
-
function
|
|
1372
|
-
|
|
1373
|
-
|
|
1504
|
+
function middlewareVarsType(typesDir, sharedFile) {
|
|
1505
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, sharedFile));
|
|
1506
|
+
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
|
|
1507
|
+
}
|
|
1508
|
+
function ownSharedFile(dir, sharedFiles) {
|
|
1509
|
+
for (let index = sharedFiles.length - 1; index >= 0; index -= 1) {
|
|
1510
|
+
if ((0, import_node_path8.dirname)(sharedFiles[index]) === dir) {
|
|
1511
|
+
return sharedFiles[index];
|
|
1512
|
+
}
|
|
1374
1513
|
}
|
|
1375
|
-
return
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1514
|
+
return void 0;
|
|
1515
|
+
}
|
|
1516
|
+
function varsType(paths, file, dir, sharedFiles) {
|
|
1517
|
+
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1518
|
+
const parts = [];
|
|
1519
|
+
if (dir !== paths.routesDir) {
|
|
1520
|
+
parts.push(`import(${JSON.stringify(importPath(file, typeFilePath(paths, (0, import_node_path8.dirname)(dir))))}).Vars`);
|
|
1521
|
+
}
|
|
1522
|
+
const ownShared = ownSharedFile(dir, sharedFiles);
|
|
1523
|
+
if (ownShared) {
|
|
1524
|
+
parts.push(middlewareVarsType(typesDir, ownShared));
|
|
1525
|
+
}
|
|
1526
|
+
return parts.length > 0 ? parts.join("\n & ") : "{}";
|
|
1379
1527
|
}
|
|
1380
1528
|
function methodExports(typesDir, verbs) {
|
|
1381
1529
|
return verbs.map(({ method, file }) => {
|
|
@@ -1386,14 +1534,14 @@ function methodExports(typesDir, verbs) {
|
|
|
1386
1534
|
});
|
|
1387
1535
|
}
|
|
1388
1536
|
async function writeParamTypes(paths, folders) {
|
|
1389
|
-
|
|
1537
|
+
await Promise.all(folders.map(({ dir, params, sharedFiles, verbs }) => {
|
|
1390
1538
|
const file = typeFilePath(paths, dir);
|
|
1391
1539
|
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1392
1540
|
const lines = [
|
|
1393
1541
|
GENERATED_HEADER,
|
|
1394
1542
|
`export type Params = ${paramsType(params)};`,
|
|
1395
1543
|
"export type RouteParams = Params;",
|
|
1396
|
-
`type Vars = ${varsType(
|
|
1544
|
+
`export type Vars = ${varsType(paths, file, dir, sharedFiles)};`,
|
|
1397
1545
|
"export type Middleware<Injects extends Record<string, unknown> = {}> =",
|
|
1398
1546
|
' import("@boon4681/giri").Middleware<Params, import("@boon4681/giri").ValidatedInput, Injects>;',
|
|
1399
1547
|
'export type Handle<Input extends import("@boon4681/giri").ValidatedInput = import("@boon4681/giri").ValidatedInput> =',
|
|
@@ -1403,10 +1551,14 @@ async function writeParamTypes(paths, folders) {
|
|
|
1403
1551
|
lines.push(...methodExports(typesDir, verbs));
|
|
1404
1552
|
}
|
|
1405
1553
|
lines.push("");
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1554
|
+
return writeGenerated(file, lines.join("\n"));
|
|
1555
|
+
}));
|
|
1408
1556
|
}
|
|
1409
1557
|
|
|
1558
|
+
// src/generator/route-meta.ts
|
|
1559
|
+
var import_node_fs6 = require("fs");
|
|
1560
|
+
var import_typescript = __toESM(require("typescript"));
|
|
1561
|
+
|
|
1410
1562
|
// src/generator/inputs.ts
|
|
1411
1563
|
function sanitize(schema) {
|
|
1412
1564
|
const { $schema, ...rest } = schema;
|
|
@@ -1467,28 +1619,315 @@ function readInput(routeModule) {
|
|
|
1467
1619
|
}
|
|
1468
1620
|
return input.body || input.query ? input : void 0;
|
|
1469
1621
|
}
|
|
1470
|
-
function
|
|
1471
|
-
|
|
1622
|
+
function hasExportModifier(node) {
|
|
1623
|
+
return import_typescript.default.canHaveModifiers(node) && (import_typescript.default.getModifiers(node)?.some((modifier) => modifier.kind === import_typescript.default.SyntaxKind.ExportKeyword) ?? false);
|
|
1624
|
+
}
|
|
1625
|
+
function unwrapExpression(expression) {
|
|
1626
|
+
let current = expression;
|
|
1627
|
+
while (import_typescript.default.isParenthesizedExpression(current) || import_typescript.default.isAsExpression(current) || import_typescript.default.isSatisfiesExpression(current)) {
|
|
1628
|
+
current = current.expression;
|
|
1629
|
+
}
|
|
1630
|
+
return current;
|
|
1631
|
+
}
|
|
1632
|
+
function staticBoolean(expression) {
|
|
1633
|
+
const value = unwrapExpression(expression);
|
|
1634
|
+
if (value.kind === import_typescript.default.SyntaxKind.TrueKeyword) {
|
|
1472
1635
|
return true;
|
|
1473
1636
|
}
|
|
1474
|
-
if (value ===
|
|
1637
|
+
if (value.kind === import_typescript.default.SyntaxKind.FalseKeyword) {
|
|
1475
1638
|
return false;
|
|
1476
1639
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1640
|
+
return void 0;
|
|
1641
|
+
}
|
|
1642
|
+
function staticString(expression) {
|
|
1643
|
+
const value = unwrapExpression(expression);
|
|
1644
|
+
return import_typescript.default.isStringLiteralLike(value) ? value.text : void 0;
|
|
1645
|
+
}
|
|
1646
|
+
function staticStringArray(expression) {
|
|
1647
|
+
const value = unwrapExpression(expression);
|
|
1648
|
+
if (!import_typescript.default.isArrayLiteralExpression(value)) {
|
|
1649
|
+
return void 0;
|
|
1650
|
+
}
|
|
1651
|
+
const strings = [];
|
|
1652
|
+
for (const element of value.elements) {
|
|
1653
|
+
const string = staticString(element);
|
|
1654
|
+
if (string === void 0) {
|
|
1655
|
+
return void 0;
|
|
1656
|
+
}
|
|
1657
|
+
strings.push(string);
|
|
1658
|
+
}
|
|
1659
|
+
return strings;
|
|
1660
|
+
}
|
|
1661
|
+
function propertyName(name) {
|
|
1662
|
+
if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
|
|
1663
|
+
return name.text;
|
|
1479
1664
|
}
|
|
1480
1665
|
return void 0;
|
|
1481
1666
|
}
|
|
1482
|
-
function
|
|
1667
|
+
function collectImportedNames(source) {
|
|
1668
|
+
const names = /* @__PURE__ */ new Set();
|
|
1669
|
+
for (const statement of source.statements) {
|
|
1670
|
+
if (!import_typescript.default.isImportDeclaration(statement)) {
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
const clause = statement.importClause;
|
|
1674
|
+
if (!clause) {
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
if (clause.name) {
|
|
1678
|
+
names.add(clause.name.text);
|
|
1679
|
+
}
|
|
1680
|
+
const bindings = clause.namedBindings;
|
|
1681
|
+
if (bindings && import_typescript.default.isNamespaceImport(bindings)) {
|
|
1682
|
+
names.add(bindings.name.text);
|
|
1683
|
+
}
|
|
1684
|
+
if (bindings && import_typescript.default.isNamedImports(bindings)) {
|
|
1685
|
+
for (const element of bindings.elements) {
|
|
1686
|
+
names.add(element.name.text);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return names;
|
|
1691
|
+
}
|
|
1692
|
+
function expressionReferencesImportedMiddleware(expression, importedNames) {
|
|
1693
|
+
let found = false;
|
|
1694
|
+
const allowedImportedHelpers = /* @__PURE__ */ new Set(["stack", "fromHono"]);
|
|
1695
|
+
const visit = (node) => {
|
|
1696
|
+
if (found) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (import_typescript.default.isIdentifier(node) && importedNames.has(node.text) && !allowedImportedHelpers.has(node.text)) {
|
|
1700
|
+
found = true;
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
import_typescript.default.forEachChild(node, visit);
|
|
1704
|
+
};
|
|
1705
|
+
visit(expression);
|
|
1706
|
+
return found;
|
|
1707
|
+
}
|
|
1708
|
+
function parseStaticOpenApi(expression) {
|
|
1709
|
+
const value = unwrapExpression(expression);
|
|
1710
|
+
const boolean = staticBoolean(value);
|
|
1711
|
+
if (boolean !== void 0) {
|
|
1712
|
+
return boolean;
|
|
1713
|
+
}
|
|
1714
|
+
if (!import_typescript.default.isObjectLiteralExpression(value)) {
|
|
1715
|
+
return void 0;
|
|
1716
|
+
}
|
|
1717
|
+
const openapi = {};
|
|
1718
|
+
for (const property of value.properties) {
|
|
1719
|
+
if (!import_typescript.default.isPropertyAssignment(property)) {
|
|
1720
|
+
return void 0;
|
|
1721
|
+
}
|
|
1722
|
+
const name = propertyName(property.name);
|
|
1723
|
+
if (!name) {
|
|
1724
|
+
return void 0;
|
|
1725
|
+
}
|
|
1726
|
+
if (name === "hidden") {
|
|
1727
|
+
const hidden = staticBoolean(property.initializer);
|
|
1728
|
+
if (hidden === void 0) {
|
|
1729
|
+
return void 0;
|
|
1730
|
+
}
|
|
1731
|
+
openapi.hidden = hidden;
|
|
1732
|
+
} else if (name === "tags") {
|
|
1733
|
+
const tags = staticStringArray(property.initializer);
|
|
1734
|
+
if (!tags) {
|
|
1735
|
+
return void 0;
|
|
1736
|
+
}
|
|
1737
|
+
openapi.tags = tags;
|
|
1738
|
+
} else if (name === "summary" || name === "description" || name === "operationId") {
|
|
1739
|
+
const string = staticString(property.initializer);
|
|
1740
|
+
if (string === void 0) {
|
|
1741
|
+
return void 0;
|
|
1742
|
+
}
|
|
1743
|
+
openapi[name] = string;
|
|
1744
|
+
} else if (name === "deprecated") {
|
|
1745
|
+
const deprecated = staticBoolean(property.initializer);
|
|
1746
|
+
if (deprecated === void 0) {
|
|
1747
|
+
return void 0;
|
|
1748
|
+
}
|
|
1749
|
+
openapi.deprecated = deprecated;
|
|
1750
|
+
} else {
|
|
1751
|
+
return void 0;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return openapi;
|
|
1755
|
+
}
|
|
1756
|
+
function readStaticModuleMeta(file) {
|
|
1757
|
+
let source;
|
|
1758
|
+
try {
|
|
1759
|
+
source = import_typescript.default.createSourceFile(file, (0, import_node_fs6.readFileSync)(file, "utf8"), import_typescript.default.ScriptTarget.Latest, true);
|
|
1760
|
+
} catch {
|
|
1761
|
+
return void 0;
|
|
1762
|
+
}
|
|
1763
|
+
const importedNames = collectImportedNames(source);
|
|
1764
|
+
const sourceText = source.getFullText();
|
|
1765
|
+
const canSkipMiddlewareRuntime = !sourceText.includes("defineMiddleware") && !sourceText.includes(".openapi");
|
|
1766
|
+
const meta = { middlewareSecurity: false };
|
|
1767
|
+
for (const statement of source.statements) {
|
|
1768
|
+
if (import_typescript.default.isImportDeclaration(statement) || import_typescript.default.isInterfaceDeclaration(statement) || import_typescript.default.isTypeAliasDeclaration(statement) || import_typescript.default.isEmptyStatement(statement) || !hasExportModifier(statement)) {
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
if (import_typescript.default.isFunctionDeclaration(statement) && statement.name?.text === "handle") {
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (import_typescript.default.isVariableStatement(statement)) {
|
|
1775
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1776
|
+
if (!import_typescript.default.isIdentifier(declaration.name)) {
|
|
1777
|
+
return void 0;
|
|
1778
|
+
}
|
|
1779
|
+
const name = declaration.name.text;
|
|
1780
|
+
if (name === "openapi") {
|
|
1781
|
+
if (!declaration.initializer) {
|
|
1782
|
+
return void 0;
|
|
1783
|
+
}
|
|
1784
|
+
const openapi = parseStaticOpenApi(declaration.initializer);
|
|
1785
|
+
if (openapi === void 0) {
|
|
1786
|
+
return void 0;
|
|
1787
|
+
}
|
|
1788
|
+
meta.openapi = openapi;
|
|
1789
|
+
} else if (name === "handle") {
|
|
1790
|
+
if (!declaration.initializer || !import_typescript.default.isArrowFunction(declaration.initializer) && !import_typescript.default.isFunctionExpression(declaration.initializer)) {
|
|
1791
|
+
return void 0;
|
|
1792
|
+
}
|
|
1793
|
+
continue;
|
|
1794
|
+
} else if (name === "middleware") {
|
|
1795
|
+
if (!declaration.initializer || !canSkipMiddlewareRuntime || expressionReferencesImportedMiddleware(declaration.initializer, importedNames)) {
|
|
1796
|
+
return void 0;
|
|
1797
|
+
}
|
|
1798
|
+
meta.middlewareSecurity = false;
|
|
1799
|
+
continue;
|
|
1800
|
+
} else {
|
|
1801
|
+
return void 0;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
return void 0;
|
|
1807
|
+
}
|
|
1808
|
+
return meta;
|
|
1809
|
+
}
|
|
1810
|
+
function resolveStaticOpenApi(route, routeModule, loadShared) {
|
|
1483
1811
|
let hidden = false;
|
|
1812
|
+
const tags = [];
|
|
1813
|
+
const meta = {};
|
|
1814
|
+
const apply = (value, isVerb) => {
|
|
1815
|
+
if (value === false) {
|
|
1816
|
+
hidden = true;
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
if (value === true) {
|
|
1820
|
+
hidden = false;
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
if (!value) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (typeof value.hidden === "boolean") {
|
|
1827
|
+
hidden = value.hidden;
|
|
1828
|
+
}
|
|
1829
|
+
if (value.tags) {
|
|
1830
|
+
tags.push(...value.tags);
|
|
1831
|
+
}
|
|
1832
|
+
if (typeof value.summary === "string") {
|
|
1833
|
+
meta.summary = value.summary;
|
|
1834
|
+
}
|
|
1835
|
+
if (typeof value.description === "string") {
|
|
1836
|
+
meta.description = value.description;
|
|
1837
|
+
}
|
|
1838
|
+
if (typeof value.deprecated === "boolean") {
|
|
1839
|
+
meta.deprecated = value.deprecated;
|
|
1840
|
+
}
|
|
1841
|
+
if (isVerb && typeof value.operationId === "string") {
|
|
1842
|
+
meta.operationId = value.operationId;
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1484
1845
|
for (const file of route.sharedFiles) {
|
|
1485
|
-
const
|
|
1486
|
-
if (
|
|
1487
|
-
|
|
1846
|
+
const shared = loadShared(file);
|
|
1847
|
+
if (!shared) {
|
|
1848
|
+
return void 0;
|
|
1488
1849
|
}
|
|
1850
|
+
apply(shared.openapi, false);
|
|
1851
|
+
}
|
|
1852
|
+
apply(routeModule.openapi, true);
|
|
1853
|
+
if (tags.length > 0) {
|
|
1854
|
+
meta.tags = [...new Set(tags)];
|
|
1855
|
+
}
|
|
1856
|
+
return { hidden, meta };
|
|
1857
|
+
}
|
|
1858
|
+
function extractStaticMeta(route, routeModule, loadShared) {
|
|
1859
|
+
const openapi = resolveStaticOpenApi(route, routeModule, loadShared);
|
|
1860
|
+
if (!openapi) {
|
|
1861
|
+
return void 0;
|
|
1862
|
+
}
|
|
1863
|
+
const meta = {};
|
|
1864
|
+
if (openapi.hidden) {
|
|
1865
|
+
meta.hidden = true;
|
|
1866
|
+
}
|
|
1867
|
+
if (Object.keys(openapi.meta).length > 0) {
|
|
1868
|
+
meta.openapi = openapi.meta;
|
|
1489
1869
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1870
|
+
return meta;
|
|
1871
|
+
}
|
|
1872
|
+
function extractRuntimeSharedMeta(route, routeModule, loadShared) {
|
|
1873
|
+
const meta = {};
|
|
1874
|
+
const security = collectSecurity(route, {}, loadShared);
|
|
1875
|
+
const { hidden, meta: openapi } = resolveOpenApi(route, { openapi: routeModule.openapi }, loadShared);
|
|
1876
|
+
if (security) {
|
|
1877
|
+
meta.security = security;
|
|
1878
|
+
}
|
|
1879
|
+
if (hidden) {
|
|
1880
|
+
meta.hidden = true;
|
|
1881
|
+
}
|
|
1882
|
+
if (Object.keys(openapi).length > 0) {
|
|
1883
|
+
meta.openapi = openapi;
|
|
1884
|
+
}
|
|
1885
|
+
return meta;
|
|
1886
|
+
}
|
|
1887
|
+
function resolveOpenApi(route, routeModule, loadShared) {
|
|
1888
|
+
let hidden = false;
|
|
1889
|
+
const tags = [];
|
|
1890
|
+
const meta = {};
|
|
1891
|
+
const apply = (value, isVerb) => {
|
|
1892
|
+
if (value === false) {
|
|
1893
|
+
hidden = true;
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (value === true) {
|
|
1897
|
+
hidden = false;
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
if (!value || typeof value !== "object") {
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
const o = value;
|
|
1904
|
+
if ("hidden" in o) {
|
|
1905
|
+
hidden = Boolean(o.hidden);
|
|
1906
|
+
}
|
|
1907
|
+
if (Array.isArray(o.tags)) {
|
|
1908
|
+
tags.push(...o.tags.filter((tag) => typeof tag === "string"));
|
|
1909
|
+
}
|
|
1910
|
+
if (typeof o.summary === "string") {
|
|
1911
|
+
meta.summary = o.summary;
|
|
1912
|
+
}
|
|
1913
|
+
if (typeof o.description === "string") {
|
|
1914
|
+
meta.description = o.description;
|
|
1915
|
+
}
|
|
1916
|
+
if (typeof o.deprecated === "boolean") {
|
|
1917
|
+
meta.deprecated = o.deprecated;
|
|
1918
|
+
}
|
|
1919
|
+
if (isVerb && typeof o.operationId === "string") {
|
|
1920
|
+
meta.operationId = o.operationId;
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
for (const file of route.sharedFiles) {
|
|
1924
|
+
apply(loadShared(file).openapi, false);
|
|
1925
|
+
}
|
|
1926
|
+
apply(routeModule.openapi, true);
|
|
1927
|
+
if (tags.length > 0) {
|
|
1928
|
+
meta.tags = [...new Set(tags)];
|
|
1929
|
+
}
|
|
1930
|
+
return { hidden, meta };
|
|
1492
1931
|
}
|
|
1493
1932
|
function collectSecurity(route, routeModule, loadShared) {
|
|
1494
1933
|
const skipInherited = Boolean(
|
|
@@ -1520,6 +1959,33 @@ function collectSecurity(route, routeModule, loadShared) {
|
|
|
1520
1959
|
}
|
|
1521
1960
|
async function extractRouteMeta(config, paths, routes) {
|
|
1522
1961
|
const byFile = /* @__PURE__ */ new Map();
|
|
1962
|
+
const remainingRoutes = [];
|
|
1963
|
+
const runtimeSharedRoutes = [];
|
|
1964
|
+
const staticCache = /* @__PURE__ */ new Map();
|
|
1965
|
+
const loadStatic = (file) => {
|
|
1966
|
+
if (!staticCache.has(file)) {
|
|
1967
|
+
staticCache.set(file, readStaticModuleMeta(file));
|
|
1968
|
+
}
|
|
1969
|
+
return staticCache.get(file);
|
|
1970
|
+
};
|
|
1971
|
+
for (const route of routes) {
|
|
1972
|
+
const routeModule = loadStatic(route.file);
|
|
1973
|
+
if (!routeModule) {
|
|
1974
|
+
remainingRoutes.push(route);
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
const meta = extractStaticMeta(route, routeModule, loadStatic);
|
|
1978
|
+
if (meta) {
|
|
1979
|
+
if (meta.hidden || meta.openapi) {
|
|
1980
|
+
byFile.set(route.file, meta);
|
|
1981
|
+
}
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
runtimeSharedRoutes.push({ route, routeModule });
|
|
1985
|
+
}
|
|
1986
|
+
if (remainingRoutes.length === 0 && runtimeSharedRoutes.length === 0) {
|
|
1987
|
+
return byFile;
|
|
1988
|
+
}
|
|
1523
1989
|
const { unregister } = await safeRegister();
|
|
1524
1990
|
const unregisterAlias = registerAliasResolver(config.alias, paths.cwd);
|
|
1525
1991
|
const sharedCache = /* @__PURE__ */ new Map();
|
|
@@ -1534,13 +2000,19 @@ async function extractRouteMeta(config, paths, routes) {
|
|
|
1534
2000
|
return sharedCache.get(file);
|
|
1535
2001
|
};
|
|
1536
2002
|
try {
|
|
1537
|
-
for (const route of
|
|
2003
|
+
for (const { route, routeModule } of runtimeSharedRoutes) {
|
|
2004
|
+
const meta = extractRuntimeSharedMeta(route, routeModule, loadShared);
|
|
2005
|
+
if (meta.input || meta.security || meta.hidden || meta.openapi) {
|
|
2006
|
+
byFile.set(route.file, meta);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
for (const route of remainingRoutes) {
|
|
1538
2010
|
try {
|
|
1539
2011
|
const routeModule = loadModule2(route.file);
|
|
1540
2012
|
const meta = {};
|
|
1541
2013
|
const input = readInput(routeModule);
|
|
1542
2014
|
const security = collectSecurity(route, routeModule, loadShared);
|
|
1543
|
-
const hidden =
|
|
2015
|
+
const { hidden, meta: openapi } = resolveOpenApi(route, routeModule, loadShared);
|
|
1544
2016
|
if (input) {
|
|
1545
2017
|
meta.input = input;
|
|
1546
2018
|
}
|
|
@@ -1550,7 +2022,10 @@ async function extractRouteMeta(config, paths, routes) {
|
|
|
1550
2022
|
if (hidden) {
|
|
1551
2023
|
meta.hidden = true;
|
|
1552
2024
|
}
|
|
1553
|
-
if (
|
|
2025
|
+
if (Object.keys(openapi).length > 0) {
|
|
2026
|
+
meta.openapi = openapi;
|
|
2027
|
+
}
|
|
2028
|
+
if (meta.input || meta.security || meta.hidden || meta.openapi) {
|
|
1554
2029
|
byFile.set(route.file, meta);
|
|
1555
2030
|
}
|
|
1556
2031
|
} catch {
|
|
@@ -1632,10 +2107,11 @@ async function typeFolders(paths, routes) {
|
|
|
1632
2107
|
verbsByDir.set(key, list);
|
|
1633
2108
|
}
|
|
1634
2109
|
const dirs = await scanRouteFolders(paths.routesDir);
|
|
2110
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
1635
2111
|
return dirs.map((dir) => ({
|
|
1636
2112
|
dir,
|
|
1637
2113
|
params: routeParamsForDir(paths.routesDir, dir),
|
|
1638
|
-
sharedFiles: sharedFilesForDir(paths.routesDir, dir),
|
|
2114
|
+
sharedFiles: sharedFilesForDir(paths.routesDir, dir, sharedCache),
|
|
1639
2115
|
verbs: verbsByDir.get(slash(dir)) ?? []
|
|
1640
2116
|
}));
|
|
1641
2117
|
}
|
|
@@ -1648,24 +2124,63 @@ async function extractResponses(paths, routes) {
|
|
|
1648
2124
|
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1649
2125
|
const files = [...new Set(routes.map((route) => route.file))];
|
|
1650
2126
|
const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
);
|
|
2127
|
+
const roots = (0, import_node_fs7.existsSync)(appTypes) ? [...files, appTypes] : files;
|
|
2128
|
+
const program = createSchemaProgram2(paths, roots, { lean: true });
|
|
2129
|
+
const fallbackFiles = [];
|
|
1655
2130
|
for (const file of files) {
|
|
1656
|
-
|
|
2131
|
+
const responses = extractRouteResponses2(program, file);
|
|
2132
|
+
byFile.set(file, responses);
|
|
2133
|
+
if (hasLooseResponseSchema(responses)) {
|
|
2134
|
+
fallbackFiles.push(file);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (fallbackFiles.length > 0) {
|
|
2138
|
+
const fullProgram = createSchemaProgram2(
|
|
2139
|
+
paths,
|
|
2140
|
+
(0, import_node_fs7.existsSync)(appTypes) ? [...fallbackFiles, appTypes] : fallbackFiles
|
|
2141
|
+
);
|
|
2142
|
+
for (const file of fallbackFiles) {
|
|
2143
|
+
byFile.set(file, extractRouteResponses2(fullProgram, file));
|
|
2144
|
+
}
|
|
1657
2145
|
}
|
|
1658
2146
|
} catch (error) {
|
|
1659
2147
|
console.warn(`giri: skipped response schema generation (${error.message}).`);
|
|
1660
2148
|
}
|
|
1661
2149
|
return byFile;
|
|
1662
2150
|
}
|
|
2151
|
+
function isLooseSchema(value) {
|
|
2152
|
+
if (!value || typeof value !== "object") {
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2155
|
+
const schema = value;
|
|
2156
|
+
const keys = Object.keys(schema);
|
|
2157
|
+
if (keys.length === 0) {
|
|
2158
|
+
return true;
|
|
2159
|
+
}
|
|
2160
|
+
if (typeof schema.$ref === "string") {
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2163
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.some(isLooseSchema)) {
|
|
2164
|
+
return true;
|
|
2165
|
+
}
|
|
2166
|
+
if (schema.items && isLooseSchema(schema.items)) {
|
|
2167
|
+
return true;
|
|
2168
|
+
}
|
|
2169
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
2170
|
+
return Object.values(schema.properties).some(isLooseSchema);
|
|
2171
|
+
}
|
|
2172
|
+
return false;
|
|
2173
|
+
}
|
|
2174
|
+
function hasLooseResponseSchema(responses) {
|
|
2175
|
+
return responses.responses.some((response) => isLooseSchema(response.schema));
|
|
2176
|
+
}
|
|
1663
2177
|
async function extractMeta(config, paths, routes) {
|
|
1664
2178
|
const inputsByFile = /* @__PURE__ */ new Map();
|
|
1665
2179
|
const securityByFile = /* @__PURE__ */ new Map();
|
|
1666
2180
|
const hiddenFiles = /* @__PURE__ */ new Set();
|
|
2181
|
+
const openapiByFile = /* @__PURE__ */ new Map();
|
|
1667
2182
|
if (routes.length === 0) {
|
|
1668
|
-
return { inputsByFile, securityByFile, hiddenFiles };
|
|
2183
|
+
return { inputsByFile, securityByFile, hiddenFiles, openapiByFile };
|
|
1669
2184
|
}
|
|
1670
2185
|
try {
|
|
1671
2186
|
const meta = await extractRouteMeta(config, paths, routes);
|
|
@@ -1679,15 +2194,19 @@ async function extractMeta(config, paths, routes) {
|
|
|
1679
2194
|
if (entry.hidden) {
|
|
1680
2195
|
hiddenFiles.add(file);
|
|
1681
2196
|
}
|
|
2197
|
+
if (entry.openapi) {
|
|
2198
|
+
openapiByFile.set(file, entry.openapi);
|
|
2199
|
+
}
|
|
1682
2200
|
}
|
|
1683
2201
|
} catch (error) {
|
|
1684
2202
|
console.warn(`giri: skipped input/security generation (${error.message}).`);
|
|
1685
2203
|
}
|
|
1686
|
-
return { inputsByFile, securityByFile, hiddenFiles };
|
|
2204
|
+
return { inputsByFile, securityByFile, hiddenFiles, openapiByFile };
|
|
1687
2205
|
}
|
|
1688
2206
|
async function syncProject(config, options = {}) {
|
|
1689
2207
|
const paths = resolveGiriPaths(config, options.cwd);
|
|
1690
2208
|
assertSafeOutDir(paths);
|
|
2209
|
+
const hadOutDir = (0, import_node_fs7.existsSync)(paths.outDir);
|
|
1691
2210
|
const routes = await scanRoutes(paths.routesDir);
|
|
1692
2211
|
const folders = await typeFolders(paths, routes);
|
|
1693
2212
|
await (0, import_promises3.mkdir)(paths.outDir, { recursive: true });
|
|
@@ -1696,46 +2215,54 @@ async function syncProject(config, options = {}) {
|
|
|
1696
2215
|
await writeAppTypes(paths);
|
|
1697
2216
|
await writeTsConfig(paths, config);
|
|
1698
2217
|
const responsesByFile = await extractResponses(paths, routes);
|
|
1699
|
-
const { inputsByFile, securityByFile, hiddenFiles } = await extractMeta(config, paths, routes);
|
|
1700
|
-
const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles };
|
|
2218
|
+
const { inputsByFile, securityByFile, hiddenFiles, openapiByFile } = await extractMeta(config, paths, routes);
|
|
2219
|
+
const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles, openapiByFile };
|
|
1701
2220
|
await writeManifest(paths, routes, data);
|
|
1702
2221
|
await writeOpenApi(paths, routes, data);
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
2222
|
+
if (hadOutDir) {
|
|
2223
|
+
await pruneDir(
|
|
2224
|
+
paths.outDir,
|
|
2225
|
+
/* @__PURE__ */ new Set([
|
|
2226
|
+
(0, import_node_path11.join)(paths.outDir, "tsconfig.json"),
|
|
2227
|
+
(0, import_node_path11.join)(paths.outDir, "manifest.json"),
|
|
2228
|
+
(0, import_node_path11.join)(paths.outDir, "openapi.json"),
|
|
2229
|
+
(0, import_node_path11.join)(paths.outDir, "routes.d.ts"),
|
|
2230
|
+
(0, import_node_path11.join)(paths.outDir, "types", "app.d.ts"),
|
|
2231
|
+
...folders.map((folder) => typeFilePath(paths, folder.dir))
|
|
2232
|
+
])
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
1714
2235
|
return { paths, routes, folders, data };
|
|
1715
2236
|
}
|
|
1716
2237
|
|
|
1717
2238
|
// src/generator/watch.ts
|
|
1718
|
-
var
|
|
1719
|
-
var
|
|
2239
|
+
var import_node_fs9 = require("fs");
|
|
2240
|
+
var import_node_path14 = require("path");
|
|
1720
2241
|
|
|
1721
|
-
// src/loader/
|
|
2242
|
+
// src/loader/import-graph.ts
|
|
2243
|
+
var import_node_fs8 = require("fs");
|
|
1722
2244
|
var import_node_path12 = require("path");
|
|
2245
|
+
var import_typescript5 = __toESM(require("typescript"));
|
|
2246
|
+
var import_tinyglobby2 = require("tinyglobby");
|
|
2247
|
+
|
|
2248
|
+
// src/loader/module-loader.ts
|
|
2249
|
+
var import_node_path13 = require("path");
|
|
1723
2250
|
|
|
1724
2251
|
// src/lifecycle.ts
|
|
1725
|
-
var
|
|
1726
|
-
var
|
|
2252
|
+
var import_node_fs10 = require("fs");
|
|
2253
|
+
var import_node_path15 = require("path");
|
|
1727
2254
|
var MAIN_EXTENSIONS2 = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
|
|
1728
2255
|
function resolveMainFile(cwd) {
|
|
1729
2256
|
for (const ext of MAIN_EXTENSIONS2) {
|
|
1730
|
-
const file = (0,
|
|
1731
|
-
if ((0,
|
|
2257
|
+
const file = (0, import_node_path15.join)(cwd, "src", `main.${ext}`);
|
|
2258
|
+
if ((0, import_node_fs10.existsSync)(file)) {
|
|
1732
2259
|
return file;
|
|
1733
2260
|
}
|
|
1734
2261
|
}
|
|
1735
2262
|
return void 0;
|
|
1736
2263
|
}
|
|
1737
2264
|
async function loadLifecycle(cwd = process.cwd()) {
|
|
1738
|
-
const file = resolveMainFile((0,
|
|
2265
|
+
const file = resolveMainFile((0, import_node_path15.resolve)(cwd));
|
|
1739
2266
|
if (!file) {
|
|
1740
2267
|
return {};
|
|
1741
2268
|
}
|