@wavemaker-ai/react-codegen 1.0.0-rc.647684 → 12.0.0-rc.335
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/command.js +52 -7
- package/command.js.map +1 -1
- package/dist/transpiler/index.d.mts +42 -1
- package/dist/transpiler/index.mjs +682 -90
- package/dist/transpiler/index.mjs.map +1 -1
- package/dist/transpiler/wm-styles.css +10 -20
- package/index.js +7 -0
- package/index.js.map +1 -1
- package/package-lock.json +102 -102
- package/package.json +1 -1
- package/src/app.generator.js +223 -35
- package/src/app.generator.js.map +1 -1
- package/src/handlebar-helpers.js +32 -0
- package/src/handlebar-helpers.js.map +1 -1
- package/src/increment-builder.js +32 -2
- package/src/increment-builder.js.map +1 -1
- package/src/profiles/profile.js +2 -0
- package/src/profiles/profile.js.map +1 -1
- package/src/profiles/web-preview.profile.js +4 -0
- package/src/profiles/web-preview.profile.js.map +1 -1
- package/src/services/designtime-collector.js +235 -0
- package/src/services/designtime-collector.js.map +1 -0
- package/src/services/doc-normalizer.js +185 -0
- package/src/services/doc-normalizer.js.map +1 -0
- package/src/services/method-emitter.js +367 -0
- package/src/services/method-emitter.js.map +1 -0
- package/src/services/model-emitter.js +281 -0
- package/src/services/model-emitter.js.map +1 -0
- package/src/services/schema-to-ts.js +50 -0
- package/src/services/schema-to-ts.js.map +1 -0
- package/src/services/service.generator.js +223 -0
- package/src/services/service.generator.js.map +1 -0
- package/src/transpile/bind.ex.transformer.js +4 -1
- package/src/transpile/bind.ex.transformer.js.map +1 -1
- package/src/transpile/components/container/accordion.transformer.js +4 -4
- package/src/transpile/components/container/accordion.transformer.js.map +1 -1
- package/src/transpile/components/container/tabs.transformer.js +4 -2
- package/src/transpile/components/container/tabs.transformer.js.map +1 -1
- package/src/transpile/components/data/list/list-transformer.js +7 -2
- package/src/transpile/components/data/list/list-transformer.js.map +1 -1
- package/src/transpile/components/data/table/table.transformer.js +14 -20
- package/src/transpile/components/data/table/table.transformer.js.map +1 -1
- package/src/transpile/components/data/table/utils.js +90 -0
- package/src/transpile/components/data/table/utils.js.map +1 -1
- package/src/transpile/components/dialogs/dialog-actions.transformer.js +4 -6
- package/src/transpile/components/dialogs/dialog-actions.transformer.js.map +1 -1
- package/src/transpile/components/dialogs/dialog.transformer.js +14 -3
- package/src/transpile/components/dialogs/dialog.transformer.js.map +1 -1
- package/src/transpile/components/layout/layout-region-factory.js +8 -2
- package/src/transpile/components/layout/layout-region-factory.js.map +1 -1
- package/src/transpile/components/nav/nav.transformer.js +3 -5
- package/src/transpile/components/nav/nav.transformer.js.map +1 -1
- package/src/transpile/components/utils.js +11 -5
- package/src/transpile/components/utils.js.map +1 -1
- package/src/transpile/transpile.js +62 -21
- package/src/transpile/transpile.js.map +1 -1
- package/src/transpile/variables-template-source.js +2 -2
- package/src/transpile/variables-template-source.js.map +1 -1
- package/src/utils.browser.js +51 -6
- package/src/utils.browser.js.map +1 -1
- package/src/variables/variable.transformer.js +623 -12
- package/src/variables/variable.transformer.js.map +1 -1
- package/templates/project/app/client.layout.tsx +7 -7
- package/templates/project/app/components.css +10 -0
- package/templates/project/app/widgetInlineStylesOverride.css +0 -20
- package/templates/project/package.json +6 -5
- package/templates/project/services/base.service.ts +361 -0
- package/templates/service/service-class.hbs +19 -0
- package/templates/variables.template +67 -1
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const bind_ex_transformer_1 = __importDefault(require("../transpile/bind.ex.transformer"));
|
|
7
|
+
const schema_to_ts_1 = require("../services/schema-to-ts");
|
|
8
|
+
const doc_normalizer_1 = require("../services/doc-normalizer");
|
|
7
9
|
const lodash_1 = require("lodash");
|
|
8
10
|
function bindingValueIsStatic(value) {
|
|
9
11
|
const v = Array.isArray(value) ? value[0] : value;
|
|
@@ -40,6 +42,42 @@ function isStaticParamsVariable(variable) {
|
|
|
40
42
|
}
|
|
41
43
|
return filterExpressionsAreStatic(variable.filterExpressions);
|
|
42
44
|
}
|
|
45
|
+
// Converts plural entity name to singular: Countries→Country, Addresses→Address, Users→User
|
|
46
|
+
function simpleSingularize(plural) {
|
|
47
|
+
const lower = plural.toLowerCase();
|
|
48
|
+
if (lower.endsWith("ies"))
|
|
49
|
+
return plural.substring(0, plural.length - 3) + "y";
|
|
50
|
+
if (lower.endsWith("es"))
|
|
51
|
+
return plural.substring(0, plural.length - 2);
|
|
52
|
+
if (lower.endsWith("s"))
|
|
53
|
+
return plural.substring(0, plural.length - 1);
|
|
54
|
+
return plural;
|
|
55
|
+
}
|
|
56
|
+
// Converts singular entity name to plural following English morphology rules:
|
|
57
|
+
// Employee→Employees, Country→Countries, Address→Addresses, Church→Churches, Day→Days
|
|
58
|
+
function simplePluralize(singular) {
|
|
59
|
+
if (!singular)
|
|
60
|
+
return singular;
|
|
61
|
+
const last = singular[singular.length - 1].toLowerCase();
|
|
62
|
+
const prev = singular.length > 1 ? singular[singular.length - 2].toLowerCase() : null;
|
|
63
|
+
const vowels = "aeiouy";
|
|
64
|
+
if (last === "x" || last === "s") {
|
|
65
|
+
return singular + "es";
|
|
66
|
+
}
|
|
67
|
+
else if (last === "y") {
|
|
68
|
+
return prev !== null && vowels.includes(prev)
|
|
69
|
+
? singular + "s" // Day → Days
|
|
70
|
+
: singular.slice(0, -1) + "ies"; // City → Cities
|
|
71
|
+
}
|
|
72
|
+
else if (last === "h") {
|
|
73
|
+
return prev !== null && (prev === "c" || prev === "s")
|
|
74
|
+
? singular + "es" // Church→Churches, Dish→Dishes
|
|
75
|
+
: singular + "s";
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return singular + "s";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
43
81
|
function parseStaticJsonValue(value) {
|
|
44
82
|
const trimmedValue = value.trim();
|
|
45
83
|
if (!trimmedValue.startsWith("{") && !trimmedValue.startsWith("[")) {
|
|
@@ -551,9 +589,350 @@ function transformModelVariable(variable, scope) {
|
|
|
551
589
|
tv.isList = !!variable.isList;
|
|
552
590
|
return tv;
|
|
553
591
|
}
|
|
554
|
-
|
|
592
|
+
const capitalizeFirstLetter = (str) => {
|
|
593
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
594
|
+
};
|
|
595
|
+
/** Matches the singleton export name emitted by service.generator.ts for a given service class. */
|
|
596
|
+
const getServiceInstanceName = (className) => className.charAt(0).toLowerCase() + className.slice(1);
|
|
597
|
+
/**
|
|
598
|
+
* The name a generated `*.variables.ts` uses to refer to a service singleton.
|
|
599
|
+
*
|
|
600
|
+
* The singleton a service file exports is named after its own class (e.g. `departmentService` in
|
|
601
|
+
* `services/hrdb/DepartmentService.ts`) — the folder, not the name, says which backend service it
|
|
602
|
+
* belongs to. But one generated variables file can import singletons from SEVERAL services at
|
|
603
|
+
* once, and two services that share an entity name (an "Employee" in both hrdb and salesdb) would
|
|
604
|
+
* then export identically-named singletons. So the local name is qualified with the owning
|
|
605
|
+
* service id (`hrdbEmployeeService` / `salesdbEmployeeService`) and the import is aliased to it
|
|
606
|
+
* (see templates/variables.template) — keeping the generated file's own export clean while every
|
|
607
|
+
* import site stays unambiguous.
|
|
608
|
+
*/
|
|
609
|
+
function getServiceLocalName(serviceId, className) {
|
|
610
|
+
const instanceName = getServiceInstanceName(className);
|
|
611
|
+
if (!serviceId) {
|
|
612
|
+
return instanceName;
|
|
613
|
+
}
|
|
614
|
+
const prefix = (0, schema_to_ts_1.toPascalCase)(serviceId);
|
|
615
|
+
const core = prefix.endsWith("Service") ? prefix.slice(0, -"Service".length) : prefix;
|
|
616
|
+
// Already service-named (e.g. class SecurityService in service securityService) — the
|
|
617
|
+
// qualified form would just double the word.
|
|
618
|
+
if (className.startsWith(core)) {
|
|
619
|
+
return instanceName;
|
|
620
|
+
}
|
|
621
|
+
return getServiceInstanceName(`${core}${className}`);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Derives service class name from controller. Strip "Controller", separators, ensure PascalCase.
|
|
625
|
+
* Matches generated API files (apiNameSuffix=Service).
|
|
626
|
+
*/
|
|
627
|
+
function getServiceClassName(prefix) {
|
|
628
|
+
if (!prefix || typeof prefix !== "string")
|
|
629
|
+
return "Service";
|
|
630
|
+
// Strip separators including underscore — OpenAPI generator also removes underscores
|
|
631
|
+
// from tag names when computing class names (e.g. "Master_LOV" → "MasterLOV")
|
|
632
|
+
const base = prefix.replace(/[\s/\\\-_]+/g, "").trim();
|
|
633
|
+
if (!base)
|
|
634
|
+
return "Service";
|
|
635
|
+
let basePascal = capitalizeFirstLetter(base);
|
|
636
|
+
if (basePascal.endsWith("Controller")) {
|
|
637
|
+
basePascal = basePascal.slice(0, -"Controller".length);
|
|
638
|
+
}
|
|
639
|
+
if (!basePascal)
|
|
640
|
+
return "Service";
|
|
641
|
+
return basePascal.endsWith("Service") ? basePascal : basePascal + "Service";
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Some operationIds are free text with spaces or other separators
|
|
645
|
+
* (e.g. "update Product Offer category", "create/update bulk rules") instead
|
|
646
|
+
* of a valid identifier. camelCase them to match the generated API method name
|
|
647
|
+
* (e.g. "updateProductOfferCategory"). Mirrors toCamelCaseIdentifier in
|
|
648
|
+
* scripts/swagger-builder.js, which sanitizes the same values before codegen.
|
|
649
|
+
*/
|
|
650
|
+
function toCamelCaseIdentifier(name) {
|
|
651
|
+
if (typeof name !== "string" || !/[^a-zA-Z0-9]/.test(name))
|
|
652
|
+
return name;
|
|
653
|
+
const words = name.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
654
|
+
if (!words.length)
|
|
655
|
+
return name;
|
|
656
|
+
let result = words
|
|
657
|
+
.map((word, i) => (i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)))
|
|
658
|
+
.join("");
|
|
659
|
+
if (/^[0-9]/.test(result))
|
|
660
|
+
result = "_" + result;
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Mirrors doc-normalizer.ts's hasMultipleDistinctTags()-gated OpenAPIService behavior: a
|
|
665
|
+
* connector whose spec never tagged operations meaningfully (every operation shares one tag,
|
|
666
|
+
* often the generic placeholder "default") gets lumped into one class named after the service
|
|
667
|
+
* itself; a connector with real distinct per-entity tags (e.g. "Department"/"Employee") splits
|
|
668
|
+
* into one class per entity, same as any other service type. `variable.controller` already
|
|
669
|
+
* carries that same signal here — WM sets it to the literal "default" placeholder for the
|
|
670
|
+
* lumped case (mirroring the doc's own generic tag) and to the real entity name otherwise — so
|
|
671
|
+
* this can be decided without needing the underlying designtime doc at variable-transform time.
|
|
672
|
+
*/
|
|
673
|
+
function resolveServiceTag(serviceId, controller, serviceType) {
|
|
674
|
+
const isLumpedOpenApiService = serviceType === "OpenAPIService" && (!controller || controller.toLowerCase() === "default");
|
|
675
|
+
const base = isLumpedOpenApiService
|
|
676
|
+
? serviceId.endsWith("Service")
|
|
677
|
+
? serviceId.slice(0, -"Service".length)
|
|
678
|
+
: serviceId
|
|
679
|
+
: controller || serviceId;
|
|
680
|
+
// Real designtime tags (mirrored onto variable.controller) are often not valid identifiers
|
|
681
|
+
// as-is (e.g. "Manage - Category", "Query - Entity Lock" — spaces and hyphens straight from a
|
|
682
|
+
// human-authored Swagger tag). A bare capitalize-first-letter leaves those characters in
|
|
683
|
+
// place, producing an invalid JS/TS class/instance name for invokeHttp to call into. Must use
|
|
684
|
+
// the exact same toPascalCase() doc-normalizer.ts uses when it computes the SAME tag on the
|
|
685
|
+
// generation side, or the two computations disagree on the class name.
|
|
686
|
+
return base ? (0, schema_to_ts_1.toPascalCase)(base) : base;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Returns serviceFile and invokeFnName for both ServiceVariable and LiveVariable.
|
|
690
|
+
* - ServiceVariable: uses controller + operation (e.g. EmployeeService.countEmployees)
|
|
691
|
+
* - LiveVariable: uses type + operation mapping (read→filterDepartments, create→createDepartment, etc.)
|
|
692
|
+
*/
|
|
693
|
+
function formatServiceDetails(variable) {
|
|
694
|
+
if (variable.operationId) {
|
|
695
|
+
// Use variable.service as the authoritative service identifier so that service names
|
|
696
|
+
// containing underscores (e.g. "Master_LOV") are handled correctly.
|
|
697
|
+
// Fallback: split operationId on first "_" (legacy behaviour for variables without service field).
|
|
698
|
+
const serviceName = variable.service;
|
|
699
|
+
let prefix;
|
|
700
|
+
let invokeFnName;
|
|
701
|
+
if (serviceName) {
|
|
702
|
+
prefix = serviceName;
|
|
703
|
+
// Always rederive via the EXACT same deriveMethodName() doc-normalizer.ts uses on the
|
|
704
|
+
// generation side, with the same candidates (service id, and the operation's own
|
|
705
|
+
// controller/tag in both its raw "<Controller>Controller" and WM-cleaned forms) — this
|
|
706
|
+
// can never drift from what was actually generated, unlike trusting variable.operation
|
|
707
|
+
// (WM's own bare-action-name guess) or hand-rolling a narrower prefix-stripping rule here:
|
|
708
|
+
// - JavaService/SoapService operationIds carry a "<Controller>_" tag layer that must be
|
|
709
|
+
// fully stripped (e.g. "GetLoggedInUserController_getUser" -> "getUser").
|
|
710
|
+
// - OpenAPIService operationIds can carry BOTH a service-id AND controller/tag layer that
|
|
711
|
+
// must both be stripped (e.g. "hrdbSwagger_DepartmentController_getDepartment" ->
|
|
712
|
+
// "getDepartment").
|
|
713
|
+
// - RestService single-operation connectors bake the service's own id in as their tag —
|
|
714
|
+
// deriveMethodName() only collapses a DOUBLED prefix to one copy rather than stripping
|
|
715
|
+
// it away (e.g. "swagger_swagger_invoke" -> "swaggerInvoke"), so two different
|
|
716
|
+
// connectors don't collide on one generic action name; a genuinely single-prefixed one
|
|
717
|
+
// (e.g. "CreateContractSpecification_invoke") still strips fully, to "invoke".
|
|
718
|
+
const controllerRaw = variable.controller ? `${variable.controller}Controller` : undefined;
|
|
719
|
+
const prefixCandidates = [serviceName, variable.controller, controllerRaw].filter(Boolean);
|
|
720
|
+
invokeFnName = (0, doc_normalizer_1.deriveMethodName)(variable.operationId, prefixCandidates);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
const sepIdx = variable.operationId.indexOf("_");
|
|
724
|
+
prefix = sepIdx >= 0 ? variable.operationId.substring(0, sepIdx) : variable.operationId;
|
|
725
|
+
invokeFnName =
|
|
726
|
+
sepIdx >= 0 ? variable.operationId.substring(sepIdx + 1) : variable.operationId;
|
|
727
|
+
}
|
|
728
|
+
// Some operationIds are free text with spaces or other separators
|
|
729
|
+
// (e.g. "update Product Offer category", "create/update bulk rules"),
|
|
730
|
+
// which isn't a valid JS identifier as-is. The generated API method name
|
|
731
|
+
// camelCases these (e.g. "updateProductOfferCategory"), so do the same
|
|
732
|
+
// here to keep the invoked method name in sync.
|
|
733
|
+
invokeFnName = toCamelCaseIdentifier(invokeFnName);
|
|
734
|
+
// variable.service is the backend service id (e.g. "hrdb", "hrdbSwagger") — the same value
|
|
735
|
+
// service.generator.ts's collected.serviceName carries. Prefer the (service, controller)
|
|
736
|
+
// pair when available (reliable, WM-Studio-provided, and required so two different backend
|
|
737
|
+
// services sharing a controller/entity name don't collide on one class) — fall back to the
|
|
738
|
+
// legacy operationId-derived `prefix` only for older variables with no `service` field.
|
|
739
|
+
const formattedServiceName = variable.service
|
|
740
|
+
? (0, schema_to_ts_1.buildServiceClassName)(variable.service, resolveServiceTag(variable.service, variable.controller, variable.serviceType))
|
|
741
|
+
: getServiceClassName(prefix);
|
|
742
|
+
return {
|
|
743
|
+
formattedServiceName,
|
|
744
|
+
invokeFnName,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
// LiveVariable: has type and operation, maps to API function names
|
|
748
|
+
if (variable.type && variable.operation) {
|
|
749
|
+
const entity = variable.type;
|
|
750
|
+
// Normalize to singular first (entity may arrive as singular or plural from WaveMaker).
|
|
751
|
+
// Then re-pluralize for read so the method name is always the correct plural form.
|
|
752
|
+
// e.g. "Employee" → singular="Employee" → plural="Employees" → filterEmployees
|
|
753
|
+
// e.g. "Addresses" → singular="Address" → plural="Addresses" → filterAddresses
|
|
754
|
+
const singular = simpleSingularize(entity);
|
|
755
|
+
const plural = simplePluralize(singular);
|
|
756
|
+
const operationMap = {
|
|
757
|
+
read: `filter${plural}`,
|
|
758
|
+
insert: `create${singular}`,
|
|
759
|
+
update: `edit${singular}`,
|
|
760
|
+
delete: `delete${singular}`,
|
|
761
|
+
};
|
|
762
|
+
const invokeFnName = operationMap[variable.operation];
|
|
763
|
+
if (!invokeFnName)
|
|
764
|
+
return null;
|
|
765
|
+
// variable.liveSource is the owning DataService's id (e.g. "hrdb", "salesdb") — same
|
|
766
|
+
// rationale as ServiceVariable above: prefix so two DataServices sharing an entity name
|
|
767
|
+
// (e.g. both have "Employee") don't collide on one generated class.
|
|
768
|
+
const formattedServiceName = variable.liveSource
|
|
769
|
+
? (0, schema_to_ts_1.buildServiceClassName)(variable.liveSource, capitalizeFirstLetter(entity))
|
|
770
|
+
: capitalizeFirstLetter(entity + "Service");
|
|
771
|
+
return {
|
|
772
|
+
formattedServiceName,
|
|
773
|
+
invokeFnName,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* A variable's backing service is only real if it was actually generated into
|
|
780
|
+
* services/apis/*.ts. validServiceClassNames is undefined when that hasn't been
|
|
781
|
+
* checked (e.g. direct unit tests) — in that case, skip validation entirely.
|
|
782
|
+
*/
|
|
783
|
+
function isServiceAvailable(className, validServiceClassNames) {
|
|
784
|
+
return !validServiceClassNames || !className || validServiceClassNames.has(className);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* The key an argument's value actually arrives under in the params object the runtime hands
|
|
788
|
+
* `invokeHttp`.
|
|
789
|
+
*
|
|
790
|
+
* For a ServiceVariable that's the swagger parameter's own name — `inputFields` is keyed by
|
|
791
|
+
* exactly those. A LiveVariable never populates `inputFields` with its DB operation's fields:
|
|
792
|
+
* LiveVariableManager builds its own `dbOperationOptions` and that object is what reaches
|
|
793
|
+
* `invokeHttp`, keyed by the runtime's OWN names (see VARIABLE_URLS.DATABASE in
|
|
794
|
+
* @wavemaker-ai/variables' variables.constants.ts and getEntityData()'s dbOperationOptions):
|
|
795
|
+
* - `page` / `size` / `sort` — same names, handled by the pageable branch above.
|
|
796
|
+
* - `data` — the request payload. For a filter/search read this is the query fragment the
|
|
797
|
+
* manager pre-builds as `"q=<query>"` (posted form-urlencoded, matching the backend's
|
|
798
|
+
* `consumes = "application/x-www-form-urlencoded"`); for insert/update it's the entity.
|
|
799
|
+
* Either way it is NEVER keyed by the swagger name (`q`, or the entity name for a body), so
|
|
800
|
+
* reading the swagger name here would silently always yield `undefined` — dropping every
|
|
801
|
+
* filter on a LiveVariable read.
|
|
802
|
+
* - `id` — the primary key for update/delete by id.
|
|
803
|
+
*/
|
|
804
|
+
/**
|
|
805
|
+
* The expression the invokeHttp glue uses to source one positional argument from the runtime's
|
|
806
|
+
* flat `params` object. `page`/`size`/`sort` collapse into one `Pageable` literal; everything else
|
|
807
|
+
* reads its key. Values pass through as they arrive — base.service.ts's serializers strip any
|
|
808
|
+
* `<name>=` prefix @wavemaker-ai/variables baked in, since they already hold the wire name.
|
|
809
|
+
*/
|
|
810
|
+
function callArgExpression(arg, isLiveVariable, isBodyTypeQueryOrProcedure = false) {
|
|
811
|
+
// A query/procedure variable (controller QueryExecution|ProcedureExecution + post|put) does not
|
|
812
|
+
// put its body under the swagger param's name: ServiceVariableUtils calls processRequestBody(),
|
|
813
|
+
// which assembles the body from that param's CHILD fields — so inputFields is FLAT
|
|
814
|
+
// ({USERNAME, PASSWORD, ...}) with no `CreateUserRequest` key. Reading the swagger name there
|
|
815
|
+
// yields undefined and the request goes out with no body.
|
|
816
|
+
if (isBodyTypeQueryOrProcedure && arg.source.kind === "body") {
|
|
817
|
+
return "params";
|
|
818
|
+
}
|
|
819
|
+
if (arg.source.kind === "pageable") {
|
|
820
|
+
const props = arg.source.paramNames
|
|
821
|
+
.map(wireName => {
|
|
822
|
+
const prop = wireName === "sort" ? "sort" : wireName === "size" ? "pageSize" : "pageNumber";
|
|
823
|
+
return `${prop}: params?.[${JSON.stringify(wireName)}]`;
|
|
824
|
+
})
|
|
825
|
+
.join(", ");
|
|
826
|
+
return `{ ${props} }`;
|
|
827
|
+
}
|
|
828
|
+
return `params?.[${JSON.stringify(liveVariableParamKey(arg, isLiveVariable))}]`;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Sets the fields templates/variables.template's `invokeHttp` glue needs to call a generated
|
|
832
|
+
* service method whose signature mirrors the backend operation's own declared parameters (see
|
|
833
|
+
* src/services/method-emitter.ts): one positional argument per parameter, in declared order.
|
|
834
|
+
*
|
|
835
|
+
* `tv.callArgs` is a list of ready-to-emit JS expressions, each sourcing one argument from the
|
|
836
|
+
* flat params object the WM runtime hands `invokeHttp` — including reconstructing the single
|
|
837
|
+
* `pageable` object the backend binds page/size/sort into. `tv.serviceImportPath` is where the
|
|
838
|
+
* owning singleton lives, now that each service has its own folder and a class name alone no
|
|
839
|
+
* longer determines its path.
|
|
840
|
+
*
|
|
841
|
+
* Both come from the tables the generator itself wrote while emitting these exact methods, so a
|
|
842
|
+
* call site can never name an argument the generated method doesn't have. When the lookup isn't
|
|
843
|
+
* available (service generation didn't run — e.g. direct unit tests) `callArgs` stays undefined
|
|
844
|
+
* and the template falls back to passing the params object straight through.
|
|
845
|
+
*/
|
|
846
|
+
function applyServiceCallShape(tv, lookupKey, methodArgsByOperation, servicePathByOperation, isLiveVariable = false, isBodyTypeQueryOrProcedure = false) {
|
|
847
|
+
var _a;
|
|
848
|
+
const importPath = servicePathByOperation === null || servicePathByOperation === void 0 ? void 0 : servicePathByOperation.get(lookupKey);
|
|
849
|
+
if (importPath) {
|
|
850
|
+
tv.serviceImportPath = importPath;
|
|
851
|
+
// The path comes from the generator itself, so it is authoritative about WHICH class owns this
|
|
852
|
+
// operation — more so than the variable's own `controller`, which can be stale (a real case:
|
|
853
|
+
// a variable claims `controller: "Manage - Category"` for an operation the designtime doc
|
|
854
|
+
// actually tags "Manage - Product Offer Group Category"). Re-derive the class and its exported
|
|
855
|
+
// singleton from the resolved path so the import names always match the file being imported;
|
|
856
|
+
// otherwise we emit `import { manageCategoryService } from '.../ManageProductOfferGroupCategoryService'`
|
|
857
|
+
// — a module that has no such export.
|
|
858
|
+
const resolvedClassName = importPath.split("/").pop();
|
|
859
|
+
if (resolvedClassName && resolvedClassName !== tv.serviceFileClass) {
|
|
860
|
+
tv.serviceFile = resolvedClassName;
|
|
861
|
+
tv.serviceFileClass = resolvedClassName;
|
|
862
|
+
tv.serviceExportName = getServiceInstanceName(resolvedClassName);
|
|
863
|
+
tv.serviceInstanceName = getServiceLocalName((_a = tv.service) !== null && _a !== void 0 ? _a : tv.liveSource, resolvedClassName);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const args = methodArgsByOperation === null || methodArgsByOperation === void 0 ? void 0 : methodArgsByOperation.get(lookupKey);
|
|
867
|
+
if (!args) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
// Handlebars treats an EMPTY array as falsy, so `{{#if callArgs}}` cannot distinguish "this
|
|
871
|
+
// operation takes no arguments" from "no lookup was available". A zero-arg method (e.g.
|
|
872
|
+
// `sample(ctx?)`) then fell through to the `(params, ctx)` fallback and was called with two
|
|
873
|
+
// arguments — a compile error. This flag records that the args ARE known, whatever their count.
|
|
874
|
+
tv.hasResolvedCallArgs = true;
|
|
875
|
+
// page/size/sort arrive under those exact names for BOTH variable kinds — LiveVariable's
|
|
876
|
+
// manager builds them into its dbOperationOptions, and a ServiceVariable's inputFields keys
|
|
877
|
+
// them by the swagger param names, which are also page/size/sort.
|
|
878
|
+
tv.callArgs = args.map(arg => callArgExpression(arg, isLiveVariable, isBodyTypeQueryOrProcedure));
|
|
879
|
+
}
|
|
880
|
+
function liveVariableParamKey(arg, isLiveVariable) {
|
|
881
|
+
const swaggerName = arg.source.paramName;
|
|
882
|
+
if (!isLiveVariable) {
|
|
883
|
+
return swaggerName;
|
|
884
|
+
}
|
|
885
|
+
switch (arg.source.kind) {
|
|
886
|
+
case "query":
|
|
887
|
+
// The only query param a LiveVariable operation declares besides the pageable trio is the
|
|
888
|
+
// filter query (`q`), which the manager delivers in `data`, already "q="-prefixed and
|
|
889
|
+
// unwrapped back to a bare value by the runtime's normalizeInvokeHttpParams().
|
|
890
|
+
return swaggerName === "q" ? "data" : swaggerName;
|
|
891
|
+
case "body":
|
|
892
|
+
case "formData":
|
|
893
|
+
return "data";
|
|
894
|
+
case "path":
|
|
895
|
+
// A LiveVariable's by-id operations receive the key as `id`, whatever the entity calls its
|
|
896
|
+
// primary key column (deptId, empId, ...).
|
|
897
|
+
return "id";
|
|
898
|
+
default:
|
|
899
|
+
return swaggerName;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function transformServiceVariable(variable, scope, validServiceClassNames, methodArgsByOperation, methodNameByOperation, servicePathByOperation) {
|
|
903
|
+
var _a;
|
|
904
|
+
// A variable whose service was never generated (no swagger definition, service removed, ...)
|
|
905
|
+
// is still emitted — just without an invokeHttp. useHttp's send() falls back to its own axios
|
|
906
|
+
// instance when `variable.invokeHttp == undefined`, so the call still goes out the pre-codegen
|
|
907
|
+
// way instead of the variable disappearing from the page.
|
|
908
|
+
const serviceDetails = formatServiceDetails(variable);
|
|
909
|
+
let skipInvokeHttp = false;
|
|
910
|
+
if (!isServiceAvailable(serviceDetails === null || serviceDetails === void 0 ? void 0 : serviceDetails.formattedServiceName, validServiceClassNames)) {
|
|
911
|
+
console.warn(`ServiceVariable "${variable.name}" references service "${variable.service}" ` +
|
|
912
|
+
`(${serviceDetails === null || serviceDetails === void 0 ? void 0 : serviceDetails.formattedServiceName}), which was not generated. Emitting the ` +
|
|
913
|
+
`variable without an invokeHttp — the runtime falls back to its own transport.`);
|
|
914
|
+
skipInvokeHttp = true;
|
|
915
|
+
}
|
|
916
|
+
// The service CLASS existing isn't enough — the specific OPERATION must exist too. A project's
|
|
917
|
+
// variable JSON can name an operationId the designtime doc no longer exposes (a real case: a
|
|
918
|
+
// variable kept an operationId for an endpoint since removed from the backend, while its
|
|
919
|
+
// service and controller are still perfectly valid). Generating for it produced a call to a
|
|
920
|
+
// method that doesn't exist AND, because the per-operation lookup missed, an import path with
|
|
921
|
+
// the service folder blank (`@/services//XService`) — a build break rather than a clear signal.
|
|
922
|
+
// When the lookup tables are available (they aren't in direct unit tests) and this operation
|
|
923
|
+
// isn't in them, treat it exactly like a missing service and skip the variable.
|
|
924
|
+
const operationKey = `${variable.service}::${variable.operationId}`;
|
|
925
|
+
if (servicePathByOperation && !servicePathByOperation.has(operationKey)) {
|
|
926
|
+
console.warn(`ServiceVariable "${variable.name}" references operation "${variable.operationId}" on ` +
|
|
927
|
+
`service "${variable.service}", which has no such operation. Emitting the variable ` +
|
|
928
|
+
`without an invokeHttp — the runtime falls back to its own transport.`);
|
|
929
|
+
skipInvokeHttp = true;
|
|
930
|
+
}
|
|
555
931
|
const tv = transformVariable(variable, scope);
|
|
556
932
|
tv.classname = "ServiceVariable";
|
|
933
|
+
// Gates the service import AND the invokeHttp block in the template: with no
|
|
934
|
+
// generated method to call, emitting either would not compile.
|
|
935
|
+
tv.hasInvokeHttp = !skipInvokeHttp;
|
|
557
936
|
tv.operationId = variable.operationId;
|
|
558
937
|
tv.isList = !!variable.isList;
|
|
559
938
|
tv.inFlightBehavior = variable.inFlightBehavior;
|
|
@@ -567,6 +946,30 @@ function transformServiceVariable(variable, scope) {
|
|
|
567
946
|
tv.autoUpdate = variable.autoUpdate;
|
|
568
947
|
tv.orderBy = variable.orderBy;
|
|
569
948
|
tv.isStaticParams = isStaticParamsVariable(variable);
|
|
949
|
+
tv.serviceFile = serviceDetails === null || serviceDetails === void 0 ? void 0 : serviceDetails.formattedServiceName;
|
|
950
|
+
tv.serviceFileClass = serviceDetails === null || serviceDetails === void 0 ? void 0 : serviceDetails.formattedServiceName;
|
|
951
|
+
// `serviceInstanceName` is the name used at the call site (and aliased to on import);
|
|
952
|
+
// `serviceExportName` is what the service file actually exports.
|
|
953
|
+
tv.serviceExportName =
|
|
954
|
+
serviceDetails && getServiceInstanceName(serviceDetails.formattedServiceName);
|
|
955
|
+
tv.serviceInstanceName =
|
|
956
|
+
serviceDetails && getServiceLocalName(variable.service, serviceDetails.formattedServiceName);
|
|
957
|
+
// Prefer the actual generated method name (looked up by the exact same key
|
|
958
|
+
// service.generator.ts built it with) over re-deriving it from variable.operationId via
|
|
959
|
+
// formatServiceDetails/deriveMethodName: WM doesn't always record a variable's operationId as
|
|
960
|
+
// the designtime doc's raw operationId verbatim (see NormalizedOperation.operationIdAliases),
|
|
961
|
+
// so blindly re-running the same stripping rules on a shorter/differently-prefixed alias can
|
|
962
|
+
// land on a name the generated class doesn't actually have (e.g. a doc-side "doubled prefix"
|
|
963
|
+
// operationId that collapses to "fooBar" on the generation side, but whose variable-side alias
|
|
964
|
+
// has only one prefix layer and over-strips down to bare "bar"). Falls back to the re-derived
|
|
965
|
+
// name only when the lookup can't help (service generation didn't run, e.g. direct unit tests).
|
|
966
|
+
const lookupKey = `${variable.service}::${variable.operationId}`;
|
|
967
|
+
tv.invokeFnName = (_a = methodNameByOperation === null || methodNameByOperation === void 0 ? void 0 : methodNameByOperation.get(lookupKey)) !== null && _a !== void 0 ? _a : serviceDetails === null || serviceDetails === void 0 ? void 0 : serviceDetails.invokeFnName;
|
|
968
|
+
// Mirrors ServiceVariableUtils' isBodyTypeQueryOrProcedure: a query/procedure variable's body
|
|
969
|
+
// is assembled from the body param's child fields, so its params object is flat.
|
|
970
|
+
const isBodyTypeQueryOrProcedure = ["QueryExecution", "ProcedureExecution"].includes(variable.controller) &&
|
|
971
|
+
["put", "post"].includes(variable.operationType);
|
|
972
|
+
applyServiceCallShape(tv, lookupKey, methodArgsByOperation, servicePathByOperation, false, isBodyTypeQueryOrProcedure);
|
|
570
973
|
return tv;
|
|
571
974
|
}
|
|
572
975
|
function transformNavigationAction(variable, scope) {
|
|
@@ -596,11 +999,13 @@ function transformNotificationAction(variable, scope, imports) {
|
|
|
596
999
|
tv.classname = "NotificationAction";
|
|
597
1000
|
tv.group = "action";
|
|
598
1001
|
tv.operation = variable.operation;
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
1002
|
+
const bindings = Array.isArray(variable.dataBinding) ? variable.dataBinding : [];
|
|
1003
|
+
const getBinding = (target) => bindings.find((b) => b.target === target);
|
|
1004
|
+
const contentBinding = getBinding("content");
|
|
1005
|
+
const rendersPartial = contentBinding
|
|
1006
|
+
? contentBinding.value === "page"
|
|
1007
|
+
: variable.operation !== "toast";
|
|
1008
|
+
const partialContent = rendersPartial ? getBinding("page") : null;
|
|
604
1009
|
if (variable.onOk || variable.onClick) {
|
|
605
1010
|
tv.onOk = appendIfNotEmpty((0, bind_ex_transformer_1.default)(variable.onOk || variable.onClick, scope, "event"), ";");
|
|
606
1011
|
}
|
|
@@ -658,9 +1063,91 @@ function transformLogoutVariable(variable, scope) {
|
|
|
658
1063
|
tv.redirectTo = variable.redirectTo;
|
|
659
1064
|
return tv;
|
|
660
1065
|
}
|
|
661
|
-
|
|
1066
|
+
/**
|
|
1067
|
+
* The `${serviceName}::${operationIdAlias}` key of the operation whose GENERATED method is named
|
|
1068
|
+
* `methodName` on service `serviceName`. LiveVariables identify their operation by that derived
|
|
1069
|
+
* method name rather than by a doc operationId (they have none), so this reverses the
|
|
1070
|
+
* methodNameByOperation table to reach the other per-operation lookups keyed the same way.
|
|
1071
|
+
*/
|
|
1072
|
+
function findOperationKeyByMethodName(serviceName, methodName, methodNameByOperation) {
|
|
1073
|
+
if (!serviceName || !methodName || !methodNameByOperation) {
|
|
1074
|
+
return undefined;
|
|
1075
|
+
}
|
|
1076
|
+
const prefix = `${serviceName}::`;
|
|
1077
|
+
for (const [key, name] of methodNameByOperation) {
|
|
1078
|
+
if (name === methodName && key.startsWith(prefix)) {
|
|
1079
|
+
return key;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return undefined;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Fallback for when findOperationKeyByMethodName's guessed name (built via
|
|
1086
|
+
* simpleSingularize/simplePluralize) doesn't match anything real: those are an English-stemming
|
|
1087
|
+
* HEURISTIC, and WM entity names aren't always actual English plurals to begin with — an entity
|
|
1088
|
+
* literally named "AllTypes" has no distinct singular in WM's own generated methods at all
|
|
1089
|
+
* (`findAllTypes`, `createAllTypes`, `filterAllTypes`, `deleteAllTypes` all keep "AllTypes"
|
|
1090
|
+
* unchanged), so the stemmer's round-trip ("AllTypes" -> "AllTyp" -> "AllTyps") produces a name
|
|
1091
|
+
* nothing was ever generated as, and the reverse lookup silently misses.
|
|
1092
|
+
*
|
|
1093
|
+
* WM's own raw operationId for a Data Service entity operation is always
|
|
1094
|
+
* `<entity>Controller_<verb><NameVariant>` — the verb always comes right after the tag,
|
|
1095
|
+
* regardless of what spelling variant WM chose for NameVariant. Searching by that verb prefix
|
|
1096
|
+
* against the RAW (un-derived) key finds the real operation without having to guess the spelling
|
|
1097
|
+
* variant at all. Among ties (e.g. an association method like `findAssociatedEmployees` also
|
|
1098
|
+
* starting with "find"), the shortest key wins — the plain CRUD operation, not one with an extra
|
|
1099
|
+
* qualifier tacked on.
|
|
1100
|
+
*/
|
|
1101
|
+
function findLiveOperationKeyByVerb(liveSource, entity, verbTokens, methodNameByOperation) {
|
|
1102
|
+
if (!liveSource || !entity || !methodNameByOperation) {
|
|
1103
|
+
return undefined;
|
|
1104
|
+
}
|
|
1105
|
+
const tagPrefix = `${liveSource}::${entity}Controller_`;
|
|
1106
|
+
let best;
|
|
1107
|
+
for (const key of methodNameByOperation.keys()) {
|
|
1108
|
+
if (!key.startsWith(tagPrefix))
|
|
1109
|
+
continue;
|
|
1110
|
+
const verbAndName = key.slice(tagPrefix.length);
|
|
1111
|
+
if (!verbTokens.some(verb => verbAndName.startsWith(verb)))
|
|
1112
|
+
continue;
|
|
1113
|
+
if (!best || key.length < best.length) {
|
|
1114
|
+
best = key;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return best;
|
|
1118
|
+
}
|
|
1119
|
+
/** WM's own verb token for each DataService action — the literal prefix its raw operationId's tail (after `<Entity>Controller_`) always starts with, independent of the entity's own singular/plural spelling. */
|
|
1120
|
+
const LIVE_OPERATION_VERBS = {
|
|
1121
|
+
readTableData: ["find"],
|
|
1122
|
+
searchTableDataWithQuery: ["filter"],
|
|
1123
|
+
searchTableData: ["search"],
|
|
1124
|
+
insertTableData: ["create"],
|
|
1125
|
+
updateTableData: ["edit"],
|
|
1126
|
+
deleteTableData: ["delete"],
|
|
1127
|
+
};
|
|
1128
|
+
/** Which DataService action formatServiceDetails' operationMap maps each `variable.operation` value to — mirrors that map's own verb choice (read -> filter/searchTableDataWithQuery, not find/readTableData) so the fallback resolves the SAME operation the guessed name was aiming for. */
|
|
1129
|
+
const LIVE_DEFAULT_ACTION_BY_OPERATION = {
|
|
1130
|
+
read: "searchTableDataWithQuery",
|
|
1131
|
+
insert: "insertTableData",
|
|
1132
|
+
update: "updateTableData",
|
|
1133
|
+
delete: "deleteTableData",
|
|
1134
|
+
};
|
|
1135
|
+
function transformLiveVariable(variable, scope, validServiceClassNames, methodArgsByOperation, methodNameByOperation, servicePathByOperation) {
|
|
1136
|
+
var _a, _b;
|
|
1137
|
+
const serviceDetails = formatServiceDetails(variable);
|
|
1138
|
+
let skipInvokeHttp = false;
|
|
1139
|
+
if (serviceDetails &&
|
|
1140
|
+
!isServiceAvailable(serviceDetails.formattedServiceName, validServiceClassNames)) {
|
|
1141
|
+
console.warn(`LiveVariable "${variable.name}" targets entity "${variable.type}" ` +
|
|
1142
|
+
`(${serviceDetails.formattedServiceName}), which was not generated. Emitting the ` +
|
|
1143
|
+
`variable without an invokeHttp — the runtime falls back to its own transport.`);
|
|
1144
|
+
skipInvokeHttp = true;
|
|
1145
|
+
}
|
|
662
1146
|
const tv = transformVariable(variable, scope);
|
|
663
1147
|
tv.classname = "LiveVariable";
|
|
1148
|
+
// Gates the service import AND the invokeHttp block in the template: with no
|
|
1149
|
+
// generated method to call, emitting either would not compile.
|
|
1150
|
+
tv.hasInvokeHttp = !skipInvokeHttp;
|
|
664
1151
|
tv.isList = !!variable.isList;
|
|
665
1152
|
tv.inFlightBehavior = variable.inFlightBehavior;
|
|
666
1153
|
tv.maxResults = variable.maxResults;
|
|
@@ -677,9 +1164,132 @@ function transformLiveVariable(variable, scope) {
|
|
|
677
1164
|
tv.filterExpressions = variable.filterExpressions;
|
|
678
1165
|
tv._id = variable._id;
|
|
679
1166
|
tv.isStaticParams = isStaticParamsVariable(variable);
|
|
1167
|
+
if (serviceDetails) {
|
|
1168
|
+
tv.serviceFile = serviceDetails.formattedServiceName;
|
|
1169
|
+
tv.serviceFileClass = serviceDetails.formattedServiceName;
|
|
1170
|
+
tv.serviceExportName = getServiceInstanceName(serviceDetails.formattedServiceName);
|
|
1171
|
+
tv.serviceInstanceName = getServiceLocalName(variable.liveSource, serviceDetails.formattedServiceName);
|
|
1172
|
+
tv.invokeFnName = serviceDetails.invokeFnName;
|
|
1173
|
+
// A LiveVariable has no raw `operationId` to key the sidecar lookups with — its method name
|
|
1174
|
+
// is derived from its operation instead (read -> filterDepartments, insert ->
|
|
1175
|
+
// createDepartment, ...). Resolve the same tables by that derived METHOD NAME, which the
|
|
1176
|
+
// generator also indexes (see findOperationKeyByMethodName), so a LiveVariable's generated
|
|
1177
|
+
// call gets the same correct positional arguments a ServiceVariable's does.
|
|
1178
|
+
let lookupKey = findOperationKeyByMethodName(variable.liveSource, serviceDetails.invokeFnName, methodNameByOperation);
|
|
1179
|
+
// The guessed name above is an English-stemming heuristic and can miss for entity names that
|
|
1180
|
+
// aren't real English plurals (see findLiveOperationKeyByVerb) — fall back to a verb-prefix
|
|
1181
|
+
// search against the raw operationId, which needs no such guess.
|
|
1182
|
+
if (!lookupKey) {
|
|
1183
|
+
lookupKey = findLiveOperationKeyByVerb(variable.liveSource, variable.type, (_a = LIVE_OPERATION_VERBS[LIVE_DEFAULT_ACTION_BY_OPERATION[variable.operation]]) !== null && _a !== void 0 ? _a : [], methodNameByOperation);
|
|
1184
|
+
}
|
|
1185
|
+
if (lookupKey) {
|
|
1186
|
+
// The fallback resolves the operation independently of the guessed name, so the guess
|
|
1187
|
+
// itself may still be wrong — realign tv.invokeFnName to the actual generated method name
|
|
1188
|
+
// rather than leaving the mismatched guess in place (this is the value applyServiceCallShape
|
|
1189
|
+
// uses at the top level when liveTargets ends up empty and the template falls back to it).
|
|
1190
|
+
tv.invokeFnName = (_b = methodNameByOperation === null || methodNameByOperation === void 0 ? void 0 : methodNameByOperation.get(lookupKey)) !== null && _b !== void 0 ? _b : tv.invokeFnName;
|
|
1191
|
+
applyServiceCallShape(tv, lookupKey, methodArgsByOperation, servicePathByOperation, true // LiveVariable — its params object is keyed by the runtime's own DB-operation names
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
// `variable.operation` is only the DEFAULT operation — the one `.invoke()` runs. Every
|
|
1195
|
+
// LiveVariable carries all four record methods (see live-variable.ts's DataSource.Operation
|
|
1196
|
+
// switch), and a table bound to it as `datasource` calls insert/update/delete on THAT
|
|
1197
|
+
// variable, not on some other one. Emitting only the default meant a grid edit on a
|
|
1198
|
+
// `read` variable ran filterX with the row data. So resolve all four and let the glue
|
|
1199
|
+
// dispatch on the DB operation the manager passes in `params.operation`.
|
|
1200
|
+
tv.liveTargets = buildLiveTargets(variable, methodArgsByOperation, methodNameByOperation, servicePathByOperation);
|
|
1201
|
+
}
|
|
680
1202
|
return tv;
|
|
681
1203
|
}
|
|
682
|
-
|
|
1204
|
+
/**
|
|
1205
|
+
* A LiveVariable's invokeHttp targets, one per DB operation the runtime can ask it for.
|
|
1206
|
+
*
|
|
1207
|
+
* Keyed by the manager's own action names (`params.operation`, set in
|
|
1208
|
+
* live-variable.manager.ts). Only the operations whose method actually generated are included, so
|
|
1209
|
+
* a service missing (say) a delete endpoint simply has no `delete` case rather than a call to a
|
|
1210
|
+
* method that does not exist.
|
|
1211
|
+
*/
|
|
1212
|
+
function buildLiveTargets(variable, methodArgsByOperation, methodNameByOperation, servicePathByOperation) {
|
|
1213
|
+
var _a, _b, _c;
|
|
1214
|
+
const entity = variable.type;
|
|
1215
|
+
if (!entity) {
|
|
1216
|
+
return undefined;
|
|
1217
|
+
}
|
|
1218
|
+
const singular = simpleSingularize(entity);
|
|
1219
|
+
const plural = simplePluralize(singular);
|
|
1220
|
+
// The manager's action -> the generated method for it. Each action is a DISTINCT backend
|
|
1221
|
+
// endpoint with its own body shape, so they cannot share one method:
|
|
1222
|
+
// readTableData / searchTableDataWithQuery -> POST /<entity>/filter, body `q=<query string>`
|
|
1223
|
+
// searchTableData -> POST /<entity>/search, body a QueryFilter[] ARRAY
|
|
1224
|
+
// Collapsing any two of these sent the wrong body to the wrong route and silently matched
|
|
1225
|
+
// nothing.
|
|
1226
|
+
const byAction = [
|
|
1227
|
+
// readTableData's template is `GET /<entity>?page=&size=&:sort` — the collection GET, which
|
|
1228
|
+
// the generator names find<Plural>. filter<Plural> is the POST /filter twin and belongs only
|
|
1229
|
+
// to the query-bearing actions.
|
|
1230
|
+
["readTableData", `find${plural}`],
|
|
1231
|
+
["searchTableDataWithQuery", `filter${plural}`],
|
|
1232
|
+
["searchTableData", `search${plural}ByQueryFilters`],
|
|
1233
|
+
["insertTableData", `create${singular}`],
|
|
1234
|
+
["updateTableData", `edit${singular}`],
|
|
1235
|
+
["deleteTableData", `delete${singular}`],
|
|
1236
|
+
];
|
|
1237
|
+
// All four methods for one entity are emitted onto ONE class (the entity's own service), and a
|
|
1238
|
+
// LiveVariable is pinned to a single liveSource — so every target shares the variable's own
|
|
1239
|
+
// import and instance. Only the method name and its argument list differ per action.
|
|
1240
|
+
const targets = [];
|
|
1241
|
+
for (const [action, guessedMethodName] of byAction) {
|
|
1242
|
+
let lookupKey = findOperationKeyByMethodName(variable.liveSource, guessedMethodName, methodNameByOperation);
|
|
1243
|
+
// Same English-stemming-guess-can-miss situation as transformLiveVariable's default target
|
|
1244
|
+
// above — fall back to a verb-prefix search that needs no singular/plural guess at all.
|
|
1245
|
+
if (!lookupKey) {
|
|
1246
|
+
lookupKey = findLiveOperationKeyByVerb(variable.liveSource, entity, (_a = LIVE_OPERATION_VERBS[action]) !== null && _a !== void 0 ? _a : [], methodNameByOperation);
|
|
1247
|
+
}
|
|
1248
|
+
if (!lookupKey || (servicePathByOperation && !servicePathByOperation.has(lookupKey))) {
|
|
1249
|
+
continue; // that operation was not generated for this entity
|
|
1250
|
+
}
|
|
1251
|
+
// The fallback resolves independently of the guess, so use the actual generated method name
|
|
1252
|
+
// (methodNameByOperation's own value for this key) rather than the possibly-wrong guess.
|
|
1253
|
+
const methodName = (_b = methodNameByOperation === null || methodNameByOperation === void 0 ? void 0 : methodNameByOperation.get(lookupKey)) !== null && _b !== void 0 ? _b : guessedMethodName;
|
|
1254
|
+
const shape = {};
|
|
1255
|
+
applyServiceCallShape(shape, lookupKey, methodArgsByOperation, servicePathByOperation, true);
|
|
1256
|
+
targets.push({
|
|
1257
|
+
action,
|
|
1258
|
+
invokeFnName: methodName,
|
|
1259
|
+
callArgs: (_c = shape.callArgs) !== null && _c !== void 0 ? _c : [],
|
|
1260
|
+
hasResolvedCallArgs: !!shape.hasResolvedCallArgs,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
return targets.length ? targets : undefined;
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* A CrudVariable's invokeHttp targets, keyed by operationType.
|
|
1267
|
+
*
|
|
1268
|
+
* Unlike a ServiceVariable (one fixed operation), a CrudVariable names a GROUP whose member is
|
|
1269
|
+
* chosen per call, so the glue dispatches on the operation the runtime records for that call.
|
|
1270
|
+
* Each entry carries everything the call needs: the singleton to import, its local alias, the
|
|
1271
|
+
* method, and how to source each positional argument.
|
|
1272
|
+
*/
|
|
1273
|
+
function buildCrudTargets(variable, crudOperationsById) {
|
|
1274
|
+
const group = crudOperationsById === null || crudOperationsById === void 0 ? void 0 : crudOperationsById.get(variable.crudOperationId);
|
|
1275
|
+
if (!group) {
|
|
1276
|
+
return undefined;
|
|
1277
|
+
}
|
|
1278
|
+
const targets = Object.entries(group).map(([operationType, target]) => {
|
|
1279
|
+
const className = target.importPath.split("/").pop();
|
|
1280
|
+
return {
|
|
1281
|
+
operationType,
|
|
1282
|
+
importPath: target.importPath,
|
|
1283
|
+
serviceExportName: getServiceInstanceName(className),
|
|
1284
|
+
serviceInstanceName: getServiceLocalName(variable.service, className),
|
|
1285
|
+
invokeFnName: target.methodName,
|
|
1286
|
+
// A CrudVariable is never a LiveVariable, so params are keyed by the swagger names.
|
|
1287
|
+
callArgs: target.args.map(arg => callArgExpression(arg, false)),
|
|
1288
|
+
};
|
|
1289
|
+
});
|
|
1290
|
+
return targets.length ? targets : undefined;
|
|
1291
|
+
}
|
|
1292
|
+
function transformCrudVariable(variable, scope, crudOperationsById) {
|
|
683
1293
|
const tv = transformVariable(variable, scope);
|
|
684
1294
|
tv._id = variable._id;
|
|
685
1295
|
tv.name = variable.name;
|
|
@@ -705,15 +1315,16 @@ function transformCrudVariable(variable, scope) {
|
|
|
705
1315
|
tv.dataBinding = variable._transformedDataBinding || variable.dataBinding;
|
|
706
1316
|
tv.classname = "CrudVariable";
|
|
707
1317
|
tv.group = "variable";
|
|
1318
|
+
tv.crudTargets = buildCrudTargets(variable, crudOperationsById);
|
|
708
1319
|
tv.isStaticParams = isStaticParamsVariable(variable);
|
|
709
1320
|
return tv;
|
|
710
1321
|
}
|
|
711
|
-
exports.default = (variable, scope, appUrl, imports) => {
|
|
1322
|
+
exports.default = (variable, scope, appUrl, imports, validServiceClassNames, methodArgsByOperation, methodNameByOperation, servicePathByOperation, crudOperationsById) => {
|
|
712
1323
|
switch (variable.category) {
|
|
713
1324
|
case "wm.Variable":
|
|
714
1325
|
return transformModelVariable(variable, scope);
|
|
715
1326
|
case "wm.ServiceVariable":
|
|
716
|
-
return transformServiceVariable(variable, scope);
|
|
1327
|
+
return transformServiceVariable(variable, scope, validServiceClassNames, methodArgsByOperation, methodNameByOperation, servicePathByOperation);
|
|
717
1328
|
case "wm.NavigationAction":
|
|
718
1329
|
return transformNavigationAction(variable, scope);
|
|
719
1330
|
case "wm.TimerAction":
|
|
@@ -727,9 +1338,9 @@ exports.default = (variable, scope, appUrl, imports) => {
|
|
|
727
1338
|
case "wm.LogoutAction":
|
|
728
1339
|
return transformLogoutVariable(variable, scope);
|
|
729
1340
|
case "wm.LiveVariable":
|
|
730
|
-
return transformLiveVariable(variable, scope);
|
|
1341
|
+
return transformLiveVariable(variable, scope, validServiceClassNames, methodArgsByOperation, methodNameByOperation, servicePathByOperation);
|
|
731
1342
|
case "wm.CrudVariable":
|
|
732
|
-
return transformCrudVariable(variable, scope);
|
|
1343
|
+
return transformCrudVariable(variable, scope, crudOperationsById);
|
|
733
1344
|
}
|
|
734
1345
|
return null;
|
|
735
1346
|
};
|