swallowkit 2.0.0-alpha.3 → 2.0.0-alpha.5
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/cli/commands/add-auth.js +37 -6
- 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/init.d.ts +6 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +19 -5
- 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 +2 -0
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +45 -16
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +4 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +85 -3
- 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 +29 -4
- package/dist/cli/index.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/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-operations.d.ts +19 -1
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +62 -35
- 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/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 +2 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +29 -5
- package/dist/core/project/capabilities.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.js +1 -1
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +23 -5
- 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 +18 -19
- 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 +289 -76
- 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/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/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 +42 -17
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -2
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +154 -15
- 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/utils/azure-cli.d.ts +44 -0
- package/dist/utils/azure-cli.d.ts.map +1 -0
- package/dist/utils/azure-cli.js +164 -0
- package/dist/utils/azure-cli.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 +6 -3
- package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
- package/src/__tests__/agent-loop-phase34.test.ts +124 -0
- package/src/__tests__/azure-cli.test.ts +199 -0
- package/src/__tests__/boundaries.test.ts +42 -0
- package/src/__tests__/csharp-schema.test.ts +261 -0
- package/src/__tests__/generated-project-matrix.test.ts +789 -0
- package/src/__tests__/mcp-version.test.ts +262 -0
- package/src/__tests__/mcp.test.ts +64 -3
- package/src/__tests__/multi-auth.test.ts +115 -4
- package/src/__tests__/plan-apply.test.ts +170 -0
- package/src/__tests__/schema-analysis.test.ts +329 -0
- package/src/cli/commands/add-auth.ts +36 -6
- package/src/cli/commands/configure-mcp.ts +54 -0
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/migrations.ts +22 -0
- package/src/cli/commands/provision.ts +53 -17
- package/src/cli/commands/scaffold.ts +105 -3
- package/src/cli/index.ts +28 -5
- package/src/core/operations/create-model.ts +51 -13
- package/src/core/operations/model-migration.ts +52 -0
- package/src/core/operations/provision-operations.ts +86 -42
- 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-templates/AGENTS.md +67 -6
- package/src/core/project/agent-templates/CLAUDE.md +7 -1
- package/src/core/project/agent-templates/copilot-instructions.md +7 -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 +32 -5
- package/src/core/project/mcp-config.ts +84 -0
- package/src/core/project/react-project.ts +1 -1
- package/src/core/project/workflows.ts +23 -5
- package/src/core/scaffold/auth-generator.ts +18 -19
- package/src/core/scaffold/model-parser.ts +413 -129
- package/src/core/scaffold/native-schema-generator.ts +313 -78
- package/src/core/scaffold/openapi-generator.ts +63 -5
- package/src/core/scaffold/ui-generator.ts +70 -8
- package/src/core/verify/index.ts +118 -6
- package/src/machine/contracts.ts +1 -0
- package/src/machine/errors.ts +4 -2
- package/src/machine/index.ts +45 -20
- package/src/mcp/index.ts +173 -18
- package/src/mcp/version-selection.ts +163 -0
- package/src/utils/azure-cli.ts +154 -0
- package/src/utils/semver.ts +59 -0
|
@@ -35,6 +35,22 @@ export interface ModelInfo {
|
|
|
35
35
|
semanticFingerprint: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export type FieldFormat = "date-time" | "date" | "time" | "email" | "uuid" | "uri";
|
|
39
|
+
|
|
40
|
+
/** Zod validation constraints that native backends can enforce. For arrays, length bounds are minItems/maxItems and other values apply to each element. */
|
|
41
|
+
export interface FieldConstraints {
|
|
42
|
+
minLength?: number;
|
|
43
|
+
maxLength?: number;
|
|
44
|
+
pattern?: string;
|
|
45
|
+
patternFlags?: string;
|
|
46
|
+
minimum?: number;
|
|
47
|
+
maximum?: number;
|
|
48
|
+
exclusiveMinimum?: boolean;
|
|
49
|
+
exclusiveMaximum?: boolean;
|
|
50
|
+
minItems?: number;
|
|
51
|
+
maxItems?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
export interface FieldInfo {
|
|
39
55
|
name: string;
|
|
40
56
|
type: string; // "string" | "number" | "boolean" | "date" | "object" | "array"
|
|
@@ -42,6 +58,10 @@ export interface FieldInfo {
|
|
|
42
58
|
isNullable?: boolean;
|
|
43
59
|
isArray: boolean;
|
|
44
60
|
enumValues?: string[]; // enum の場合の選択肢
|
|
61
|
+
format?: FieldFormat; // 明示された文字列形式(z.iso.datetime() など)
|
|
62
|
+
isInteger?: boolean; // z.number().int() / z.int()
|
|
63
|
+
constraints?: FieldConstraints;
|
|
64
|
+
objectFields?: FieldInfo[]; // インライン z.object({...}) の形
|
|
45
65
|
isForeignKey?: boolean; // 外部キーかどうか
|
|
46
66
|
referencedModel?: string; // 参照先のモデル名(例: "Category")
|
|
47
67
|
isNestedSchema?: boolean; // ネストしたスキーマ参照かどうか
|
|
@@ -113,6 +133,10 @@ export async function parseModelFile(modelPath: string, options: ModelParserOpti
|
|
|
113
133
|
// フィールド情報を抽出(動的インポートを使用)
|
|
114
134
|
const parsedSchema = await extractFieldsFromSchema(modelPath, schemaName, options.executeNode ?? execFileSync);
|
|
115
135
|
const fields = parsedSchema.fields;
|
|
136
|
+
const parserWarnings = [...parsedSchema.warnings];
|
|
137
|
+
|
|
138
|
+
// 外部キーは参照先モデルが shared/models に実在する場合だけ推測する
|
|
139
|
+
applyForeignKeys(fields, modelName, modelPath, parseReferences(content), parserWarnings);
|
|
116
140
|
|
|
117
141
|
// ネストスキーマ情報をフィールドにマージ
|
|
118
142
|
mergeNestedSchemaInfo(fields, nestedSchemaRefs);
|
|
@@ -147,7 +171,7 @@ export async function parseModelFile(modelPath: string, options: ModelParserOpti
|
|
|
147
171
|
...(connectorConfig ? { connectorConfig } : {}),
|
|
148
172
|
...(authPolicy ? { authPolicy } : {}),
|
|
149
173
|
parserMode: parsedSchema.mode,
|
|
150
|
-
parserWarnings
|
|
174
|
+
parserWarnings,
|
|
151
175
|
semanticFingerprint: "",
|
|
152
176
|
};
|
|
153
177
|
modelInfo.semanticFingerprint = createModelSemanticFingerprint(modelInfo);
|
|
@@ -396,6 +420,16 @@ interface ParsedSchemaFields {
|
|
|
396
420
|
warnings: string[];
|
|
397
421
|
}
|
|
398
422
|
|
|
423
|
+
const STRING_FORMATS: Record<string, FieldFormat> = {
|
|
424
|
+
email: "email",
|
|
425
|
+
url: "uri",
|
|
426
|
+
uuid: "uuid",
|
|
427
|
+
guid: "uuid",
|
|
428
|
+
datetime: "date-time",
|
|
429
|
+
date: "date",
|
|
430
|
+
time: "time",
|
|
431
|
+
};
|
|
432
|
+
|
|
399
433
|
function staticParseFields(modelPath: string, schemaName: string): FieldInfo[] | null {
|
|
400
434
|
const source = ts.createSourceFile(modelPath, fs.readFileSync(modelPath, "utf-8"), ts.ScriptTarget.Latest, true);
|
|
401
435
|
const constants = new Map<string, ts.Expression>();
|
|
@@ -444,7 +478,137 @@ function staticParseFields(modelPath: string, schemaName: string): FieldInfo[] |
|
|
|
444
478
|
return values;
|
|
445
479
|
};
|
|
446
480
|
|
|
447
|
-
|
|
481
|
+
type ParsedField = Omit<FieldInfo, "name">;
|
|
482
|
+
|
|
483
|
+
const numericArgument = (expression: ts.Expression | undefined): number | null => {
|
|
484
|
+
if (!expression) return null;
|
|
485
|
+
if (ts.isNumericLiteral(expression)) return Number(expression.text);
|
|
486
|
+
if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(expression.operand)) {
|
|
487
|
+
return -Number(expression.operand.text);
|
|
488
|
+
}
|
|
489
|
+
if (ts.isIdentifier(expression)) {
|
|
490
|
+
const resolved = constants.get(expression.text);
|
|
491
|
+
return resolved ? numericArgument(resolved) : null;
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const withConstraints = (field: ParsedField, update: FieldConstraints): ParsedField => ({
|
|
497
|
+
...field,
|
|
498
|
+
constraints: { ...(field.constraints ?? {}), ...update },
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const lowerBound = (field: ParsedField, key: "minLength" | "minimum" | "minItems", value: number, exclusive = false): ParsedField => {
|
|
502
|
+
const current = field.constraints?.[key];
|
|
503
|
+
if (current !== undefined && current > value) return field;
|
|
504
|
+
const constraints: FieldConstraints = { ...(field.constraints ?? {}), [key]: value };
|
|
505
|
+
if (key === "minimum") {
|
|
506
|
+
// An equal bound stays exclusive if either check was exclusive (gt(0).min(0) still rejects 0).
|
|
507
|
+
if (exclusive || (current === value && field.constraints?.exclusiveMinimum)) constraints.exclusiveMinimum = true;
|
|
508
|
+
else delete constraints.exclusiveMinimum;
|
|
509
|
+
}
|
|
510
|
+
return { ...field, constraints };
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const upperBound = (field: ParsedField, key: "maxLength" | "maximum" | "maxItems", value: number, exclusive = false): ParsedField => {
|
|
514
|
+
const current = field.constraints?.[key];
|
|
515
|
+
if (current !== undefined && current < value) return field;
|
|
516
|
+
const constraints: FieldConstraints = { ...(field.constraints ?? {}), [key]: value };
|
|
517
|
+
if (key === "maximum") {
|
|
518
|
+
if (exclusive || (current === value && field.constraints?.exclusiveMaximum)) constraints.exclusiveMaximum = true;
|
|
519
|
+
else delete constraints.exclusiveMaximum;
|
|
520
|
+
}
|
|
521
|
+
return { ...field, constraints };
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const applyCheck = (inner: ParsedField, method: string, args: ts.NodeArray<ts.Expression>): ParsedField | null => {
|
|
525
|
+
const lengthTarget = inner.isArray ? "items" : inner.type === "string" ? "length" : inner.type === "number" ? "value" : null;
|
|
526
|
+
const value = numericArgument(args[0]);
|
|
527
|
+
switch (method) {
|
|
528
|
+
case "optional":
|
|
529
|
+
case "default":
|
|
530
|
+
return { ...inner, isOptional: true };
|
|
531
|
+
case "nullable":
|
|
532
|
+
return { ...inner, isNullable: true };
|
|
533
|
+
case "nullish":
|
|
534
|
+
return { ...inner, isOptional: true, isNullable: true };
|
|
535
|
+
case "describe":
|
|
536
|
+
case "trim":
|
|
537
|
+
case "toLowerCase":
|
|
538
|
+
case "toUpperCase":
|
|
539
|
+
case "strict":
|
|
540
|
+
case "strip":
|
|
541
|
+
case "passthrough":
|
|
542
|
+
case "loose":
|
|
543
|
+
return inner;
|
|
544
|
+
case "int":
|
|
545
|
+
return inner.type === "number" && !inner.isArray ? { ...inner, isInteger: true } : null;
|
|
546
|
+
case "positive":
|
|
547
|
+
return inner.type === "number" && !inner.isArray ? lowerBound(inner, "minimum", 0, true) : null;
|
|
548
|
+
case "nonnegative":
|
|
549
|
+
return inner.type === "number" && !inner.isArray ? lowerBound(inner, "minimum", 0) : null;
|
|
550
|
+
case "negative":
|
|
551
|
+
return inner.type === "number" && !inner.isArray ? upperBound(inner, "maximum", 0, true) : null;
|
|
552
|
+
case "nonpositive":
|
|
553
|
+
return inner.type === "number" && !inner.isArray ? upperBound(inner, "maximum", 0) : null;
|
|
554
|
+
case "gt":
|
|
555
|
+
case "gte":
|
|
556
|
+
return inner.type === "number" && !inner.isArray && value !== null ? lowerBound(inner, "minimum", value, method === "gt") : null;
|
|
557
|
+
case "lt":
|
|
558
|
+
case "lte":
|
|
559
|
+
return inner.type === "number" && !inner.isArray && value !== null ? upperBound(inner, "maximum", value, method === "lt") : null;
|
|
560
|
+
case "min":
|
|
561
|
+
if (value === null || !lengthTarget) return null;
|
|
562
|
+
return lowerBound(inner, lengthTarget === "items" ? "minItems" : lengthTarget === "length" ? "minLength" : "minimum", value);
|
|
563
|
+
case "max":
|
|
564
|
+
if (value === null || !lengthTarget) return null;
|
|
565
|
+
return upperBound(inner, lengthTarget === "items" ? "maxItems" : lengthTarget === "length" ? "maxLength" : "maximum", value);
|
|
566
|
+
case "length":
|
|
567
|
+
if (value === null || (lengthTarget !== "items" && lengthTarget !== "length")) return null;
|
|
568
|
+
return lengthTarget === "items"
|
|
569
|
+
? upperBound(lowerBound(inner, "minItems", value), "maxItems", value)
|
|
570
|
+
: upperBound(lowerBound(inner, "minLength", value), "maxLength", value);
|
|
571
|
+
case "nonempty":
|
|
572
|
+
if (lengthTarget !== "items" && lengthTarget !== "length") return null;
|
|
573
|
+
return lowerBound(inner, lengthTarget === "items" ? "minItems" : "minLength", 1);
|
|
574
|
+
case "regex": {
|
|
575
|
+
const literal = args[0];
|
|
576
|
+
if (!literal || !ts.isRegularExpressionLiteral(literal) || inner.type !== "string" || inner.isArray) return null;
|
|
577
|
+
const text = literal.text;
|
|
578
|
+
const separator = text.lastIndexOf("/");
|
|
579
|
+
return withConstraints(inner, {
|
|
580
|
+
pattern: text.slice(1, separator),
|
|
581
|
+
...(text.slice(separator + 1) ? { patternFlags: text.slice(separator + 1) } : {}),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
case "email":
|
|
585
|
+
case "url":
|
|
586
|
+
case "uuid":
|
|
587
|
+
case "guid":
|
|
588
|
+
case "datetime":
|
|
589
|
+
case "date":
|
|
590
|
+
case "time":
|
|
591
|
+
if (inner.type !== "string" || inner.isArray) return null;
|
|
592
|
+
return { ...inner, format: STRING_FORMATS[method] };
|
|
593
|
+
default:
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const parseObjectFields = (literal: ts.ObjectLiteralExpression): FieldInfo[] | null => {
|
|
599
|
+
const objectFields: FieldInfo[] = [];
|
|
600
|
+
for (const property of literal.properties) {
|
|
601
|
+
if (!ts.isPropertyAssignment(property)) return null;
|
|
602
|
+
const name = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) ? property.name.text : null;
|
|
603
|
+
if (!name) return null;
|
|
604
|
+
const parsed = parseExpression(property.initializer);
|
|
605
|
+
if (!parsed) return null;
|
|
606
|
+
objectFields.push({ name, ...parsed });
|
|
607
|
+
}
|
|
608
|
+
return objectFields;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
function parseExpression(expression: ts.Expression): ParsedField | null {
|
|
448
612
|
if (ts.isIdentifier(expression)) {
|
|
449
613
|
const resolved = constants.get(expression.text);
|
|
450
614
|
if (resolved) return parseExpression(resolved);
|
|
@@ -455,54 +619,48 @@ function staticParseFields(modelPath: string, schemaName: string): FieldInfo[] |
|
|
|
455
619
|
if (!ts.isCallExpression(expression) || !ts.isPropertyAccessExpression(expression.expression)) return null;
|
|
456
620
|
const method = expression.expression.name.text;
|
|
457
621
|
const receiver = expression.expression.expression;
|
|
458
|
-
const
|
|
459
|
-
|
|
622
|
+
const base = (type: string, extra: Partial<ParsedField> = {}): ParsedField => ({ type, isOptional: false, isNullable: false, isArray: false, ...extra });
|
|
623
|
+
|
|
624
|
+
// z.iso.datetime() / z.iso.date() / z.iso.time()
|
|
625
|
+
if (ts.isPropertyAccessExpression(receiver) && ts.isIdentifier(receiver.expression) && receiver.expression.text === "z" && receiver.name.text === "iso") {
|
|
626
|
+
return method === "datetime" || method === "date" || method === "time" ? base("string", { format: STRING_FORMATS[method] }) : null;
|
|
627
|
+
}
|
|
628
|
+
if (!(ts.isIdentifier(receiver) && receiver.text === "z")) {
|
|
460
629
|
const inner = parseExpression(receiver);
|
|
461
|
-
|
|
462
|
-
if (method === "optional" || method === "default") inner.isOptional = true;
|
|
463
|
-
if (method === "nullable") inner.isNullable = true;
|
|
464
|
-
return inner;
|
|
630
|
+
return inner ? applyCheck(inner, method, expression.arguments) : null;
|
|
465
631
|
}
|
|
466
|
-
if (
|
|
467
|
-
if (
|
|
468
|
-
|
|
632
|
+
if (["string", "boolean", "date"].includes(method)) return base(method);
|
|
633
|
+
if (method === "number") return base("number");
|
|
634
|
+
if (method === "int") return base("number", { isInteger: true });
|
|
635
|
+
if (method === "email" || method === "url" || method === "uuid" || method === "guid") return base("string", { format: STRING_FORMATS[method] });
|
|
636
|
+
if (method === "object") {
|
|
637
|
+
const literal = expression.arguments[0];
|
|
638
|
+
if (!literal || !ts.isObjectLiteralExpression(literal)) return null;
|
|
639
|
+
// Unsupported members keep the previous untyped-object behavior instead of failing the whole model.
|
|
640
|
+
const objectFields = parseObjectFields(literal);
|
|
641
|
+
return objectFields ? base("object", { objectFields }) : base("object");
|
|
469
642
|
}
|
|
470
|
-
if (method === "object") return { type: "object", isOptional: false, isNullable: false, isArray: false };
|
|
471
643
|
if (method === "array" && expression.arguments[0]) {
|
|
472
644
|
const element = parseExpression(expression.arguments[0]);
|
|
473
645
|
return element ? { ...element, isArray: true } : null;
|
|
474
646
|
}
|
|
475
647
|
if (method === "enum" && expression.arguments[0]) {
|
|
476
648
|
const enumValues = literalValues(expression.arguments[0]);
|
|
477
|
-
return enumValues ?
|
|
649
|
+
return enumValues ? base("string", { enumValues }) : null;
|
|
478
650
|
}
|
|
479
651
|
if (method === "literal" && expression.arguments[0]) {
|
|
480
652
|
const value = expression.arguments[0];
|
|
481
653
|
if (ts.isStringLiteral(value) || ts.isNumericLiteral(value)) {
|
|
482
|
-
return
|
|
654
|
+
return base(ts.isNumericLiteral(value) ? "number" : "string", { enumValues: [value.text] });
|
|
483
655
|
}
|
|
484
656
|
if (value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword) {
|
|
485
|
-
return
|
|
657
|
+
return base("boolean", { enumValues: [value.kind === ts.SyntaxKind.TrueKeyword ? "true" : "false"] });
|
|
486
658
|
}
|
|
487
659
|
}
|
|
488
660
|
return null;
|
|
489
|
-
}
|
|
661
|
+
}
|
|
490
662
|
|
|
491
|
-
|
|
492
|
-
for (const property of schema.arguments[0].properties) {
|
|
493
|
-
if (!ts.isPropertyAssignment(property)) return null;
|
|
494
|
-
const name = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) ? property.name.text : null;
|
|
495
|
-
if (!name) return null;
|
|
496
|
-
const parsed = parseExpression(property.initializer);
|
|
497
|
-
if (!parsed) return null;
|
|
498
|
-
const isForeignKey = name.endsWith("Id") && name.length > 2 && parsed.type === "string";
|
|
499
|
-
fields.push({
|
|
500
|
-
name,
|
|
501
|
-
...parsed,
|
|
502
|
-
...(isForeignKey ? { isForeignKey: true, referencedModel: name.slice(0, -2).replace(/^./, char => char.toUpperCase()) } : {}),
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
return fields;
|
|
663
|
+
return parseObjectFields(schema.arguments[0] as ts.ObjectLiteralExpression);
|
|
506
664
|
}
|
|
507
665
|
|
|
508
666
|
async function extractFieldsFromSchema(
|
|
@@ -532,110 +690,160 @@ async function extractFieldsFromSchema(
|
|
|
532
690
|
const scriptCode = `
|
|
533
691
|
import { ${schemaName} as schema } from './${path.basename(compiledModel)}';
|
|
534
692
|
|
|
535
|
-
// Zod v3とv4の両方に対応
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
fieldDef = fieldDef._def.innerType;
|
|
562
|
-
unwrapped = true;
|
|
563
|
-
} else if (typeName === 'ZodNullable') {
|
|
564
|
-
isNullable = true;
|
|
565
|
-
fieldDef = fieldDef._def.innerType;
|
|
566
|
-
unwrapped = true;
|
|
567
|
-
} else if (typeName === 'ZodDefault') {
|
|
568
|
-
// .default() は optional と同様に扱う
|
|
569
|
-
isOptional = true;
|
|
570
|
-
fieldDef = fieldDef._def.innerType;
|
|
571
|
-
unwrapped = true;
|
|
572
|
-
} else if (typeName === 'ZodEffects') {
|
|
573
|
-
// .min(), .max(), .regex() などの Effects を unwrap
|
|
574
|
-
fieldDef = fieldDef._def.schema;
|
|
575
|
-
unwrapped = true;
|
|
576
|
-
}
|
|
577
|
-
} while (unwrapped);
|
|
578
|
-
|
|
579
|
-
// ZodArray をチェック
|
|
580
|
-
if (getTypeName(fieldDef) === 'ZodArray') {
|
|
581
|
-
isArray = true;
|
|
582
|
-
fieldDef = fieldDef._def.element || fieldDef._def.type;
|
|
693
|
+
// Zod v3 (_def.typeName) と v4 (_zod.def.type) の両方に対応
|
|
694
|
+
const FORMATS = { email: 'email', url: 'uri', uuid: 'uuid', guid: 'uuid', datetime: 'date-time', date: 'date', time: 'time' };
|
|
695
|
+
const v4 = (s) => (s && s._zod && s._zod.def) || null;
|
|
696
|
+
const v3Name = (s) => (s && s._def && s._def.typeName) || '';
|
|
697
|
+
|
|
698
|
+
function lower(c, key, value, exclusive) {
|
|
699
|
+
if (typeof value !== 'number' || (c[key] !== undefined && c[key] > value)) return;
|
|
700
|
+
const keepExclusive = c[key] === value && c.exclusiveMinimum;
|
|
701
|
+
c[key] = value;
|
|
702
|
+
if (key === 'minimum') { if (exclusive || keepExclusive) c.exclusiveMinimum = true; else delete c.exclusiveMinimum; }
|
|
703
|
+
}
|
|
704
|
+
function upper(c, key, value, exclusive) {
|
|
705
|
+
if (typeof value !== 'number' || (c[key] !== undefined && c[key] < value)) return;
|
|
706
|
+
const keepExclusive = c[key] === value && c.exclusiveMaximum;
|
|
707
|
+
c[key] = value;
|
|
708
|
+
if (key === 'maximum') { if (exclusive || keepExclusive) c.exclusiveMaximum = true; else delete c.exclusiveMaximum; }
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function unwrap(s, info) {
|
|
712
|
+
for (;;) {
|
|
713
|
+
const d = v4(s);
|
|
714
|
+
if (d) {
|
|
715
|
+
if (d.type === 'optional' || d.type === 'default' || d.type === 'prefault') { info.isOptional = true; s = d.innerType; continue; }
|
|
716
|
+
if (d.type === 'nullable') { info.isNullable = true; s = d.innerType; continue; }
|
|
717
|
+
if (d.type === 'readonly' || d.type === 'nonoptional' || d.type === 'catch') { s = d.innerType; continue; }
|
|
718
|
+
return s;
|
|
583
719
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
if (
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
enumValues = Array.isArray(fieldDef._def.entries)
|
|
608
|
-
? fieldDef._def.entries
|
|
609
|
-
: Object.values(fieldDef._def.entries);
|
|
610
|
-
}
|
|
611
|
-
} else if (typeName === 'ZodLiteral') {
|
|
612
|
-
const rawLiteralValues = fieldDef.values || fieldDef._def.values ||
|
|
613
|
-
(fieldDef._def.value !== undefined ? [fieldDef._def.value] : undefined);
|
|
614
|
-
const literalValues = rawLiteralValues instanceof Set ? Array.from(rawLiteralValues) : rawLiteralValues;
|
|
615
|
-
if (Array.isArray(literalValues) && literalValues.length > 0) {
|
|
616
|
-
const literalValue = literalValues[0];
|
|
617
|
-
type = typeof literalValue === 'number' ? 'number' : typeof literalValue === 'boolean' ? 'boolean' : 'string';
|
|
618
|
-
enumValues = literalValues.map(value => String(value));
|
|
720
|
+
const name = v3Name(s);
|
|
721
|
+
if (name === 'ZodOptional' || name === 'ZodDefault') { info.isOptional = true; s = s._def.innerType; continue; }
|
|
722
|
+
if (name === 'ZodNullable') { info.isNullable = true; s = s._def.innerType; continue; }
|
|
723
|
+
if (name === 'ZodEffects') { s = s._def.schema; continue; }
|
|
724
|
+
if (name === 'ZodReadonly' || name === 'ZodCatch') { s = s._def.innerType; continue; }
|
|
725
|
+
return s;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function describe(key, input) {
|
|
730
|
+
const info = { name: key, type: undefined, isOptional: false, isNullable: false, isArray: false };
|
|
731
|
+
const constraints = {};
|
|
732
|
+
let s = unwrap(input, info);
|
|
733
|
+
|
|
734
|
+
const arrayDef = v4(s);
|
|
735
|
+
if ((arrayDef && arrayDef.type === 'array') || v3Name(s) === 'ZodArray') {
|
|
736
|
+
info.isArray = true;
|
|
737
|
+
if (arrayDef) {
|
|
738
|
+
for (const check of arrayDef.checks || []) {
|
|
739
|
+
const cd = check._zod.def;
|
|
740
|
+
if (cd.check === 'min_length') lower(constraints, 'minItems', cd.minimum);
|
|
741
|
+
if (cd.check === 'max_length') upper(constraints, 'maxItems', cd.maximum);
|
|
742
|
+
if (cd.check === 'length_equals') { lower(constraints, 'minItems', cd.length); upper(constraints, 'maxItems', cd.length); }
|
|
619
743
|
}
|
|
744
|
+
s = arrayDef.element;
|
|
745
|
+
} else {
|
|
746
|
+
const ad = s._def;
|
|
747
|
+
if (ad.minLength) lower(constraints, 'minItems', ad.minLength.value);
|
|
748
|
+
if (ad.maxLength) upper(constraints, 'maxItems', ad.maxLength.value);
|
|
749
|
+
if (ad.exactLength) { lower(constraints, 'minItems', ad.exactLength.value); upper(constraints, 'maxItems', ad.exactLength.value); }
|
|
750
|
+
s = ad.type;
|
|
620
751
|
}
|
|
752
|
+
s = unwrap(s, {});
|
|
753
|
+
}
|
|
621
754
|
|
|
622
|
-
|
|
623
|
-
|
|
755
|
+
const d = v4(s);
|
|
756
|
+
const name = v3Name(s);
|
|
757
|
+
let enumValues;
|
|
758
|
+
if (d) {
|
|
759
|
+
if (d.type === 'string') {
|
|
760
|
+
info.type = 'string';
|
|
761
|
+
if (d.format && FORMATS[d.format]) info.format = FORMATS[d.format];
|
|
762
|
+
if (d.format === 'regex' && d.pattern) { constraints.pattern = d.pattern.source; if (d.pattern.flags) constraints.patternFlags = d.pattern.flags; }
|
|
763
|
+
} else if (d.type === 'number') {
|
|
764
|
+
info.type = 'number';
|
|
765
|
+
if (d.format && /int/.test(d.format)) info.isInteger = true;
|
|
766
|
+
} else if (d.type === 'boolean') info.type = 'boolean';
|
|
767
|
+
else if (d.type === 'date') info.type = 'date';
|
|
768
|
+
else if (d.type === 'object') {
|
|
769
|
+
info.type = 'object';
|
|
770
|
+
const shape = typeof d.shape === 'function' ? d.shape() : d.shape;
|
|
771
|
+
info.objectFields = Object.keys(shape).map((child) => describe(child, shape[child]));
|
|
772
|
+
} else if (d.type === 'enum') {
|
|
773
|
+
info.type = 'string';
|
|
774
|
+
enumValues = Object.values(d.entries || {}).map(String);
|
|
775
|
+
} else if (d.type === 'literal') {
|
|
776
|
+
const values = Array.isArray(d.values) ? d.values : Array.from(d.values || []);
|
|
777
|
+
if (values.length > 0) {
|
|
778
|
+
info.type = typeof values[0] === 'number' ? 'number' : typeof values[0] === 'boolean' ? 'boolean' : 'string';
|
|
779
|
+
enumValues = values.map(String);
|
|
780
|
+
}
|
|
624
781
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
782
|
+
for (const check of d.checks || []) {
|
|
783
|
+
const cd = check._zod.def;
|
|
784
|
+
const lengthKey = info.type === 'string';
|
|
785
|
+
if (cd.check === 'min_length' && lengthKey) lower(constraints, 'minLength', cd.minimum);
|
|
786
|
+
if (cd.check === 'max_length' && lengthKey) upper(constraints, 'maxLength', cd.maximum);
|
|
787
|
+
if (cd.check === 'length_equals' && lengthKey) { lower(constraints, 'minLength', cd.length); upper(constraints, 'maxLength', cd.length); }
|
|
788
|
+
if (cd.check === 'greater_than') lower(constraints, 'minimum', Number(cd.value), !cd.inclusive);
|
|
789
|
+
if (cd.check === 'less_than') upper(constraints, 'maximum', Number(cd.value), !cd.inclusive);
|
|
790
|
+
if (cd.check === 'number_format' && /int/.test(cd.format || '')) info.isInteger = true;
|
|
791
|
+
if (cd.check === 'string_format') {
|
|
792
|
+
if (cd.format === 'regex' && cd.pattern) { constraints.pattern = cd.pattern.source; if (cd.pattern.flags) constraints.patternFlags = cd.pattern.flags; }
|
|
793
|
+
else if (FORMATS[cd.format]) info.format = FORMATS[cd.format];
|
|
794
|
+
}
|
|
634
795
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
796
|
+
} else {
|
|
797
|
+
const def = s && s._def;
|
|
798
|
+
if (name === 'ZodString') {
|
|
799
|
+
info.type = 'string';
|
|
800
|
+
for (const check of def.checks || []) {
|
|
801
|
+
if (check.kind === 'min') lower(constraints, 'minLength', check.value);
|
|
802
|
+
if (check.kind === 'max') upper(constraints, 'maxLength', check.value);
|
|
803
|
+
if (check.kind === 'length') { lower(constraints, 'minLength', check.value); upper(constraints, 'maxLength', check.value); }
|
|
804
|
+
if (check.kind === 'regex') { constraints.pattern = check.regex.source; if (check.regex.flags) constraints.patternFlags = check.regex.flags; }
|
|
805
|
+
if (FORMATS[check.kind]) info.format = FORMATS[check.kind];
|
|
806
|
+
}
|
|
807
|
+
} else if (name === 'ZodNumber') {
|
|
808
|
+
info.type = 'number';
|
|
809
|
+
for (const check of def.checks || []) {
|
|
810
|
+
if (check.kind === 'int') info.isInteger = true;
|
|
811
|
+
if (check.kind === 'min') lower(constraints, 'minimum', check.value, !check.inclusive);
|
|
812
|
+
if (check.kind === 'max') upper(constraints, 'maximum', check.value, !check.inclusive);
|
|
813
|
+
}
|
|
814
|
+
} else if (name === 'ZodBoolean') info.type = 'boolean';
|
|
815
|
+
else if (name === 'ZodDate') info.type = 'date';
|
|
816
|
+
else if (name === 'ZodObject') {
|
|
817
|
+
info.type = 'object';
|
|
818
|
+
const shape = typeof def.shape === 'function' ? def.shape() : def.shape;
|
|
819
|
+
info.objectFields = Object.keys(shape).map((child) => describe(child, shape[child]));
|
|
820
|
+
} else if (name === 'ZodEnum' || name === 'ZodNativeEnum') {
|
|
821
|
+
info.type = 'string';
|
|
822
|
+
const values = s.options || def.values || def.entries;
|
|
823
|
+
enumValues = (Array.isArray(values) ? values : Object.values(values || {})).map(String);
|
|
824
|
+
} else if (name === 'ZodLiteral') {
|
|
825
|
+
const value = def.value;
|
|
826
|
+
info.type = typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'boolean' : 'string';
|
|
827
|
+
enumValues = [String(value)];
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const typeLabel = (d && d.type) || name || 'unknown';
|
|
832
|
+
if (!info.type || ((typeLabel === 'enum' || name === 'ZodEnum' || name === 'ZodNativeEnum') && (!enumValues || enumValues.length === 0))) {
|
|
833
|
+
throw new Error('Unsupported or ambiguous Zod field "' + key + '" (' + typeLabel + ')');
|
|
834
|
+
}
|
|
835
|
+
if (enumValues) info.enumValues = enumValues;
|
|
836
|
+
if (Object.keys(constraints).length > 0) info.constraints = constraints;
|
|
837
|
+
return info;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const root = unwrap(schema, {});
|
|
841
|
+
const rootDef = v4(root);
|
|
842
|
+
const rootShape = rootDef && rootDef.type === 'object'
|
|
843
|
+
? (typeof rootDef.shape === 'function' ? rootDef.shape() : rootDef.shape)
|
|
844
|
+
: v3Name(root) === 'ZodObject' ? (typeof root._def.shape === 'function' ? root._def.shape() : root._def.shape) : null;
|
|
845
|
+
if (rootShape) {
|
|
846
|
+
const fields = Object.keys(rootShape).map((key) => describe(key, rootShape[key]));
|
|
639
847
|
console.log(JSON.stringify(fields));
|
|
640
848
|
} else {
|
|
641
849
|
throw new Error('Exported schema is not a Zod object');
|
|
@@ -862,6 +1070,82 @@ export function parseAuthPolicy(content: string): ModelAuthPolicy | undefined {
|
|
|
862
1070
|
};
|
|
863
1071
|
}
|
|
864
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* `export const references = { userId: 'User', legacyId: false }` を静的に抽出する。
|
|
1075
|
+
* 値が false / null の場合は名前からの推測を無効にする。
|
|
1076
|
+
*/
|
|
1077
|
+
export function parseReferences(content: string): Record<string, string | false> {
|
|
1078
|
+
const source = ts.createSourceFile("model.ts", content, ts.ScriptTarget.Latest, true);
|
|
1079
|
+
const references: Record<string, string | false> = {};
|
|
1080
|
+
for (const statement of source.statements) {
|
|
1081
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
1082
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1083
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== "references" || !declaration.initializer) continue;
|
|
1084
|
+
let initializer = declaration.initializer;
|
|
1085
|
+
while (ts.isAsExpression(initializer) || ts.isSatisfiesExpression(initializer)) initializer = initializer.expression;
|
|
1086
|
+
if (!ts.isObjectLiteralExpression(initializer)) continue;
|
|
1087
|
+
for (const property of initializer.properties) {
|
|
1088
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
1089
|
+
const key = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) ? property.name.text : null;
|
|
1090
|
+
if (!key) continue;
|
|
1091
|
+
const value = property.initializer;
|
|
1092
|
+
if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)) references[key] = value.text;
|
|
1093
|
+
else if (value.kind === ts.SyntaxKind.FalseKeyword || value.kind === ts.SyntaxKind.NullKeyword) references[key] = false;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return references;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function listModelNames(modelPath: string): Set<string> {
|
|
1101
|
+
const directory = path.dirname(modelPath);
|
|
1102
|
+
const names = new Set<string>();
|
|
1103
|
+
for (const entry of fs.readdirSync(directory)) {
|
|
1104
|
+
if (!entry.endsWith(".ts") || entry.endsWith(".d.ts") || entry === "index.ts") continue;
|
|
1105
|
+
names.add(toPascalCase(path.basename(entry, ".ts")));
|
|
1106
|
+
}
|
|
1107
|
+
return names;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function applyForeignKeys(
|
|
1111
|
+
fields: FieldInfo[],
|
|
1112
|
+
modelName: string,
|
|
1113
|
+
modelPath: string,
|
|
1114
|
+
references: Record<string, string | false>,
|
|
1115
|
+
warnings: string[]
|
|
1116
|
+
): void {
|
|
1117
|
+
const models = listModelNames(modelPath);
|
|
1118
|
+
for (const field of fields) {
|
|
1119
|
+
delete field.isForeignKey;
|
|
1120
|
+
delete field.referencedModel;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
for (const [fieldName, target] of Object.entries(references)) {
|
|
1124
|
+
const field = fields.find((candidate) => candidate.name === fieldName);
|
|
1125
|
+
if (!field) {
|
|
1126
|
+
warnings.push(`reference-field-not-found: ${modelName}.references declares ${fieldName}, but the schema has no such field.`);
|
|
1127
|
+
} else if (target !== false && (field.type !== "string" || field.isArray || field.enumValues?.length)) {
|
|
1128
|
+
warnings.push(`reference-field-unsupported: ${modelName}.${fieldName} must be a single string id to reference ${target}.`);
|
|
1129
|
+
} else if (target !== false && !models.has(target)) {
|
|
1130
|
+
warnings.push(`reference-model-not-found: ${modelName}.${fieldName} references ${target}, but shared/models has no ${target} model; it is treated as a plain field.`);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
for (const field of fields) {
|
|
1135
|
+
if (field.type !== "string" || field.isArray || field.enumValues?.length || field.isNestedSchema) continue;
|
|
1136
|
+
const declared = references[field.name];
|
|
1137
|
+
if (declared === false) continue;
|
|
1138
|
+
const inferred = field.name.length > 2 && field.name.endsWith("Id")
|
|
1139
|
+
? field.name.slice(0, -2).replace(/^./, (char) => char.toUpperCase())
|
|
1140
|
+
: undefined;
|
|
1141
|
+
const target = declared ?? inferred;
|
|
1142
|
+
if (target && models.has(target)) {
|
|
1143
|
+
field.isForeignKey = true;
|
|
1144
|
+
field.referencedModel = target;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
865
1149
|
/**
|
|
866
1150
|
* パーティションキーを抽出する
|
|
867
1151
|
* export const partitionKey = '/tenantId' のようなエクスポートを検出
|