@jskit-ai/crud-server-generator 0.1.62 → 0.1.64
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 +13 -30
- package/package.json +8 -8
- package/src/server/buildTemplateContext.js +449 -486
- package/src/server/subcommands/addField.js +8 -80
- package/src/server/subcommands/resourceAst.js +40 -393
- package/src/shared/crud/crudResource.js +85 -186
- package/templates/src/local-package/package.descriptor.mjs +3 -6
- package/templates/src/local-package/package.json +0 -1
- package/templates/src/local-package/server/CrudProvider.js +28 -21
- package/templates/src/local-package/server/actions.js +42 -54
- package/templates/src/local-package/server/registerRoutes.js +22 -50
- package/templates/src/local-package/server/repository.js +82 -36
- package/templates/src/local-package/server/service.js +45 -73
- package/templates/src/local-package/shared/crudResource.js +15 -140
- package/test/addFieldSubcommand.test.js +100 -77
- package/test/buildTemplateContext.test.js +139 -199
- package/test/crudResource.test.js +26 -31
- package/test/crudServerGuards.test.js +157 -42
- package/test/crudService.test.js +91 -173
- package/test/packageDescriptor.test.js +3 -11
- package/test/routeInputContracts.test.js +77 -8
- package/test/templateSymbolConsistency.test.js +19 -3
- package/test-support/templateServerFixture.js +155 -112
- package/templates/src/local-package/server/actionIds.js +0 -9
- package/templates/src/local-package/server/listConfig.js +0 -5
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
toKnexClientId
|
|
10
10
|
} from "@jskit-ai/database-runtime/shared";
|
|
11
11
|
import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
|
|
12
|
-
import { checkCrudLookupFormControl } from "@jskit-ai/crud-core/shared/
|
|
12
|
+
import { checkCrudLookupFormControl } from "@jskit-ai/crud-core/shared/crudFieldSupport";
|
|
13
13
|
import {
|
|
14
14
|
importFreshModuleFromAbsolutePath,
|
|
15
15
|
loadAppConfigFromModuleUrl,
|
|
@@ -663,322 +663,408 @@ function renderObjectPropertyKey(value) {
|
|
|
663
663
|
return isIdentifier(key) ? key : JSON.stringify(key);
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
function
|
|
667
|
-
const
|
|
668
|
-
if (isIdentifier(normalizedKey)) {
|
|
669
|
-
return `${sourceName}.${normalizedKey}`;
|
|
670
|
-
}
|
|
671
|
-
return `${sourceName}[${JSON.stringify(normalizedKey)}]`;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function renderIntegerSchema(column) {
|
|
675
|
-
const options = [];
|
|
666
|
+
function renderBoundedNumberEntries(column) {
|
|
667
|
+
const entries = [];
|
|
676
668
|
if (Number.isFinite(column?.minimum)) {
|
|
677
|
-
|
|
678
|
-
} else if (
|
|
679
|
-
|
|
680
|
-
} else if (column.unsigned === true) {
|
|
681
|
-
options.push("minimum: 0");
|
|
669
|
+
entries.push(`min: ${column.minimum}`);
|
|
670
|
+
} else if (column?.unsigned === true) {
|
|
671
|
+
entries.push("min: 0");
|
|
682
672
|
}
|
|
683
673
|
if (Number.isFinite(column?.maximum)) {
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
if (Number.isFinite(column?.exclusiveMaximum)) {
|
|
687
|
-
options.push(`exclusiveMaximum: ${column.exclusiveMaximum}`);
|
|
674
|
+
entries.push(`max: ${column.maximum}`);
|
|
688
675
|
}
|
|
689
|
-
|
|
690
|
-
return `Type.Integer({ ${options.join(", ")} })`;
|
|
691
|
-
}
|
|
692
|
-
return "Type.Integer()";
|
|
676
|
+
return entries;
|
|
693
677
|
}
|
|
694
678
|
|
|
695
|
-
function
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
options.push(`enum: ${JSON.stringify(enumValues)}`);
|
|
679
|
+
function resolveCanonicalResourceFieldRequired(column = {}) {
|
|
680
|
+
return column?.nullable !== true && column?.hasDefault !== true;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function renderCanonicalResourceOperations(column = {}) {
|
|
684
|
+
if (column?.isOwnerColumn === true) {
|
|
685
|
+
return "{}";
|
|
703
686
|
}
|
|
704
|
-
|
|
705
|
-
|
|
687
|
+
|
|
688
|
+
const entries = [
|
|
689
|
+
'output: { required: true }'
|
|
690
|
+
];
|
|
691
|
+
|
|
692
|
+
if (column?.writable === true) {
|
|
693
|
+
entries.push(`create: { required: ${resolveCanonicalResourceFieldRequired(column)} }`);
|
|
694
|
+
entries.push("patch: { required: false }");
|
|
706
695
|
}
|
|
707
|
-
|
|
696
|
+
|
|
697
|
+
return [
|
|
698
|
+
"{",
|
|
699
|
+
...entries.map((entry, index) => ` ${entry}${index < entries.length - 1 ? "," : ""}`),
|
|
700
|
+
"}"
|
|
701
|
+
].join("\n");
|
|
708
702
|
}
|
|
709
703
|
|
|
710
|
-
function
|
|
711
|
-
|
|
712
|
-
const typeKind = String(column
|
|
704
|
+
function renderCanonicalResourceFieldSchema(column, { fieldContractEntry = null } = {}) {
|
|
705
|
+
const entries = [];
|
|
706
|
+
const typeKind = String(column?.typeKind || "");
|
|
707
|
+
const isRequired = resolveCanonicalResourceFieldRequired(column);
|
|
708
|
+
|
|
713
709
|
if (typeKind === "string") {
|
|
714
|
-
|
|
710
|
+
entries.push('type: "string"');
|
|
711
|
+
if (Number.isInteger(column?.maxLength) && column.maxLength > 0) {
|
|
712
|
+
entries.push(`maxLength: ${column.maxLength}`);
|
|
713
|
+
}
|
|
714
|
+
const enumValues = Array.isArray(column?.enumValues) ? column.enumValues.filter((entry) => entry != null) : [];
|
|
715
|
+
if (enumValues.length > 0) {
|
|
716
|
+
entries.push(`enum: ${JSON.stringify(enumValues)}`);
|
|
717
|
+
}
|
|
715
718
|
} else if (typeKind === "integer") {
|
|
716
719
|
if (column?.isRecordIdColumn === true) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
+
entries.push('type: "id"');
|
|
721
|
+
} else {
|
|
722
|
+
entries.push('type: "integer"');
|
|
723
|
+
entries.push(...renderBoundedNumberEntries(column));
|
|
720
724
|
}
|
|
721
|
-
schemaExpression = renderIntegerSchema(column);
|
|
722
725
|
} else if (typeKind === "number") {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
options.push(`minimum: ${column.minimum}`);
|
|
726
|
-
}
|
|
727
|
-
if (Number.isFinite(column?.exclusiveMinimum)) {
|
|
728
|
-
options.push(`exclusiveMinimum: ${column.exclusiveMinimum}`);
|
|
729
|
-
}
|
|
730
|
-
if (Number.isFinite(column?.maximum)) {
|
|
731
|
-
options.push(`maximum: ${column.maximum}`);
|
|
732
|
-
}
|
|
733
|
-
if (Number.isFinite(column?.exclusiveMaximum)) {
|
|
734
|
-
options.push(`exclusiveMaximum: ${column.exclusiveMaximum}`);
|
|
735
|
-
}
|
|
736
|
-
schemaExpression = options.length > 0
|
|
737
|
-
? `Type.Number({ ${options.join(", ")} })`
|
|
738
|
-
: "Type.Number()";
|
|
726
|
+
entries.push('type: "number"');
|
|
727
|
+
entries.push(...renderBoundedNumberEntries(column));
|
|
739
728
|
} else if (typeKind === "boolean") {
|
|
740
|
-
|
|
729
|
+
entries.push('type: "boolean"');
|
|
741
730
|
} else if (typeKind === "datetime") {
|
|
742
|
-
|
|
731
|
+
entries.push('type: "dateTime"');
|
|
732
|
+
const normalizedDefault = normalizeText(column?.defaultValue).toLowerCase();
|
|
733
|
+
if (normalizedDefault === "current_timestamp" || normalizedDefault === "current_timestamp()") {
|
|
734
|
+
entries.push('default: "now()"');
|
|
735
|
+
}
|
|
743
736
|
} else if (typeKind === "date") {
|
|
744
|
-
|
|
737
|
+
entries.push('type: "date"');
|
|
745
738
|
} else if (typeKind === "time") {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
} else if (typeKind === "json") {
|
|
750
|
-
schemaExpression = "Type.Any()";
|
|
739
|
+
entries.push('type: "time"');
|
|
740
|
+
} else {
|
|
741
|
+
entries.push('type: "none"');
|
|
751
742
|
}
|
|
752
743
|
|
|
753
|
-
if (
|
|
754
|
-
|
|
744
|
+
if (isRequired) {
|
|
745
|
+
entries.push("required: true");
|
|
746
|
+
}
|
|
747
|
+
if (column?.nullable === true) {
|
|
748
|
+
entries.push("nullable: true");
|
|
749
|
+
}
|
|
750
|
+
if (shouldRenderJsonRestSearch(column)) {
|
|
751
|
+
entries.push("search: true");
|
|
752
|
+
}
|
|
753
|
+
if (column?.isOwnerColumn === true) {
|
|
754
|
+
entries.push("hidden: true");
|
|
755
755
|
}
|
|
756
|
-
return schemaExpression;
|
|
757
|
-
}
|
|
758
756
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
757
|
+
const actualField = normalizeText(fieldContractEntry?.actualField);
|
|
758
|
+
if (actualField) {
|
|
759
|
+
entries.push(`actualField: ${JSON.stringify(actualField)}`);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const parentRouteParamKey = normalizeText(fieldContractEntry?.parentRouteParamKey);
|
|
763
|
+
if (parentRouteParamKey) {
|
|
764
|
+
entries.push(`parentRouteParamKey: ${JSON.stringify(parentRouteParamKey)}`);
|
|
768
765
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
766
|
+
|
|
767
|
+
const relation = fieldContractEntry?.relation && typeof fieldContractEntry.relation === "object"
|
|
768
|
+
? fieldContractEntry.relation
|
|
769
|
+
: null;
|
|
770
|
+
if (relation) {
|
|
771
|
+
const relationNamespace =
|
|
772
|
+
normalizeCrudLookupNamespace(relation.namespace) ||
|
|
773
|
+
normalizeCrudLookupNamespace(relation.apiPath) ||
|
|
774
|
+
normalizeCrudLookupNamespace(relation?.source?.path) ||
|
|
775
|
+
normalizeCrudLookupNamespace(relation.targetResource);
|
|
776
|
+
if (!relationNamespace) {
|
|
777
|
+
throw new Error(`crud template context field "${normalizeText(column?.key)}" lookup relation requires namespace.`);
|
|
772
778
|
}
|
|
779
|
+
|
|
780
|
+
const relationEntries = [
|
|
781
|
+
`kind: ${JSON.stringify(normalizeText(relation.kind) || "lookup")}`,
|
|
782
|
+
`namespace: ${JSON.stringify(relationNamespace)}`,
|
|
783
|
+
`valueKey: ${JSON.stringify(normalizeText(relation.valueKey) || "id")}`
|
|
784
|
+
];
|
|
785
|
+
const labelKey = normalizeText(relation.labelKey);
|
|
786
|
+
if (labelKey) {
|
|
787
|
+
relationEntries.push(`labelKey: ${JSON.stringify(labelKey)}`);
|
|
788
|
+
}
|
|
789
|
+
entries.push(`relation: { ${relationEntries.join(", ")} }`);
|
|
773
790
|
}
|
|
774
|
-
return `import {\n ${imports.join(",\n ")}\n} from "@jskit-ai/kernel/shared/validators";`;
|
|
775
|
-
}
|
|
776
791
|
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
792
|
+
const relationshipScopeName = resolveJsonRestRelationshipScopeName(fieldContractEntry);
|
|
793
|
+
const relationshipAlias = resolveJsonRestRelationshipAlias(column);
|
|
794
|
+
if (
|
|
795
|
+
relationshipScopeName &&
|
|
796
|
+
relationshipAlias &&
|
|
797
|
+
column?.isOwnerColumn !== true &&
|
|
798
|
+
column?.isForeignIdColumn === true
|
|
799
|
+
) {
|
|
800
|
+
entries.push(`belongsTo: ${JSON.stringify(relationshipScopeName)}`);
|
|
801
|
+
entries.push(`as: ${JSON.stringify(relationshipAlias)}`);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const fieldUiOptions = normalizeFieldMetaUiOptions(fieldContractEntry?.ui?.options);
|
|
805
|
+
const formControl = checkCrudLookupFormControl(fieldContractEntry?.ui?.formControl, {
|
|
806
|
+
context: `resource schema field "${normalizeText(column?.key)}" ui.formControl`,
|
|
807
|
+
defaultValue: relation ? "autocomplete" : (fieldUiOptions.length > 0 ? "select" : "")
|
|
808
|
+
});
|
|
809
|
+
if (formControl || fieldUiOptions.length > 0) {
|
|
810
|
+
const uiEntries = [];
|
|
811
|
+
if (formControl) {
|
|
812
|
+
uiEntries.push(`formControl: ${JSON.stringify(formControl)}`);
|
|
782
813
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
: "HTML_TIME_STRING_SCHEMA";
|
|
786
|
-
if (!imports.includes(importName)) {
|
|
787
|
-
imports.push(importName);
|
|
814
|
+
if (fieldUiOptions.length > 0) {
|
|
815
|
+
uiEntries.push(`options: ${JSON.stringify(fieldUiOptions)}`);
|
|
788
816
|
}
|
|
817
|
+
entries.push(`ui: { ${uiEntries.join(", ")} }`);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const storageEntries = [];
|
|
821
|
+
if (fieldContractEntry?.storage?.mode === "virtual") {
|
|
822
|
+
storageEntries.push("virtual: true");
|
|
823
|
+
}
|
|
824
|
+
if (toSnakeCase(normalizeText(column?.key)) !== normalizeText(column?.name)) {
|
|
825
|
+
storageEntries.push(`column: ${JSON.stringify(column.name)}`);
|
|
789
826
|
}
|
|
790
|
-
|
|
827
|
+
if (normalizeText(column?.typeKind).toLowerCase() === "datetime") {
|
|
828
|
+
storageEntries.push('writeSerializer: "datetime-utc"');
|
|
829
|
+
}
|
|
830
|
+
if (storageEntries.length > 0) {
|
|
831
|
+
entries.push(`storage: { ${storageEntries.join(", ")} }`);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const operationsLines = renderCanonicalResourceOperations(column).split("\n");
|
|
835
|
+
const lines = [
|
|
836
|
+
"{",
|
|
837
|
+
...entries.map((entry) => ` ${entry},`),
|
|
838
|
+
` operations: ${operationsLines[0]}`
|
|
839
|
+
];
|
|
840
|
+
|
|
841
|
+
for (const line of operationsLines.slice(1)) {
|
|
842
|
+
lines.push(` ${line}`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
lines.push("}");
|
|
846
|
+
return lines.join("\n");
|
|
791
847
|
}
|
|
792
848
|
|
|
793
|
-
function
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
849
|
+
function renderCanonicalResourceSchemaPropertyLines(columns = [], { fieldContractEntries = [] } = {}) {
|
|
850
|
+
const fieldContractByKey = Object.fromEntries(
|
|
851
|
+
(Array.isArray(fieldContractEntries) ? fieldContractEntries : [])
|
|
852
|
+
.map((entry) => [normalizeText(entry?.key), entry])
|
|
853
|
+
.filter(([key]) => key)
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
return (Array.isArray(columns) ? columns : [])
|
|
857
|
+
.filter((column) => column?.isIdColumn !== true)
|
|
858
|
+
.map((column) => {
|
|
859
|
+
const key = renderObjectPropertyKey(column.key);
|
|
860
|
+
const schemaLines = renderCanonicalResourceFieldSchema(column, {
|
|
861
|
+
fieldContractEntry: fieldContractByKey[normalizeText(column?.key)] || null
|
|
862
|
+
}).split("\n");
|
|
863
|
+
const lines = [` ${key}: ${schemaLines[0]}`];
|
|
864
|
+
for (const line of schemaLines.slice(1)) {
|
|
865
|
+
lines.push(` ${line}`);
|
|
866
|
+
}
|
|
867
|
+
lines[lines.length - 1] = `${lines[lines.length - 1]},`;
|
|
868
|
+
return lines.join("\n");
|
|
869
|
+
})
|
|
797
870
|
.join("\n");
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function resolveJsonRestRelationshipScopeName(fieldContractEntry = null) {
|
|
874
|
+
const namespace = normalizeText(fieldContractEntry?.relation?.namespace);
|
|
875
|
+
if (!namespace) {
|
|
876
|
+
return "";
|
|
802
877
|
}
|
|
803
|
-
|
|
878
|
+
|
|
879
|
+
return toCamelCase(namespace.replace(/\//g, "-"));
|
|
804
880
|
}
|
|
805
881
|
|
|
806
|
-
function
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
return "normalizeText";
|
|
882
|
+
function resolveJsonRestRelationshipAlias(column = null) {
|
|
883
|
+
const key = normalizeText(column?.key);
|
|
884
|
+
if (!key) {
|
|
885
|
+
return "";
|
|
811
886
|
}
|
|
812
|
-
if (
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
887
|
+
if (key.endsWith("Id") && key.length > 2) {
|
|
888
|
+
return `${key.slice(0, -2).slice(0, 1).toLowerCase()}${key.slice(0, -2).slice(1)}`;
|
|
889
|
+
}
|
|
890
|
+
return "";
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function resolveJsonRestFieldType(column = {}) {
|
|
894
|
+
if (column?.isRecordIdColumn === true) {
|
|
895
|
+
return "id";
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const typeKind = normalizeText(column?.typeKind).toLowerCase();
|
|
899
|
+
if (typeKind === "string") {
|
|
900
|
+
return "string";
|
|
817
901
|
}
|
|
818
902
|
if (typeKind === "integer") {
|
|
819
|
-
|
|
820
|
-
if (nullable) {
|
|
821
|
-
return "(value) => normalizeRecordId(value, { fallback: null })";
|
|
822
|
-
}
|
|
823
|
-
return "normalizeRecordId";
|
|
824
|
-
}
|
|
825
|
-
return "normalizeFiniteInteger";
|
|
903
|
+
return "integer";
|
|
826
904
|
}
|
|
827
905
|
if (typeKind === "number") {
|
|
828
|
-
return "
|
|
906
|
+
return "number";
|
|
829
907
|
}
|
|
830
908
|
if (typeKind === "boolean") {
|
|
831
|
-
return "
|
|
909
|
+
return "boolean";
|
|
832
910
|
}
|
|
833
911
|
if (typeKind === "datetime") {
|
|
834
|
-
|
|
835
|
-
return "(value) => { const normalized = normalizeText(value); return normalized ? toDatabaseDateTimeUtc(normalized) : null; }";
|
|
836
|
-
}
|
|
837
|
-
return "toDatabaseDateTimeUtc";
|
|
912
|
+
return "dateTime";
|
|
838
913
|
}
|
|
839
914
|
if (typeKind === "date") {
|
|
840
|
-
|
|
841
|
-
return "(value) => { const normalized = normalizeText(value); return normalized ? toIsoString(normalized).slice(0, 10) : null; }";
|
|
842
|
-
}
|
|
843
|
-
return "(value) => toIsoString(value).slice(0, 10)";
|
|
915
|
+
return "date";
|
|
844
916
|
}
|
|
845
|
-
if (typeKind === "
|
|
846
|
-
return "
|
|
917
|
+
if (typeKind === "time") {
|
|
918
|
+
return "time";
|
|
847
919
|
}
|
|
848
|
-
return "
|
|
920
|
+
return "string";
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function shouldRenderJsonRestSearch(column = {}) {
|
|
924
|
+
return column?.isCreatedAtColumn !== true && column?.isUpdatedAtColumn !== true;
|
|
849
925
|
}
|
|
850
926
|
|
|
851
|
-
function
|
|
852
|
-
const
|
|
853
|
-
const
|
|
854
|
-
if (
|
|
855
|
-
return
|
|
927
|
+
function shouldRenderJsonRestStorage(column = {}) {
|
|
928
|
+
const key = normalizeText(column?.key);
|
|
929
|
+
const columnName = normalizeText(column?.name);
|
|
930
|
+
if (!key || !columnName) {
|
|
931
|
+
return false;
|
|
856
932
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
933
|
+
|
|
934
|
+
if (toSnakeCase(key) !== columnName) {
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return normalizeText(column?.typeKind).toLowerCase() === "datetime";
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function renderJsonRestFieldSchema(column, { fieldContractEntry = null } = {}) {
|
|
942
|
+
const entries = [];
|
|
943
|
+
const type = resolveJsonRestFieldType(column);
|
|
944
|
+
entries.push(`type: ${JSON.stringify(type)}`);
|
|
945
|
+
|
|
946
|
+
if (column?.isIdColumn === true) {
|
|
947
|
+
entries.push("primary: true");
|
|
948
|
+
entries.push("required: true");
|
|
949
|
+
entries.push("search: true");
|
|
950
|
+
} else {
|
|
951
|
+
const required = column?.nullable !== true && column?.hasDefault !== true;
|
|
952
|
+
entries.push(`required: ${required}`);
|
|
953
|
+
if (shouldRenderJsonRestSearch(column)) {
|
|
954
|
+
entries.push("search: true");
|
|
863
955
|
}
|
|
864
|
-
return "normalizeFiniteInteger";
|
|
865
956
|
}
|
|
866
|
-
|
|
867
|
-
|
|
957
|
+
|
|
958
|
+
if (column?.nullable === true) {
|
|
959
|
+
entries.push("nullable: true");
|
|
868
960
|
}
|
|
869
|
-
|
|
870
|
-
|
|
961
|
+
|
|
962
|
+
if (type === "string" && Number.isInteger(column?.maxLength) && column.maxLength > 0) {
|
|
963
|
+
entries.push(`max: ${column.maxLength}`);
|
|
871
964
|
}
|
|
872
|
-
|
|
873
|
-
|
|
965
|
+
|
|
966
|
+
if (column?.isOwnerColumn === true) {
|
|
967
|
+
entries.push("hidden: true");
|
|
874
968
|
}
|
|
875
|
-
|
|
876
|
-
|
|
969
|
+
|
|
970
|
+
const relationshipScopeName = resolveJsonRestRelationshipScopeName(fieldContractEntry);
|
|
971
|
+
const relationshipAlias = resolveJsonRestRelationshipAlias(column);
|
|
972
|
+
if (
|
|
973
|
+
relationshipScopeName &&
|
|
974
|
+
relationshipAlias &&
|
|
975
|
+
column?.isOwnerColumn !== true &&
|
|
976
|
+
column?.isForeignIdColumn === true
|
|
977
|
+
) {
|
|
978
|
+
entries.push(`belongsTo: ${JSON.stringify(relationshipScopeName)}`);
|
|
979
|
+
entries.push(`as: ${JSON.stringify(relationshipAlias)}`);
|
|
877
980
|
}
|
|
878
|
-
|
|
879
|
-
|
|
981
|
+
|
|
982
|
+
if (shouldRenderJsonRestStorage(column)) {
|
|
983
|
+
const storageEntries = [];
|
|
984
|
+
if (toSnakeCase(normalizeText(column?.key)) !== normalizeText(column?.name)) {
|
|
985
|
+
storageEntries.push(`column: ${JSON.stringify(column.name)}`);
|
|
986
|
+
}
|
|
987
|
+
if (normalizeText(column?.typeKind).toLowerCase() === "datetime") {
|
|
988
|
+
storageEntries.push("serialize: serializeNullableDateTime");
|
|
989
|
+
}
|
|
990
|
+
entries.push(`storage: { ${storageEntries.join(", ")} }`);
|
|
880
991
|
}
|
|
881
|
-
return "";
|
|
882
|
-
}
|
|
883
992
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const schemaExpression = renderResourceFieldSchema(column, { forOutput });
|
|
890
|
-
return ` ${key}: ${schemaExpression},`;
|
|
891
|
-
})
|
|
892
|
-
.join("\n");
|
|
993
|
+
return [
|
|
994
|
+
"{",
|
|
995
|
+
...entries.map((entry, index) => ` ${entry}${index < entries.length - 1 ? "," : ""}`),
|
|
996
|
+
"}"
|
|
997
|
+
].join("\n");
|
|
893
998
|
}
|
|
894
999
|
|
|
895
|
-
function
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
return ` normalizeIfInSource(source, normalized, ${keyLiteral}, ${normalizer});`;
|
|
902
|
-
})
|
|
903
|
-
.join("\n");
|
|
904
|
-
}
|
|
1000
|
+
function renderJsonRestSchemaPropertyLines(columns = [], { fieldContractEntries = [] } = {}) {
|
|
1001
|
+
const fieldContractByKey = Object.fromEntries(
|
|
1002
|
+
(Array.isArray(fieldContractEntries) ? fieldContractEntries : [])
|
|
1003
|
+
.map((entry) => [normalizeText(entry?.key), entry])
|
|
1004
|
+
.filter(([key]) => key)
|
|
1005
|
+
);
|
|
905
1006
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
return sourceColumns
|
|
1007
|
+
return (Array.isArray(columns) ? columns : [])
|
|
1008
|
+
.filter((column) => column?.isIdColumn !== true)
|
|
909
1009
|
.map((column) => {
|
|
910
1010
|
const key = renderObjectPropertyKey(column.key);
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1011
|
+
const schemaLines = renderJsonRestFieldSchema(column, {
|
|
1012
|
+
fieldContractEntry: fieldContractByKey[normalizeText(column?.key)] || null
|
|
1013
|
+
}).split("\n");
|
|
1014
|
+
const lines = [` ${key}: ${schemaLines[0]}`];
|
|
1015
|
+
for (const line of schemaLines.slice(1)) {
|
|
1016
|
+
lines.push(` ${line}`);
|
|
915
1017
|
}
|
|
916
|
-
|
|
917
|
-
return
|
|
1018
|
+
lines[lines.length - 1] = `${lines[lines.length - 1]},`;
|
|
1019
|
+
return lines.join("\n");
|
|
918
1020
|
})
|
|
919
1021
|
.join("\n");
|
|
920
1022
|
}
|
|
921
1023
|
|
|
922
|
-
function
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return `import {\n ${imports.join(",\n ")}\n} from "@jskit-ai/database-runtime/shared";`;
|
|
934
|
-
}
|
|
1024
|
+
function renderJsonRestSearchSchemaLines(columns = []) {
|
|
1025
|
+
const searchableStringKeys = (Array.isArray(columns) ? columns : [])
|
|
1026
|
+
.filter((column) =>
|
|
1027
|
+
normalizeText(column?.typeKind).toLowerCase() === "string" &&
|
|
1028
|
+
column?.isOwnerColumn !== true &&
|
|
1029
|
+
column?.isIdColumn !== true &&
|
|
1030
|
+
column?.isCreatedAtColumn !== true &&
|
|
1031
|
+
column?.isUpdatedAtColumn !== true
|
|
1032
|
+
)
|
|
1033
|
+
.map((column) => normalizeText(column?.key))
|
|
1034
|
+
.filter(Boolean);
|
|
935
1035
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1036
|
+
const lines = [
|
|
1037
|
+
' id: { type: "id", actualField: "id" },'
|
|
1038
|
+
];
|
|
1039
|
+
|
|
1040
|
+
if (searchableStringKeys.length > 0) {
|
|
1041
|
+
lines.push(
|
|
1042
|
+
` q: { type: "string", oneOf: ${JSON.stringify(searchableStringKeys)}, filterOperator: "like", splitBy: " ", matchAll: true },`
|
|
1043
|
+
);
|
|
939
1044
|
}
|
|
940
|
-
|
|
1045
|
+
|
|
1046
|
+
return lines.join("\n");
|
|
941
1047
|
}
|
|
942
1048
|
|
|
943
|
-
function
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
needsNormalizeRecordId = false,
|
|
949
|
-
needsNormalizeIfInSource = false,
|
|
950
|
-
needsNormalizeIfPresent = false,
|
|
951
|
-
needsNormalizeOrNull = false
|
|
952
|
-
} = {}) {
|
|
953
|
-
const imports = [];
|
|
954
|
-
if (needsNormalizeText) {
|
|
955
|
-
imports.push("normalizeText");
|
|
956
|
-
}
|
|
957
|
-
if (needsNormalizeBoolean) {
|
|
958
|
-
imports.push("normalizeBoolean");
|
|
959
|
-
}
|
|
960
|
-
if (needsNormalizeFiniteNumber) {
|
|
961
|
-
imports.push("normalizeFiniteNumber");
|
|
962
|
-
}
|
|
963
|
-
if (needsNormalizeFiniteInteger) {
|
|
964
|
-
imports.push("normalizeFiniteInteger");
|
|
965
|
-
}
|
|
966
|
-
if (needsNormalizeRecordId) {
|
|
967
|
-
imports.push("normalizeRecordId");
|
|
968
|
-
}
|
|
969
|
-
if (needsNormalizeIfInSource) {
|
|
970
|
-
imports.push("normalizeIfInSource");
|
|
971
|
-
}
|
|
972
|
-
if (needsNormalizeIfPresent) {
|
|
973
|
-
imports.push("normalizeIfPresent");
|
|
974
|
-
}
|
|
975
|
-
if (needsNormalizeOrNull) {
|
|
976
|
-
imports.push("normalizeOrNull");
|
|
1049
|
+
function renderJsonRestDefaultSortLine(columns = []) {
|
|
1050
|
+
const sourceColumns = Array.isArray(columns) ? columns : [];
|
|
1051
|
+
const createdAtColumn = sourceColumns.find((column) => column?.isCreatedAtColumn === true);
|
|
1052
|
+
if (createdAtColumn?.key) {
|
|
1053
|
+
return ` defaultSort: ${JSON.stringify([`-${createdAtColumn.key}`])},`;
|
|
977
1054
|
}
|
|
978
|
-
|
|
979
|
-
|
|
1055
|
+
|
|
1056
|
+
const idColumn = sourceColumns.find((column) => column?.isIdColumn === true);
|
|
1057
|
+
if (idColumn?.key) {
|
|
1058
|
+
return ` defaultSort: ${JSON.stringify([`-${idColumn.key}`])},`;
|
|
980
1059
|
}
|
|
981
|
-
|
|
1060
|
+
|
|
1061
|
+
return "";
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function renderResourceDefaultSortLiteral(columns = []) {
|
|
1065
|
+
const sortLine = renderJsonRestDefaultSortLine(columns);
|
|
1066
|
+
const match = sortLine.match(/defaultSort:\s*(.+),$/);
|
|
1067
|
+
return match?.[1] || "[]";
|
|
982
1068
|
}
|
|
983
1069
|
|
|
984
1070
|
function renderMigrationDefaultClause(column) {
|
|
@@ -1411,7 +1497,7 @@ function resolveEnumFieldMetaUiOptions(enumValues = []) {
|
|
|
1411
1497
|
return normalizeFieldMetaUiOptions(options);
|
|
1412
1498
|
}
|
|
1413
1499
|
|
|
1414
|
-
function
|
|
1500
|
+
function buildFieldContractEntries({ outputColumns = [], writableColumns = [], snapshot = {} } = {}) {
|
|
1415
1501
|
const fieldColumns = [...outputColumns, ...writableColumns];
|
|
1416
1502
|
const fieldColumnsByName = new Map();
|
|
1417
1503
|
const fieldColumnsByKey = new Map();
|
|
@@ -1509,127 +1595,6 @@ function buildFieldMetaEntries({ outputColumns = [], writableColumns = [], snaps
|
|
|
1509
1595
|
return mergeFieldMetaEntries(repositoryEntries, relationEntries, enumEntries);
|
|
1510
1596
|
}
|
|
1511
1597
|
|
|
1512
|
-
function renderFieldMetaEntryLines(entry = {}) {
|
|
1513
|
-
const lines = ["RESOURCE_FIELD_META.push({"];
|
|
1514
|
-
const topLevelProperties = [`key: ${JSON.stringify(entry.key)}`];
|
|
1515
|
-
const repositoryColumn = normalizeText(entry?.repository?.column);
|
|
1516
|
-
if (repositoryColumn) {
|
|
1517
|
-
topLevelProperties.push([
|
|
1518
|
-
"repository: {",
|
|
1519
|
-
` column: ${JSON.stringify(repositoryColumn)}`,
|
|
1520
|
-
"}"
|
|
1521
|
-
].join("\n"));
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
const relation = entry.relation && typeof entry.relation === "object" ? entry.relation : null;
|
|
1525
|
-
if (relation) {
|
|
1526
|
-
const targetResourceNamespace = normalizeCrudLookupNamespace(relation.targetResource);
|
|
1527
|
-
const relationNamespace =
|
|
1528
|
-
normalizeCrudLookupNamespace(relation.namespace) ||
|
|
1529
|
-
normalizeCrudLookupNamespace(relation.apiPath) ||
|
|
1530
|
-
normalizeCrudLookupNamespace(relation?.source?.path) ||
|
|
1531
|
-
targetResourceNamespace;
|
|
1532
|
-
if (!relationNamespace) {
|
|
1533
|
-
throw new Error(`crud template context fieldMeta["${normalizeText(entry.key)}"] lookup relation requires namespace.`);
|
|
1534
|
-
}
|
|
1535
|
-
const relationLines = [
|
|
1536
|
-
"relation: {",
|
|
1537
|
-
` kind: ${JSON.stringify(normalizeText(relation.kind) || "lookup")},`,
|
|
1538
|
-
` namespace: ${JSON.stringify(relationNamespace)},`,
|
|
1539
|
-
` valueKey: ${JSON.stringify(normalizeText(relation.valueKey) || "id")},`
|
|
1540
|
-
];
|
|
1541
|
-
const labelKey = normalizeText(relation.labelKey);
|
|
1542
|
-
if (labelKey) {
|
|
1543
|
-
relationLines.push(` labelKey: ${JSON.stringify(labelKey)}`);
|
|
1544
|
-
} else {
|
|
1545
|
-
relationLines[relationLines.length - 1] = relationLines[relationLines.length - 1].replace(/,$/, "");
|
|
1546
|
-
}
|
|
1547
|
-
relationLines.push("}");
|
|
1548
|
-
topLevelProperties.push(relationLines.join("\n"));
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
const fieldUiOptions = normalizeFieldMetaUiOptions(entry?.ui?.options);
|
|
1552
|
-
const formControl = checkCrudLookupFormControl(entry?.ui?.formControl, {
|
|
1553
|
-
context: `resource.fieldMeta["${normalizeText(entry.key)}"].ui.formControl`,
|
|
1554
|
-
defaultValue: relation ? "autocomplete" : (fieldUiOptions.length > 0 ? "select" : "")
|
|
1555
|
-
});
|
|
1556
|
-
if (formControl || fieldUiOptions.length > 0) {
|
|
1557
|
-
const uiPropertyBlocks = [];
|
|
1558
|
-
if (formControl) {
|
|
1559
|
-
uiPropertyBlocks.push([
|
|
1560
|
-
`formControl: ${JSON.stringify(formControl)}${relation ? " // or \"select\"" : ""}`
|
|
1561
|
-
]);
|
|
1562
|
-
}
|
|
1563
|
-
if (fieldUiOptions.length > 0) {
|
|
1564
|
-
const optionsJsonLines = JSON.stringify(fieldUiOptions, null, 2).split("\n");
|
|
1565
|
-
const optionPropertyLines = [`options: ${optionsJsonLines[0]}`];
|
|
1566
|
-
for (const jsonLine of optionsJsonLines.slice(1)) {
|
|
1567
|
-
optionPropertyLines.push(jsonLine);
|
|
1568
|
-
}
|
|
1569
|
-
uiPropertyBlocks.push(optionPropertyLines);
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
const uiLines = ["ui: {"];
|
|
1573
|
-
for (const [propertyIndex, propertyLines] of uiPropertyBlocks.entries()) {
|
|
1574
|
-
const isLastProperty = propertyIndex >= uiPropertyBlocks.length - 1;
|
|
1575
|
-
const propertySuffix = isLastProperty ? "" : ",";
|
|
1576
|
-
for (const [lineIndex, line] of propertyLines.entries()) {
|
|
1577
|
-
const isLastLine = lineIndex >= propertyLines.length - 1;
|
|
1578
|
-
uiLines.push(` ${line}${isLastLine ? propertySuffix : ""}`);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
uiLines.push("}");
|
|
1582
|
-
topLevelProperties.push(
|
|
1583
|
-
uiLines.join("\n")
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
for (const [index, propertyBlock] of topLevelProperties.entries()) {
|
|
1588
|
-
const blockLines = String(propertyBlock || "").split("\n");
|
|
1589
|
-
const isLastProperty = index >= topLevelProperties.length - 1;
|
|
1590
|
-
const propertySuffix = isLastProperty ? "" : ",";
|
|
1591
|
-
for (const [lineIndex, line] of blockLines.entries()) {
|
|
1592
|
-
const isLastLine = lineIndex >= blockLines.length - 1;
|
|
1593
|
-
lines.push(` ${line}${isLastLine ? propertySuffix : ""}`);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
lines.push("});");
|
|
1598
|
-
return lines.join("\n");
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
function renderResourceFieldMetaPushLines(entries = []) {
|
|
1602
|
-
const sourceEntries = Array.isArray(entries) ? entries : [];
|
|
1603
|
-
if (sourceEntries.length < 1) {
|
|
1604
|
-
return "";
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
return sourceEntries.map((entry) => renderFieldMetaEntryLines(entry)).join("\n\n");
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
function renderRepositoryListConfigLines(snapshot = {}) {
|
|
1611
|
-
const commentLines = [
|
|
1612
|
-
" // defaultLimit: 20,",
|
|
1613
|
-
" // maxLimit: 100,",
|
|
1614
|
-
" // searchColumns: [\"name\"],"
|
|
1615
|
-
];
|
|
1616
|
-
const sourceColumns = Array.isArray(snapshot?.columns) ? snapshot.columns : [];
|
|
1617
|
-
const hasCreatedAtColumn = sourceColumns.some((column = {}) => normalizeText(column?.name) === "created_at");
|
|
1618
|
-
if (!hasCreatedAtColumn) {
|
|
1619
|
-
return commentLines.join("\n");
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
return [
|
|
1623
|
-
...commentLines,
|
|
1624
|
-
" orderBy: [",
|
|
1625
|
-
" {",
|
|
1626
|
-
" column: \"created_at\",",
|
|
1627
|
-
" direction: \"desc\"",
|
|
1628
|
-
" }",
|
|
1629
|
-
" ]"
|
|
1630
|
-
].join("\n");
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
1598
|
function buildCrudPermissionIds(namespace = "") {
|
|
1634
1599
|
const permissionNamespace = toSnakeCase(namespace);
|
|
1635
1600
|
if (!permissionNamespace) {
|
|
@@ -1728,14 +1693,10 @@ function renderRouteParamsValidatorLine(operation = "", { surfaceRequiresWorkspa
|
|
|
1728
1693
|
if (!surfaceRequiresWorkspace) {
|
|
1729
1694
|
return "";
|
|
1730
1695
|
}
|
|
1731
|
-
return "
|
|
1696
|
+
return " params: routeParamsValidator,";
|
|
1732
1697
|
}
|
|
1733
1698
|
|
|
1734
|
-
|
|
1735
|
-
return " paramsValidator: recordIdParamsValidator,";
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
return " paramsValidator: [routeParamsValidator, recordIdParamsValidator],";
|
|
1699
|
+
return " params: recordRouteParamsValidator,";
|
|
1739
1700
|
}
|
|
1740
1701
|
|
|
1741
1702
|
function renderRouteInputLines(operation = "", { surfaceRequiresWorkspace = true } = {}) {
|
|
@@ -1758,13 +1719,13 @@ function renderRouteInputLines(operation = "", { surfaceRequiresWorkspace = true
|
|
|
1758
1719
|
}
|
|
1759
1720
|
|
|
1760
1721
|
if (normalizedOperation === "create") {
|
|
1761
|
-
lines.push("
|
|
1722
|
+
lines.push(" ...(request.input.body || {})");
|
|
1762
1723
|
return lines.join("\n");
|
|
1763
1724
|
}
|
|
1764
1725
|
|
|
1765
1726
|
if (normalizedOperation === "update") {
|
|
1766
1727
|
lines.push(" recordId: request.input.params.recordId,");
|
|
1767
|
-
lines.push("
|
|
1728
|
+
lines.push(" ...(request.input.body || {})");
|
|
1768
1729
|
return lines.join("\n");
|
|
1769
1730
|
}
|
|
1770
1731
|
|
|
@@ -1772,32 +1733,89 @@ function renderRouteInputLines(operation = "", { surfaceRequiresWorkspace = true
|
|
|
1772
1733
|
return lines.join("\n");
|
|
1773
1734
|
}
|
|
1774
1735
|
|
|
1775
|
-
function
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1736
|
+
function renderObjectSchemaDefinition(lines = [], { mode = "patch" } = {}) {
|
|
1737
|
+
const entries = (Array.isArray(lines) ? lines : [])
|
|
1738
|
+
.map((line) => String(line || "").trim())
|
|
1739
|
+
.filter(Boolean)
|
|
1740
|
+
.map((line) => line.endsWith(",") ? line.slice(0, -1) : line);
|
|
1741
|
+
|
|
1742
|
+
if (entries.length < 1) {
|
|
1743
|
+
throw new TypeError("renderObjectSchemaDefinition requires at least one schema definition.");
|
|
1744
|
+
}
|
|
1778
1745
|
|
|
1779
|
-
if (
|
|
1780
|
-
|
|
1746
|
+
if (entries.length === 1) {
|
|
1747
|
+
return entries[0];
|
|
1781
1748
|
}
|
|
1782
1749
|
|
|
1783
|
-
if (
|
|
1784
|
-
|
|
1785
|
-
"
|
|
1786
|
-
|
|
1787
|
-
"
|
|
1788
|
-
|
|
1789
|
-
);
|
|
1790
|
-
} else if (normalizedOperation === "view") {
|
|
1791
|
-
validators.push("recordIdParamsValidator", "lookupIncludeQueryValidator");
|
|
1792
|
-
} else if (normalizedOperation === "create") {
|
|
1793
|
-
validators.push("{ payload: resource.operations.create.bodyValidator }");
|
|
1794
|
-
} else if (normalizedOperation === "update") {
|
|
1795
|
-
validators.push("recordIdParamsValidator", "{ patch: resource.operations.patch.bodyValidator }");
|
|
1796
|
-
} else {
|
|
1797
|
-
validators.push("recordIdParamsValidator");
|
|
1750
|
+
if (normalizeText(mode).toLowerCase() === "patch") {
|
|
1751
|
+
return [
|
|
1752
|
+
"composeSchemaDefinitions([",
|
|
1753
|
+
...entries.map((line) => ` ${line},`),
|
|
1754
|
+
"])"
|
|
1755
|
+
].join("\n");
|
|
1798
1756
|
}
|
|
1799
1757
|
|
|
1800
|
-
return
|
|
1758
|
+
return [
|
|
1759
|
+
"composeSchemaDefinitions([",
|
|
1760
|
+
...entries.map((line) => ` ${line},`),
|
|
1761
|
+
"], {",
|
|
1762
|
+
` mode: ${JSON.stringify(mode)}`,
|
|
1763
|
+
"})"
|
|
1764
|
+
].join("\n");
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function renderActionInputExpressions({ surfaceRequiresWorkspace = true } = {}) {
|
|
1768
|
+
const listLines = [];
|
|
1769
|
+
const viewLines = [];
|
|
1770
|
+
const createLines = [];
|
|
1771
|
+
const updateLines = [];
|
|
1772
|
+
const deleteLines = [];
|
|
1773
|
+
|
|
1774
|
+
if (surfaceRequiresWorkspace) {
|
|
1775
|
+
listLines.push("workspaceSlugParamsValidator,");
|
|
1776
|
+
viewLines.push("workspaceSlugParamsValidator,");
|
|
1777
|
+
createLines.push("workspaceSlugParamsValidator,");
|
|
1778
|
+
updateLines.push("workspaceSlugParamsValidator,");
|
|
1779
|
+
deleteLines.push("workspaceSlugParamsValidator,");
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
listLines.push(
|
|
1783
|
+
"listCursorPaginationQueryValidator,",
|
|
1784
|
+
"listSearchQueryValidator,",
|
|
1785
|
+
"listParentFilterQueryValidator,",
|
|
1786
|
+
"lookupIncludeQueryValidator,"
|
|
1787
|
+
);
|
|
1788
|
+
viewLines.push(
|
|
1789
|
+
"recordIdParamsValidator,",
|
|
1790
|
+
"lookupIncludeQueryValidator,"
|
|
1791
|
+
);
|
|
1792
|
+
createLines.push("resource.operations.create.body,");
|
|
1793
|
+
updateLines.push(
|
|
1794
|
+
"recordIdParamsValidator,",
|
|
1795
|
+
"resource.operations.patch.body,"
|
|
1796
|
+
);
|
|
1797
|
+
deleteLines.push("recordIdParamsValidator,");
|
|
1798
|
+
|
|
1799
|
+
return Object.freeze({
|
|
1800
|
+
list: renderObjectSchemaDefinition(listLines),
|
|
1801
|
+
view: renderObjectSchemaDefinition(viewLines),
|
|
1802
|
+
create: renderObjectSchemaDefinition(createLines, { mode: "create" }),
|
|
1803
|
+
update: renderObjectSchemaDefinition(updateLines),
|
|
1804
|
+
delete: renderObjectSchemaDefinition(deleteLines)
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
function renderRouteValidatorConstants({ surfaceRequiresWorkspace = true } = {}) {
|
|
1809
|
+
if (!surfaceRequiresWorkspace) {
|
|
1810
|
+
return "";
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
return [
|
|
1814
|
+
"const recordRouteParamsValidator = composeSchemaDefinitions([",
|
|
1815
|
+
" routeParamsValidator,",
|
|
1816
|
+
" recordIdParamsValidator",
|
|
1817
|
+
"]);"
|
|
1818
|
+
].join("\n");
|
|
1801
1819
|
}
|
|
1802
1820
|
|
|
1803
1821
|
function buildReplacementsFromSnapshot({
|
|
@@ -1811,39 +1829,25 @@ function buildReplacementsFromSnapshot({
|
|
|
1811
1829
|
const scaffoldColumns = resolveScaffoldColumns(snapshot);
|
|
1812
1830
|
const outputColumns = scaffoldColumns.filter((column) => !column.isOwnerColumn);
|
|
1813
1831
|
const writableColumns = scaffoldColumns.filter((column) => column.writable);
|
|
1814
|
-
const
|
|
1815
|
-
.filter((column) => !column.nullable && column.hasDefault !== true)
|
|
1816
|
-
.map((column) => column.key);
|
|
1817
|
-
const resourceColumns = [...outputColumns, ...writableColumns];
|
|
1818
|
-
const fieldMetaEntries = buildFieldMetaEntries({
|
|
1832
|
+
const fieldContractEntries = buildFieldContractEntries({
|
|
1819
1833
|
outputColumns,
|
|
1820
1834
|
writableColumns,
|
|
1821
1835
|
snapshot
|
|
1822
1836
|
});
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
const
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
);
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
const
|
|
1836
|
-
const
|
|
1837
|
-
const needsNormalizeText = resourceColumns.some((column) =>
|
|
1838
|
-
column.typeKind === "string" || column.typeKind === "time"
|
|
1839
|
-
) || needsNullableDateTimeInput || needsNullableDateInput;
|
|
1840
|
-
const needsNormalizeBoolean = resourceColumns.some((column) => column.typeKind === "boolean");
|
|
1841
|
-
const needsNormalizeIfInSource = writableColumns.length > 0;
|
|
1842
|
-
const outputColumnsWithNormalizer = outputColumns.filter(
|
|
1843
|
-
(column) => Boolean(renderOutputNormalizerExpression(column))
|
|
1844
|
-
);
|
|
1845
|
-
const needsNormalizeIfPresent = outputColumnsWithNormalizer.some((column) => column.nullable !== true);
|
|
1846
|
-
const needsNormalizeOrNull = outputColumnsWithNormalizer.some((column) => column.nullable === true);
|
|
1837
|
+
const actionInputExpressions = renderActionInputExpressions({
|
|
1838
|
+
surfaceRequiresWorkspace
|
|
1839
|
+
});
|
|
1840
|
+
const resourceSchemaColumns = scaffoldColumns;
|
|
1841
|
+
const resourceSchemaPropertyLines = renderCanonicalResourceSchemaPropertyLines(resourceSchemaColumns, {
|
|
1842
|
+
fieldContractEntries
|
|
1843
|
+
});
|
|
1844
|
+
const resourceSearchSchemaLines = renderJsonRestSearchSchemaLines(resourceSchemaColumns);
|
|
1845
|
+
const resourceDefaultSortLiteral = renderResourceDefaultSortLiteral(resourceSchemaColumns);
|
|
1846
|
+
const jsonRestSchemaPropertyLines = renderJsonRestSchemaPropertyLines(resourceSchemaColumns, {
|
|
1847
|
+
fieldContractEntries
|
|
1848
|
+
});
|
|
1849
|
+
const jsonRestSearchSchemaLines = renderJsonRestSearchSchemaLines(resourceSchemaColumns);
|
|
1850
|
+
const jsonRestDefaultSortLine = renderJsonRestDefaultSortLine(resourceSchemaColumns);
|
|
1847
1851
|
|
|
1848
1852
|
const replacements = Object.freeze({
|
|
1849
1853
|
__JSKIT_CRUD_TABLE_NAME__: JSON.stringify(snapshot.tableName),
|
|
@@ -1856,36 +1860,26 @@ function buildReplacementsFromSnapshot({
|
|
|
1856
1860
|
__JSKIT_CRUD_ACTION_WORKSPACE_VALIDATOR_IMPORT__: renderActionWorkspaceValidatorImport({
|
|
1857
1861
|
surfaceRequiresWorkspace
|
|
1858
1862
|
}),
|
|
1863
|
+
__JSKIT_CRUD_LIST_ACTION_INPUT__: actionInputExpressions.list,
|
|
1864
|
+
__JSKIT_CRUD_VIEW_ACTION_INPUT__: actionInputExpressions.view,
|
|
1865
|
+
__JSKIT_CRUD_CREATE_ACTION_INPUT__: actionInputExpressions.create,
|
|
1866
|
+
__JSKIT_CRUD_UPDATE_ACTION_INPUT__: actionInputExpressions.update,
|
|
1867
|
+
__JSKIT_CRUD_DELETE_ACTION_INPUT__: actionInputExpressions.delete,
|
|
1859
1868
|
__JSKIT_CRUD_LIST_ACTION_PERMISSION__: renderActionPermissionExpression("list", {
|
|
1860
1869
|
requiresNamedPermissions
|
|
1861
1870
|
}),
|
|
1862
|
-
__JSKIT_CRUD_LIST_ACTION_INPUT_VALIDATOR__: renderActionInputValidatorExpression("list", {
|
|
1863
|
-
surfaceRequiresWorkspace
|
|
1864
|
-
}),
|
|
1865
1871
|
__JSKIT_CRUD_VIEW_ACTION_PERMISSION__: renderActionPermissionExpression("view", {
|
|
1866
1872
|
requiresNamedPermissions
|
|
1867
1873
|
}),
|
|
1868
|
-
__JSKIT_CRUD_VIEW_ACTION_INPUT_VALIDATOR__: renderActionInputValidatorExpression("view", {
|
|
1869
|
-
surfaceRequiresWorkspace
|
|
1870
|
-
}),
|
|
1871
1874
|
__JSKIT_CRUD_CREATE_ACTION_PERMISSION__: renderActionPermissionExpression("create", {
|
|
1872
1875
|
requiresNamedPermissions
|
|
1873
1876
|
}),
|
|
1874
|
-
__JSKIT_CRUD_CREATE_ACTION_INPUT_VALIDATOR__: renderActionInputValidatorExpression("create", {
|
|
1875
|
-
surfaceRequiresWorkspace
|
|
1876
|
-
}),
|
|
1877
1877
|
__JSKIT_CRUD_UPDATE_ACTION_PERMISSION__: renderActionPermissionExpression("update", {
|
|
1878
1878
|
requiresNamedPermissions
|
|
1879
1879
|
}),
|
|
1880
|
-
__JSKIT_CRUD_UPDATE_ACTION_INPUT_VALIDATOR__: renderActionInputValidatorExpression("update", {
|
|
1881
|
-
surfaceRequiresWorkspace
|
|
1882
|
-
}),
|
|
1883
1880
|
__JSKIT_CRUD_DELETE_ACTION_PERMISSION__: renderActionPermissionExpression("delete", {
|
|
1884
1881
|
requiresNamedPermissions
|
|
1885
1882
|
}),
|
|
1886
|
-
__JSKIT_CRUD_DELETE_ACTION_INPUT_VALIDATOR__: renderActionInputValidatorExpression("delete", {
|
|
1887
|
-
surfaceRequiresWorkspace
|
|
1888
|
-
}),
|
|
1889
1883
|
__JSKIT_CRUD_ROLE_CATALOG_PERMISSION_GRANTS__: renderRoleCatalogPermissionGrants(namespace, {
|
|
1890
1884
|
requiresNamedPermissions
|
|
1891
1885
|
}),
|
|
@@ -1894,6 +1888,10 @@ function buildReplacementsFromSnapshot({
|
|
|
1894
1888
|
__JSKIT_CRUD_ROUTE_WORKSPACE_SUPPORT_IMPORTS__: renderRouteWorkspaceSupportImports({
|
|
1895
1889
|
surfaceRequiresWorkspace
|
|
1896
1890
|
}),
|
|
1891
|
+
__JSKIT_CRUD_ROUTE_CONTRACTS_RESOURCE_ARGS__: surfaceRequiresWorkspace ? ",\n routeParamsValidator" : "",
|
|
1892
|
+
__JSKIT_CRUD_ROUTE_VALIDATOR_CONSTANTS__: renderRouteValidatorConstants({
|
|
1893
|
+
surfaceRequiresWorkspace
|
|
1894
|
+
}),
|
|
1897
1895
|
__JSKIT_CRUD_LIST_ROUTE_PARAMS_VALIDATOR_LINE__: renderRouteParamsValidatorLine("list", {
|
|
1898
1896
|
surfaceRequiresWorkspace
|
|
1899
1897
|
}),
|
|
@@ -1924,45 +1922,15 @@ function buildReplacementsFromSnapshot({
|
|
|
1924
1922
|
__JSKIT_CRUD_DELETE_ROUTE_INPUT_LINES__: renderRouteInputLines("delete", {
|
|
1925
1923
|
surfaceRequiresWorkspace
|
|
1926
1924
|
}),
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
)
|
|
1937
|
-
}),
|
|
1938
|
-
__JSKIT_CRUD_RESOURCE_DATABASE_RUNTIME_IMPORT__: renderResourceDatabaseRuntimeImport({
|
|
1939
|
-
needsToIsoString: needsDateTimeOutput || needsDate,
|
|
1940
|
-
needsToDatabaseDateTimeUtc: needsDateTimeInput
|
|
1941
|
-
}),
|
|
1942
|
-
__JSKIT_CRUD_RESOURCE_NORMALIZE_SUPPORT_IMPORT__: renderResourceNormalizeSupportImport({
|
|
1943
|
-
needsNormalizeText,
|
|
1944
|
-
needsNormalizeBoolean,
|
|
1945
|
-
needsNormalizeFiniteNumber: needsFiniteNumber,
|
|
1946
|
-
needsNormalizeFiniteInteger: needsFiniteInteger,
|
|
1947
|
-
needsNormalizeRecordId: needsRecordIdSchemas,
|
|
1948
|
-
needsNormalizeIfInSource,
|
|
1949
|
-
needsNormalizeIfPresent,
|
|
1950
|
-
needsNormalizeOrNull
|
|
1951
|
-
}),
|
|
1952
|
-
__JSKIT_CRUD_RESOURCE_JSON_IMPORT__: renderResourceJsonImport({
|
|
1953
|
-
needsJson
|
|
1954
|
-
}),
|
|
1955
|
-
__JSKIT_CRUD_RESOURCE_OUTPUT_SCHEMA_PROPERTIES__: renderResourceSchemaPropertyLines(outputColumns, {
|
|
1956
|
-
forOutput: true
|
|
1957
|
-
}),
|
|
1958
|
-
__JSKIT_CRUD_RESOURCE_CREATE_SCHEMA_PROPERTIES__: renderResourceSchemaPropertyLines(writableColumns, {
|
|
1959
|
-
forOutput: false
|
|
1960
|
-
}),
|
|
1961
|
-
__JSKIT_CRUD_RESOURCE_INPUT_NORMALIZATION_LINES__: renderResourceInputNormalizationLines(writableColumns),
|
|
1962
|
-
__JSKIT_CRUD_RESOURCE_OUTPUT_NORMALIZATION_LINES__: renderResourceOutputNormalizationLines(outputColumns),
|
|
1963
|
-
__JSKIT_CRUD_RESOURCE_CREATE_REQUIRED_FIELDS__: JSON.stringify(createRequiredFieldKeys),
|
|
1964
|
-
__JSKIT_CRUD_RESOURCE_FIELD_META_PUSH_LINES__: renderResourceFieldMetaPushLines(fieldMetaEntries),
|
|
1965
|
-
__JSKIT_CRUD_LIST_CONFIG_LINES__: renderRepositoryListConfigLines(snapshot),
|
|
1925
|
+
__JSKIT_CRUD_RESOURCE_SCHEMA_PROPERTIES__: resourceSchemaPropertyLines,
|
|
1926
|
+
__JSKIT_CRUD_RESOURCE_SEARCH_SCHEMA_LINES__: resourceSearchSchemaLines,
|
|
1927
|
+
__JSKIT_CRUD_RESOURCE_DEFAULT_SORT__: resourceDefaultSortLiteral,
|
|
1928
|
+
__JSKIT_CRUD_RESOURCE_AUTOFILTER__: JSON.stringify(resolvedOwnershipFilter),
|
|
1929
|
+
__JSKIT_CRUD_JSONREST_SCOPE_NAME__: JSON.stringify(toCamelCase(namespace)),
|
|
1930
|
+
__JSKIT_CRUD_JSONREST_AUTOFILTER__: JSON.stringify(resolvedOwnershipFilter),
|
|
1931
|
+
__JSKIT_CRUD_JSONREST_SEARCH_SCHEMA_LINES__: jsonRestSearchSchemaLines,
|
|
1932
|
+
__JSKIT_CRUD_JSONREST_SCHEMA_PROPERTIES__: jsonRestSchemaPropertyLines,
|
|
1933
|
+
__JSKIT_CRUD_JSONREST_DEFAULT_SORT_LINE__: jsonRestDefaultSortLine,
|
|
1966
1934
|
__JSKIT_CRUD_MIGRATION_COLUMN_LINES__: renderMigrationColumnLines(snapshot),
|
|
1967
1935
|
__JSKIT_CRUD_MIGRATION_INDEX_LINES__: renderMigrationIndexLines(snapshot),
|
|
1968
1936
|
__JSKIT_CRUD_MIGRATION_FOREIGN_KEY_LINES__: renderMigrationForeignKeyLines(snapshot),
|
|
@@ -2076,13 +2044,10 @@ const __testables = Object.freeze({
|
|
|
2076
2044
|
renderMigrationCheckConstraintLines,
|
|
2077
2045
|
renderMigrationForeignKeyLine,
|
|
2078
2046
|
resolveScaffoldColumns,
|
|
2079
|
-
renderPropertyAccess,
|
|
2080
|
-
renderResourceFieldSchema,
|
|
2081
|
-
renderInputNormalizer,
|
|
2082
|
-
renderOutputNormalizerExpression,
|
|
2083
2047
|
resolveCrudGenerationTableName,
|
|
2084
2048
|
resolveGenerationSnapshot,
|
|
2085
|
-
|
|
2049
|
+
buildFieldContractEntries,
|
|
2050
|
+
renderCanonicalResourceFieldSchema,
|
|
2086
2051
|
resolveDefaultCrudSurfaceIdFromAppConfig,
|
|
2087
2052
|
resolveCrudGenerationSurfaceId,
|
|
2088
2053
|
resolveCrudSurfaceRequiresWorkspace,
|
|
@@ -2090,7 +2055,8 @@ const __testables = Object.freeze({
|
|
|
2090
2055
|
renderRoleCatalogPermissionGrants,
|
|
2091
2056
|
renderActionPermissionSupport,
|
|
2092
2057
|
renderActionPermissionExpression,
|
|
2093
|
-
|
|
2058
|
+
renderActionInputExpressions,
|
|
2059
|
+
renderRouteValidatorConstants,
|
|
2094
2060
|
renderRouteParamsValidatorLine,
|
|
2095
2061
|
renderRouteInputLines
|
|
2096
2062
|
});
|
|
@@ -2098,12 +2064,9 @@ const __testables = Object.freeze({
|
|
|
2098
2064
|
export {
|
|
2099
2065
|
buildTemplateContext,
|
|
2100
2066
|
resolveScaffoldColumns,
|
|
2101
|
-
renderPropertyAccess,
|
|
2102
2067
|
resolveGenerationSnapshot,
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
renderOutputNormalizerExpression,
|
|
2106
|
-
buildFieldMetaEntries,
|
|
2068
|
+
renderCanonicalResourceFieldSchema,
|
|
2069
|
+
buildFieldContractEntries,
|
|
2107
2070
|
resolveCrudGenerationSurfaceId,
|
|
2108
2071
|
__testables
|
|
2109
2072
|
};
|