swallowkit 1.1.2 → 2.0.0-alpha.10
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/README.ja.md +61 -7
- package/README.md +61 -7
- package/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +103 -27
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/configure-mcp.d.ts +12 -0
- package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
- package/dist/cli/commands/configure-mcp.js +42 -0
- package/dist/cli/commands/configure-mcp.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +5 -2
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +143 -67
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +14 -7
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +149 -170
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrations.d.ts.map +1 -1
- package/dist/cli/commands/migrations.js +25 -0
- package/dist/cli/commands/migrations.js.map +1 -1
- package/dist/cli/commands/provision.d.ts +5 -0
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +278 -55
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +5 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +123 -18
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +49 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/operations/create-model.d.ts +2 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +60 -8
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/migration-generator.d.ts.map +1 -1
- package/dist/core/operations/migration-generator.js +12 -1
- package/dist/core/operations/migration-generator.js.map +1 -1
- package/dist/core/operations/model-migration.d.ts +22 -0
- package/dist/core/operations/model-migration.d.ts.map +1 -0
- package/dist/core/operations/model-migration.js +36 -0
- package/dist/core/operations/model-migration.js.map +1 -0
- package/dist/core/operations/provision-deployment.d.ts +52 -0
- package/dist/core/operations/provision-deployment.d.ts.map +1 -0
- package/dist/core/operations/provision-deployment.js +164 -0
- package/dist/core/operations/provision-deployment.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +62 -6
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +653 -115
- package/dist/core/operations/provision-operations.js.map +1 -1
- package/dist/core/operations/scaffold-apply.d.ts +1 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
- package/dist/core/operations/scaffold-apply.js +1 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -1
- package/dist/core/operations/scaffold-machine.d.ts +1 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
- package/dist/core/operations/scaffold-machine.js.map +1 -1
- package/dist/core/operations/scaffold-plan.d.ts +2 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
- package/dist/core/operations/scaffold-plan.js +3 -1
- package/dist/core/operations/scaffold-plan.js.map +1 -1
- package/dist/core/project/agent-instructions.d.ts +2 -1
- package/dist/core/project/agent-instructions.d.ts.map +1 -1
- package/dist/core/project/agent-instructions.js +15 -0
- package/dist/core/project/agent-instructions.js.map +1 -1
- package/dist/core/project/boundaries.d.ts +5 -2
- package/dist/core/project/boundaries.d.ts.map +1 -1
- package/dist/core/project/boundaries.js +51 -20
- package/dist/core/project/boundaries.js.map +1 -1
- package/dist/core/project/capabilities.d.ts +17 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +45 -6
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/home-page.d.ts +4 -0
- package/dist/core/project/home-page.d.ts.map +1 -0
- package/dist/core/project/home-page.js +113 -0
- package/dist/core/project/home-page.js.map +1 -0
- package/dist/core/project/manifest.d.ts +3 -1
- package/dist/core/project/manifest.d.ts.map +1 -1
- package/dist/core/project/manifest.js +47 -25
- package/dist/core/project/manifest.js.map +1 -1
- package/dist/core/project/mcp-config.d.ts +26 -0
- package/dist/core/project/mcp-config.d.ts.map +1 -0
- package/dist/core/project/mcp-config.js +117 -0
- package/dist/core/project/mcp-config.js.map +1 -0
- package/dist/core/project/react-project.d.ts +26 -0
- package/dist/core/project/react-project.d.ts.map +1 -0
- package/dist/core/project/react-project.js +443 -0
- package/dist/core/project/react-project.js.map +1 -0
- package/dist/core/project/state.d.ts +7 -0
- package/dist/core/project/state.d.ts.map +1 -1
- package/dist/core/project/state.js +86 -0
- package/dist/core/project/state.js.map +1 -1
- package/dist/core/project/validation.d.ts.map +1 -1
- package/dist/core/project/validation.js +32 -10
- package/dist/core/project/validation.js.map +1 -1
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +53 -7
- package/dist/core/project/workflows.js.map +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +26 -27
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +23 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +407 -137
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
- package/dist/core/scaffold/native-schema-generator.js +294 -91
- package/dist/core/scaffold/native-schema-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +5 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
- package/dist/core/scaffold/openapi-generator.js +53 -3
- package/dist/core/scaffold/openapi-generator.js.map +1 -1
- package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
- package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-bff-generator.js +252 -0
- package/dist/core/scaffold/react-bff-generator.js.map +1 -0
- package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
- package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-ui-generator.js +41 -0
- package/dist/core/scaffold/react-ui-generator.js.map +1 -0
- package/dist/core/scaffold/ui-generator.d.ts +0 -3
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +68 -7
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +4 -3
- package/dist/core/verify/index.d.ts.map +1 -1
- package/dist/core/verify/index.js +122 -5
- package/dist/core/verify/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +1 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +3 -2
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +2 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +61 -30
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts +6 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +210 -25
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/version-selection.d.ts +55 -0
- package/dist/mcp/version-selection.d.ts.map +1 -0
- package/dist/mcp/version-selection.js +152 -0
- package/dist/mcp/version-selection.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/azure-cli.d.ts +56 -0
- package/dist/utils/azure-cli.d.ts.map +1 -0
- package/dist/utils/azure-cli.js +326 -0
- package/dist/utils/azure-cli.js.map +1 -0
- package/dist/utils/azure-diagnostics.d.ts +16 -0
- package/dist/utils/azure-diagnostics.d.ts.map +1 -0
- package/dist/utils/azure-diagnostics.js +68 -0
- package/dist/utils/azure-diagnostics.js.map +1 -0
- package/dist/utils/package-manager.d.ts +2 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +6 -3
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +5 -7
- package/dist/utils/python-uv.d.ts.map +1 -1
- package/dist/utils/python-uv.js +50 -24
- package/dist/utils/python-uv.js.map +1 -1
- package/dist/utils/react-dev.d.ts +4 -0
- package/dist/utils/react-dev.d.ts.map +1 -0
- package/dist/utils/react-dev.js +35 -0
- package/dist/utils/react-dev.js.map +1 -0
- package/dist/utils/semver.d.ts +14 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +61 -0
- package/dist/utils/semver.js.map +1 -0
- package/package.json +7 -4
- package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
- package/src/__tests__/agent-instructions.test.ts +6 -1
- package/src/__tests__/agent-loop-phase34.test.ts +143 -8
- package/src/__tests__/auth.test.ts +9 -0
- package/src/__tests__/azure-cli.test.ts +202 -0
- package/src/__tests__/boundaries.test.ts +42 -0
- package/src/__tests__/csharp-schema.test.ts +261 -0
- package/src/__tests__/dev.test.ts +13 -6
- package/src/__tests__/fixtures/az wrapper.cmd +2 -0
- package/src/__tests__/fixtures/azure-cli.cjs +153 -0
- package/src/__tests__/fixtures/provision-runner.cjs +28 -0
- package/src/__tests__/generated-project-matrix.test.ts +789 -0
- package/src/__tests__/home-page.test.ts +50 -0
- package/src/__tests__/init.test.ts +145 -5
- package/src/__tests__/mcp-version.test.ts +262 -0
- package/src/__tests__/mcp.test.ts +120 -3
- package/src/__tests__/multi-auth.test.ts +115 -4
- package/src/__tests__/package-manager.test.ts +17 -2
- package/src/__tests__/plan-apply.test.ts +179 -0
- package/src/__tests__/provision-runtime.test.ts +722 -0
- package/src/__tests__/python-uv.test.ts +50 -14
- package/src/__tests__/python-venv.test.ts +48 -0
- package/src/__tests__/react-dev.test.ts +72 -0
- package/src/__tests__/react.test.ts +838 -0
- package/src/__tests__/release-metadata.test.ts +121 -0
- package/src/__tests__/schema-analysis.test.ts +329 -0
- package/src/cli/commands/add-auth.ts +103 -29
- package/src/cli/commands/configure-mcp.ts +54 -0
- package/src/cli/commands/dev.ts +145 -73
- package/src/cli/commands/init.ts +147 -172
- package/src/cli/commands/migrations.ts +22 -0
- package/src/cli/commands/provision.ts +294 -56
- package/src/cli/commands/scaffold.ts +145 -22
- package/src/cli/index.ts +46 -10
- package/src/core/config.ts +21 -1
- package/src/core/operations/create-model.ts +51 -13
- package/src/core/operations/migration-generator.ts +14 -1
- package/src/core/operations/model-migration.ts +52 -0
- package/src/core/operations/provision-deployment.ts +183 -0
- package/src/core/operations/provision-operations.ts +662 -154
- package/src/core/operations/scaffold-apply.ts +2 -0
- package/src/core/operations/scaffold-machine.ts +1 -0
- package/src/core/operations/scaffold-plan.ts +5 -1
- package/src/core/project/agent-instructions.ts +17 -1
- package/src/core/project/agent-templates/AGENTS.md +113 -15
- package/src/core/project/agent-templates/CLAUDE.md +14 -1
- package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
- package/src/core/project/agent-templates/copilot-instructions.md +15 -1
- package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
- package/src/core/project/boundaries.ts +62 -20
- package/src/core/project/capabilities.ts +63 -6
- package/src/core/project/home-page.ts +114 -0
- package/src/core/project/manifest.ts +52 -27
- package/src/core/project/mcp-config.ts +84 -0
- package/src/core/project/react-project.ts +529 -0
- package/src/core/project/state.ts +70 -0
- package/src/core/project/validation.ts +32 -10
- package/src/core/project/workflows.ts +53 -7
- package/src/core/scaffold/auth-generator.ts +26 -27
- package/src/core/scaffold/model-parser.ts +413 -129
- package/src/core/scaffold/native-schema-generator.ts +319 -103
- package/src/core/scaffold/openapi-generator.ts +63 -5
- package/src/core/scaffold/react-bff-generator.ts +292 -0
- package/src/core/scaffold/react-ui-generator.ts +51 -0
- package/src/core/scaffold/ui-generator.ts +70 -8
- package/src/core/verify/index.ts +118 -6
- package/src/index.ts +3 -1
- package/src/machine/contracts.ts +1 -0
- package/src/machine/errors.ts +4 -2
- package/src/machine/index.ts +67 -36
- package/src/mcp/index.ts +221 -31
- package/src/mcp/version-selection.ts +163 -0
- package/src/types/index.ts +4 -0
- package/src/utils/azure-cli.ts +315 -0
- package/src/utils/azure-diagnostics.ts +75 -0
- package/src/utils/package-manager.ts +7 -4
- package/src/utils/python-uv.ts +53 -33
- package/src/utils/react-dev.ts +31 -0
- package/src/utils/semver.ts +59 -0
|
@@ -42,6 +42,7 @@ exports.toPascalCase = toPascalCase;
|
|
|
42
42
|
exports.toCamelCase = toCamelCase;
|
|
43
43
|
exports.parseConnectorConfig = parseConnectorConfig;
|
|
44
44
|
exports.parseAuthPolicy = parseAuthPolicy;
|
|
45
|
+
exports.parseReferences = parseReferences;
|
|
45
46
|
exports.parsePartitionKey = parsePartitionKey;
|
|
46
47
|
exports.toKebabCase = toKebabCase;
|
|
47
48
|
exports.getAllModels = getAllModels;
|
|
@@ -91,6 +92,9 @@ async function parseModelFile(modelPath, options = {}) {
|
|
|
91
92
|
// フィールド情報を抽出(動的インポートを使用)
|
|
92
93
|
const parsedSchema = await extractFieldsFromSchema(modelPath, schemaName, options.executeNode ?? child_process_1.execFileSync);
|
|
93
94
|
const fields = parsedSchema.fields;
|
|
95
|
+
const parserWarnings = [...parsedSchema.warnings];
|
|
96
|
+
// 外部キーは参照先モデルが shared/models に実在する場合だけ推測する
|
|
97
|
+
applyForeignKeys(fields, modelName, modelPath, parseReferences(content), parserWarnings);
|
|
94
98
|
// ネストスキーマ情報をフィールドにマージ
|
|
95
99
|
mergeNestedSchemaInfo(fields, nestedSchemaRefs);
|
|
96
100
|
// id フィールドの存在確認
|
|
@@ -121,7 +125,7 @@ async function parseModelFile(modelPath, options = {}) {
|
|
|
121
125
|
...(connectorConfig ? { connectorConfig } : {}),
|
|
122
126
|
...(authPolicy ? { authPolicy } : {}),
|
|
123
127
|
parserMode: parsedSchema.mode,
|
|
124
|
-
parserWarnings
|
|
128
|
+
parserWarnings,
|
|
125
129
|
semanticFingerprint: "",
|
|
126
130
|
};
|
|
127
131
|
modelInfo.semanticFingerprint = createModelSemanticFingerprint(modelInfo);
|
|
@@ -339,6 +343,15 @@ function compileModelModule(modelPath, directory, modules = new Map()) {
|
|
|
339
343
|
fs.writeFileSync(outputPath, output.outputText);
|
|
340
344
|
return outputPath;
|
|
341
345
|
}
|
|
346
|
+
const STRING_FORMATS = {
|
|
347
|
+
email: "email",
|
|
348
|
+
url: "uri",
|
|
349
|
+
uuid: "uuid",
|
|
350
|
+
guid: "uuid",
|
|
351
|
+
datetime: "date-time",
|
|
352
|
+
date: "date",
|
|
353
|
+
time: "time",
|
|
354
|
+
};
|
|
342
355
|
function staticParseFields(modelPath, schemaName) {
|
|
343
356
|
const source = ts.createSourceFile(modelPath, fs.readFileSync(modelPath, "utf-8"), ts.ScriptTarget.Latest, true);
|
|
344
357
|
const constants = new Map();
|
|
@@ -390,7 +403,146 @@ function staticParseFields(modelPath, schemaName) {
|
|
|
390
403
|
}
|
|
391
404
|
return values;
|
|
392
405
|
};
|
|
393
|
-
const
|
|
406
|
+
const numericArgument = (expression) => {
|
|
407
|
+
if (!expression)
|
|
408
|
+
return null;
|
|
409
|
+
if (ts.isNumericLiteral(expression))
|
|
410
|
+
return Number(expression.text);
|
|
411
|
+
if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(expression.operand)) {
|
|
412
|
+
return -Number(expression.operand.text);
|
|
413
|
+
}
|
|
414
|
+
if (ts.isIdentifier(expression)) {
|
|
415
|
+
const resolved = constants.get(expression.text);
|
|
416
|
+
return resolved ? numericArgument(resolved) : null;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
};
|
|
420
|
+
const withConstraints = (field, update) => ({
|
|
421
|
+
...field,
|
|
422
|
+
constraints: { ...(field.constraints ?? {}), ...update },
|
|
423
|
+
});
|
|
424
|
+
const lowerBound = (field, key, value, exclusive = false) => {
|
|
425
|
+
const current = field.constraints?.[key];
|
|
426
|
+
if (current !== undefined && current > value)
|
|
427
|
+
return field;
|
|
428
|
+
const constraints = { ...(field.constraints ?? {}), [key]: value };
|
|
429
|
+
if (key === "minimum") {
|
|
430
|
+
// An equal bound stays exclusive if either check was exclusive (gt(0).min(0) still rejects 0).
|
|
431
|
+
if (exclusive || (current === value && field.constraints?.exclusiveMinimum))
|
|
432
|
+
constraints.exclusiveMinimum = true;
|
|
433
|
+
else
|
|
434
|
+
delete constraints.exclusiveMinimum;
|
|
435
|
+
}
|
|
436
|
+
return { ...field, constraints };
|
|
437
|
+
};
|
|
438
|
+
const upperBound = (field, key, value, exclusive = false) => {
|
|
439
|
+
const current = field.constraints?.[key];
|
|
440
|
+
if (current !== undefined && current < value)
|
|
441
|
+
return field;
|
|
442
|
+
const constraints = { ...(field.constraints ?? {}), [key]: value };
|
|
443
|
+
if (key === "maximum") {
|
|
444
|
+
if (exclusive || (current === value && field.constraints?.exclusiveMaximum))
|
|
445
|
+
constraints.exclusiveMaximum = true;
|
|
446
|
+
else
|
|
447
|
+
delete constraints.exclusiveMaximum;
|
|
448
|
+
}
|
|
449
|
+
return { ...field, constraints };
|
|
450
|
+
};
|
|
451
|
+
const applyCheck = (inner, method, args) => {
|
|
452
|
+
const lengthTarget = inner.isArray ? "items" : inner.type === "string" ? "length" : inner.type === "number" ? "value" : null;
|
|
453
|
+
const value = numericArgument(args[0]);
|
|
454
|
+
switch (method) {
|
|
455
|
+
case "optional":
|
|
456
|
+
case "default":
|
|
457
|
+
return { ...inner, isOptional: true };
|
|
458
|
+
case "nullable":
|
|
459
|
+
return { ...inner, isNullable: true };
|
|
460
|
+
case "nullish":
|
|
461
|
+
return { ...inner, isOptional: true, isNullable: true };
|
|
462
|
+
case "describe":
|
|
463
|
+
case "trim":
|
|
464
|
+
case "toLowerCase":
|
|
465
|
+
case "toUpperCase":
|
|
466
|
+
case "strict":
|
|
467
|
+
case "strip":
|
|
468
|
+
case "passthrough":
|
|
469
|
+
case "loose":
|
|
470
|
+
return inner;
|
|
471
|
+
case "int":
|
|
472
|
+
return inner.type === "number" && !inner.isArray ? { ...inner, isInteger: true } : null;
|
|
473
|
+
case "positive":
|
|
474
|
+
return inner.type === "number" && !inner.isArray ? lowerBound(inner, "minimum", 0, true) : null;
|
|
475
|
+
case "nonnegative":
|
|
476
|
+
return inner.type === "number" && !inner.isArray ? lowerBound(inner, "minimum", 0) : null;
|
|
477
|
+
case "negative":
|
|
478
|
+
return inner.type === "number" && !inner.isArray ? upperBound(inner, "maximum", 0, true) : null;
|
|
479
|
+
case "nonpositive":
|
|
480
|
+
return inner.type === "number" && !inner.isArray ? upperBound(inner, "maximum", 0) : null;
|
|
481
|
+
case "gt":
|
|
482
|
+
case "gte":
|
|
483
|
+
return inner.type === "number" && !inner.isArray && value !== null ? lowerBound(inner, "minimum", value, method === "gt") : null;
|
|
484
|
+
case "lt":
|
|
485
|
+
case "lte":
|
|
486
|
+
return inner.type === "number" && !inner.isArray && value !== null ? upperBound(inner, "maximum", value, method === "lt") : null;
|
|
487
|
+
case "min":
|
|
488
|
+
if (value === null || !lengthTarget)
|
|
489
|
+
return null;
|
|
490
|
+
return lowerBound(inner, lengthTarget === "items" ? "minItems" : lengthTarget === "length" ? "minLength" : "minimum", value);
|
|
491
|
+
case "max":
|
|
492
|
+
if (value === null || !lengthTarget)
|
|
493
|
+
return null;
|
|
494
|
+
return upperBound(inner, lengthTarget === "items" ? "maxItems" : lengthTarget === "length" ? "maxLength" : "maximum", value);
|
|
495
|
+
case "length":
|
|
496
|
+
if (value === null || (lengthTarget !== "items" && lengthTarget !== "length"))
|
|
497
|
+
return null;
|
|
498
|
+
return lengthTarget === "items"
|
|
499
|
+
? upperBound(lowerBound(inner, "minItems", value), "maxItems", value)
|
|
500
|
+
: upperBound(lowerBound(inner, "minLength", value), "maxLength", value);
|
|
501
|
+
case "nonempty":
|
|
502
|
+
if (lengthTarget !== "items" && lengthTarget !== "length")
|
|
503
|
+
return null;
|
|
504
|
+
return lowerBound(inner, lengthTarget === "items" ? "minItems" : "minLength", 1);
|
|
505
|
+
case "regex": {
|
|
506
|
+
const literal = args[0];
|
|
507
|
+
if (!literal || !ts.isRegularExpressionLiteral(literal) || inner.type !== "string" || inner.isArray)
|
|
508
|
+
return null;
|
|
509
|
+
const text = literal.text;
|
|
510
|
+
const separator = text.lastIndexOf("/");
|
|
511
|
+
return withConstraints(inner, {
|
|
512
|
+
pattern: text.slice(1, separator),
|
|
513
|
+
...(text.slice(separator + 1) ? { patternFlags: text.slice(separator + 1) } : {}),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
case "email":
|
|
517
|
+
case "url":
|
|
518
|
+
case "uuid":
|
|
519
|
+
case "guid":
|
|
520
|
+
case "datetime":
|
|
521
|
+
case "date":
|
|
522
|
+
case "time":
|
|
523
|
+
if (inner.type !== "string" || inner.isArray)
|
|
524
|
+
return null;
|
|
525
|
+
return { ...inner, format: STRING_FORMATS[method] };
|
|
526
|
+
default:
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
const parseObjectFields = (literal) => {
|
|
531
|
+
const objectFields = [];
|
|
532
|
+
for (const property of literal.properties) {
|
|
533
|
+
if (!ts.isPropertyAssignment(property))
|
|
534
|
+
return null;
|
|
535
|
+
const name = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) ? property.name.text : null;
|
|
536
|
+
if (!name)
|
|
537
|
+
return null;
|
|
538
|
+
const parsed = parseExpression(property.initializer);
|
|
539
|
+
if (!parsed)
|
|
540
|
+
return null;
|
|
541
|
+
objectFields.push({ name, ...parsed });
|
|
542
|
+
}
|
|
543
|
+
return objectFields;
|
|
544
|
+
};
|
|
545
|
+
function parseExpression(expression) {
|
|
394
546
|
if (ts.isIdentifier(expression)) {
|
|
395
547
|
const resolved = constants.get(expression.text);
|
|
396
548
|
if (resolved)
|
|
@@ -403,61 +555,51 @@ function staticParseFields(modelPath, schemaName) {
|
|
|
403
555
|
return null;
|
|
404
556
|
const method = expression.expression.name.text;
|
|
405
557
|
const receiver = expression.expression.expression;
|
|
406
|
-
const
|
|
407
|
-
|
|
558
|
+
const base = (type, extra = {}) => ({ type, isOptional: false, isNullable: false, isArray: false, ...extra });
|
|
559
|
+
// z.iso.datetime() / z.iso.date() / z.iso.time()
|
|
560
|
+
if (ts.isPropertyAccessExpression(receiver) && ts.isIdentifier(receiver.expression) && receiver.expression.text === "z" && receiver.name.text === "iso") {
|
|
561
|
+
return method === "datetime" || method === "date" || method === "time" ? base("string", { format: STRING_FORMATS[method] }) : null;
|
|
562
|
+
}
|
|
563
|
+
if (!(ts.isIdentifier(receiver) && receiver.text === "z")) {
|
|
408
564
|
const inner = parseExpression(receiver);
|
|
409
|
-
|
|
410
|
-
return null;
|
|
411
|
-
if (method === "optional" || method === "default")
|
|
412
|
-
inner.isOptional = true;
|
|
413
|
-
if (method === "nullable")
|
|
414
|
-
inner.isNullable = true;
|
|
415
|
-
return inner;
|
|
565
|
+
return inner ? applyCheck(inner, method, expression.arguments) : null;
|
|
416
566
|
}
|
|
417
|
-
if (
|
|
418
|
-
return
|
|
419
|
-
if (
|
|
420
|
-
return
|
|
567
|
+
if (["string", "boolean", "date"].includes(method))
|
|
568
|
+
return base(method);
|
|
569
|
+
if (method === "number")
|
|
570
|
+
return base("number");
|
|
571
|
+
if (method === "int")
|
|
572
|
+
return base("number", { isInteger: true });
|
|
573
|
+
if (method === "email" || method === "url" || method === "uuid" || method === "guid")
|
|
574
|
+
return base("string", { format: STRING_FORMATS[method] });
|
|
575
|
+
if (method === "object") {
|
|
576
|
+
const literal = expression.arguments[0];
|
|
577
|
+
if (!literal || !ts.isObjectLiteralExpression(literal))
|
|
578
|
+
return null;
|
|
579
|
+
// Unsupported members keep the previous untyped-object behavior instead of failing the whole model.
|
|
580
|
+
const objectFields = parseObjectFields(literal);
|
|
581
|
+
return objectFields ? base("object", { objectFields }) : base("object");
|
|
421
582
|
}
|
|
422
|
-
if (method === "object")
|
|
423
|
-
return { type: "object", isOptional: false, isNullable: false, isArray: false };
|
|
424
583
|
if (method === "array" && expression.arguments[0]) {
|
|
425
584
|
const element = parseExpression(expression.arguments[0]);
|
|
426
585
|
return element ? { ...element, isArray: true } : null;
|
|
427
586
|
}
|
|
428
587
|
if (method === "enum" && expression.arguments[0]) {
|
|
429
588
|
const enumValues = literalValues(expression.arguments[0]);
|
|
430
|
-
return enumValues ?
|
|
589
|
+
return enumValues ? base("string", { enumValues }) : null;
|
|
431
590
|
}
|
|
432
591
|
if (method === "literal" && expression.arguments[0]) {
|
|
433
592
|
const value = expression.arguments[0];
|
|
434
593
|
if (ts.isStringLiteral(value) || ts.isNumericLiteral(value)) {
|
|
435
|
-
return
|
|
594
|
+
return base(ts.isNumericLiteral(value) ? "number" : "string", { enumValues: [value.text] });
|
|
436
595
|
}
|
|
437
596
|
if (value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword) {
|
|
438
|
-
return
|
|
597
|
+
return base("boolean", { enumValues: [value.kind === ts.SyntaxKind.TrueKeyword ? "true" : "false"] });
|
|
439
598
|
}
|
|
440
599
|
}
|
|
441
600
|
return null;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
for (const property of schema.arguments[0].properties) {
|
|
445
|
-
if (!ts.isPropertyAssignment(property))
|
|
446
|
-
return null;
|
|
447
|
-
const name = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) ? property.name.text : null;
|
|
448
|
-
if (!name)
|
|
449
|
-
return null;
|
|
450
|
-
const parsed = parseExpression(property.initializer);
|
|
451
|
-
if (!parsed)
|
|
452
|
-
return null;
|
|
453
|
-
const isForeignKey = name.endsWith("Id") && name.length > 2 && parsed.type === "string";
|
|
454
|
-
fields.push({
|
|
455
|
-
name,
|
|
456
|
-
...parsed,
|
|
457
|
-
...(isForeignKey ? { isForeignKey: true, referencedModel: name.slice(0, -2).replace(/^./, char => char.toUpperCase()) } : {}),
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
return fields;
|
|
601
|
+
}
|
|
602
|
+
return parseObjectFields(schema.arguments[0]);
|
|
461
603
|
}
|
|
462
604
|
async function extractFieldsFromSchema(modelPath, schemaName, executeNode) {
|
|
463
605
|
const staticFields = staticParseFields(modelPath, schemaName);
|
|
@@ -481,110 +623,160 @@ async function extractFieldsFromSchema(modelPath, schemaName, executeNode) {
|
|
|
481
623
|
const scriptCode = `
|
|
482
624
|
import { ${schemaName} as schema } from './${path.basename(compiledModel)}';
|
|
483
625
|
|
|
484
|
-
// Zod v3とv4の両方に対応
|
|
485
|
-
const
|
|
486
|
-
|
|
626
|
+
// Zod v3 (_def.typeName) と v4 (_zod.def.type) の両方に対応
|
|
627
|
+
const FORMATS = { email: 'email', url: 'uri', uuid: 'uuid', guid: 'uuid', datetime: 'date-time', date: 'date', time: 'time' };
|
|
628
|
+
const v4 = (s) => (s && s._zod && s._zod.def) || null;
|
|
629
|
+
const v3Name = (s) => (s && s._def && s._def.typeName) || '';
|
|
630
|
+
|
|
631
|
+
function lower(c, key, value, exclusive) {
|
|
632
|
+
if (typeof value !== 'number' || (c[key] !== undefined && c[key] > value)) return;
|
|
633
|
+
const keepExclusive = c[key] === value && c.exclusiveMinimum;
|
|
634
|
+
c[key] = value;
|
|
635
|
+
if (key === 'minimum') { if (exclusive || keepExclusive) c.exclusiveMinimum = true; else delete c.exclusiveMinimum; }
|
|
636
|
+
}
|
|
637
|
+
function upper(c, key, value, exclusive) {
|
|
638
|
+
if (typeof value !== 'number' || (c[key] !== undefined && c[key] < value)) return;
|
|
639
|
+
const keepExclusive = c[key] === value && c.exclusiveMaximum;
|
|
640
|
+
c[key] = value;
|
|
641
|
+
if (key === 'maximum') { if (exclusive || keepExclusive) c.exclusiveMaximum = true; else delete c.exclusiveMaximum; }
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function unwrap(s, info) {
|
|
645
|
+
for (;;) {
|
|
646
|
+
const d = v4(s);
|
|
647
|
+
if (d) {
|
|
648
|
+
if (d.type === 'optional' || d.type === 'default' || d.type === 'prefault') { info.isOptional = true; s = d.innerType; continue; }
|
|
649
|
+
if (d.type === 'nullable') { info.isNullable = true; s = d.innerType; continue; }
|
|
650
|
+
if (d.type === 'readonly' || d.type === 'nonoptional' || d.type === 'catch') { s = d.innerType; continue; }
|
|
651
|
+
return s;
|
|
652
|
+
}
|
|
653
|
+
const name = v3Name(s);
|
|
654
|
+
if (name === 'ZodOptional' || name === 'ZodDefault') { info.isOptional = true; s = s._def.innerType; continue; }
|
|
655
|
+
if (name === 'ZodNullable') { info.isNullable = true; s = s._def.innerType; continue; }
|
|
656
|
+
if (name === 'ZodEffects') { s = s._def.schema; continue; }
|
|
657
|
+
if (name === 'ZodReadonly' || name === 'ZodCatch') { s = s._def.innerType; continue; }
|
|
658
|
+
return s;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function describe(key, input) {
|
|
663
|
+
const info = { name: key, type: undefined, isOptional: false, isNullable: false, isArray: false };
|
|
664
|
+
const constraints = {};
|
|
665
|
+
let s = unwrap(input, info);
|
|
487
666
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
667
|
+
const arrayDef = v4(s);
|
|
668
|
+
if ((arrayDef && arrayDef.type === 'array') || v3Name(s) === 'ZodArray') {
|
|
669
|
+
info.isArray = true;
|
|
670
|
+
if (arrayDef) {
|
|
671
|
+
for (const check of arrayDef.checks || []) {
|
|
672
|
+
const cd = check._zod.def;
|
|
673
|
+
if (cd.check === 'min_length') lower(constraints, 'minItems', cd.minimum);
|
|
674
|
+
if (cd.check === 'max_length') upper(constraints, 'maxItems', cd.maximum);
|
|
675
|
+
if (cd.check === 'length_equals') { lower(constraints, 'minItems', cd.length); upper(constraints, 'maxItems', cd.length); }
|
|
676
|
+
}
|
|
677
|
+
s = arrayDef.element;
|
|
678
|
+
} else {
|
|
679
|
+
const ad = s._def;
|
|
680
|
+
if (ad.minLength) lower(constraints, 'minItems', ad.minLength.value);
|
|
681
|
+
if (ad.maxLength) upper(constraints, 'maxItems', ad.maxLength.value);
|
|
682
|
+
if (ad.exactLength) { lower(constraints, 'minItems', ad.exactLength.value); upper(constraints, 'maxItems', ad.exactLength.value); }
|
|
683
|
+
s = ad.type;
|
|
684
|
+
}
|
|
685
|
+
s = unwrap(s, {});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const d = v4(s);
|
|
689
|
+
const name = v3Name(s);
|
|
690
|
+
let enumValues;
|
|
691
|
+
if (d) {
|
|
692
|
+
if (d.type === 'string') {
|
|
693
|
+
info.type = 'string';
|
|
694
|
+
if (d.format && FORMATS[d.format]) info.format = FORMATS[d.format];
|
|
695
|
+
if (d.format === 'regex' && d.pattern) { constraints.pattern = d.pattern.source; if (d.pattern.flags) constraints.patternFlags = d.pattern.flags; }
|
|
696
|
+
} else if (d.type === 'number') {
|
|
697
|
+
info.type = 'number';
|
|
698
|
+
if (d.format && /int/.test(d.format)) info.isInteger = true;
|
|
699
|
+
} else if (d.type === 'boolean') info.type = 'boolean';
|
|
700
|
+
else if (d.type === 'date') info.type = 'date';
|
|
701
|
+
else if (d.type === 'object') {
|
|
702
|
+
info.type = 'object';
|
|
703
|
+
const shape = typeof d.shape === 'function' ? d.shape() : d.shape;
|
|
704
|
+
info.objectFields = Object.keys(shape).map((child) => describe(child, shape[child]));
|
|
705
|
+
} else if (d.type === 'enum') {
|
|
706
|
+
info.type = 'string';
|
|
707
|
+
enumValues = Object.values(d.entries || {}).map(String);
|
|
708
|
+
} else if (d.type === 'literal') {
|
|
709
|
+
const values = Array.isArray(d.values) ? d.values : Array.from(d.values || []);
|
|
710
|
+
if (values.length > 0) {
|
|
711
|
+
info.type = typeof values[0] === 'number' ? 'number' : typeof values[0] === 'boolean' ? 'boolean' : 'string';
|
|
712
|
+
enumValues = values.map(String);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
for (const check of d.checks || []) {
|
|
716
|
+
const cd = check._zod.def;
|
|
717
|
+
const lengthKey = info.type === 'string';
|
|
718
|
+
if (cd.check === 'min_length' && lengthKey) lower(constraints, 'minLength', cd.minimum);
|
|
719
|
+
if (cd.check === 'max_length' && lengthKey) upper(constraints, 'maxLength', cd.maximum);
|
|
720
|
+
if (cd.check === 'length_equals' && lengthKey) { lower(constraints, 'minLength', cd.length); upper(constraints, 'maxLength', cd.length); }
|
|
721
|
+
if (cd.check === 'greater_than') lower(constraints, 'minimum', Number(cd.value), !cd.inclusive);
|
|
722
|
+
if (cd.check === 'less_than') upper(constraints, 'maximum', Number(cd.value), !cd.inclusive);
|
|
723
|
+
if (cd.check === 'number_format' && /int/.test(cd.format || '')) info.isInteger = true;
|
|
724
|
+
if (cd.check === 'string_format') {
|
|
725
|
+
if (cd.format === 'regex' && cd.pattern) { constraints.pattern = cd.pattern.source; if (cd.pattern.flags) constraints.patternFlags = cd.pattern.flags; }
|
|
726
|
+
else if (FORMATS[cd.format]) info.format = FORMATS[cd.format];
|
|
525
727
|
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
else if (typeName === 'ZodNumber') type = 'number';
|
|
538
|
-
else if (typeName === 'ZodBoolean') type = 'boolean';
|
|
539
|
-
else if (typeName === 'ZodDate') type = 'date';
|
|
540
|
-
else if (typeName === 'ZodObject') type = 'object';
|
|
541
|
-
else if (typeName === 'ZodEnum' || typeName === 'ZodNativeEnum') {
|
|
542
|
-
type = 'string';
|
|
543
|
-
// enum の選択肢を取得(複数のZodバージョンに対応)
|
|
544
|
-
if (fieldDef.options) {
|
|
545
|
-
// Zod v3.23+ では options プロパティを使用
|
|
546
|
-
enumValues = Array.isArray(fieldDef.options)
|
|
547
|
-
? fieldDef.options
|
|
548
|
-
: Object.values(fieldDef.options);
|
|
549
|
-
} else if (fieldDef._def.values) {
|
|
550
|
-
// 古いバージョンでは _def.values を使用
|
|
551
|
-
enumValues = Array.isArray(fieldDef._def.values)
|
|
552
|
-
? fieldDef._def.values
|
|
553
|
-
: Object.values(fieldDef._def.values);
|
|
554
|
-
} else if (fieldDef._def.entries) {
|
|
555
|
-
// さらに古いバージョンでは _def.entries を使用
|
|
556
|
-
enumValues = Array.isArray(fieldDef._def.entries)
|
|
557
|
-
? fieldDef._def.entries
|
|
558
|
-
: Object.values(fieldDef._def.entries);
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
const def = s && s._def;
|
|
731
|
+
if (name === 'ZodString') {
|
|
732
|
+
info.type = 'string';
|
|
733
|
+
for (const check of def.checks || []) {
|
|
734
|
+
if (check.kind === 'min') lower(constraints, 'minLength', check.value);
|
|
735
|
+
if (check.kind === 'max') upper(constraints, 'maxLength', check.value);
|
|
736
|
+
if (check.kind === 'length') { lower(constraints, 'minLength', check.value); upper(constraints, 'maxLength', check.value); }
|
|
737
|
+
if (check.kind === 'regex') { constraints.pattern = check.regex.source; if (check.regex.flags) constraints.patternFlags = check.regex.flags; }
|
|
738
|
+
if (FORMATS[check.kind]) info.format = FORMATS[check.kind];
|
|
559
739
|
}
|
|
560
|
-
} else if (
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
type = typeof literalValue === 'number' ? 'number' : typeof literalValue === 'boolean' ? 'boolean' : 'string';
|
|
567
|
-
enumValues = literalValues.map(value => String(value));
|
|
740
|
+
} else if (name === 'ZodNumber') {
|
|
741
|
+
info.type = 'number';
|
|
742
|
+
for (const check of def.checks || []) {
|
|
743
|
+
if (check.kind === 'int') info.isInteger = true;
|
|
744
|
+
if (check.kind === 'min') lower(constraints, 'minimum', check.value, !check.inclusive);
|
|
745
|
+
if (check.kind === 'max') upper(constraints, 'maximum', check.value, !check.inclusive);
|
|
568
746
|
}
|
|
747
|
+
} else if (name === 'ZodBoolean') info.type = 'boolean';
|
|
748
|
+
else if (name === 'ZodDate') info.type = 'date';
|
|
749
|
+
else if (name === 'ZodObject') {
|
|
750
|
+
info.type = 'object';
|
|
751
|
+
const shape = typeof def.shape === 'function' ? def.shape() : def.shape;
|
|
752
|
+
info.objectFields = Object.keys(shape).map((child) => describe(child, shape[child]));
|
|
753
|
+
} else if (name === 'ZodEnum' || name === 'ZodNativeEnum') {
|
|
754
|
+
info.type = 'string';
|
|
755
|
+
const values = s.options || def.values || def.entries;
|
|
756
|
+
enumValues = (Array.isArray(values) ? values : Object.values(values || {})).map(String);
|
|
757
|
+
} else if (name === 'ZodLiteral') {
|
|
758
|
+
const value = def.value;
|
|
759
|
+
info.type = typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'boolean' : 'string';
|
|
760
|
+
enumValues = [String(value)];
|
|
569
761
|
}
|
|
762
|
+
}
|
|
570
763
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
764
|
+
const typeLabel = (d && d.type) || name || 'unknown';
|
|
765
|
+
if (!info.type || ((typeLabel === 'enum' || name === 'ZodEnum' || name === 'ZodNativeEnum') && (!enumValues || enumValues.length === 0))) {
|
|
766
|
+
throw new Error('Unsupported or ambiguous Zod field "' + key + '" (' + typeLabel + ')');
|
|
767
|
+
}
|
|
768
|
+
if (enumValues) info.enumValues = enumValues;
|
|
769
|
+
if (Object.keys(constraints).length > 0) info.constraints = constraints;
|
|
770
|
+
return info;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const root = unwrap(schema, {});
|
|
774
|
+
const rootDef = v4(root);
|
|
775
|
+
const rootShape = rootDef && rootDef.type === 'object'
|
|
776
|
+
? (typeof rootDef.shape === 'function' ? rootDef.shape() : rootDef.shape)
|
|
777
|
+
: v3Name(root) === 'ZodObject' ? (typeof root._def.shape === 'function' ? root._def.shape() : root._def.shape) : null;
|
|
778
|
+
if (rootShape) {
|
|
779
|
+
const fields = Object.keys(rootShape).map((key) => describe(key, rootShape[key]));
|
|
588
780
|
console.log(JSON.stringify(fields));
|
|
589
781
|
} else {
|
|
590
782
|
throw new Error('Exported schema is not a Zod object');
|
|
@@ -788,6 +980,84 @@ function parseAuthPolicy(content) {
|
|
|
788
980
|
...(namedPolicy ? { policy: namedPolicy } : {}),
|
|
789
981
|
};
|
|
790
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* `export const references = { userId: 'User', legacyId: false }` を静的に抽出する。
|
|
985
|
+
* 値が false / null の場合は名前からの推測を無効にする。
|
|
986
|
+
*/
|
|
987
|
+
function parseReferences(content) {
|
|
988
|
+
const source = ts.createSourceFile("model.ts", content, ts.ScriptTarget.Latest, true);
|
|
989
|
+
const references = {};
|
|
990
|
+
for (const statement of source.statements) {
|
|
991
|
+
if (!ts.isVariableStatement(statement))
|
|
992
|
+
continue;
|
|
993
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
994
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== "references" || !declaration.initializer)
|
|
995
|
+
continue;
|
|
996
|
+
let initializer = declaration.initializer;
|
|
997
|
+
while (ts.isAsExpression(initializer) || ts.isSatisfiesExpression(initializer))
|
|
998
|
+
initializer = initializer.expression;
|
|
999
|
+
if (!ts.isObjectLiteralExpression(initializer))
|
|
1000
|
+
continue;
|
|
1001
|
+
for (const property of initializer.properties) {
|
|
1002
|
+
if (!ts.isPropertyAssignment(property))
|
|
1003
|
+
continue;
|
|
1004
|
+
const key = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) ? property.name.text : null;
|
|
1005
|
+
if (!key)
|
|
1006
|
+
continue;
|
|
1007
|
+
const value = property.initializer;
|
|
1008
|
+
if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
|
|
1009
|
+
references[key] = value.text;
|
|
1010
|
+
else if (value.kind === ts.SyntaxKind.FalseKeyword || value.kind === ts.SyntaxKind.NullKeyword)
|
|
1011
|
+
references[key] = false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return references;
|
|
1016
|
+
}
|
|
1017
|
+
function listModelNames(modelPath) {
|
|
1018
|
+
const directory = path.dirname(modelPath);
|
|
1019
|
+
const names = new Set();
|
|
1020
|
+
for (const entry of fs.readdirSync(directory)) {
|
|
1021
|
+
if (!entry.endsWith(".ts") || entry.endsWith(".d.ts") || entry === "index.ts")
|
|
1022
|
+
continue;
|
|
1023
|
+
names.add(toPascalCase(path.basename(entry, ".ts")));
|
|
1024
|
+
}
|
|
1025
|
+
return names;
|
|
1026
|
+
}
|
|
1027
|
+
function applyForeignKeys(fields, modelName, modelPath, references, warnings) {
|
|
1028
|
+
const models = listModelNames(modelPath);
|
|
1029
|
+
for (const field of fields) {
|
|
1030
|
+
delete field.isForeignKey;
|
|
1031
|
+
delete field.referencedModel;
|
|
1032
|
+
}
|
|
1033
|
+
for (const [fieldName, target] of Object.entries(references)) {
|
|
1034
|
+
const field = fields.find((candidate) => candidate.name === fieldName);
|
|
1035
|
+
if (!field) {
|
|
1036
|
+
warnings.push(`reference-field-not-found: ${modelName}.references declares ${fieldName}, but the schema has no such field.`);
|
|
1037
|
+
}
|
|
1038
|
+
else if (target !== false && (field.type !== "string" || field.isArray || field.enumValues?.length)) {
|
|
1039
|
+
warnings.push(`reference-field-unsupported: ${modelName}.${fieldName} must be a single string id to reference ${target}.`);
|
|
1040
|
+
}
|
|
1041
|
+
else if (target !== false && !models.has(target)) {
|
|
1042
|
+
warnings.push(`reference-model-not-found: ${modelName}.${fieldName} references ${target}, but shared/models has no ${target} model; it is treated as a plain field.`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
for (const field of fields) {
|
|
1046
|
+
if (field.type !== "string" || field.isArray || field.enumValues?.length || field.isNestedSchema)
|
|
1047
|
+
continue;
|
|
1048
|
+
const declared = references[field.name];
|
|
1049
|
+
if (declared === false)
|
|
1050
|
+
continue;
|
|
1051
|
+
const inferred = field.name.length > 2 && field.name.endsWith("Id")
|
|
1052
|
+
? field.name.slice(0, -2).replace(/^./, (char) => char.toUpperCase())
|
|
1053
|
+
: undefined;
|
|
1054
|
+
const target = declared ?? inferred;
|
|
1055
|
+
if (target && models.has(target)) {
|
|
1056
|
+
field.isForeignKey = true;
|
|
1057
|
+
field.referencedModel = target;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
791
1061
|
/**
|
|
792
1062
|
* パーティションキーを抽出する
|
|
793
1063
|
* export const partitionKey = '/tenantId' のようなエクスポートを検出
|