@jskit-ai/crud-server-generator 0.1.26 → 0.1.28
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/package.descriptor.mjs +19 -14
- package/package.json +9 -7
- package/src/server/{CrudServiceProvider.js → CrudProvider.js} +2 -2
- package/src/server/buildTemplateContext.js +278 -32
- package/src/server/subcommands/addField.js +238 -0
- package/src/server/subcommands/resourceAst.js +632 -0
- package/src/shared/crud/crudResource.js +93 -98
- package/templates/migrations/crud_initial.cjs +1 -0
- package/templates/src/local-package/package.descriptor.mjs +2 -2
- package/templates/src/local-package/server/{CrudServiceProvider.js → CrudProvider.js} +13 -8
- package/templates/src/local-package/server/actions.js +24 -10
- package/templates/src/local-package/server/registerRoutes.js +32 -33
- package/templates/src/local-package/server/repository.js +33 -132
- package/templates/src/local-package/server/service.js +88 -47
- package/templates/src/local-package/shared/crudResource.js +77 -45
- package/test/addFieldSubcommand.test.js +167 -0
- package/test/buildTemplateContext.test.js +198 -4
- package/test/crudResource.test.js +6 -0
- package/test/crudServerGuards.test.js +43 -49
- package/test/crudService.test.js +93 -5
- package/test/routeInputContracts.test.js +144 -41
- package/test-support/templateServerFixture.js +169 -0
- package/src/server/actionIds.js +0 -22
- package/src/server/actions.js +0 -152
- package/src/server/registerRoutes.js +0 -234
- package/src/server/repository.js +0 -162
- package/src/server/service.js +0 -96
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/crud-server-generator",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.28",
|
|
5
5
|
kind: "generator",
|
|
6
6
|
description: "CRUD server generator with routes, actions, and persistence scaffolding.",
|
|
7
7
|
options: {
|
|
@@ -15,9 +15,8 @@ export default Object.freeze({
|
|
|
15
15
|
surface: {
|
|
16
16
|
required: true,
|
|
17
17
|
inputType: "text",
|
|
18
|
-
defaultFromConfig: "surfaceDefaultId",
|
|
19
18
|
promptLabel: "Target surface",
|
|
20
|
-
promptHint: "
|
|
19
|
+
promptHint: "Must match an enabled surface id."
|
|
21
20
|
},
|
|
22
21
|
"ownership-filter": {
|
|
23
22
|
required: true,
|
|
@@ -74,13 +73,19 @@ export default Object.freeze({
|
|
|
74
73
|
server: {
|
|
75
74
|
providers: [
|
|
76
75
|
{
|
|
77
|
-
entrypoint: "src/server/
|
|
78
|
-
export: "
|
|
76
|
+
entrypoint: "src/server/CrudProvider.js",
|
|
77
|
+
export: "CrudProvider"
|
|
79
78
|
}
|
|
80
79
|
]
|
|
81
80
|
}
|
|
82
81
|
},
|
|
83
82
|
metadata: {
|
|
83
|
+
generatorSubcommands: {
|
|
84
|
+
"add-field": {
|
|
85
|
+
entrypoint: "src/server/subcommands/addField.js",
|
|
86
|
+
export: "runGeneratorSubcommand"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
84
89
|
apiSummary: {
|
|
85
90
|
surfaces: [
|
|
86
91
|
{
|
|
@@ -100,13 +105,13 @@ export default Object.freeze({
|
|
|
100
105
|
mutations: {
|
|
101
106
|
dependencies: {
|
|
102
107
|
runtime: {
|
|
103
|
-
"@jskit-ai/auth-core": "0.1.
|
|
104
|
-
"@jskit-ai/crud-core": "0.1.
|
|
105
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
106
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
107
|
-
"@jskit-ai/kernel": "0.1.
|
|
108
|
-
"@jskit-ai/realtime": "0.1.
|
|
109
|
-
"@jskit-ai/users-core": "0.1.
|
|
108
|
+
"@jskit-ai/auth-core": "0.1.19",
|
|
109
|
+
"@jskit-ai/crud-core": "0.1.28",
|
|
110
|
+
"@jskit-ai/database-runtime": "0.1.20",
|
|
111
|
+
"@jskit-ai/http-runtime": "0.1.19",
|
|
112
|
+
"@jskit-ai/kernel": "0.1.20",
|
|
113
|
+
"@jskit-ai/realtime": "0.1.19",
|
|
114
|
+
"@jskit-ai/users-core": "0.1.29",
|
|
110
115
|
"@local/${option:namespace|kebab}": "file:packages/${option:namespace|kebab}",
|
|
111
116
|
"typebox": "^1.0.81"
|
|
112
117
|
},
|
|
@@ -145,8 +150,8 @@ export default Object.freeze({
|
|
|
145
150
|
id: "crud-local-package-descriptor-${option:namespace|snake}"
|
|
146
151
|
},
|
|
147
152
|
{
|
|
148
|
-
from: "templates/src/local-package/server/
|
|
149
|
-
to: "packages/${option:namespace|kebab}/src/server/${option:namespace|pascal}
|
|
153
|
+
from: "templates/src/local-package/server/CrudProvider.js",
|
|
154
|
+
to: "packages/${option:namespace|kebab}/src/server/${option:namespace|pascal}Provider.js",
|
|
150
155
|
reason: "Install app-local CRUD server provider.",
|
|
151
156
|
category: "crud",
|
|
152
157
|
id: "crud-local-package-server-provider-${option:namespace|snake}",
|
package/package.json
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/crud-server-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"exports": {
|
|
9
|
-
"./server/
|
|
9
|
+
"./server/CrudProvider": "./src/server/CrudProvider.js",
|
|
10
10
|
"./server/crudModuleConfig": "./src/server/crudModuleConfig.js",
|
|
11
11
|
"./shared": "./src/shared/index.js",
|
|
12
12
|
"./shared/crud/crudResource": "./src/shared/crud/crudResource.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@
|
|
16
|
-
"@jskit-ai/
|
|
17
|
-
"@jskit-ai/
|
|
18
|
-
"@jskit-ai/
|
|
19
|
-
"@jskit-ai/
|
|
15
|
+
"@babel/parser": "^7.29.2",
|
|
16
|
+
"@jskit-ai/crud-core": "0.1.28",
|
|
17
|
+
"@jskit-ai/database-runtime": "0.1.20",
|
|
18
|
+
"@jskit-ai/http-runtime": "0.1.19",
|
|
19
|
+
"@jskit-ai/kernel": "0.1.20",
|
|
20
|
+
"@jskit-ai/users-core": "0.1.29",
|
|
21
|
+
"recast": "^0.23.11",
|
|
20
22
|
"typebox": "^1.0.81"
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
resolveDatabaseConnectionFromEnvironment,
|
|
9
9
|
toKnexClientId
|
|
10
10
|
} from "@jskit-ai/database-runtime/shared";
|
|
11
|
-
import {
|
|
11
|
+
import { checkCrudLookupFormControl } from "@jskit-ai/crud-core/shared/crudFieldMetaSupport";
|
|
12
|
+
import { normalizeCrudLookupNamespace } from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
13
|
+
import { toCamelCase, toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
|
|
12
14
|
|
|
13
15
|
const DEFAULT_ID_COLUMN = "id";
|
|
14
16
|
const OWNERSHIP_FILTER_AUTO = "auto";
|
|
@@ -380,7 +382,14 @@ function renderResourceFieldSchema(column, { forOutput = false } = {}) {
|
|
|
380
382
|
|
|
381
383
|
function renderInputNormalizer(column) {
|
|
382
384
|
const typeKind = String(column.typeKind || "");
|
|
383
|
-
|
|
385
|
+
const nullable = column?.nullable === true;
|
|
386
|
+
if (typeKind === "string") {
|
|
387
|
+
return "normalizeText";
|
|
388
|
+
}
|
|
389
|
+
if (typeKind === "time") {
|
|
390
|
+
if (nullable) {
|
|
391
|
+
return "(value) => { const normalized = normalizeText(value); return normalized || null; }";
|
|
392
|
+
}
|
|
384
393
|
return "normalizeText";
|
|
385
394
|
}
|
|
386
395
|
if (typeKind === "integer") {
|
|
@@ -393,9 +402,15 @@ function renderInputNormalizer(column) {
|
|
|
393
402
|
return "normalizeBoolean";
|
|
394
403
|
}
|
|
395
404
|
if (typeKind === "datetime") {
|
|
405
|
+
if (nullable) {
|
|
406
|
+
return "(value) => { const normalized = normalizeText(value); return normalized ? toDatabaseDateTimeUtc(normalized) : null; }";
|
|
407
|
+
}
|
|
396
408
|
return "toDatabaseDateTimeUtc";
|
|
397
409
|
}
|
|
398
410
|
if (typeKind === "date") {
|
|
411
|
+
if (nullable) {
|
|
412
|
+
return "(value) => { const normalized = normalizeText(value); return normalized ? toIsoString(normalized).slice(0, 10) : null; }";
|
|
413
|
+
}
|
|
399
414
|
return "(value) => toIsoString(value).slice(0, 10)";
|
|
400
415
|
}
|
|
401
416
|
if (typeKind === "json") {
|
|
@@ -692,8 +707,233 @@ function renderMigrationIndexLines(snapshot) {
|
|
|
692
707
|
return lines.join("\n");
|
|
693
708
|
}
|
|
694
709
|
|
|
710
|
+
function renderMigrationForeignKeyLine(foreignKey = {}) {
|
|
711
|
+
const columns = Array.isArray(foreignKey.columns)
|
|
712
|
+
? foreignKey.columns
|
|
713
|
+
.map((column) => normalizeText(column?.name))
|
|
714
|
+
.filter(Boolean)
|
|
715
|
+
: [];
|
|
716
|
+
const referencedColumns = Array.isArray(foreignKey.columns)
|
|
717
|
+
? foreignKey.columns
|
|
718
|
+
.map((column) => normalizeText(column?.referencedName))
|
|
719
|
+
.filter(Boolean)
|
|
720
|
+
: [];
|
|
721
|
+
const referencedTableName = normalizeText(foreignKey.referencedTableName);
|
|
722
|
+
const foreignKeyName = normalizeText(foreignKey.name);
|
|
723
|
+
if (columns.length < 1 || referencedColumns.length < 1 || !referencedTableName) {
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
let line = ` table.foreign(${JSON.stringify(columns)}`;
|
|
728
|
+
if (foreignKeyName) {
|
|
729
|
+
line += `, ${JSON.stringify(foreignKeyName)}`;
|
|
730
|
+
}
|
|
731
|
+
line += `).references(${JSON.stringify(referencedColumns)}).inTable(${JSON.stringify(referencedTableName)})`;
|
|
732
|
+
|
|
733
|
+
const updateRule = normalizeText(foreignKey.updateRule).toUpperCase();
|
|
734
|
+
if (updateRule) {
|
|
735
|
+
line += `.onUpdate(${JSON.stringify(updateRule)})`;
|
|
736
|
+
}
|
|
737
|
+
const deleteRule = normalizeText(foreignKey.deleteRule).toUpperCase();
|
|
738
|
+
if (deleteRule) {
|
|
739
|
+
line += `.onDelete(${JSON.stringify(deleteRule)})`;
|
|
740
|
+
}
|
|
741
|
+
line += ";";
|
|
742
|
+
|
|
743
|
+
return line;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function renderMigrationForeignKeyLines(snapshot) {
|
|
747
|
+
const foreignKeys = Array.isArray(snapshot.foreignKeys) ? snapshot.foreignKeys : [];
|
|
748
|
+
const lines = foreignKeys
|
|
749
|
+
.map((foreignKey) => renderMigrationForeignKeyLine(foreignKey))
|
|
750
|
+
.filter(Boolean);
|
|
751
|
+
return lines.join("\n");
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function mergeFieldMetaEntries(baseEntries = [], patchEntries = []) {
|
|
755
|
+
const mergedByKey = new Map();
|
|
756
|
+
for (const sourceEntry of [...baseEntries, ...patchEntries]) {
|
|
757
|
+
const key = normalizeText(sourceEntry?.key);
|
|
758
|
+
if (!key) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
const existing = mergedByKey.get(key) || {};
|
|
762
|
+
const next = {
|
|
763
|
+
...existing,
|
|
764
|
+
...sourceEntry,
|
|
765
|
+
key
|
|
766
|
+
};
|
|
767
|
+
if (existing.relation || sourceEntry.relation) {
|
|
768
|
+
next.relation = {
|
|
769
|
+
...(existing.relation && typeof existing.relation === "object" ? existing.relation : {}),
|
|
770
|
+
...(sourceEntry.relation && typeof sourceEntry.relation === "object" ? sourceEntry.relation : {})
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (existing.ui || sourceEntry.ui) {
|
|
774
|
+
next.ui = {
|
|
775
|
+
...(existing.ui && typeof existing.ui === "object" ? existing.ui : {}),
|
|
776
|
+
...(sourceEntry.ui && typeof sourceEntry.ui === "object" ? sourceEntry.ui : {})
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
mergedByKey.set(key, next);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return [...mergedByKey.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function resolveLookupNamespaceFromTableName(tableName = "") {
|
|
786
|
+
const normalizedTableName = toSnakeCase(normalizeText(tableName));
|
|
787
|
+
if (!normalizedTableName) {
|
|
788
|
+
return "";
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return normalizedTableName.replace(/_/g, "-");
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function buildFieldMetaEntries({ outputColumns = [], writableColumns = [], snapshot = {} } = {}) {
|
|
795
|
+
const fieldColumns = [...outputColumns, ...writableColumns];
|
|
796
|
+
const fieldColumnsByName = new Map();
|
|
797
|
+
const fieldColumnsByKey = new Map();
|
|
798
|
+
for (const column of fieldColumns) {
|
|
799
|
+
const columnName = normalizeText(column?.name);
|
|
800
|
+
const key = normalizeText(column?.key);
|
|
801
|
+
if (columnName && !fieldColumnsByName.has(columnName)) {
|
|
802
|
+
fieldColumnsByName.set(columnName, column);
|
|
803
|
+
}
|
|
804
|
+
if (key && !fieldColumnsByKey.has(key)) {
|
|
805
|
+
fieldColumnsByKey.set(key, column);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const dbColumnEntries = [];
|
|
810
|
+
for (const column of fieldColumnsByKey.values()) {
|
|
811
|
+
const key = normalizeText(column?.key);
|
|
812
|
+
const name = normalizeText(column?.name);
|
|
813
|
+
if (!key || !name) {
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (toSnakeCase(key) === name) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
dbColumnEntries.push({
|
|
820
|
+
key,
|
|
821
|
+
dbColumn: name
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const relationEntries = [];
|
|
826
|
+
const foreignKeys = Array.isArray(snapshot.foreignKeys) ? snapshot.foreignKeys : [];
|
|
827
|
+
for (const foreignKey of foreignKeys) {
|
|
828
|
+
const columns = Array.isArray(foreignKey?.columns) ? foreignKey.columns : [];
|
|
829
|
+
if (columns.length !== 1) {
|
|
830
|
+
const name = normalizeText(foreignKey?.name) || "unnamed_foreign_key";
|
|
831
|
+
throw new Error(
|
|
832
|
+
`CRUD generation supports only single-column foreign keys. Constraint "${name}" has ${columns.length} columns.`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const localColumnName = normalizeText(columns[0]?.name);
|
|
837
|
+
const referencedColumnName = normalizeText(columns[0]?.referencedName);
|
|
838
|
+
const referencedTableName = normalizeText(foreignKey?.referencedTableName);
|
|
839
|
+
if (!localColumnName || !referencedColumnName || !referencedTableName) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const localColumn = fieldColumnsByName.get(localColumnName);
|
|
844
|
+
if (!localColumn || localColumn.isOwnerColumn === true) {
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
relationEntries.push({
|
|
849
|
+
key: localColumn.key,
|
|
850
|
+
relation: {
|
|
851
|
+
kind: "lookup",
|
|
852
|
+
namespace: resolveLookupNamespaceFromTableName(referencedTableName),
|
|
853
|
+
valueKey: toCamelCase(referencedColumnName)
|
|
854
|
+
},
|
|
855
|
+
ui: {
|
|
856
|
+
formControl: "autocomplete"
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return mergeFieldMetaEntries(dbColumnEntries, relationEntries);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function renderFieldMetaEntryLines(entry = {}) {
|
|
865
|
+
const lines = ["RESOURCE_FIELD_META.push({"];
|
|
866
|
+
const topLevelProperties = [`key: ${JSON.stringify(entry.key)}`];
|
|
867
|
+
const dbColumn = normalizeText(entry.dbColumn);
|
|
868
|
+
if (dbColumn) {
|
|
869
|
+
topLevelProperties.push(`dbColumn: ${JSON.stringify(dbColumn)}`);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const relation = entry.relation && typeof entry.relation === "object" ? entry.relation : null;
|
|
873
|
+
if (relation) {
|
|
874
|
+
const targetResourceNamespace = normalizeCrudLookupNamespace(relation.targetResource);
|
|
875
|
+
const relationNamespace =
|
|
876
|
+
normalizeCrudLookupNamespace(relation.namespace) ||
|
|
877
|
+
normalizeCrudLookupNamespace(relation.apiPath) ||
|
|
878
|
+
normalizeCrudLookupNamespace(relation?.source?.path) ||
|
|
879
|
+
targetResourceNamespace;
|
|
880
|
+
if (!relationNamespace) {
|
|
881
|
+
throw new Error(`crud template context fieldMeta["${normalizeText(entry.key)}"] lookup relation requires namespace.`);
|
|
882
|
+
}
|
|
883
|
+
const relationLines = [
|
|
884
|
+
"relation: {",
|
|
885
|
+
` kind: ${JSON.stringify(normalizeText(relation.kind) || "lookup")},`,
|
|
886
|
+
` namespace: ${JSON.stringify(relationNamespace)},`,
|
|
887
|
+
` valueKey: ${JSON.stringify(normalizeText(relation.valueKey) || "id")},`
|
|
888
|
+
];
|
|
889
|
+
const labelKey = normalizeText(relation.labelKey);
|
|
890
|
+
if (labelKey) {
|
|
891
|
+
relationLines.push(` labelKey: ${JSON.stringify(labelKey)}`);
|
|
892
|
+
} else {
|
|
893
|
+
relationLines[relationLines.length - 1] = relationLines[relationLines.length - 1].replace(/,$/, "");
|
|
894
|
+
}
|
|
895
|
+
relationLines.push("}");
|
|
896
|
+
topLevelProperties.push(relationLines.join("\n"));
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const formControl = checkCrudLookupFormControl(entry?.ui?.formControl, {
|
|
900
|
+
context: `resource.fieldMeta["${normalizeText(entry.key)}"].ui.formControl`,
|
|
901
|
+
defaultValue: relation ? "autocomplete" : ""
|
|
902
|
+
});
|
|
903
|
+
if (formControl) {
|
|
904
|
+
topLevelProperties.push(
|
|
905
|
+
[
|
|
906
|
+
"ui: {",
|
|
907
|
+
` formControl: ${JSON.stringify(formControl)} // or "select"`,
|
|
908
|
+
"}"
|
|
909
|
+
].join("\n")
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
for (const [index, propertyBlock] of topLevelProperties.entries()) {
|
|
914
|
+
const blockLines = String(propertyBlock || "").split("\n");
|
|
915
|
+
const isLastProperty = index >= topLevelProperties.length - 1;
|
|
916
|
+
const propertySuffix = isLastProperty ? "" : ",";
|
|
917
|
+
for (const [lineIndex, line] of blockLines.entries()) {
|
|
918
|
+
const isLastLine = lineIndex >= blockLines.length - 1;
|
|
919
|
+
lines.push(` ${line}${isLastLine ? propertySuffix : ""}`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
lines.push("});");
|
|
924
|
+
return lines.join("\n");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function renderResourceFieldMetaPushLines(entries = []) {
|
|
928
|
+
const sourceEntries = Array.isArray(entries) ? entries : [];
|
|
929
|
+
if (sourceEntries.length < 1) {
|
|
930
|
+
return "";
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return sourceEntries.map((entry) => renderFieldMetaEntryLines(entry)).join("\n\n");
|
|
934
|
+
}
|
|
935
|
+
|
|
695
936
|
function buildReplacementsFromSnapshot({
|
|
696
|
-
namespace,
|
|
697
937
|
snapshot,
|
|
698
938
|
resolvedOwnershipFilter
|
|
699
939
|
}) {
|
|
@@ -704,34 +944,26 @@ function buildReplacementsFromSnapshot({
|
|
|
704
944
|
.filter((column) => !column.nullable && column.hasDefault !== true)
|
|
705
945
|
.map((column) => column.key);
|
|
706
946
|
const resourceColumns = [...outputColumns, ...writableColumns];
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
for (const column of [...outputColumns, ...writableColumns]) {
|
|
713
|
-
const key = column.key;
|
|
714
|
-
if (!key || seenOverrideKeys.has(key)) {
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
seenOverrideKeys.add(key);
|
|
718
|
-
const guessedColumn = toSnakeCase(key);
|
|
719
|
-
const actualColumn = column.name;
|
|
720
|
-
if (typeof actualColumn === "string" && actualColumn && actualColumn !== guessedColumn) {
|
|
721
|
-
columnOverrides[key] = actualColumn;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
const createdAtColumn = scaffoldColumns.find((column) => column.isCreatedAtColumn)?.name || "";
|
|
725
|
-
const updatedAtColumn = scaffoldColumns.find((column) => column.isUpdatedAtColumn)?.name || "";
|
|
947
|
+
const fieldMetaEntries = buildFieldMetaEntries({
|
|
948
|
+
outputColumns,
|
|
949
|
+
writableColumns,
|
|
950
|
+
snapshot
|
|
951
|
+
});
|
|
726
952
|
const needsFiniteInteger = resourceColumns.some((column) => column.typeKind === "integer");
|
|
727
953
|
const needsFiniteNumber = resourceColumns.some((column) => column.typeKind === "number");
|
|
728
954
|
const needsDateTimeOutput = outputColumns.some((column) => column.typeKind === "datetime");
|
|
729
955
|
const needsDateTimeInput = writableColumns.some((column) => column.typeKind === "datetime");
|
|
956
|
+
const needsNullableDateTimeInput = writableColumns.some(
|
|
957
|
+
(column) => column.typeKind === "datetime" && column.nullable === true
|
|
958
|
+
);
|
|
959
|
+
const needsNullableDateInput = writableColumns.some(
|
|
960
|
+
(column) => column.typeKind === "date" && column.nullable === true
|
|
961
|
+
);
|
|
730
962
|
const needsDate = resourceColumns.some((column) => column.typeKind === "date");
|
|
731
963
|
const needsJson = resourceColumns.some((column) => column.typeKind === "json");
|
|
732
964
|
const needsNormalizeText = resourceColumns.some((column) =>
|
|
733
965
|
column.typeKind === "string" || column.typeKind === "time"
|
|
734
|
-
);
|
|
966
|
+
) || needsNullableDateTimeInput || needsNullableDateInput;
|
|
735
967
|
const needsNormalizeBoolean = resourceColumns.some((column) => column.typeKind === "boolean");
|
|
736
968
|
const needsNormalizeIfInSource = writableColumns.length > 0;
|
|
737
969
|
const outputColumnsWithNormalizer = outputColumns.filter(
|
|
@@ -769,13 +1001,10 @@ function buildReplacementsFromSnapshot({
|
|
|
769
1001
|
__JSKIT_CRUD_RESOURCE_INPUT_NORMALIZATION_LINES__: renderResourceInputNormalizationLines(writableColumns),
|
|
770
1002
|
__JSKIT_CRUD_RESOURCE_OUTPUT_NORMALIZATION_LINES__: renderResourceOutputNormalizationLines(outputColumns),
|
|
771
1003
|
__JSKIT_CRUD_RESOURCE_CREATE_REQUIRED_FIELDS__: JSON.stringify(createRequiredFieldKeys),
|
|
772
|
-
|
|
773
|
-
__JSKIT_CRUD_REPOSITORY_WRITE_KEYS__: JSON.stringify(writeKeys),
|
|
774
|
-
__JSKIT_CRUD_REPOSITORY_COLUMN_OVERRIDES__: JSON.stringify(columnOverrides),
|
|
775
|
-
__JSKIT_CRUD_REPOSITORY_CREATED_AT_COLUMN__: JSON.stringify(createdAtColumn),
|
|
776
|
-
__JSKIT_CRUD_REPOSITORY_UPDATED_AT_COLUMN__: JSON.stringify(updatedAtColumn),
|
|
1004
|
+
__JSKIT_CRUD_RESOURCE_FIELD_META_PUSH_LINES__: renderResourceFieldMetaPushLines(fieldMetaEntries),
|
|
777
1005
|
__JSKIT_CRUD_MIGRATION_COLUMN_LINES__: renderMigrationColumnLines(snapshot),
|
|
778
|
-
__JSKIT_CRUD_MIGRATION_INDEX_LINES__: renderMigrationIndexLines(snapshot)
|
|
1006
|
+
__JSKIT_CRUD_MIGRATION_INDEX_LINES__: renderMigrationIndexLines(snapshot),
|
|
1007
|
+
__JSKIT_CRUD_MIGRATION_FOREIGN_KEY_LINES__: renderMigrationForeignKeyLines(snapshot)
|
|
779
1008
|
});
|
|
780
1009
|
|
|
781
1010
|
return replacements;
|
|
@@ -839,7 +1068,6 @@ async function buildCrudTemplateContext(input = {}) {
|
|
|
839
1068
|
);
|
|
840
1069
|
|
|
841
1070
|
return buildReplacementsFromSnapshot({
|
|
842
|
-
namespace,
|
|
843
1071
|
snapshot,
|
|
844
1072
|
resolvedOwnershipFilter
|
|
845
1073
|
});
|
|
@@ -865,7 +1093,25 @@ const __testables = Object.freeze({
|
|
|
865
1093
|
resolveOwnershipFilterForGeneration,
|
|
866
1094
|
buildReplacementsFromSnapshot,
|
|
867
1095
|
parseDotEnvLine,
|
|
868
|
-
renderMigrationColumnLine
|
|
1096
|
+
renderMigrationColumnLine,
|
|
1097
|
+
renderMigrationForeignKeyLine,
|
|
1098
|
+
resolveScaffoldColumns,
|
|
1099
|
+
renderPropertyAccess,
|
|
1100
|
+
renderResourceFieldSchema,
|
|
1101
|
+
renderInputNormalizer,
|
|
1102
|
+
renderOutputNormalizerExpression,
|
|
1103
|
+
resolveGenerationSnapshot,
|
|
1104
|
+
buildFieldMetaEntries
|
|
869
1105
|
});
|
|
870
1106
|
|
|
871
|
-
export {
|
|
1107
|
+
export {
|
|
1108
|
+
buildTemplateContext,
|
|
1109
|
+
resolveScaffoldColumns,
|
|
1110
|
+
renderPropertyAccess,
|
|
1111
|
+
resolveGenerationSnapshot,
|
|
1112
|
+
renderResourceFieldSchema,
|
|
1113
|
+
renderInputNormalizer,
|
|
1114
|
+
renderOutputNormalizerExpression,
|
|
1115
|
+
buildFieldMetaEntries,
|
|
1116
|
+
__testables
|
|
1117
|
+
};
|