apexrest 1.3.0-beta.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -140
- package/dist/codex-compat/.claude-plugin/marketplace.json +20 -0
- package/dist/codex-compat/plugins/apexrest-apex/.claude-plugin/plugin.json +28 -0
- package/dist/codex-compat/plugins/apexrest-apex/.codex-plugin/plugin.json +7 -7
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/generator.json +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/manifest.json +6 -6
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/packages/analytics/status-summary/1.0.0/block.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/packages/crud/report-dialog/1.0.0/block.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/packages/read/detail/1.0.0/block.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/packages/read/filtered-list/1.0.0/block.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/packages/read/history/1.0.0/block.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/blocks/packages/read/master-detail/1.0.0/block.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-blueprint.schema.json +5 -4
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-contract.schema.json +2 -2
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-entity.schema.json +2 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-field.schema.json +2 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-instance.schema.json +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-operation.schema.json +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/composer-plan.schema.json +5 -4
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/deploy-plan.schema.json +18 -24
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/operations.schema.json +352 -569
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/policy.schema.json +21 -0
- package/dist/codex-compat/plugins/apexrest-apex/resources/schemas/project.schema.json +4 -7
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/blank-app/README.md +0 -2
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/customer-crm/README.md +1 -3
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/existing-app/README.md +0 -2
- package/dist/codex-compat/plugins/apexrest-apex/runtime/apexrest.mjs +70 -44
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-5PCC7GZM.mjs +4090 -0
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-BVPAS3TD.mjs +18 -0
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-CVMECW6D.mjs +65 -0
- package/dist/codex-compat/plugins/apexrest-apex/runtime/{chunk-EBBEN4AV.mjs → chunk-JYN3YHP3.mjs} +4220 -10639
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-KGTE7Q5Y.mjs +23 -0
- package/dist/{runtime/chunk-33GE2NWZ.mjs → codex-compat/plugins/apexrest-apex/runtime/chunk-KKRFM7Q7.mjs} +21 -4
- package/dist/codex-compat/plugins/apexrest-apex/runtime/{chunk-2VBZESVC.mjs → chunk-MNU54A7W.mjs} +7 -1
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-N4C2SKCN.mjs +6912 -0
- package/dist/{runtime/chunk-KQ5IDT7Z.mjs → codex-compat/plugins/apexrest-apex/runtime/chunk-P3CVQAZW.mjs} +822 -102
- package/dist/{runtime/chunk-BAHECVGO.mjs → codex-compat/plugins/apexrest-apex/runtime/chunk-QKYF6N6U.mjs} +192 -114
- package/dist/codex-compat/plugins/apexrest-apex/runtime/{chunk-G3KR57BY.mjs → chunk-RBJJNQ5O.mjs} +7 -7
- package/dist/codex-compat/plugins/apexrest-apex/runtime/{chunk-YPLIIQ4Y.mjs → chunk-TMSMKVRP.mjs} +486 -352
- package/dist/codex-compat/plugins/apexrest-apex/runtime/{chunk-2Z3BZF66.mjs → chunk-WPS3CSQJ.mjs} +124 -11
- package/dist/codex-compat/plugins/apexrest-apex/runtime/mcp.mjs +5 -10
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-apexlang/SKILL.md +22 -25
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-apexlang/agents/openai.yaml +3 -3
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-apexlang/references/component-routes.md +1 -5
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-pattern-catalog/SKILL.md +4 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-pattern-catalog/agents/openai.yaml +1 -1
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-pattern-catalog/references/capture-and-curation.md +2 -2
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-safety/SKILL.md +18 -0
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-safety/agents/openai.yaml +4 -0
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-setup/SKILL.md +20 -10
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-setup/agents/openai.yaml +3 -3
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-work/SKILL.md +11 -8
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-work/agents/openai.yaml +3 -3
- package/dist/resources/blocks/generator.json +1 -1
- package/dist/resources/blocks/manifest.json +6 -6
- package/dist/resources/blocks/packages/analytics/status-summary/1.0.0/block.yaml +1 -1
- package/dist/resources/blocks/packages/crud/report-dialog/1.0.0/block.yaml +1 -1
- package/dist/resources/blocks/packages/read/detail/1.0.0/block.yaml +1 -1
- package/dist/resources/blocks/packages/read/filtered-list/1.0.0/block.yaml +1 -1
- package/dist/resources/blocks/packages/read/history/1.0.0/block.yaml +1 -1
- package/dist/resources/blocks/packages/read/master-detail/1.0.0/block.yaml +1 -1
- package/dist/resources/schemas/composer-blueprint.schema.json +5 -4
- package/dist/resources/schemas/composer-contract.schema.json +2 -2
- package/dist/resources/schemas/composer-entity.schema.json +2 -1
- package/dist/resources/schemas/composer-field.schema.json +2 -1
- package/dist/resources/schemas/composer-instance.schema.json +1 -1
- package/dist/resources/schemas/composer-operation.schema.json +1 -1
- package/dist/resources/schemas/composer-plan.schema.json +5 -4
- package/dist/resources/schemas/deploy-plan.schema.json +18 -24
- package/dist/resources/schemas/operations.schema.json +352 -569
- package/dist/resources/schemas/policy.schema.json +21 -0
- package/dist/resources/schemas/project.schema.json +4 -7
- package/dist/resources/templates/blank-app/README.md +0 -2
- package/dist/resources/templates/customer-crm/README.md +1 -3
- package/dist/resources/templates/existing-app/README.md +0 -2
- package/dist/runtime/apexrest.mjs +70 -44
- package/dist/runtime/chunk-5PCC7GZM.mjs +4090 -0
- package/dist/runtime/chunk-BVPAS3TD.mjs +18 -0
- package/dist/runtime/chunk-CVMECW6D.mjs +65 -0
- package/dist/runtime/{chunk-EBBEN4AV.mjs → chunk-JYN3YHP3.mjs} +4220 -10639
- package/dist/runtime/chunk-KGTE7Q5Y.mjs +23 -0
- package/dist/{codex-compat/plugins/apexrest-apex/runtime/chunk-33GE2NWZ.mjs → runtime/chunk-KKRFM7Q7.mjs} +21 -4
- package/dist/runtime/{chunk-2VBZESVC.mjs → chunk-MNU54A7W.mjs} +7 -1
- package/dist/runtime/chunk-N4C2SKCN.mjs +6912 -0
- package/dist/{codex-compat/plugins/apexrest-apex/runtime/chunk-KQ5IDT7Z.mjs → runtime/chunk-P3CVQAZW.mjs} +822 -102
- package/dist/{codex-compat/plugins/apexrest-apex/runtime/chunk-BAHECVGO.mjs → runtime/chunk-QKYF6N6U.mjs} +192 -114
- package/dist/runtime/{chunk-G3KR57BY.mjs → chunk-RBJJNQ5O.mjs} +7 -7
- package/dist/runtime/{chunk-YPLIIQ4Y.mjs → chunk-TMSMKVRP.mjs} +486 -352
- package/dist/runtime/{chunk-2Z3BZF66.mjs → chunk-WPS3CSQJ.mjs} +124 -11
- package/dist/runtime/mcp.mjs +5 -10
- package/package.json +6 -6
- package/README.uk.md +0 -212
- package/dist/codex-compat/plugins/apexrest-apex/resources/panel/index.html +0 -68
- package/dist/codex-compat/plugins/apexrest-apex/resources/panel/panel.css +0 -627
- package/dist/codex-compat/plugins/apexrest-apex/resources/panel/panel.js +0 -861
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/blank-app/README.uk.md +0 -5
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/customer-crm/README.uk.md +0 -7
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/database-control/install.sql +0 -17
- package/dist/codex-compat/plugins/apexrest-apex/resources/templates/existing-app/README.uk.md +0 -9
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__@playwright__test__LICENSE +0 -202
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__@playwright__test__NOTICE +0 -5
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__@types__node__LICENSE +0 -21
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__@types__yauzl__LICENSE +0 -21
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__@typescript__typescript-darwin-arm64__LICENSE +0 -55
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__@typescript__typescript-darwin-arm64__NOTICE.txt +0 -436
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__esbuild__LICENSE.md +0 -21
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__fflate__LICENSE +0 -21
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__marked__LICENSE +0 -44
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__playwright-core__LICENSE +0 -202
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__playwright-core__NOTICE +0 -5
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__playwright__LICENSE +0 -202
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__playwright__NOTICE +0 -5
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__prettier__LICENSE +0 -7
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__typescript__LICENSE +0 -55
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__typescript__NOTICE.txt +0 -436
- package/dist/codex-compat/plugins/apexrest-apex/resources/third-party/node_modules__undici-types__LICENSE +0 -21
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-2BBNJVKW.mjs +0 -1996
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-5JI3XEAK.mjs +0 -14
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-CYPFCUNE.mjs +0 -150
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-ESDVN7DS.mjs +0 -14
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-FTTPESNK.mjs +0 -203
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-HKJVCGQQ.mjs +0 -1302
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-RWGJITHZ.mjs +0 -992
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-TLYYCRQA.mjs +0 -329
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-WXP75UUP.mjs +0 -18
- package/dist/codex-compat/plugins/apexrest-apex/runtime/chunk-YQHHPRJH.mjs +0 -14
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-compose/SKILL.md +0 -18
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-compose/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-compose/references/workflow.md +0 -15
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-database/SKILL.md +0 -9
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-database/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-debug/SKILL.md +0 -9
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-debug/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-deploy/SKILL.md +0 -21
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-deploy/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-install-dependencies/SKILL.md +0 -38
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-install-dependencies/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-menu/SKILL.md +0 -30
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-menu/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-menu/references/cli-catalog.md +0 -30
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-panel/SKILL.md +0 -14
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-panel/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-project/SKILL.md +0 -11
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-project/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-review/SKILL.md +0 -11
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-review/agents/openai.yaml +0 -4
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-test/SKILL.md +0 -29
- package/dist/codex-compat/plugins/apexrest-apex/skills/apexrest-test/agents/openai.yaml +0 -4
- package/dist/resources/panel/index.html +0 -68
- package/dist/resources/panel/panel.css +0 -627
- package/dist/resources/panel/panel.js +0 -861
- package/dist/resources/templates/blank-app/README.uk.md +0 -5
- package/dist/resources/templates/customer-crm/README.uk.md +0 -7
- package/dist/resources/templates/database-control/install.sql +0 -17
- package/dist/resources/templates/existing-app/README.uk.md +0 -9
- package/dist/resources/third-party/node_modules__@playwright__test__LICENSE +0 -202
- package/dist/resources/third-party/node_modules__@playwright__test__NOTICE +0 -5
- package/dist/resources/third-party/node_modules__@types__node__LICENSE +0 -21
- package/dist/resources/third-party/node_modules__@types__yauzl__LICENSE +0 -21
- package/dist/resources/third-party/node_modules__@typescript__typescript-darwin-arm64__LICENSE +0 -55
- package/dist/resources/third-party/node_modules__@typescript__typescript-darwin-arm64__NOTICE.txt +0 -436
- package/dist/resources/third-party/node_modules__esbuild__LICENSE.md +0 -21
- package/dist/resources/third-party/node_modules__fflate__LICENSE +0 -21
- package/dist/resources/third-party/node_modules__marked__LICENSE +0 -44
- package/dist/resources/third-party/node_modules__playwright-core__LICENSE +0 -202
- package/dist/resources/third-party/node_modules__playwright-core__NOTICE +0 -5
- package/dist/resources/third-party/node_modules__playwright__LICENSE +0 -202
- package/dist/resources/third-party/node_modules__playwright__NOTICE +0 -5
- package/dist/resources/third-party/node_modules__prettier__LICENSE +0 -7
- package/dist/resources/third-party/node_modules__typescript__LICENSE +0 -55
- package/dist/resources/third-party/node_modules__typescript__NOTICE.txt +0 -436
- package/dist/resources/third-party/node_modules__undici-types__LICENSE +0 -21
- package/dist/runtime/chunk-2BBNJVKW.mjs +0 -1996
- package/dist/runtime/chunk-5JI3XEAK.mjs +0 -14
- package/dist/runtime/chunk-CYPFCUNE.mjs +0 -150
- package/dist/runtime/chunk-ESDVN7DS.mjs +0 -14
- package/dist/runtime/chunk-FTTPESNK.mjs +0 -203
- package/dist/runtime/chunk-HKJVCGQQ.mjs +0 -1302
- package/dist/runtime/chunk-RWGJITHZ.mjs +0 -992
- package/dist/runtime/chunk-TLYYCRQA.mjs +0 -329
- package/dist/runtime/chunk-WXP75UUP.mjs +0 -18
- package/dist/runtime/chunk-YQHHPRJH.mjs +0 -14
|
@@ -1,1996 +0,0 @@
|
|
|
1
|
-
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
ArtifactService,
|
|
4
|
-
SyncStore,
|
|
5
|
-
assessPlan,
|
|
6
|
-
blueprintSchema,
|
|
7
|
-
browserPreferences,
|
|
8
|
-
browserPreferencesSchema,
|
|
9
|
-
canonical,
|
|
10
|
-
catalogRead,
|
|
11
|
-
catalogSearch,
|
|
12
|
-
digest,
|
|
13
|
-
freeze,
|
|
14
|
-
instanceSchema,
|
|
15
|
-
journal,
|
|
16
|
-
loadCatalog,
|
|
17
|
-
materialize,
|
|
18
|
-
openVerificationBrowser,
|
|
19
|
-
planDigest,
|
|
20
|
-
planSchema,
|
|
21
|
-
readDocument,
|
|
22
|
-
readPlan,
|
|
23
|
-
recoveryPlan,
|
|
24
|
-
resolvePackages,
|
|
25
|
-
safePath,
|
|
26
|
-
semanticDigest,
|
|
27
|
-
stagePlan,
|
|
28
|
-
stateSchema,
|
|
29
|
-
validate
|
|
30
|
-
} from "./chunk-YPLIIQ4Y.mjs";
|
|
31
|
-
import {
|
|
32
|
-
VERSION
|
|
33
|
-
} from "./chunk-G3KR57BY.mjs";
|
|
34
|
-
import {
|
|
35
|
-
OracleAdapter,
|
|
36
|
-
configureConnection,
|
|
37
|
-
configureSqlcl,
|
|
38
|
-
connections,
|
|
39
|
-
environment,
|
|
40
|
-
external_exports,
|
|
41
|
-
identifier,
|
|
42
|
-
loadProject,
|
|
43
|
-
ordsUrl,
|
|
44
|
-
ordsUsername,
|
|
45
|
-
parse,
|
|
46
|
-
policy,
|
|
47
|
-
refName,
|
|
48
|
-
relativePath,
|
|
49
|
-
requireTrust,
|
|
50
|
-
resolveConnection,
|
|
51
|
-
runProcess,
|
|
52
|
-
savedConnectionName,
|
|
53
|
-
sqlclConfig,
|
|
54
|
-
sqlclConfigSchema
|
|
55
|
-
} from "./chunk-EBBEN4AV.mjs";
|
|
56
|
-
import {
|
|
57
|
-
Fault,
|
|
58
|
-
atomicWrite,
|
|
59
|
-
contained,
|
|
60
|
-
exists,
|
|
61
|
-
hash,
|
|
62
|
-
inventory,
|
|
63
|
-
readJson,
|
|
64
|
-
sanitized,
|
|
65
|
-
withLock,
|
|
66
|
-
writeJson
|
|
67
|
-
} from "./chunk-2Z3BZF66.mjs";
|
|
68
|
-
|
|
69
|
-
// packages/core/src/composer/service.ts
|
|
70
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
71
|
-
|
|
72
|
-
// packages/core/src/metadata.ts
|
|
73
|
-
var metadataOffset = external_exports.number().int().min(0).max(1e5);
|
|
74
|
-
var metadataLimit = external_exports.number().int().min(1).max(100);
|
|
75
|
-
var metadataRequest = external_exports.strictObject({
|
|
76
|
-
kind: external_exports.enum([
|
|
77
|
-
"objects",
|
|
78
|
-
"columns",
|
|
79
|
-
"constraints",
|
|
80
|
-
"constraint-columns",
|
|
81
|
-
"signatures",
|
|
82
|
-
"applications",
|
|
83
|
-
"pages"
|
|
84
|
-
]),
|
|
85
|
-
schema: identifier,
|
|
86
|
-
name: identifier.optional(),
|
|
87
|
-
offset: metadataOffset.default(0),
|
|
88
|
-
limit: metadataLimit.default(30)
|
|
89
|
-
});
|
|
90
|
-
var metadataRequests = external_exports.array(metadataRequest).min(1).max(8);
|
|
91
|
-
var metadataBatchRequest = external_exports.strictObject({ requests: metadataRequests });
|
|
92
|
-
var metadataInputSchema = external_exports.strictObject({
|
|
93
|
-
kind: metadataRequest.shape.kind.optional(),
|
|
94
|
-
schema: metadataRequest.shape.schema.optional(),
|
|
95
|
-
name: metadataRequest.shape.name,
|
|
96
|
-
offset: metadataOffset.optional(),
|
|
97
|
-
limit: metadataLimit.optional(),
|
|
98
|
-
requests: metadataRequests.optional()
|
|
99
|
-
});
|
|
100
|
-
var queries = {
|
|
101
|
-
objects: "select object_name, object_type from all_objects where owner=:p_owner and object_type in ('TABLE','VIEW','PACKAGE') and (:p_name is null or object_name=:p_name) order by object_name, object_type",
|
|
102
|
-
columns: "select table_name,column_name,data_type,data_length,char_length,char_used,data_precision,data_scale,nullable,column_id from all_tab_columns where owner=:p_owner and table_name=:p_name order by column_id",
|
|
103
|
-
constraints: "select table_name,constraint_name,constraint_type,r_owner,r_constraint_name,status,validated from all_constraints where owner=:p_owner and table_name=:p_name order by constraint_name",
|
|
104
|
-
"constraint-columns": "select c.table_name,c.constraint_name,c.constraint_type,c.status,c.validated,cc.column_name,cc.position,c.r_owner,c.r_constraint_name,rc.table_name referenced_table,rcc.column_name referenced_column from all_constraints c join all_cons_columns cc on cc.owner=c.owner and cc.constraint_name=c.constraint_name and cc.table_name=c.table_name left join all_constraints rc on rc.owner=c.r_owner and rc.constraint_name=c.r_constraint_name and rc.owner=:p_owner left join all_cons_columns rcc on rcc.owner=rc.owner and rcc.constraint_name=rc.constraint_name and rcc.position=cc.position where c.owner=:p_owner and c.table_name=:p_name order by c.constraint_name,cc.position",
|
|
105
|
-
signatures: "select package_name,object_name,argument_name,position,sequence,data_level,in_out,data_type,type_owner,type_name,type_subname,defaulted,overload,subprogram_id from all_arguments where owner=:p_owner and package_name=:p_name order by object_name,overload,sequence",
|
|
106
|
-
applications: "select application_id,application_name,alias from apex_applications where owner=:p_owner and application_id=:p_app_id order by application_id",
|
|
107
|
-
pages: "select application_id,page_id,page_name,page_alias from apex_application_pages where application_id=:p_app_id and workspace=:p_workspace order by page_id"
|
|
108
|
-
};
|
|
109
|
-
async function metadataRead(adapter, env, connection, value) {
|
|
110
|
-
const input = parse(external_exports.union([metadataRequest, metadataBatchRequest]), value);
|
|
111
|
-
const batch = "requests" in input;
|
|
112
|
-
const requests = batch ? input.requests : [input];
|
|
113
|
-
for (const r of requests) {
|
|
114
|
-
if (r.schema !== env.parsingSchema)
|
|
115
|
-
throw new Fault("SCHEMA_DENIED", "Metadata is restricted to the configured parsing schema.", 4);
|
|
116
|
-
if (["columns", "constraints", "constraint-columns", "signatures"].includes(r.kind) && !r.name)
|
|
117
|
-
throw new Fault("OBJECT_REQUIRED", "Select a specific object first.", 2);
|
|
118
|
-
}
|
|
119
|
-
await adapter.verifyTarget(env, connection);
|
|
120
|
-
const read = async (r) => {
|
|
121
|
-
const rows = await adapter.jsonQuery(
|
|
122
|
-
queries[r.kind] + " offset :p_offset rows fetch next :p_limit rows only",
|
|
123
|
-
connection,
|
|
124
|
-
{
|
|
125
|
-
p_owner: r.schema,
|
|
126
|
-
p_name: r.name ?? "",
|
|
127
|
-
p_app_id: env.applicationId,
|
|
128
|
-
p_workspace: env.workspace,
|
|
129
|
-
p_offset: r.offset,
|
|
130
|
-
p_limit: r.limit
|
|
131
|
-
}
|
|
132
|
-
);
|
|
133
|
-
return {
|
|
134
|
-
dataClassification: "untrusted_database_content",
|
|
135
|
-
rows,
|
|
136
|
-
offset: r.offset,
|
|
137
|
-
nextOffset: rows.length === r.limit ? r.offset + r.limit : null
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
if (!batch) return read(input);
|
|
141
|
-
const results = [];
|
|
142
|
-
for (const [index, r] of requests.entries())
|
|
143
|
-
results.push({ index, kind: r.kind, ...r.name ? { name: r.name } : {}, ...await read(r) });
|
|
144
|
-
return { results, targetVerifiedOnce: true };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// packages/core/src/composer/planner.ts
|
|
148
|
-
import { readFile, readdir } from "node:fs/promises";
|
|
149
|
-
|
|
150
|
-
// packages/core/src/composer/reader.ts
|
|
151
|
-
function declarations(source2) {
|
|
152
|
-
if (Buffer.byteLength(source2) > 8 * 1024 * 1024)
|
|
153
|
-
throw new Fault("TRANSFORM_UNSUPPORTED", "Source exceeds the bounded reader limit.", 5);
|
|
154
|
-
const lines = [...source2.matchAll(/[^\r\n]*(?:\r\n|\r|\n|$)/g)].filter((m) => m[0].length);
|
|
155
|
-
const stack = [], out = [];
|
|
156
|
-
let fence = false;
|
|
157
|
-
for (const line of lines) {
|
|
158
|
-
const text = line[0].replace(/[\r\n]+$/, "");
|
|
159
|
-
if (/^\s*```/.test(text)) {
|
|
160
|
-
fence = !fence;
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
if (fence) continue;
|
|
164
|
-
const open = text.match(/^( *)([A-Za-z][\w]*)(?:\s+(.*?))?\s*\(\s*$/);
|
|
165
|
-
if (open) stack.push({ kind: open[2], key: open[3] ?? "", start: line.index, depth: open[1].length });
|
|
166
|
-
else if (/^ *\)\s*$/.test(text)) {
|
|
167
|
-
const node2 = stack.pop();
|
|
168
|
-
if (!node2 || node2.depth !== text.indexOf(")"))
|
|
169
|
-
throw new Fault("TRANSFORM_UNSUPPORTED", "Unbalanced declaration boundary.", 5);
|
|
170
|
-
const end = line.index + line[0].length;
|
|
171
|
-
out.push({ ...node2, end, digest: hash(source2.slice(node2.start, end)) });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (fence || stack.length) throw new Fault("TRANSFORM_UNSUPPORTED", "Unclosed literal or declaration.", 5);
|
|
175
|
-
return out.sort((a, b) => a.start - b.start);
|
|
176
|
-
}
|
|
177
|
-
function inventorySymbols(sources) {
|
|
178
|
-
const pages = /* @__PURE__ */ new Set(), symbols = /* @__PURE__ */ new Set();
|
|
179
|
-
for (const source2 of Object.values(sources))
|
|
180
|
-
for (const node2 of declarations(source2)) {
|
|
181
|
-
if (node2.kind === "page" && /^\d+$/.test(node2.key)) pages.add(Number(node2.key));
|
|
182
|
-
if (node2.key) symbols.add(node2.key.toUpperCase());
|
|
183
|
-
}
|
|
184
|
-
return { pages, symbols };
|
|
185
|
-
}
|
|
186
|
-
function editSpans(source2, edits) {
|
|
187
|
-
let result = source2, previous = source2.length + 1;
|
|
188
|
-
for (const edit of [...edits].sort((a, b) => b.start - a.start)) {
|
|
189
|
-
if (edit.start < 0 || edit.end > previous || edit.start > edit.end || hash(source2.slice(edit.start, edit.end)) !== edit.expectedDigest)
|
|
190
|
-
throw new Fault("TRANSFORM_CONFLICT", "Overlapping or stale structural edit.", 5);
|
|
191
|
-
result = result.slice(0, edit.start) + edit.content + result.slice(edit.end);
|
|
192
|
-
previous = edit.start;
|
|
193
|
-
}
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
function threeWay(base, local, next) {
|
|
197
|
-
if (local === base) return next;
|
|
198
|
-
if (next === base || local === next) return local;
|
|
199
|
-
const variants = [base, local, next], roots = variants.map(declarations);
|
|
200
|
-
const rootDepth = roots[0]?.[0]?.depth;
|
|
201
|
-
if (rootDepth !== void 0 && roots.every((nodes) => nodes[0]?.depth === rootDepth)) {
|
|
202
|
-
const children = roots.map((nodes) => nodes.filter((node2) => node2.depth === rootDepth + 4));
|
|
203
|
-
const maps = children.map(
|
|
204
|
-
(nodes, i) => new Map(
|
|
205
|
-
nodes.map((node2) => [
|
|
206
|
-
node2.kind + ":" + node2.key,
|
|
207
|
-
{ node: node2, content: variants[i].slice(node2.start, node2.end) }
|
|
208
|
-
])
|
|
209
|
-
)
|
|
210
|
-
);
|
|
211
|
-
if (maps.some((map, i) => map.size !== children[i].length))
|
|
212
|
-
throw new Fault("COMPOSITION_CONFLICT", "Repeated sibling anchors are unsupported.", 5);
|
|
213
|
-
const scaffold = (value, nodes) => editSpans(
|
|
214
|
-
value,
|
|
215
|
-
nodes.map((node2) => ({ ...node2, expectedDigest: node2.digest, content: "" }))
|
|
216
|
-
);
|
|
217
|
-
const wrappers = variants.map((v, i) => scaffold(v, children[i]));
|
|
218
|
-
const wrapper = mergeLines(wrappers[0], wrappers[1], wrappers[2]);
|
|
219
|
-
const edits = [];
|
|
220
|
-
const additions = [];
|
|
221
|
-
for (const key of /* @__PURE__ */ new Set([...maps[1].keys(), ...maps[2].keys()])) {
|
|
222
|
-
const b = maps[0].get(key), l = maps[1].get(key), n = maps[2].get(key);
|
|
223
|
-
let content;
|
|
224
|
-
if (!b) {
|
|
225
|
-
if (l && n && l.content !== n.content)
|
|
226
|
-
throw new Fault("COMPOSITION_CONFLICT", "New declaration collides with local source.", 5);
|
|
227
|
-
content = l?.content ?? n?.content;
|
|
228
|
-
} else if (!n) {
|
|
229
|
-
if (l && l.content !== b.content)
|
|
230
|
-
throw new Fault("COMPOSITION_CONFLICT", "Removing an edited declaration requires detach.", 5);
|
|
231
|
-
content = "";
|
|
232
|
-
} else if (!l) {
|
|
233
|
-
if (n.content !== b.content)
|
|
234
|
-
throw new Fault(
|
|
235
|
-
"COMPOSITION_CONFLICT",
|
|
236
|
-
"A locally removed declaration changed in the generator.",
|
|
237
|
-
5
|
|
238
|
-
);
|
|
239
|
-
} else content = threeWay(b.content, l.content, n.content);
|
|
240
|
-
if (l && content !== void 0 && content !== l.content)
|
|
241
|
-
edits.push({ ...l.node, expectedDigest: l.node.digest, content });
|
|
242
|
-
else if (!l && !b && content) additions.push(content);
|
|
243
|
-
}
|
|
244
|
-
const translate = (offset) => {
|
|
245
|
-
let skipped = 0;
|
|
246
|
-
for (const child of children[1]) {
|
|
247
|
-
if (child.start - skipped > offset) break;
|
|
248
|
-
skipped += child.end - child.start;
|
|
249
|
-
}
|
|
250
|
-
return offset + skipped;
|
|
251
|
-
};
|
|
252
|
-
let cursor = 0;
|
|
253
|
-
const originalLines = wrappers[1].split("\n"), mergedLines = wrapper.split("\n");
|
|
254
|
-
originalLines.forEach((line, i) => {
|
|
255
|
-
if (line !== mergedLines[i]) {
|
|
256
|
-
const start = translate(cursor), end = start + line.length;
|
|
257
|
-
edits.push({ start, end, expectedDigest: hash(local.slice(start, end)), content: mergedLines[i] });
|
|
258
|
-
}
|
|
259
|
-
cursor += line.length + 1;
|
|
260
|
-
});
|
|
261
|
-
if (additions.length) {
|
|
262
|
-
const root = roots[1][0], close = local.lastIndexOf(")", root.end - 1), start = local.lastIndexOf("\n", close) + 1;
|
|
263
|
-
edits.push({ start, end: start, expectedDigest: hash(""), content: additions.join("") });
|
|
264
|
-
}
|
|
265
|
-
return editSpans(local, edits);
|
|
266
|
-
}
|
|
267
|
-
return mergeLines(base, local, next);
|
|
268
|
-
}
|
|
269
|
-
function mergeLines(base, local, next) {
|
|
270
|
-
if (local === base) return next;
|
|
271
|
-
if (next === base || local === next) return local;
|
|
272
|
-
const literals = [base, local, next].map(
|
|
273
|
-
(source2) => [...source2.matchAll(/```[^\r\n]*[\r\n]+[\s\S]*?^[ \t]*```/gm)].map((match) => match[0])
|
|
274
|
-
);
|
|
275
|
-
if (literals[0].length !== literals[1].length || literals[0].length !== literals[2].length || literals[0].some(
|
|
276
|
-
(value, i) => value !== literals[1][i] && value !== literals[2][i] && literals[1][i] !== literals[2][i]
|
|
277
|
-
))
|
|
278
|
-
throw new Fault(
|
|
279
|
-
"COMPOSITION_CONFLICT",
|
|
280
|
-
"Concurrent changes to an opaque code literal require explicit review.",
|
|
281
|
-
5
|
|
282
|
-
);
|
|
283
|
-
const b = base.split("\n"), l = local.split("\n"), n = next.split("\n");
|
|
284
|
-
if (b.length !== l.length || b.length !== n.length)
|
|
285
|
-
throw new Fault(
|
|
286
|
-
"COMPOSITION_CONFLICT",
|
|
287
|
-
"Structural changes require explicit adoption or conflict resolution.",
|
|
288
|
-
5
|
|
289
|
-
);
|
|
290
|
-
return b.map((line, i) => {
|
|
291
|
-
if (l[i] === line) return n[i];
|
|
292
|
-
if (n[i] === line || l[i] === n[i]) return l[i];
|
|
293
|
-
throw new Fault("COMPOSITION_CONFLICT", `Both local and generated source changed line ${i + 1}.`, 5);
|
|
294
|
-
}).join("\n");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// packages/core/src/composer/binding.ts
|
|
298
|
-
function keyFields(entity) {
|
|
299
|
-
return entity.read.key.map((key) => {
|
|
300
|
-
const entries = Object.entries(entity.read.fields).filter(
|
|
301
|
-
([name, field]) => name === key || field.column === key
|
|
302
|
-
);
|
|
303
|
-
if (entries.length > 1)
|
|
304
|
-
throw new Fault("KEY_MAPPING_AMBIGUOUS", "A key part must identify exactly one projected field.", 5);
|
|
305
|
-
const entry = entries[0];
|
|
306
|
-
if (!entry) throw new Fault("KEY_MAPPING_MISSING", "Every key part must map to a projected field.", 5);
|
|
307
|
-
return entry[0];
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
function expression(value) {
|
|
311
|
-
if (!value.trim() || /[\r\n;]|```|\b(?:commit|rollback|grant|revoke|insert|update|delete|drop|alter|create|execute\s+immediate|host|connect)\b|&/i.test(
|
|
312
|
-
value
|
|
313
|
-
))
|
|
314
|
-
throw new Fault("CONTRACT_SQL_UNSAFE", "Row predicates accept reviewed expressions only.", 5);
|
|
315
|
-
if (/:[a-z0-9_]+/gi.test(value) && [...value.matchAll(/:([a-z0-9_]+)/gi)].some(
|
|
316
|
-
(m) => !["APP_USER", "APP_ID", "APP_SESSION"].includes(m[1].toUpperCase())
|
|
317
|
-
))
|
|
318
|
-
throw new Fault(
|
|
319
|
-
"AUTH_CONTEXT_UNTRUSTED",
|
|
320
|
-
"Row access may only use server-owned APEX session bindings.",
|
|
321
|
-
5
|
|
322
|
-
);
|
|
323
|
-
return value;
|
|
324
|
-
}
|
|
325
|
-
function bind(blueprint, instance, metadata) {
|
|
326
|
-
const ref = instance.bindings.records;
|
|
327
|
-
if (!ref.startsWith("entity:"))
|
|
328
|
-
throw new Fault("BINDING_MISSING", "Expected an explicit entity binding.", 5);
|
|
329
|
-
const entity = blueprint.entities[ref.slice(7)];
|
|
330
|
-
if (!entity) throw new Fault("BINDING_MISSING", "Entity binding is absent.", 5);
|
|
331
|
-
if (!Object.keys(entity.read.fields).length || Object.keys(entity.read.fields).length > 32)
|
|
332
|
-
throw new Fault("CONTRACT_FIELD_LIMIT", "Entities support 1\u201332 scalar fields.", 5);
|
|
333
|
-
const fieldNames = Object.keys(entity.read.fields);
|
|
334
|
-
if (fieldNames.some((field) => !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(field)) || new Set(fieldNames.map((field) => field.toUpperCase())).size !== fieldNames.length)
|
|
335
|
-
throw new Fault(
|
|
336
|
-
"FIELD_SYMBOL_COLLISION",
|
|
337
|
-
"Field names must form unique case-insensitive APEX item suffixes.",
|
|
338
|
-
5
|
|
339
|
-
);
|
|
340
|
-
const keys = keyFields(entity), auth = blueprint.contracts[entity.authorization.readContract];
|
|
341
|
-
if (new Set(keys).size !== keys.length)
|
|
342
|
-
throw new Fault("KEY_MAPPING_DUPLICATE", "Each ordered key part must occur once.", 5);
|
|
343
|
-
if (!auth || auth.kind !== "authorization" || !auth.rowPredicate)
|
|
344
|
-
throw new Fault("AUTH_CONTRACT_MISSING", "A reviewed server row-read contract is required.", 5);
|
|
345
|
-
const predicate = expression(auth.rowPredicate);
|
|
346
|
-
if (entity.read.kind === "oracle-view") {
|
|
347
|
-
const key = blueprint.contracts[entity.read.keyContract ?? ""];
|
|
348
|
-
if (!key || key.kind !== "key" || JSON.stringify(key.fields) !== JSON.stringify(keys))
|
|
349
|
-
throw new Fault("KEY_CONTRACT_MISSING", "Views require an explicit ordered key contract.", 5);
|
|
350
|
-
}
|
|
351
|
-
if (new Set(Object.values(entity.read.fields).map((f) => f.column.toUpperCase())).size !== Object.keys(entity.read.fields).length)
|
|
352
|
-
throw new Fault("COLUMN_COLLISION", "Projected field mappings must be unique.", 5);
|
|
353
|
-
for (const name of [
|
|
354
|
-
...instance.parameters.editableFields,
|
|
355
|
-
instance.parameters.groupField,
|
|
356
|
-
instance.parameters.filterField,
|
|
357
|
-
instance.parameters.detailField,
|
|
358
|
-
instance.parameters.timeField,
|
|
359
|
-
instance.parameters.parentField
|
|
360
|
-
].filter(Boolean))
|
|
361
|
-
if (!entity.read.fields[name])
|
|
362
|
-
throw new Fault("FIELD_MAPPING_MISSING", "A selected field is not in the entity contract.", 5);
|
|
363
|
-
const writable = instance.parameters.createEnabled || instance.parameters.editEnabled;
|
|
364
|
-
let command;
|
|
365
|
-
let writeExpression;
|
|
366
|
-
if (writable) {
|
|
367
|
-
if (keys.length !== 1)
|
|
368
|
-
throw new Fault("COMPOSITE_WRITE_UNSUPPORTED", "Write CRUD requires one scalar key.", 5);
|
|
369
|
-
const version = entity.capabilities.optimisticLock?.field;
|
|
370
|
-
if (!version || entity.read.fields[version]?.type !== "integer" || !entity.read.fields[version] || entity.read.fields[version].nullable || entity.read.fields[keys[0]].nullable)
|
|
371
|
-
throw new Fault("OPTIMISTIC_LOCK_MISSING", "Persisted key/version fields must be non-null.", 5);
|
|
372
|
-
if (instance.parameters.editableFields.some((field) => keys.includes(field) || field === version))
|
|
373
|
-
throw new Fault("MASS_ASSIGNMENT_DENIED", "Key and row version are server managed.", 5);
|
|
374
|
-
if (!["string", "integer", "decimal"].includes(entity.read.fields[keys[0]].type) || instance.parameters.editableFields.some((f) => entity.read.fields[f].type === "boolean"))
|
|
375
|
-
throw new Fault(
|
|
376
|
-
"WRITE_TYPE_UNSUPPORTED",
|
|
377
|
-
"This write adapter supports text/numeric keys and scalar text/number/date/timestamp fields.",
|
|
378
|
-
5
|
|
379
|
-
);
|
|
380
|
-
const ref2 = instance.bindings.saveRecord;
|
|
381
|
-
command = ref2?.startsWith("command:") ? blueprint.commands[ref2.slice(8)] : void 0;
|
|
382
|
-
if (!command)
|
|
383
|
-
throw new Fault("COMMAND_BINDING_MISSING", "Write mode requires an explicit API command.", 5);
|
|
384
|
-
const signature = blueprint.contracts[command.signatureRef], writeAuth = blueprint.contracts[command.authorizationContract], errors = blueprint.contracts[command.errorContract];
|
|
385
|
-
if (!signature || signature.kind !== "command" || signature.transaction !== "caller-owned" || !signature.parameters || !writeAuth || writeAuth.kind !== "authorization" || !errors || errors.kind !== "errors" || command.authorizationContract !== entity.authorization.writeContract)
|
|
386
|
-
throw new Fault(
|
|
387
|
-
"COMMAND_CONTRACT_MISSING",
|
|
388
|
-
"Write mode requires exact reviewed signature, authorization and error contracts.",
|
|
389
|
-
5
|
|
390
|
-
);
|
|
391
|
-
if (!writeAuth.expression)
|
|
392
|
-
throw new Fault(
|
|
393
|
-
"AUTH_CONTRACT_MISSING",
|
|
394
|
-
"Write authorization requires a reviewed server expression.",
|
|
395
|
-
5
|
|
396
|
-
);
|
|
397
|
-
writeExpression = expression(writeAuth.expression);
|
|
398
|
-
if (command.outputs.recordKey.from === command.outputs.recordVersion.from)
|
|
399
|
-
throw new Fault(
|
|
400
|
-
"COMMAND_ARGUMENT_MISMATCH",
|
|
401
|
-
"Record key and version require distinct API output arguments.",
|
|
402
|
-
5
|
|
403
|
-
);
|
|
404
|
-
const supplied = /* @__PURE__ */ new Set([
|
|
405
|
-
...Object.keys(command.inputs),
|
|
406
|
-
...Object.values(command.outputs).map((o) => o.from)
|
|
407
|
-
]);
|
|
408
|
-
if (Object.keys(signature.parameters).some((p) => !supplied.has(p)) || [...supplied].some((p) => !signature.parameters[p]))
|
|
409
|
-
throw new Fault("COMMAND_ARGUMENT_MISMATCH", "Every exact signature argument must be mapped.", 5);
|
|
410
|
-
const types = (field) => {
|
|
411
|
-
const type = entity.read.fields[field]?.type;
|
|
412
|
-
return type === "integer" || type === "decimal" ? ["NUMBER", "PLS_INTEGER", "BINARY_INTEGER"] : type === "date" ? ["DATE"] : type === "timestamp" ? ["TIMESTAMP"] : ["VARCHAR2", "CHAR", "NVARCHAR2", "NCHAR"];
|
|
413
|
-
};
|
|
414
|
-
for (const [argument, mapping] of Object.entries(command.inputs)) {
|
|
415
|
-
const field = mapping.from.replace(/^record\./, "");
|
|
416
|
-
if (mapping.mode === "in-out" && field !== keys[0])
|
|
417
|
-
throw new Fault(
|
|
418
|
-
"COMMAND_ARGUMENT_MISMATCH",
|
|
419
|
-
"Only the record key supports IN OUT in this adapter.",
|
|
420
|
-
5
|
|
421
|
-
);
|
|
422
|
-
if (!types(field).includes(signature.parameters[argument]?.type ?? ""))
|
|
423
|
-
throw new Fault(
|
|
424
|
-
"COMMAND_ARGUMENT_MISMATCH",
|
|
425
|
-
"API argument scalar datatype differs from the field contract.",
|
|
426
|
-
5
|
|
427
|
-
);
|
|
428
|
-
if (!mapping.from.startsWith("record.") || !entity.read.fields[field] || signature.parameters[argument]?.mode !== mapping.mode)
|
|
429
|
-
throw new Fault(
|
|
430
|
-
"COMMAND_ARGUMENT_MISMATCH",
|
|
431
|
-
"API argument mode or field mapping does not match its contract.",
|
|
432
|
-
5
|
|
433
|
-
);
|
|
434
|
-
if (!keys.includes(field) && field !== version && !instance.parameters.editableFields.includes(field))
|
|
435
|
-
throw new Fault(
|
|
436
|
-
"COMMAND_INPUT_UNAVAILABLE",
|
|
437
|
-
"API inputs must map to an editable field or the managed record key/version.",
|
|
438
|
-
5
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
if (!types(keys[0]).includes(signature.parameters[command.outputs.recordKey.from]?.type ?? "") || !types(version).includes(signature.parameters[command.outputs.recordVersion.from]?.type ?? ""))
|
|
442
|
-
throw new Fault("COMMAND_ARGUMENT_MISMATCH", "Key/version output datatypes differ from the entity.", 5);
|
|
443
|
-
if (!Object.values(command.inputs).some((m) => m.from === "record." + version && m.mode === "in"))
|
|
444
|
-
throw new Fault("OPTIMISTIC_LOCK_MISSING", "Expected version must be passed explicitly to the API.", 5);
|
|
445
|
-
for (const output of Object.values(command.outputs))
|
|
446
|
-
if (!["out", "in-out"].includes(signature.parameters[output.from]?.mode ?? ""))
|
|
447
|
-
throw new Fault("COMMAND_ARGUMENT_MISMATCH", "API key/version outputs must be OUT or IN OUT.", 5);
|
|
448
|
-
if (metadata) {
|
|
449
|
-
const rows = metadata.signatures[command.package]?.filter((row) => row.OBJECT_NAME === command.procedure) ?? [];
|
|
450
|
-
const overloads = new Set(rows.map((row) => String(row.OVERLOAD ?? row.SUBPROGRAM_ID ?? "")));
|
|
451
|
-
if (overloads.size !== 1 && !signature.overload)
|
|
452
|
-
throw new Fault("COMMAND_OVERLOAD_AMBIGUOUS", "An exact reviewed API overload is required.", 5);
|
|
453
|
-
const selected = rows.filter(
|
|
454
|
-
(row) => !signature.overload || String(row.OVERLOAD ?? "") === signature.overload
|
|
455
|
-
);
|
|
456
|
-
if (new Set(selected.map((row) => String(row.SUBPROGRAM_ID))).size !== 1 || selected.length !== Object.keys(signature.parameters).length || selected.some((row) => Number(row.DATA_LEVEL ?? 0) !== 0 || Number(row.POSITION) === 0))
|
|
457
|
-
throw new Fault(
|
|
458
|
-
"COMMAND_ARGUMENT_MISMATCH",
|
|
459
|
-
"The complete live scalar procedure signature must match the reviewed contract.",
|
|
460
|
-
5
|
|
461
|
-
);
|
|
462
|
-
for (const [argument, spec] of Object.entries(signature.parameters)) {
|
|
463
|
-
const row = selected.find(
|
|
464
|
-
(r) => r.ARGUMENT_NAME === argument && (!signature.overload || String(r.OVERLOAD ?? "") === signature.overload)
|
|
465
|
-
);
|
|
466
|
-
if (!row || String(row.IN_OUT).toLowerCase().replace(/\s*\/\s*|\s+/g, "-") !== spec.mode || String(row.DATA_TYPE) !== spec.type || row.DEFAULTED === "Y" !== spec.defaulted)
|
|
467
|
-
throw new Fault(
|
|
468
|
-
"COMMAND_ARGUMENT_MISMATCH",
|
|
469
|
-
"Live API signature differs from its reviewed contract.",
|
|
470
|
-
5
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
if (metadata) {
|
|
476
|
-
const object = metadata.objects[entity.read.object];
|
|
477
|
-
if (!object) throw new Fault("OBJECT_BINDING_MISSING", "The selected Oracle object was not verified.", 5);
|
|
478
|
-
for (const field of Object.values(entity.read.fields)) {
|
|
479
|
-
const column = object.columns.find((row) => row.COLUMN_NAME === field.column);
|
|
480
|
-
if (!column || column.NULLABLE === "Y" && !field.nullable)
|
|
481
|
-
throw new Fault("COLUMN_CONTRACT_MISMATCH", "Live field nullability or column mapping differs.", 5);
|
|
482
|
-
const expected = ["integer", "decimal"].includes(field.type) ? ["NUMBER", "FLOAT"] : field.type === "date" ? ["DATE"] : field.type === "timestamp" ? ["TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE"] : ["VARCHAR2", "CHAR", "NVARCHAR2", "NCHAR"];
|
|
483
|
-
if (!expected.includes(String(column.DATA_TYPE)))
|
|
484
|
-
throw new Fault("COLUMN_TYPE_UNSUPPORTED", "Live datatype needs an explicit supported adapter.", 5);
|
|
485
|
-
if (field.maxLength && Number(column.CHAR_LENGTH ?? column.DATA_LENGTH) > field.maxLength)
|
|
486
|
-
throw new Fault("COLUMN_CONTRACT_MISMATCH", "Producer length exceeds consumer capacity.", 5);
|
|
487
|
-
if (field.precision !== void 0 && (column.DATA_PRECISION == null || Number(column.DATA_PRECISION) > field.precision) || field.scale !== void 0 && Number(column.DATA_SCALE) !== field.scale)
|
|
488
|
-
throw new Fault(
|
|
489
|
-
"COLUMN_CONTRACT_MISMATCH",
|
|
490
|
-
"Live numeric precision or scale differs from the field contract.",
|
|
491
|
-
5
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
if (entity.read.kind === "oracle-table") {
|
|
495
|
-
const primary = object.constraints.find(
|
|
496
|
-
(row) => row.CONSTRAINT_TYPE === "P" && row.STATUS === "ENABLED" && row.VALIDATED === "VALIDATED"
|
|
497
|
-
);
|
|
498
|
-
const columns = object.constraintColumns.filter((row) => row.CONSTRAINT_NAME === primary?.CONSTRAINT_NAME).sort((a, b) => Number(a.POSITION) - Number(b.POSITION)).map((row) => row.COLUMN_NAME);
|
|
499
|
-
if (!primary || JSON.stringify(columns) !== JSON.stringify(keys.map((k) => entity.read.fields[k].column)))
|
|
500
|
-
throw new Fault(
|
|
501
|
-
"PRIMARY_KEY_MISMATCH",
|
|
502
|
-
"Live enabled/validated primary key differs from the contract.",
|
|
503
|
-
5
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return { entity, keys, predicate, command, writable, entityRef: ref, writeExpression };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// packages/core/src/composer/emitter.ts
|
|
511
|
-
var indent = (value, depth = 4) => value.split("\n").map((line) => line ? " ".repeat(depth) + line : "").join("\n");
|
|
512
|
-
var group = (key, value) => `${key} {
|
|
513
|
-
${indent(value)}
|
|
514
|
-
}`;
|
|
515
|
-
var node = (kind, key, value) => `${kind} ${key} (
|
|
516
|
-
${indent(value)}
|
|
517
|
-
)
|
|
518
|
-
`;
|
|
519
|
-
var scalar = (value) => {
|
|
520
|
-
if (/[\r\n\x00-\x1f{}()`]/.test(value))
|
|
521
|
-
throw new Fault("PARAMETER_UNSUPPORTED", "Labels must be single-line literal values.", 2);
|
|
522
|
-
return value;
|
|
523
|
-
};
|
|
524
|
-
var code = (language, source2) => `
|
|
525
|
-
\`\`\`${language}
|
|
526
|
-
${indent(source2, 4)}
|
|
527
|
-
\`\`\``;
|
|
528
|
-
var layout = (sequence, slot = "body") => group("layout", `sequence: ${sequence}
|
|
529
|
-
slot: ${slot}`);
|
|
530
|
-
var appearance = (template) => group("appearance", `template: @/${template}
|
|
531
|
-
templateOptions: #DEFAULT#`);
|
|
532
|
-
var page = (allocation, title, body, dialog = false) => node(
|
|
533
|
-
"page",
|
|
534
|
-
String(dialog ? allocation.dialog : allocation.page),
|
|
535
|
-
[
|
|
536
|
-
`name: ${scalar(title)}`,
|
|
537
|
-
`alias: ${allocation.prefix.toUpperCase()}${dialog ? "_EDIT" : ""}`,
|
|
538
|
-
`title: ${scalar(title)}`,
|
|
539
|
-
group(
|
|
540
|
-
"appearance",
|
|
541
|
-
dialog ? "pageMode: modalDialog\ndialogTemplate: @/modal-dialog\ntemplateOptions: #DEFAULT#" : "pageTemplate: @/standard\ntemplateOptions: #DEFAULT#"
|
|
542
|
-
),
|
|
543
|
-
group("security", "pageAccessProtection: argumentsMustHaveChecksum"),
|
|
544
|
-
body
|
|
545
|
-
].join("\n")
|
|
546
|
-
);
|
|
547
|
-
var source = (sql, submit) => group(
|
|
548
|
-
"source",
|
|
549
|
-
`location: localDatabase
|
|
550
|
-
type: sqlQuery
|
|
551
|
-
${submit ? `pageItemsToSubmit: ${submit}
|
|
552
|
-
` : ""}sqlQuery:${code("sql", sql)}`
|
|
553
|
-
);
|
|
554
|
-
var button = (key, label, region, behavior) => node(
|
|
555
|
-
"button",
|
|
556
|
-
key,
|
|
557
|
-
[
|
|
558
|
-
`buttonName: ${key.replaceAll("-", "_").toUpperCase()}`,
|
|
559
|
-
`label: ${scalar(label)}`,
|
|
560
|
-
group("layout", `sequence: 10
|
|
561
|
-
region: @${region}
|
|
562
|
-
slot: NEXT`),
|
|
563
|
-
group("appearance", "buttonTemplate: @/text\ntemplateOptions: #DEFAULT#"),
|
|
564
|
-
group("behavior", behavior)
|
|
565
|
-
].join("\n")
|
|
566
|
-
);
|
|
567
|
-
function dynamic(key, event, selection, actions) {
|
|
568
|
-
return node(
|
|
569
|
-
"dynamicAction",
|
|
570
|
-
key,
|
|
571
|
-
`name: ${key}
|
|
572
|
-
${group("execution", "sequence: 10")}
|
|
573
|
-
${group("when", `event: ${event}
|
|
574
|
-
${selection}`)}
|
|
575
|
-
${actions}`
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
function refresh(key, region, sequence) {
|
|
579
|
-
return node(
|
|
580
|
-
"action",
|
|
581
|
-
key,
|
|
582
|
-
`action: refresh
|
|
583
|
-
${group("affectedElements", `selectionType: region
|
|
584
|
-
region: @${region}`)}
|
|
585
|
-
${group("execution", `sequence: ${sequence}
|
|
586
|
-
fireOnInit: false`)}`
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
function jsAction(key, javascript) {
|
|
590
|
-
return node(
|
|
591
|
-
"action",
|
|
592
|
-
key,
|
|
593
|
-
`action: executeJsCode
|
|
594
|
-
${group("settings", `jsCode:${code("javascript", javascript)}`)}
|
|
595
|
-
${group("execution", "sequence: 10\nfireOnInit: false")}`
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
function process2(key, point, sql) {
|
|
599
|
-
return node(
|
|
600
|
-
"process",
|
|
601
|
-
key,
|
|
602
|
-
`name: ${key}
|
|
603
|
-
type: executeCode
|
|
604
|
-
${group("source", `plsqlCode:${code("plsql", sql)}`)}
|
|
605
|
-
${group("execution", `sequence: 10
|
|
606
|
-
point: ${point}`)}`
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
function item(name, field, sequence, hidden, required) {
|
|
610
|
-
return node(
|
|
611
|
-
"pageItem",
|
|
612
|
-
name,
|
|
613
|
-
[
|
|
614
|
-
`type: ${hidden ? "hidden" : "textField"}`,
|
|
615
|
-
hidden ? "" : group("label", `label: ${scalar(field)}
|
|
616
|
-
alignment: left`),
|
|
617
|
-
group("layout", `sequence: ${sequence}
|
|
618
|
-
region: @form
|
|
619
|
-
slot: regionBody`),
|
|
620
|
-
hidden ? group("security", "sessionStateProtection: checksumRequiredSessionLevel") : group("appearance", "template: @/optional-floating\ntemplateOptions: #DEFAULT#\nwidth: 32"),
|
|
621
|
-
hidden ? "" : group("validation", `valueRequired: ${required}`)
|
|
622
|
-
].filter(Boolean).join("\n")
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
function summaryRegion(blueprint, instance, allocation) {
|
|
626
|
-
const { entity, predicate } = bind(blueprint, instance), field = entity.read.fields[instance.parameters.groupField ?? ""];
|
|
627
|
-
if (!field) throw new Fault("GROUP_FIELD_REQUIRED", "Summary blocks require a mapped grouping field.", 5);
|
|
628
|
-
return node(
|
|
629
|
-
"region",
|
|
630
|
-
allocation.prefix + "-summary",
|
|
631
|
-
[
|
|
632
|
-
`name: ${scalar(instance.parameters.title)}`,
|
|
633
|
-
"type: cards",
|
|
634
|
-
source(
|
|
635
|
-
`select ${field.column} ID, ${field.column} TITLE, to_char(count(*)) STATUS from ${entity.read.object} where ${predicate} group by ${field.column}`
|
|
636
|
-
),
|
|
637
|
-
layout(30),
|
|
638
|
-
appearance("cards-container"),
|
|
639
|
-
group("advanced", `htmlDomId: ${allocation.prefix}_summary`),
|
|
640
|
-
group("card", "primaryKeyColumn1: ID"),
|
|
641
|
-
group("title", "column: TITLE"),
|
|
642
|
-
group("body", "column: STATUS")
|
|
643
|
-
].join("\n")
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
function render(blueprint, id, instance, block, allocation, summaries = []) {
|
|
647
|
-
const binding = bind(blueprint, instance), { entity, keys, predicate, command, writable } = binding;
|
|
648
|
-
const fields = Object.entries(entity.read.fields).sort(([a], [b]) => a < b ? -1 : 1);
|
|
649
|
-
const file = (number) => `pages/p${String(number).padStart(5, "0")}-${allocation.prefix}${number === allocation.dialog ? "_edit" : ""}.apx`;
|
|
650
|
-
if (block.renderer === "status-summary")
|
|
651
|
-
return {
|
|
652
|
-
[file(allocation.page)]: page(
|
|
653
|
-
allocation,
|
|
654
|
-
instance.parameters.title,
|
|
655
|
-
summaryRegion(blueprint, instance, allocation)
|
|
656
|
-
)
|
|
657
|
-
};
|
|
658
|
-
if (block.renderer === "read-only-detail") {
|
|
659
|
-
if (keys.length !== 1)
|
|
660
|
-
throw new Fault(
|
|
661
|
-
"COMPOSITE_DETAIL_UNSUPPORTED",
|
|
662
|
-
"This detail adapter requires one scalar route key.",
|
|
663
|
-
5
|
|
664
|
-
);
|
|
665
|
-
const keyItem = `P${allocation.page}_${keys[0].toUpperCase()}`;
|
|
666
|
-
let body2 = node(
|
|
667
|
-
"region",
|
|
668
|
-
"form",
|
|
669
|
-
`name: ${scalar(instance.parameters.title)}
|
|
670
|
-
type: staticContent
|
|
671
|
-
${layout(10)}
|
|
672
|
-
${appearance("standard")}`
|
|
673
|
-
);
|
|
674
|
-
body2 += item(keyItem, keys[0], 0, true, false);
|
|
675
|
-
for (const [field] of fields.filter(([f]) => !keys.includes(f)))
|
|
676
|
-
body2 += node(
|
|
677
|
-
"pageItem",
|
|
678
|
-
`P${allocation.page}_${field.toUpperCase()}`,
|
|
679
|
-
`type: displayOnly
|
|
680
|
-
${group("label", `label: ${field}`)}
|
|
681
|
-
${group("layout", `sequence: ${(fields.findIndex(([f]) => f === field) + 1) * 10}
|
|
682
|
-
region: @form
|
|
683
|
-
slot: regionBody`)}
|
|
684
|
-
${appearance("optional")}`
|
|
685
|
-
);
|
|
686
|
-
body2 += process2(
|
|
687
|
-
allocation.prefix + "-detail",
|
|
688
|
-
"beforeHeader",
|
|
689
|
-
`begin
|
|
690
|
-
if :${keyItem} is not null then
|
|
691
|
-
select ${fields.map(([, f]) => f.column).join(", ")} into ${fields.map(([f]) => ":P" + allocation.page + "_" + f.toUpperCase()).join(", ")} from ${entity.read.object} where (${predicate}) and ${entity.read.fields[keys[0]].column}=:${keyItem};
|
|
692
|
-
end if;
|
|
693
|
-
end;`
|
|
694
|
-
);
|
|
695
|
-
return { [file(allocation.page)]: page(allocation, instance.parameters.title, body2) };
|
|
696
|
-
}
|
|
697
|
-
if (block.renderer === "history-timeline") {
|
|
698
|
-
const time = entity.read.fields[instance.parameters.timeField ?? ""], title = entity.read.fields[instance.parameters.detailField ?? ""], status = entity.read.fields[instance.parameters.groupField ?? ""];
|
|
699
|
-
if (!time || !["date", "timestamp"].includes(time.type) || !title || !status)
|
|
700
|
-
throw new Fault(
|
|
701
|
-
"TIMELINE_BINDING_REQUIRED",
|
|
702
|
-
"Timeline needs a temporal field, title and status mappings.",
|
|
703
|
-
5
|
|
704
|
-
);
|
|
705
|
-
const sql = `select 'EV' USER_AVATAR, apex_escape.html(${title.column}) USER_NAME, ${time.column} EVENT_DATE, apex_escape.html(${title.column}) EVENT_TITLE, apex_escape.html(${status.column}) EVENT_DESC, 'fa-history' EVENT_ICON, apex_escape.html(${status.column}) EVENT_STATUS, cast(null as varchar2(100)) EVENT_LINK, 'History' EVENT_TYPE from ${entity.read.object} where (${predicate}) order by ${time.column}, ${keys.map((k) => entity.read.fields[k].column).join(", ")}`;
|
|
706
|
-
const names = [
|
|
707
|
-
"USER_AVATAR",
|
|
708
|
-
"USER_NAME",
|
|
709
|
-
"EVENT_DATE",
|
|
710
|
-
"EVENT_TITLE",
|
|
711
|
-
"EVENT_DESC",
|
|
712
|
-
"EVENT_ICON",
|
|
713
|
-
"EVENT_STATUS",
|
|
714
|
-
"EVENT_LINK",
|
|
715
|
-
"EVENT_TYPE"
|
|
716
|
-
];
|
|
717
|
-
const cols = names.map(
|
|
718
|
-
(name, i) => node(
|
|
719
|
-
"column",
|
|
720
|
-
name,
|
|
721
|
-
`reportColumnQueryId: ${i + 1}
|
|
722
|
-
derivedColumn: N
|
|
723
|
-
${group("heading", `heading: ${name}`)}
|
|
724
|
-
${group("layout", `sequence: ${(i + 1) * 10}`)}`
|
|
725
|
-
)
|
|
726
|
-
).join("");
|
|
727
|
-
const timeline = node(
|
|
728
|
-
"region",
|
|
729
|
-
allocation.prefix + "-history",
|
|
730
|
-
`name: ${scalar(instance.parameters.title)}
|
|
731
|
-
type: classicReport
|
|
732
|
-
${source(sql)}
|
|
733
|
-
${layout(10)}
|
|
734
|
-
${appearance("standard")}
|
|
735
|
-
${group("componentAppearance", "template: @/timeline\ntemplateOptions: #DEFAULT#")}
|
|
736
|
-
${cols}`
|
|
737
|
-
);
|
|
738
|
-
return { [file(allocation.page)]: page(allocation, instance.parameters.title, timeline) };
|
|
739
|
-
}
|
|
740
|
-
if (block.renderer === "master-detail") {
|
|
741
|
-
if (keys.length !== 1 || !instance.parameters.parentField)
|
|
742
|
-
throw new Fault(
|
|
743
|
-
"MASTER_DETAIL_BINDING_REQUIRED",
|
|
744
|
-
"A self-referencing parent field and scalar key are required.",
|
|
745
|
-
5
|
|
746
|
-
);
|
|
747
|
-
const key = entity.read.fields[keys[0]], parent = entity.read.fields[instance.parameters.parentField];
|
|
748
|
-
if (parent.type !== key.type)
|
|
749
|
-
throw new Fault("MASTER_DETAIL_KEY_MISMATCH", "Parent and key types differ.", 5);
|
|
750
|
-
const selected = `P${allocation.page}_PARENT`, master = allocation.prefix + "-master", detail = allocation.prefix + "-detail";
|
|
751
|
-
const cols = () => fields.map(
|
|
752
|
-
([name, field], i) => node(
|
|
753
|
-
"column",
|
|
754
|
-
field.column,
|
|
755
|
-
`type: plainText
|
|
756
|
-
${group("heading", `heading: ${name}`)}
|
|
757
|
-
${group("layout", `sequence: ${(i + 1) * 10}`)}
|
|
758
|
-
${group("source", `dataType: ${["integer", "decimal"].includes(field.type) ? "NUMBER" : ["date", "timestamp"].includes(field.type) ? "DATE" : "STRING"}`)}`
|
|
759
|
-
)
|
|
760
|
-
).join("");
|
|
761
|
-
const list = (region2, predicateSQL, sequence, link = false) => node(
|
|
762
|
-
"region",
|
|
763
|
-
region2,
|
|
764
|
-
`name: ${link ? "Master records" : "Related records"}
|
|
765
|
-
type: interactiveReport
|
|
766
|
-
${source(`select ${fields.map(([, f]) => f.column).join(", ")} from ${entity.read.object} where (${predicate}) and ${predicateSQL}`)}
|
|
767
|
-
${layout(sequence)}
|
|
768
|
-
${appearance("interactive-report")}
|
|
769
|
-
${link ? group("link", `linkColumn: customTarget
|
|
770
|
-
target: {
|
|
771
|
-
page: ${allocation.page}
|
|
772
|
-
items: {
|
|
773
|
-
${selected}: #${key.column}#
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
linkIcon: View`) : ""}
|
|
777
|
-
${cols()}`
|
|
778
|
-
);
|
|
779
|
-
const hidden = node(
|
|
780
|
-
"pageItem",
|
|
781
|
-
selected,
|
|
782
|
-
`type: hidden
|
|
783
|
-
${group("layout", `sequence: 1
|
|
784
|
-
region: @${master}
|
|
785
|
-
slot: regionBody`)}
|
|
786
|
-
${group("security", "sessionStateProtection: checksumRequiredSessionLevel")}`
|
|
787
|
-
);
|
|
788
|
-
return {
|
|
789
|
-
[file(allocation.page)]: page(
|
|
790
|
-
allocation,
|
|
791
|
-
instance.parameters.title,
|
|
792
|
-
list(master, `${parent.column} is null`, 10, true) + hidden + list(detail, `${parent.column}=:${selected}`, 20)
|
|
793
|
-
)
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
const region = allocation.prefix + "-records", filterItem = `P${allocation.page}_FILTER`;
|
|
797
|
-
const where = instance.parameters.filterField ? `${predicate} and (${entity.read.fields[instance.parameters.filterField].column} = :${filterItem} or :${filterItem} is null)` : predicate;
|
|
798
|
-
const columns = fields.map(
|
|
799
|
-
([name, field], i) => node(
|
|
800
|
-
"column",
|
|
801
|
-
field.column,
|
|
802
|
-
`type: ${keys.includes(name) && writable ? "hidden" : "plainText"}
|
|
803
|
-
${group("heading", `heading: ${scalar(name)}`)}
|
|
804
|
-
${group("layout", `sequence: ${(i + 1) * 10}`)}
|
|
805
|
-
${group("source", `dataType: ${["integer", "decimal"].includes(field.type) ? "NUMBER" : ["date", "timestamp"].includes(field.type) ? "DATE" : "STRING"}`)}`
|
|
806
|
-
)
|
|
807
|
-
).join("\n");
|
|
808
|
-
const report = node(
|
|
809
|
-
"region",
|
|
810
|
-
region,
|
|
811
|
-
[
|
|
812
|
-
`name: ${scalar(instance.parameters.title)}`,
|
|
813
|
-
"type: interactiveReport",
|
|
814
|
-
source(
|
|
815
|
-
`select ${fields.map(([, f]) => f.column).join(", ")} from ${entity.read.object} where ${where}`,
|
|
816
|
-
instance.parameters.filterField ? filterItem : void 0
|
|
817
|
-
),
|
|
818
|
-
layout(10),
|
|
819
|
-
appearance("interactive-report"),
|
|
820
|
-
group("advanced", `htmlDomId: ${allocation.prefix}_records`),
|
|
821
|
-
writable && instance.parameters.editEnabled ? group(
|
|
822
|
-
"link",
|
|
823
|
-
`linkColumn: customTarget
|
|
824
|
-
target: {
|
|
825
|
-
page: ${allocation.dialog}
|
|
826
|
-
items: {
|
|
827
|
-
P${allocation.dialog}_${keys[0].toUpperCase()}: #${entity.read.fields[keys[0]].column}#
|
|
828
|
-
}
|
|
829
|
-
clearCache: ${allocation.dialog}
|
|
830
|
-
}
|
|
831
|
-
linkIcon: <span class="fa fa-edit" aria-label="Edit"></span>`
|
|
832
|
-
) : "",
|
|
833
|
-
columns
|
|
834
|
-
].filter(Boolean).join("\n")
|
|
835
|
-
);
|
|
836
|
-
let body = report;
|
|
837
|
-
if (instance.parameters.filterField) {
|
|
838
|
-
const filter = instance.parameters.filterField;
|
|
839
|
-
body += node(
|
|
840
|
-
"pageItem",
|
|
841
|
-
filterItem,
|
|
842
|
-
`type: textField
|
|
843
|
-
${group("label", `label: ${filter}`)}
|
|
844
|
-
${group("layout", `sequence: 5
|
|
845
|
-
region: @${region}
|
|
846
|
-
slot: regionBody`)}
|
|
847
|
-
${group("appearance", "template: @/optional-floating\ntemplateOptions: #DEFAULT#")}`
|
|
848
|
-
);
|
|
849
|
-
body += dynamic(
|
|
850
|
-
allocation.prefix + "-filter",
|
|
851
|
-
"change",
|
|
852
|
-
`selectionType: items
|
|
853
|
-
items: ${filterItem}`,
|
|
854
|
-
refresh("refresh", region, 10)
|
|
855
|
-
);
|
|
856
|
-
}
|
|
857
|
-
if (writable && instance.parameters.createEnabled)
|
|
858
|
-
body += button(
|
|
859
|
-
allocation.prefix + "-create",
|
|
860
|
-
"Create",
|
|
861
|
-
region,
|
|
862
|
-
`action: redirectThisApp
|
|
863
|
-
target: {
|
|
864
|
-
page: ${allocation.dialog}
|
|
865
|
-
clearCache: ${allocation.dialog}
|
|
866
|
-
}`
|
|
867
|
-
);
|
|
868
|
-
for (const summary of summaries) body += summaryRegion(blueprint, summary.instance, summary.allocation);
|
|
869
|
-
if (writable) {
|
|
870
|
-
const targets = [
|
|
871
|
-
allocation.prefix + "_records",
|
|
872
|
-
...summaries.map((s) => s.allocation.prefix + "_summary")
|
|
873
|
-
];
|
|
874
|
-
const refreshes = jsAction(
|
|
875
|
-
"refresh-bound-regions",
|
|
876
|
-
`var e=this.data; if(!e||e.originInstance!==${JSON.stringify(id)}||e.entityRef!==${JSON.stringify(binding.entityRef)}||!e.recordKey||!e.recordVersion||!e.correlationId||!['create','edit'].includes(e.operation)) return; var host=document.getElementById(${JSON.stringify(allocation.prefix + "_records")}); if(host.dataset.composerCorrelation===e.correlationId) return; host.dataset.composerCorrelation=e.correlationId; ${JSON.stringify(targets)}.forEach(function(id){var region=apex.region(id); if(region) region.refresh();});`
|
|
877
|
-
);
|
|
878
|
-
body += dynamic(
|
|
879
|
-
allocation.prefix + "-saved",
|
|
880
|
-
"apexafterclosedialog",
|
|
881
|
-
`selectionType: region
|
|
882
|
-
region: @${region}`,
|
|
883
|
-
refreshes
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
const result = {
|
|
887
|
-
[file(allocation.page)]: page(allocation, instance.parameters.title, body)
|
|
888
|
-
};
|
|
889
|
-
if (!writable || !command || !allocation.dialog) return result;
|
|
890
|
-
const dialog = allocation.dialog, version = entity.capabilities.optimisticLock.field;
|
|
891
|
-
const itemName = (field) => `P${dialog}_${field.toUpperCase()}`;
|
|
892
|
-
const numberFormat = "99999999999999999999999999999999999999D99999999999999999999999999999999999999";
|
|
893
|
-
const inputExpression = (field) => {
|
|
894
|
-
const spec = entity.read.fields[field], value = ":" + itemName(field);
|
|
895
|
-
return ["integer", "decimal"].includes(spec.type) ? `to_number(${value},'${numberFormat}','NLS_NUMERIC_CHARACTERS=''.,''')` : spec.type === "date" ? `to_date(${value},'FXYYYY-MM-DD')` : spec.type === "timestamp" ? `to_timestamp(${value},'FXYYYY-MM-DD"T"HH24:MI:SS.FF6')` : value;
|
|
896
|
-
};
|
|
897
|
-
const readExpression = (field) => {
|
|
898
|
-
const spec = entity.read.fields[field];
|
|
899
|
-
return ["integer", "decimal"].includes(spec.type) ? `to_char(${spec.column},'TM9','NLS_NUMERIC_CHARACTERS=''.,''')` : spec.type === "date" ? `to_char(${spec.column},'YYYY-MM-DD')` : spec.type === "timestamp" ? `to_char(${spec.column},'YYYY-MM-DD"T"HH24:MI:SS.FF6')` : spec.column;
|
|
900
|
-
};
|
|
901
|
-
const validation = instance.parameters.editableFields.map((field) => {
|
|
902
|
-
const spec = entity.read.fields[field], value = ":" + itemName(field), checks = [];
|
|
903
|
-
if (!spec.nullable) checks.push(`${value} is null`);
|
|
904
|
-
if (spec.maxLength) checks.push(`length(${value})>${spec.maxLength}`);
|
|
905
|
-
if (spec.enum?.length)
|
|
906
|
-
checks.push(
|
|
907
|
-
`${value} not in (${spec.enum.map((v) => "'" + v.replaceAll("'", "''") + "'").join(", ")})`
|
|
908
|
-
);
|
|
909
|
-
if (["integer", "decimal"].includes(spec.type))
|
|
910
|
-
checks.push(
|
|
911
|
-
`${value} is not null and not regexp_like(${value},'${spec.type === "integer" ? "^[+-]?[0-9]+$" : "^[+-]?[0-9]+([.][0-9]+)?$"}')`
|
|
912
|
-
);
|
|
913
|
-
return checks.length ? `if ${checks.map((c) => "(" + c + ")").join(" or ")} then raise_application_error(-20002,'Invalid ${field}'); end if;` : "";
|
|
914
|
-
}).join("\n ");
|
|
915
|
-
let form = node(
|
|
916
|
-
"region",
|
|
917
|
-
"form",
|
|
918
|
-
`name: ${scalar(instance.parameters.title)}
|
|
919
|
-
type: staticContent
|
|
920
|
-
${layout(10, "contentBody")}
|
|
921
|
-
${appearance("standard")}`
|
|
922
|
-
);
|
|
923
|
-
form += node(
|
|
924
|
-
"region",
|
|
925
|
-
"buttons",
|
|
926
|
-
`name: Actions
|
|
927
|
-
type: staticContent
|
|
928
|
-
${layout(20, "dialogFooter")}
|
|
929
|
-
${appearance("buttons-container")}`
|
|
930
|
-
);
|
|
931
|
-
for (const [field, spec] of fields)
|
|
932
|
-
if (instance.parameters.editableFields.includes(field) || keys.includes(field) || field === version)
|
|
933
|
-
form += item(
|
|
934
|
-
itemName(field),
|
|
935
|
-
field,
|
|
936
|
-
fields.findIndex(([n]) => n === field) * 10 + 10,
|
|
937
|
-
!instance.parameters.editableFields.includes(field),
|
|
938
|
-
!spec.nullable
|
|
939
|
-
);
|
|
940
|
-
form += button("save", "Save", "buttons", "action: definedByDynamicAction");
|
|
941
|
-
form += button("cancel", "Cancel", "buttons", "action: definedByDynamicAction");
|
|
942
|
-
form += dynamic(
|
|
943
|
-
"cancel-dialog",
|
|
944
|
-
"click",
|
|
945
|
-
"selectionType: button\nbutton: @cancel",
|
|
946
|
-
node(
|
|
947
|
-
"action",
|
|
948
|
-
"cancel",
|
|
949
|
-
"action: cancelDialog\n" + group("execution", "sequence: 10\nfireOnInit: false")
|
|
950
|
-
)
|
|
951
|
-
);
|
|
952
|
-
const mappedFields = fields.filter(
|
|
953
|
-
([field]) => instance.parameters.editableFields.includes(field) || keys.includes(field) || field === version
|
|
954
|
-
);
|
|
955
|
-
form += process2(
|
|
956
|
-
allocation.prefix + "-read",
|
|
957
|
-
"beforeHeader",
|
|
958
|
-
`begin
|
|
959
|
-
if :${itemName(keys[0])} is not null then
|
|
960
|
-
select ${mappedFields.map(([f]) => readExpression(f)).join(", ")} into ${mappedFields.map(([f]) => ":" + itemName(f)).join(", ")} from ${entity.read.object} where ${entity.read.fields[keys[0]].column} = :${itemName(keys[0])} and (${predicate});
|
|
961
|
-
end if;
|
|
962
|
-
end;`
|
|
963
|
-
);
|
|
964
|
-
const variables = /* @__PURE__ */ new Map();
|
|
965
|
-
for (const [argument, mapping] of Object.entries(command.inputs).sort(([a], [b]) => a < b ? -1 : 1))
|
|
966
|
-
variables.set(argument, mapping.mode === "in-out" ? "l_key" : inputExpression(mapping.from.slice(7)));
|
|
967
|
-
variables.set(command.outputs.recordKey.from, "l_key");
|
|
968
|
-
variables.set(command.outputs.recordVersion.from, "l_version");
|
|
969
|
-
const saveName = allocation.prefix + "_SAVE";
|
|
970
|
-
const server = `declare
|
|
971
|
-
l_key ${entity.read.object}.${entity.read.fields[keys[0]].column}%type;
|
|
972
|
-
l_authorized boolean;
|
|
973
|
-
l_version ${entity.read.object}.${entity.read.fields[version].column}%type;
|
|
974
|
-
begin
|
|
975
|
-
savepoint composer_save;
|
|
976
|
-
l_authorized := (${binding.writeExpression});
|
|
977
|
-
if l_authorized is null or not l_authorized or not apex_authentication.is_authenticated then raise_application_error(-20001, 'Authorization denied'); end if;
|
|
978
|
-
l_key := ${inputExpression(keys[0])};
|
|
979
|
-
if apex_application.g_x01 = 'create' then
|
|
980
|
-
if ${instance.parameters.createEnabled ? "false" : "true"} or l_key is not null or :${itemName(version)} is not null then raise_application_error(-20002, 'Invalid create draft'); end if;
|
|
981
|
-
elsif apex_application.g_x01 = 'edit' then
|
|
982
|
-
if ${instance.parameters.editEnabled ? "false" : "true"} or l_key is null or :${itemName(version)} is null then raise_application_error(-20002, 'Invalid edit draft'); end if;
|
|
983
|
-
else raise_application_error(-20002, 'Invalid operation'); end if;
|
|
984
|
-
${validation}
|
|
985
|
-
${instance.extensions.beforeSaveValidation ?? ""}
|
|
986
|
-
${command.package}.${command.procedure}(${[...variables].map(([argument, value]) => `${argument} => ${value}`).join(", ")});
|
|
987
|
-
if l_key is null or l_version is null then raise_application_error(-20004, 'API output contract violated'); end if;
|
|
988
|
-
${instance.extensions.afterSaveNotification ?? ""}
|
|
989
|
-
apex_json.open_object; apex_json.write('ok',true); apex_json.write('recordKey',${["integer", "decimal"].includes(entity.read.fields[keys[0]].type) ? "to_char(l_key,'TM9','NLS_NUMERIC_CHARACTERS=''.,''')" : "l_key"}); apex_json.write('recordVersion',to_char(l_version,'TM9','NLS_NUMERIC_CHARACTERS=''.,''')); apex_json.close_object;
|
|
990
|
-
exception when others then
|
|
991
|
-
rollback to composer_save;
|
|
992
|
-
apex_json.open_object; apex_json.write('ok',false); apex_json.write('code',case sqlcode when -20001 then 'authorization' when -20002 then 'validation' when -20003 then 'conflict' else 'server-error' end); apex_json.write('message','Save failed. Review fields and reload after a conflict.'); apex_json.close_object;
|
|
993
|
-
end;`;
|
|
994
|
-
form += process2(saveName, "ajaxCallback", server);
|
|
995
|
-
const pageItems = mappedFields.map(([f]) => "#" + itemName(f)).join(",");
|
|
996
|
-
const js = `var button = this.triggeringElement; if (button.disabled) return; button.disabled = true;
|
|
997
|
-
apex.server.process(${JSON.stringify(saveName)}, {x01: apex.item(${JSON.stringify(itemName(keys[0]))}).getValue() ? 'edit' : 'create', pageItems: ${JSON.stringify(pageItems)}}, {dataType: 'json', success: function(data) { if (data.ok) { apex.navigation.dialog.close(true, {entityRef: ${JSON.stringify(binding.entityRef)}, recordKey: data.recordKey, recordVersion: data.recordVersion, operation: apex.item(${JSON.stringify(itemName(keys[0]))}).getValue() ? 'edit' : 'create', originInstance: ${JSON.stringify(id)}, correlationId: crypto.randomUUID()}); } else { apex.message.showErrors([{type:'error',location:'page',message:data.message,unsafe:false}]); } }, error: function() {apex.message.showErrors([{type:'error',location:'page',message:'Save request failed.',unsafe:false}]);}, complete: function() {button.disabled = false;} });`;
|
|
998
|
-
form += dynamic("save-dialog", "click", "selectionType: button\nbutton: @save", jsAction("save-api", js));
|
|
999
|
-
result[file(dialog)] = page(allocation, instance.parameters.title, form, true);
|
|
1000
|
-
return result;
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// packages/core/src/composer/planner.ts
|
|
1004
|
-
async function snapshot(ctx, blueprintPath, options = {}) {
|
|
1005
|
-
const root = await safePath(ctx.root, ctx.config.application.sourceDir), sourceInventory = await inventory(root);
|
|
1006
|
-
const sources = {};
|
|
1007
|
-
for (const file of Object.keys(sourceInventory).filter((file2) => file2.endsWith(".apx"))) {
|
|
1008
|
-
const bytes = await readFile(await safePath(root, file)), text = bytes.toString("utf8");
|
|
1009
|
-
if (!Buffer.from(text).equals(bytes))
|
|
1010
|
-
throw new Fault("TRANSFORM_UNSUPPORTED", "Source must be valid UTF-8.", 5);
|
|
1011
|
-
sources[file] = text;
|
|
1012
|
-
}
|
|
1013
|
-
const stateFile = await safePath(ctx.root, ".apexrest-composer/state.json");
|
|
1014
|
-
const state = await exists(stateFile) ? await readDocument(ctx.root, ".apexrest-composer/state.json", stateSchema) : null;
|
|
1015
|
-
const bases = {};
|
|
1016
|
-
const baseRoot = await safePath(ctx.root, ".apexrest-composer/bases");
|
|
1017
|
-
if (await exists(baseRoot))
|
|
1018
|
-
for (const entry of await readdir(baseRoot)) {
|
|
1019
|
-
if (!/^[a-f0-9]{64}\.apx$/.test(entry))
|
|
1020
|
-
throw new Fault("GENERATION_BASE_CORRUPT", "Unexpected generated base entry.", 5);
|
|
1021
|
-
const bytes = await readFile(await safePath(baseRoot, entry)), digest2 = entry.slice(0, -4);
|
|
1022
|
-
if (hash(bytes) !== digest2)
|
|
1023
|
-
throw new Fault("GENERATION_BASE_CORRUPT", "Generated base integrity failed.", 5);
|
|
1024
|
-
bases[digest2] = bytes.toString("utf8");
|
|
1025
|
-
}
|
|
1026
|
-
if (state) {
|
|
1027
|
-
for (const owner of Object.values(state.owners))
|
|
1028
|
-
for (const digest2 of Object.values(owner.bases))
|
|
1029
|
-
if (!(digest2 in bases))
|
|
1030
|
-
throw new Fault("GENERATION_BASE_MISSING", "Previous generated base is unavailable.", 5);
|
|
1031
|
-
}
|
|
1032
|
-
const blueprint = await readDocument(ctx.root, blueprintPath, blueprintSchema);
|
|
1033
|
-
const mode = options.mode ?? "offline";
|
|
1034
|
-
if (mode === "connected" && (!options.environment || !options.metadata))
|
|
1035
|
-
throw new Fault(
|
|
1036
|
-
"ENVIRONMENT_REQUIRED",
|
|
1037
|
-
"Connected planning requires explicit environment and metadata.",
|
|
1038
|
-
2
|
|
1039
|
-
);
|
|
1040
|
-
if (options.environment && blueprint.application.environment && options.environment !== blueprint.application.environment)
|
|
1041
|
-
throw new Fault("ENVIRONMENT_MISMATCH", "Blueprint and requested environment differ.", 5);
|
|
1042
|
-
return {
|
|
1043
|
-
blueprint,
|
|
1044
|
-
blueprintPath,
|
|
1045
|
-
catalog: await loadCatalog(ctx.root),
|
|
1046
|
-
state,
|
|
1047
|
-
sources,
|
|
1048
|
-
sourceInventory,
|
|
1049
|
-
bases,
|
|
1050
|
-
configurationDigest: semanticDigest(ctx.config),
|
|
1051
|
-
toolchainDigest: hash(await readFile(await safePath(ctx.root, ctx.config.toolchain.lockFile))),
|
|
1052
|
-
sourceDir: ctx.config.application.sourceDir,
|
|
1053
|
-
projectId: ctx.config.projectId,
|
|
1054
|
-
mode,
|
|
1055
|
-
environment: options.environment ?? null,
|
|
1056
|
-
metadata: options.metadata ?? null,
|
|
1057
|
-
validation: options.validation ?? "compiler"
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
function planComposition(input) {
|
|
1061
|
-
const { blueprint, state, catalog } = input;
|
|
1062
|
-
const diagnostics = [], operations = [];
|
|
1063
|
-
const blueprintDigest = semanticDigest(blueprint);
|
|
1064
|
-
const plan = {
|
|
1065
|
-
schemaVersion: 1,
|
|
1066
|
-
generatorVersion: "1",
|
|
1067
|
-
kind: "composition",
|
|
1068
|
-
status: "blocked",
|
|
1069
|
-
projectId: input.projectId,
|
|
1070
|
-
blueprintPath: input.blueprintPath,
|
|
1071
|
-
blueprintDigest,
|
|
1072
|
-
catalogDigest: catalog.digest,
|
|
1073
|
-
configurationDigest: input.configurationDigest,
|
|
1074
|
-
toolchainDigest: input.toolchainDigest,
|
|
1075
|
-
sourceInventory: input.sourceInventory,
|
|
1076
|
-
stateDigest: state ? semanticDigest(state) : null,
|
|
1077
|
-
validation: input.validation,
|
|
1078
|
-
mode: input.mode,
|
|
1079
|
-
contextDigest: semanticDigest(
|
|
1080
|
-
input.metadata ?? { assumptions: blueprint.application.compatibilityProfile }
|
|
1081
|
-
),
|
|
1082
|
-
environment: input.environment,
|
|
1083
|
-
review: {
|
|
1084
|
-
entities: blueprint.entities,
|
|
1085
|
-
commands: blueprint.commands,
|
|
1086
|
-
contracts: blueprint.contracts,
|
|
1087
|
-
packages: {}
|
|
1088
|
-
},
|
|
1089
|
-
allocations: {},
|
|
1090
|
-
operations,
|
|
1091
|
-
state: null,
|
|
1092
|
-
lock: null,
|
|
1093
|
-
diagnostics,
|
|
1094
|
-
digest: "0".repeat(64)
|
|
1095
|
-
};
|
|
1096
|
-
try {
|
|
1097
|
-
let visit = function(id) {
|
|
1098
|
-
if (visiting.has(id)) throw new Fault("INTERACTION_CYCLE", "Runtime event cycles are unsupported.", 5);
|
|
1099
|
-
if (visited.has(id)) return;
|
|
1100
|
-
visiting.add(id);
|
|
1101
|
-
for (const target of graph.get(id) ?? []) visit(target);
|
|
1102
|
-
visiting.delete(id);
|
|
1103
|
-
visited.add(id);
|
|
1104
|
-
};
|
|
1105
|
-
if (Object.keys(blueprint.blocks).length > 256)
|
|
1106
|
-
throw new Fault("INSTANCE_LIMIT", "At most 256 instances are supported.", 2);
|
|
1107
|
-
const packages = resolvePackages(
|
|
1108
|
-
catalog,
|
|
1109
|
-
Object.values(blueprint.blocks).map((b) => b.use),
|
|
1110
|
-
blueprint.application.compatibilityProfile
|
|
1111
|
-
);
|
|
1112
|
-
plan.review.packages = Object.fromEntries(
|
|
1113
|
-
[...packages].map(([id, p]) => [
|
|
1114
|
-
id,
|
|
1115
|
-
{
|
|
1116
|
-
digest: p.digest,
|
|
1117
|
-
origin: p.manifest.origin,
|
|
1118
|
-
license: p.manifest.license,
|
|
1119
|
-
effects: p.manifest.effects,
|
|
1120
|
-
dependencies: p.manifest.requires.blocks
|
|
1121
|
-
}
|
|
1122
|
-
])
|
|
1123
|
-
);
|
|
1124
|
-
for (const pkg of packages.values())
|
|
1125
|
-
if (pkg.manifest.status === "deprecated")
|
|
1126
|
-
diagnostics.push({
|
|
1127
|
-
code: "BLOCK_DEPRECATED",
|
|
1128
|
-
severity: "warning",
|
|
1129
|
-
message: "Selected exact block is deprecated; review replacement separately."
|
|
1130
|
-
});
|
|
1131
|
-
const symbols = inventorySymbols(input.sources), used = new Set(symbols.pages), prefixes = /* @__PURE__ */ new Set();
|
|
1132
|
-
const allocated = {};
|
|
1133
|
-
const nextPage = () => {
|
|
1134
|
-
for (let page2 = 100; page2 < 9999; page2++)
|
|
1135
|
-
if (!used.has(page2)) {
|
|
1136
|
-
used.add(page2);
|
|
1137
|
-
return page2;
|
|
1138
|
-
}
|
|
1139
|
-
throw new Fault("ALLOCATION_EXHAUSTED", "No available page identity.", 5);
|
|
1140
|
-
};
|
|
1141
|
-
for (const [id, instance] of Object.entries(blueprint.blocks).sort(([a], [b]) => a < b ? -1 : 1)) {
|
|
1142
|
-
const binding = bind(blueprint, instance, input.metadata ?? void 0);
|
|
1143
|
-
if (Object.keys(instance.extensions).length && instance.ownership !== "extended")
|
|
1144
|
-
throw new Fault("EXTENSION_MODE_REQUIRED", "Extension hooks require explicit extended ownership.", 5);
|
|
1145
|
-
for (const source2 of Object.values(instance.extensions))
|
|
1146
|
-
if (/```|\b(?:commit|rollback|grant|revoke|host|connect|execute\s+immediate)\b/i.test(source2))
|
|
1147
|
-
throw new Fault(
|
|
1148
|
-
"EXTENSION_UNSAFE",
|
|
1149
|
-
"Extension code must preserve caller-owned transactions and literal boundaries.",
|
|
1150
|
-
5
|
|
1151
|
-
);
|
|
1152
|
-
const previous = state?.owners[id], prefix = previous?.allocation.prefix ?? "cmp_" + id.toLowerCase().replaceAll("-", "_").slice(0, 24) + "_" + hash(id).slice(0, 8);
|
|
1153
|
-
if (prefixes.has(prefix.toUpperCase()) || !previous && symbols.symbols.has(prefix.toUpperCase()))
|
|
1154
|
-
throw new Fault("SYMBOL_COLLISION", "A block namespace collides with existing source.", 5);
|
|
1155
|
-
prefixes.add(prefix.toUpperCase());
|
|
1156
|
-
if (previous)
|
|
1157
|
-
for (const file of Object.keys(previous.files)) {
|
|
1158
|
-
if (!input.sources[file])
|
|
1159
|
-
throw new Fault(
|
|
1160
|
-
"OWNED_SOURCE_MISSING",
|
|
1161
|
-
"Owned source is missing; explicit adoption is required.",
|
|
1162
|
-
5
|
|
1163
|
-
);
|
|
1164
|
-
const page2 = declarations(input.sources[file]).find((n) => n.kind === "page" && n.depth === 0);
|
|
1165
|
-
if (!page2 || ![previous.allocation.page, previous.allocation.dialog].includes(Number(page2.key)))
|
|
1166
|
-
throw new Fault("OWNED_IDENTITY_CHANGED", "Owned page identity changed.", 5);
|
|
1167
|
-
}
|
|
1168
|
-
allocated[id] = {
|
|
1169
|
-
page: previous?.allocation.page ?? nextPage(),
|
|
1170
|
-
dialog: binding.writable ? previous?.allocation.dialog ?? nextPage() : null,
|
|
1171
|
-
prefix
|
|
1172
|
-
};
|
|
1173
|
-
const pkg = packages.get(instance.use);
|
|
1174
|
-
if (binding.writable && pkg.manifest.renderer !== "report-dialog")
|
|
1175
|
-
throw new Fault(
|
|
1176
|
-
"BLOCK_WRITE_UNSUPPORTED",
|
|
1177
|
-
"Only the report-dialog adapter implements create/edit commands.",
|
|
1178
|
-
5
|
|
1179
|
-
);
|
|
1180
|
-
const providers = /* @__PURE__ */ new Set([
|
|
1181
|
-
"key",
|
|
1182
|
-
"readAuthorization",
|
|
1183
|
-
...binding.writable ? ["optimisticLock", "writeAuthorization"] : []
|
|
1184
|
-
]);
|
|
1185
|
-
for (const capability of pkg.manifest.requires.capabilities)
|
|
1186
|
-
if (!providers.has(capability) && !(pkg.manifest.renderer === "report-dialog" && !binding.writable && ["optimisticLock", "writeAuthorization"].includes(capability)))
|
|
1187
|
-
throw new Fault("CAPABILITY_MISSING", "A required block capability has no reviewed provider.", 5);
|
|
1188
|
-
}
|
|
1189
|
-
const hosts = /* @__PURE__ */ new Map(), graph = /* @__PURE__ */ new Map();
|
|
1190
|
-
for (const connection of blueprint.connections) {
|
|
1191
|
-
const from = connection.from.match(/^([\w-]+)\.events\.saved$/), to = connection.to.match(/^([\w-]+)\.actions\.refresh$/);
|
|
1192
|
-
if (!from || !to || !blueprint.blocks[from[1]] || !blueprint.blocks[to[1]])
|
|
1193
|
-
throw new Fault("CONNECTION_INVALID", "Unknown or unsupported event/action port.", 5);
|
|
1194
|
-
const publisher = blueprint.blocks[from[1]], consumer = blueprint.blocks[to[1]];
|
|
1195
|
-
if (!bind(blueprint, publisher).writable || packages.get(publisher.use).manifest.renderer !== "report-dialog" || packages.get(consumer.use).manifest.renderer !== "status-summary" || publisher.bindings.records !== consumer.bindings.records)
|
|
1196
|
-
throw new Fault(
|
|
1197
|
-
"CONNECTION_CONTRACT_MISMATCH",
|
|
1198
|
-
"Saved/refresh connections require a writable report and summary of the same row scope.",
|
|
1199
|
-
5
|
|
1200
|
-
);
|
|
1201
|
-
if (hosts.has(to[1]))
|
|
1202
|
-
throw new Fault("CONNECTION_DUPLICATE", "A summary can have one explicit host.", 5);
|
|
1203
|
-
hosts.set(to[1], from[1]);
|
|
1204
|
-
graph.set(from[1], [...graph.get(from[1]) ?? [], to[1]]);
|
|
1205
|
-
}
|
|
1206
|
-
const visiting = /* @__PURE__ */ new Set(), visited = /* @__PURE__ */ new Set();
|
|
1207
|
-
for (const id of graph.keys()) visit(id);
|
|
1208
|
-
for (const [id, previous] of Object.entries(state?.owners ?? {})) {
|
|
1209
|
-
for (const host of previous.consumers) {
|
|
1210
|
-
const childDetached = blueprint.blocks[id]?.ownership === "detached" || previous.mode === "detached";
|
|
1211
|
-
const hostDetached = blueprint.blocks[host]?.ownership === "detached" || state?.owners[host]?.mode === "detached";
|
|
1212
|
-
if (childDetached !== hostDetached)
|
|
1213
|
-
throw new Fault(
|
|
1214
|
-
"SHARED_OWNERSHIP_DETACH_REQUIRED",
|
|
1215
|
-
"Detach the hosted summary and its page owner together to preserve shared source.",
|
|
1216
|
-
5
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
for (const [child, host] of hosts)
|
|
1221
|
-
if (blueprint.blocks[child].ownership === "detached" || blueprint.blocks[host].ownership === "detached")
|
|
1222
|
-
throw new Fault(
|
|
1223
|
-
"DETACHED_CONNECTION",
|
|
1224
|
-
"Remove managed connections when detaching both connected instances.",
|
|
1225
|
-
5
|
|
1226
|
-
);
|
|
1227
|
-
for (const [id, previous] of Object.entries(state?.owners ?? {}).sort(([a], [b]) => a < b ? -1 : 1))
|
|
1228
|
-
if (previous.consumers.length && blueprint.blocks[id] && blueprint.blocks[id].ownership !== "detached" && !hosts.has(id))
|
|
1229
|
-
allocated[id].page = nextPage();
|
|
1230
|
-
for (const [child, host] of hosts) allocated[child].page = allocated[host].page;
|
|
1231
|
-
plan.allocations = allocated;
|
|
1232
|
-
const desired = {}, owners = {};
|
|
1233
|
-
for (const [id, instance] of Object.entries(blueprint.blocks).sort(([a], [b]) => a < b ? -1 : 1)) {
|
|
1234
|
-
const pkg = packages.get(instance.use), previous = state?.owners[id];
|
|
1235
|
-
if (instance.ownership === "detached") {
|
|
1236
|
-
if (!previous)
|
|
1237
|
-
throw new Fault("DETACH_UNOWNED", "Only an existing owned instance can be detached.", 5);
|
|
1238
|
-
owners[id] = { ...previous, mode: "detached" };
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
1241
|
-
if (previous?.mode === "detached")
|
|
1242
|
-
throw new Fault(
|
|
1243
|
-
"REATTACH_REQUIRES_ADOPTION",
|
|
1244
|
-
"Detached instances need explicit reviewed adoption.",
|
|
1245
|
-
5
|
|
1246
|
-
);
|
|
1247
|
-
const summaries = [...hosts].filter(([, host]) => host === id).map(([child]) => ({ instance: blueprint.blocks[child], allocation: allocated[child] }));
|
|
1248
|
-
const files = hosts.has(id) ? {} : render(blueprint, id, instance, pkg.manifest, allocated[id], summaries);
|
|
1249
|
-
for (const [file, source2] of Object.entries(files)) {
|
|
1250
|
-
if (desired[file])
|
|
1251
|
-
throw new Fault("OWNERSHIP_COLLISION", "Two instances own the same source file.", 5);
|
|
1252
|
-
desired[file] = source2;
|
|
1253
|
-
}
|
|
1254
|
-
owners[id] = {
|
|
1255
|
-
instanceId: id,
|
|
1256
|
-
blockId: pkg.manifest.id,
|
|
1257
|
-
version: pkg.manifest.version,
|
|
1258
|
-
mode: instance.ownership,
|
|
1259
|
-
allocation: allocated[id],
|
|
1260
|
-
files: {},
|
|
1261
|
-
bases: {},
|
|
1262
|
-
consumers: hosts.has(id) ? [hosts.get(id)] : [],
|
|
1263
|
-
provenance: semanticDigest({
|
|
1264
|
-
package: pkg.digest,
|
|
1265
|
-
instance,
|
|
1266
|
-
bindings: blueprint.entities,
|
|
1267
|
-
commands: blueprint.commands,
|
|
1268
|
-
contracts: blueprint.contracts
|
|
1269
|
-
})
|
|
1270
|
-
};
|
|
1271
|
-
for (const [file, source2] of Object.entries(files)) {
|
|
1272
|
-
owners[id].files[file] = hash(source2);
|
|
1273
|
-
owners[id].bases[file] = hash(source2);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
const oldFiles = /* @__PURE__ */ new Map();
|
|
1277
|
-
if (state)
|
|
1278
|
-
for (const owner of Object.values(state.owners))
|
|
1279
|
-
for (const file of Object.keys(owner.files)) {
|
|
1280
|
-
if (oldFiles.has(file))
|
|
1281
|
-
throw new Fault("OWNERSHIP_COLLISION", "Ambiguous previous file ownership.", 5);
|
|
1282
|
-
oldFiles.set(file, owner);
|
|
1283
|
-
}
|
|
1284
|
-
const effectiveSources = { ...input.sources };
|
|
1285
|
-
for (const [file, next] of Object.entries(desired)) {
|
|
1286
|
-
const local = input.sources[file], old = oldFiles.get(file);
|
|
1287
|
-
let content = next;
|
|
1288
|
-
if (local !== void 0 && !old)
|
|
1289
|
-
throw new Fault("UNMANAGED_COLLISION", "A generated path already contains unmanaged source.", 5);
|
|
1290
|
-
if (local !== void 0 && old) {
|
|
1291
|
-
const base = input.bases[old.bases[file]];
|
|
1292
|
-
if (base === void 0)
|
|
1293
|
-
throw new Fault("GENERATION_BASE_MISSING", "Generated base is unavailable.", 5);
|
|
1294
|
-
content = threeWay(base, local, next);
|
|
1295
|
-
}
|
|
1296
|
-
effectiveSources[file] = content;
|
|
1297
|
-
const owner = Object.values(owners).find((o) => file in o.files);
|
|
1298
|
-
owner.files[file] = hash(content);
|
|
1299
|
-
if (local !== content)
|
|
1300
|
-
operations.push({
|
|
1301
|
-
path: input.sourceDir + "/" + file,
|
|
1302
|
-
before: input.sourceInventory[file] ?? null,
|
|
1303
|
-
after: hash(content),
|
|
1304
|
-
content,
|
|
1305
|
-
reason: old ? "update-owned-source" : "create-owned-source"
|
|
1306
|
-
});
|
|
1307
|
-
const basePath = `.apexrest-composer/bases/${hash(next)}.apx`;
|
|
1308
|
-
if (!(hash(next) in input.bases))
|
|
1309
|
-
operations.push({
|
|
1310
|
-
path: basePath,
|
|
1311
|
-
before: null,
|
|
1312
|
-
after: hash(next),
|
|
1313
|
-
content: next,
|
|
1314
|
-
reason: "retain-generated-base"
|
|
1315
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
const removedFiles = new Set(
|
|
1318
|
-
[...oldFiles].filter(
|
|
1319
|
-
([file, owner]) => !(file in desired) && owners[owner.instanceId]?.mode !== "detached" && owner.mode !== "detached"
|
|
1320
|
-
).map(([file]) => file)
|
|
1321
|
-
);
|
|
1322
|
-
for (const [file, owner] of oldFiles)
|
|
1323
|
-
if (!(file in desired)) {
|
|
1324
|
-
const existing = owners[owner.instanceId];
|
|
1325
|
-
if (existing?.mode === "detached" || owner.mode === "detached") {
|
|
1326
|
-
if (!existing) owners[owner.instanceId] = owner;
|
|
1327
|
-
continue;
|
|
1328
|
-
}
|
|
1329
|
-
if (Object.values(state.owners).some(
|
|
1330
|
-
(o) => o.mode === "detached" && o.allocation.page === owner.allocation.page
|
|
1331
|
-
))
|
|
1332
|
-
throw new Fault("SHARED_CONSUMER_RETAINED", "A detached consumer still uses the owned page.", 5);
|
|
1333
|
-
if (input.sources[file] !== input.bases[owner.bases[file]])
|
|
1334
|
-
throw new Fault(
|
|
1335
|
-
"REMOVAL_CONFLICT",
|
|
1336
|
-
"Owned source has manual changes; detach instead of deleting.",
|
|
1337
|
-
5
|
|
1338
|
-
);
|
|
1339
|
-
const targetPage = declarations(input.sources[file]).find(
|
|
1340
|
-
(n) => n.kind === "page" && n.depth === 0
|
|
1341
|
-
)?.key;
|
|
1342
|
-
for (const [consumer, source2] of Object.entries(effectiveSources))
|
|
1343
|
-
if (consumer !== file && !removedFiles.has(consumer) && targetPage && (new RegExp("\\bpage:\\s*" + targetPage + "(?:\\s|$)").test(source2) || source2.includes(":" + targetPage + ":")))
|
|
1344
|
-
throw new Fault(
|
|
1345
|
-
"UNKNOWN_CONSUMER_RETAINED",
|
|
1346
|
-
"A remaining or unmanaged source still references the removed page.",
|
|
1347
|
-
5
|
|
1348
|
-
);
|
|
1349
|
-
operations.push({
|
|
1350
|
-
path: input.sourceDir + "/" + file,
|
|
1351
|
-
before: input.sourceInventory[file],
|
|
1352
|
-
after: null,
|
|
1353
|
-
content: null,
|
|
1354
|
-
reason: "remove-owned-source"
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
const lock = {
|
|
1358
|
-
schemaVersion: 1,
|
|
1359
|
-
generatorVersion: "1",
|
|
1360
|
-
resolverPolicyVersion: "1",
|
|
1361
|
-
blueprintSemanticDigest: blueprintDigest,
|
|
1362
|
-
catalogDigest: catalog.digest,
|
|
1363
|
-
compatibilityProfile: blueprint.application.compatibilityProfile,
|
|
1364
|
-
packages: Object.fromEntries([...packages].map(([key, p]) => [key, p.digest])),
|
|
1365
|
-
contractDigest: semanticDigest({
|
|
1366
|
-
entities: blueprint.entities,
|
|
1367
|
-
commands: blueprint.commands,
|
|
1368
|
-
contracts: blueprint.contracts
|
|
1369
|
-
})
|
|
1370
|
-
};
|
|
1371
|
-
const lockDigest = semanticDigest(lock), generationDigest = semanticDigest({ blueprintDigest, lockDigest, owners });
|
|
1372
|
-
plan.state = {
|
|
1373
|
-
schemaVersion: 1,
|
|
1374
|
-
generatorVersion: "1",
|
|
1375
|
-
generationDigest,
|
|
1376
|
-
blueprintDigest,
|
|
1377
|
-
lockDigest,
|
|
1378
|
-
owners
|
|
1379
|
-
};
|
|
1380
|
-
plan.lock = lock;
|
|
1381
|
-
plan.status = "materializable";
|
|
1382
|
-
if (input.validation === "source-only")
|
|
1383
|
-
diagnostics.push({
|
|
1384
|
-
code: "SOURCE_ONLY_DRAFT",
|
|
1385
|
-
severity: "warning",
|
|
1386
|
-
message: "Structural draft only; compiler and runtime qualification are unavailable."
|
|
1387
|
-
});
|
|
1388
|
-
diagnostics.push({
|
|
1389
|
-
code: "RUNTIME_NOT_RUN",
|
|
1390
|
-
severity: "info",
|
|
1391
|
-
message: "Composition does not establish live Oracle, import, authorization or browser evidence."
|
|
1392
|
-
});
|
|
1393
|
-
} catch (error) {
|
|
1394
|
-
if (!(error instanceof Fault)) throw error;
|
|
1395
|
-
operations.splice(0);
|
|
1396
|
-
plan.state = null;
|
|
1397
|
-
plan.lock = null;
|
|
1398
|
-
diagnostics.push({ code: error.code, severity: "error", message: error.message });
|
|
1399
|
-
}
|
|
1400
|
-
plan.operations = [...new Map(operations.map((op) => [op.path, op])).values()].sort(
|
|
1401
|
-
(a, b) => a.path < b.path ? -1 : 1
|
|
1402
|
-
);
|
|
1403
|
-
plan.digest = planDigest(plan);
|
|
1404
|
-
return validate(planSchema, plan);
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// packages/core/src/composer/service.ts
|
|
1408
|
-
var composePlanInput = external_exports.strictObject({
|
|
1409
|
-
project: external_exports.string().min(1).max(4096).optional(),
|
|
1410
|
-
blueprint: relativePath.default("app.blueprint.yaml"),
|
|
1411
|
-
out: relativePath,
|
|
1412
|
-
mode: external_exports.enum(["offline", "connected"]).default("offline"),
|
|
1413
|
-
env: refName.optional(),
|
|
1414
|
-
validation: external_exports.enum(["compiler", "source-only"]).default("compiler"),
|
|
1415
|
-
action: external_exports.enum(["compose", "recover-resume", "recover-restore"]).default("compose")
|
|
1416
|
-
});
|
|
1417
|
-
var composeMaterializeInput = external_exports.strictObject({
|
|
1418
|
-
project: external_exports.string().min(1).max(4096).optional(),
|
|
1419
|
-
plan: relativePath.optional(),
|
|
1420
|
-
artifactId: external_exports.uuid().optional(),
|
|
1421
|
-
expectedDigest: digest
|
|
1422
|
-
});
|
|
1423
|
-
async function context(ctx, blueprintFile, envName, oracle) {
|
|
1424
|
-
const env = environment(ctx, envName), connection = await resolveConnection(env.readConnectionRef);
|
|
1425
|
-
const blueprint = await readDocument(ctx.root, blueprintFile, blueprintSchema);
|
|
1426
|
-
const result = { schema: env.parsingSchema, objects: {}, signatures: {} };
|
|
1427
|
-
async function read(kind, name) {
|
|
1428
|
-
const rows = [];
|
|
1429
|
-
let offset = 0;
|
|
1430
|
-
while (rows.length < 1e4) {
|
|
1431
|
-
const response = await metadataRead(oracle, env, connection, {
|
|
1432
|
-
kind,
|
|
1433
|
-
name,
|
|
1434
|
-
schema: env.parsingSchema,
|
|
1435
|
-
offset,
|
|
1436
|
-
limit: 100
|
|
1437
|
-
});
|
|
1438
|
-
if (!("rows" in response))
|
|
1439
|
-
throw new Fault("METADATA_INVALID", "Expected one scoped metadata result.", 5);
|
|
1440
|
-
rows.push(...response.rows);
|
|
1441
|
-
if (response.nextOffset === null) return rows;
|
|
1442
|
-
offset = response.nextOffset;
|
|
1443
|
-
}
|
|
1444
|
-
throw new Fault("METADATA_LIMIT", "Object metadata exceeds the composition limit.", 5);
|
|
1445
|
-
}
|
|
1446
|
-
for (const entity of Object.values(blueprint.entities))
|
|
1447
|
-
if (!result.objects[entity.read.object])
|
|
1448
|
-
result.objects[entity.read.object] = {
|
|
1449
|
-
columns: await read("columns", entity.read.object),
|
|
1450
|
-
constraints: await read("constraints", entity.read.object),
|
|
1451
|
-
constraintColumns: await read("constraint-columns", entity.read.object)
|
|
1452
|
-
};
|
|
1453
|
-
for (const command of Object.values(blueprint.commands))
|
|
1454
|
-
if (!result.signatures[command.package])
|
|
1455
|
-
result.signatures[command.package] = await read("signatures", command.package);
|
|
1456
|
-
return result;
|
|
1457
|
-
}
|
|
1458
|
-
async function composePlan(ctx, request, oracle = new OracleAdapter(), signal) {
|
|
1459
|
-
await requireTrust(ctx.root);
|
|
1460
|
-
if (request.validation === "source-only" && !ctx.config.composer?.allowSourceOnly)
|
|
1461
|
-
throw new Fault(
|
|
1462
|
-
"SOURCE_ONLY_POLICY_REQUIRED",
|
|
1463
|
-
"Source-only composition requires explicit project composer.allowSourceOnly policy.",
|
|
1464
|
-
4
|
|
1465
|
-
);
|
|
1466
|
-
if (request.mode === "connected" && !request.env)
|
|
1467
|
-
throw new Fault("ENVIRONMENT_REQUIRED", "Connected planning requires --env.", 2);
|
|
1468
|
-
if (request.action !== "compose" && request.mode !== "offline")
|
|
1469
|
-
throw new Fault("INVALID_INPUT", "Local recovery has no connected mode.", 2);
|
|
1470
|
-
const pending = await journal(ctx);
|
|
1471
|
-
if (request.action === "compose" && pending && pending.phase !== "completed")
|
|
1472
|
-
throw new Fault(
|
|
1473
|
-
"RECOVERY_REQUIRED",
|
|
1474
|
-
"Create an explicit recovery plan for the interrupted composition.",
|
|
1475
|
-
5
|
|
1476
|
-
);
|
|
1477
|
-
const metadata = request.mode === "connected" ? await context(ctx, request.blueprint, request.env, oracle) : void 0;
|
|
1478
|
-
const input = request.action === "compose" ? await snapshot(ctx, request.blueprint, {
|
|
1479
|
-
mode: request.mode,
|
|
1480
|
-
validation: request.validation,
|
|
1481
|
-
...request.env ? { environment: request.env } : {},
|
|
1482
|
-
...metadata ? { metadata } : {}
|
|
1483
|
-
}) : null;
|
|
1484
|
-
const plan = input ? planComposition(input) : await recoveryPlan(ctx, request.action === "recover-resume" ? "resume" : "restore");
|
|
1485
|
-
let compiler = {
|
|
1486
|
-
status: "not-run",
|
|
1487
|
-
reason: request.action === "compose" ? "Source-only or blocked draft." : "Local journal recovery."
|
|
1488
|
-
};
|
|
1489
|
-
let compilerValidated = false;
|
|
1490
|
-
if (plan.status === "materializable" && request.action === "compose" && request.validation === "compiler") {
|
|
1491
|
-
const staged = await stagePlan(ctx, plan);
|
|
1492
|
-
compiler = await oracle.validate(staged.directory, signal);
|
|
1493
|
-
const actual = compiler;
|
|
1494
|
-
if (actual.mmd.mmdVersion !== "26.1.0+3102" || !/Release 26[.]1[.]/.test(actual.compiler.version))
|
|
1495
|
-
throw new Fault(
|
|
1496
|
-
"PROFILE_COMPILER_MISMATCH",
|
|
1497
|
-
"Real compiler/MMD differs from the pinned Composer profile.",
|
|
1498
|
-
5
|
|
1499
|
-
);
|
|
1500
|
-
compilerValidated = true;
|
|
1501
|
-
}
|
|
1502
|
-
if (metadata)
|
|
1503
|
-
await writeJson(
|
|
1504
|
-
await safePath(ctx.root, `.apexrest/composer/contexts/${plan.contextDigest}.json`),
|
|
1505
|
-
metadata
|
|
1506
|
-
);
|
|
1507
|
-
await freeze(ctx, plan, request.out);
|
|
1508
|
-
const artifactId = await new ArtifactService(ctx).saveJson(plan, "composition-plan");
|
|
1509
|
-
await writeJson(await safePath(ctx.root, `.apexrest/composer/plan-artifacts/${artifactId}.json`), {
|
|
1510
|
-
digest: plan.digest,
|
|
1511
|
-
file: `.apexrest/composer/plans/${plan.digest}.json`
|
|
1512
|
-
});
|
|
1513
|
-
return {
|
|
1514
|
-
status: plan.status,
|
|
1515
|
-
kind: plan.kind,
|
|
1516
|
-
plan: request.out,
|
|
1517
|
-
planDigest: plan.digest,
|
|
1518
|
-
artifactId,
|
|
1519
|
-
diagnostics: plan.diagnostics,
|
|
1520
|
-
operations: plan.operations.map(({ path: path3, reason, before, after }) => ({ path: path3, reason, before, after })),
|
|
1521
|
-
allocationCount: Object.keys(plan.allocations).length,
|
|
1522
|
-
compiler,
|
|
1523
|
-
databaseEffects: [],
|
|
1524
|
-
qualification: compilerValidated ? "offline-compiler" : "unverified",
|
|
1525
|
-
nextActions: plan.status === "materializable" ? ["Review the plan, then materialize using its exact digest."] : ["Resolve the reported binding or ownership diagnostics."]
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1528
|
-
async function composeMaterialize(ctx, request, signal) {
|
|
1529
|
-
if (Boolean(request.plan) === Boolean(request.artifactId))
|
|
1530
|
-
throw new Fault("INVALID_INPUT", "Supply exactly one plan path or registered artifact ID.", 2);
|
|
1531
|
-
let file = request.plan;
|
|
1532
|
-
if (request.artifactId) {
|
|
1533
|
-
const ref = JSON.parse(
|
|
1534
|
-
await readFile2(
|
|
1535
|
-
await safePath(ctx.root, `.apexrest/composer/plan-artifacts/${request.artifactId}.json`),
|
|
1536
|
-
"utf8"
|
|
1537
|
-
)
|
|
1538
|
-
);
|
|
1539
|
-
if (ref.digest !== request.expectedDigest)
|
|
1540
|
-
throw new Fault("PLAN_TAMPERED", "Artifact and expected plan digest differ.", 5);
|
|
1541
|
-
file = ref.file;
|
|
1542
|
-
}
|
|
1543
|
-
return materialize(ctx, await readPlan(ctx, file, request.expectedDigest), signal ? { signal } : {});
|
|
1544
|
-
}
|
|
1545
|
-
async function blueprintAdd(ctx, file, instanceId, instance, expectedDigest, apply = false) {
|
|
1546
|
-
await requireTrust(ctx.root);
|
|
1547
|
-
const blueprint = await readDocument(ctx.root, file, blueprintSchema);
|
|
1548
|
-
if (semanticDigest(blueprint) !== expectedDigest)
|
|
1549
|
-
throw new Fault("BLUEPRINT_CONFLICT", "Blueprint changed while editing catalog parameters.", 5);
|
|
1550
|
-
if (blueprint.blocks[instanceId]) throw new Fault("INSTANCE_EXISTS", "Choose a new instance identity.", 5);
|
|
1551
|
-
const block = validate(instanceSchema, instance), catalog = await loadCatalog(ctx.root);
|
|
1552
|
-
if (!catalog.packages.has(block.use))
|
|
1553
|
-
throw new Fault("PACKAGE_NOT_AVAILABLE_OFFLINE", "Unknown block version.", 3);
|
|
1554
|
-
const next = validate(blueprintSchema, {
|
|
1555
|
-
...blueprint,
|
|
1556
|
-
blocks: { ...blueprint.blocks, [instanceId]: block }
|
|
1557
|
-
});
|
|
1558
|
-
const content = JSON.stringify(next, null, 2) + "\n";
|
|
1559
|
-
if (apply) {
|
|
1560
|
-
if (semanticDigest(await readDocument(ctx.root, file, blueprintSchema)) !== expectedDigest)
|
|
1561
|
-
throw new Fault("BLUEPRINT_CONFLICT", "Concurrent blueprint edit.", 5);
|
|
1562
|
-
await withLock(await safePath(ctx.root, ".apexrest/composer/ownership.lock"), async () => {
|
|
1563
|
-
if (semanticDigest(await readDocument(ctx.root, file, blueprintSchema)) !== expectedDigest)
|
|
1564
|
-
throw new Fault("BLUEPRINT_CONFLICT", "Concurrent blueprint edit.", 5);
|
|
1565
|
-
await atomicWrite(await safePath(ctx.root, file), content);
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
|
-
return {
|
|
1569
|
-
applied: apply,
|
|
1570
|
-
expectedDigest,
|
|
1571
|
-
nextDigest: semanticDigest(next),
|
|
1572
|
-
before: canonical(blueprint),
|
|
1573
|
-
after: content,
|
|
1574
|
-
databaseEffects: []
|
|
1575
|
-
};
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
// packages/core/src/panel-schema.ts
|
|
1579
|
-
var panelPreferencesSchema = external_exports.strictObject({
|
|
1580
|
-
browserMode: browserPreferencesSchema.shape.browserMode.removeDefault().optional()
|
|
1581
|
-
});
|
|
1582
|
-
var panelReadSchema = external_exports.strictObject({
|
|
1583
|
-
project: external_exports.string().min(1).max(4096).optional()
|
|
1584
|
-
});
|
|
1585
|
-
var connectionActionSchema = external_exports.strictObject({
|
|
1586
|
-
kind: external_exports.literal("connection"),
|
|
1587
|
-
name: refName,
|
|
1588
|
-
sqlclName: savedConnectionName.optional(),
|
|
1589
|
-
ordsUrl: ordsUrl.optional(),
|
|
1590
|
-
ordsUsername: ordsUsername.optional(),
|
|
1591
|
-
password: external_exports.string().min(1).max(4096).optional()
|
|
1592
|
-
});
|
|
1593
|
-
var panelActionSchema = external_exports.strictObject({
|
|
1594
|
-
project: external_exports.string().min(1).max(4096).optional(),
|
|
1595
|
-
action: external_exports.discriminatedUnion("kind", [
|
|
1596
|
-
external_exports.strictObject({ kind: external_exports.literal("preferences"), settings: panelPreferencesSchema }),
|
|
1597
|
-
external_exports.strictObject({ kind: external_exports.literal("sqlcl"), settings: sqlclConfigSchema }),
|
|
1598
|
-
external_exports.strictObject({ kind: external_exports.literal("saved-connections") }),
|
|
1599
|
-
connectionActionSchema,
|
|
1600
|
-
external_exports.strictObject({
|
|
1601
|
-
kind: external_exports.literal("catalog-search"),
|
|
1602
|
-
query: external_exports.string().min(1).max(256),
|
|
1603
|
-
profile: external_exports.string().max(200).optional()
|
|
1604
|
-
}),
|
|
1605
|
-
external_exports.strictObject({
|
|
1606
|
-
kind: external_exports.literal("catalog-read"),
|
|
1607
|
-
id: external_exports.string().min(1).max(200),
|
|
1608
|
-
offset: external_exports.number().int().min(0).default(0)
|
|
1609
|
-
}),
|
|
1610
|
-
external_exports.strictObject({ kind: external_exports.literal("blueprint-read"), blueprint: relativePath }),
|
|
1611
|
-
external_exports.strictObject({
|
|
1612
|
-
kind: external_exports.literal("blueprint-add"),
|
|
1613
|
-
blueprint: relativePath,
|
|
1614
|
-
instanceId: external_exports.string().min(1).max(64),
|
|
1615
|
-
instance: instanceSchema,
|
|
1616
|
-
expectedDigest: digest,
|
|
1617
|
-
apply: external_exports.boolean().default(false)
|
|
1618
|
-
}),
|
|
1619
|
-
composePlanInput.omit({ project: true }).extend({ kind: external_exports.literal("compose-plan") }),
|
|
1620
|
-
composeMaterializeInput.omit({ project: true }).extend({ kind: external_exports.literal("compose-materialize") }),
|
|
1621
|
-
external_exports.strictObject({ kind: external_exports.literal("compose-status"), id: external_exports.uuid() }),
|
|
1622
|
-
external_exports.strictObject({ kind: external_exports.literal("cancel-job"), id: external_exports.uuid() }),
|
|
1623
|
-
external_exports.strictObject({ kind: external_exports.literal("validate") }),
|
|
1624
|
-
external_exports.strictObject({ kind: external_exports.literal("browser"), env: external_exports.string().min(1).max(100) }),
|
|
1625
|
-
external_exports.strictObject({
|
|
1626
|
-
kind: external_exports.literal("test"),
|
|
1627
|
-
suite: external_exports.enum(["unit", "sql", "api", "e2e", "all"]),
|
|
1628
|
-
env: external_exports.string().min(1).max(100).optional()
|
|
1629
|
-
}),
|
|
1630
|
-
external_exports.strictObject({ kind: external_exports.literal("plan"), env: external_exports.string().min(1).max(100) })
|
|
1631
|
-
])
|
|
1632
|
-
});
|
|
1633
|
-
var publicPanelActionSchema = panelActionSchema.extend({
|
|
1634
|
-
action: external_exports.discriminatedUnion("kind", [
|
|
1635
|
-
panelActionSchema.shape.action.options[0],
|
|
1636
|
-
...panelActionSchema.shape.action.options.slice(1).map(
|
|
1637
|
-
(option) => option.shape.kind.value === "connection" ? connectionActionSchema.omit({ password: true }) : option
|
|
1638
|
-
)
|
|
1639
|
-
])
|
|
1640
|
-
});
|
|
1641
|
-
|
|
1642
|
-
// packages/core/src/jobs.ts
|
|
1643
|
-
import path from "node:path";
|
|
1644
|
-
import { spawn } from "node:child_process";
|
|
1645
|
-
import { randomUUID } from "node:crypto";
|
|
1646
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
1647
|
-
var JobService = class {
|
|
1648
|
-
constructor(ctx) {
|
|
1649
|
-
this.ctx = ctx;
|
|
1650
|
-
}
|
|
1651
|
-
ctx;
|
|
1652
|
-
async start(operation, input, runtime) {
|
|
1653
|
-
await requireTrust(this.ctx.root);
|
|
1654
|
-
if (![
|
|
1655
|
-
"compose.plan",
|
|
1656
|
-
"compose.materialize",
|
|
1657
|
-
"apex.sync",
|
|
1658
|
-
"apex.generate",
|
|
1659
|
-
"apex.export",
|
|
1660
|
-
"apex.validate",
|
|
1661
|
-
"deploy.plan",
|
|
1662
|
-
"deploy.apply",
|
|
1663
|
-
"test.run"
|
|
1664
|
-
].includes(operation))
|
|
1665
|
-
throw new Fault("INVALID_JOB_OPERATION", "Operation cannot run as a background job.", 2);
|
|
1666
|
-
const id = randomUUID(), root = await contained(this.ctx.root, ".apexrest/jobs/" + id);
|
|
1667
|
-
await writeJson(path.join(root, "request.json"), {
|
|
1668
|
-
id,
|
|
1669
|
-
operation,
|
|
1670
|
-
input: { ...input, project: this.ctx.root }
|
|
1671
|
-
});
|
|
1672
|
-
await writeJson(path.join(root, "state.json"), {
|
|
1673
|
-
id,
|
|
1674
|
-
status: "queued",
|
|
1675
|
-
operation,
|
|
1676
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1677
|
-
});
|
|
1678
|
-
const worker = spawn(process.execPath, [runtime, "--job-worker", this.ctx.root, id], {
|
|
1679
|
-
cwd: this.ctx.root,
|
|
1680
|
-
env: process.env,
|
|
1681
|
-
detached: true,
|
|
1682
|
-
stdio: "ignore",
|
|
1683
|
-
windowsHide: true
|
|
1684
|
-
});
|
|
1685
|
-
await new Promise((resolve, reject) => {
|
|
1686
|
-
worker.once("spawn", resolve);
|
|
1687
|
-
worker.once("error", reject);
|
|
1688
|
-
});
|
|
1689
|
-
worker.unref();
|
|
1690
|
-
return {
|
|
1691
|
-
jobId: id,
|
|
1692
|
-
status: "queued",
|
|
1693
|
-
nextAction: "Wait for this job with apexrest_job_status and waitSeconds:25; never rerun the operation to fetch results. Cancellation does not imply database rollback."
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
async status(id, waitSeconds = 0, signal) {
|
|
1697
|
-
parse(external_exports.uuid(), id);
|
|
1698
|
-
parse(external_exports.number().int().min(0).max(30), waitSeconds);
|
|
1699
|
-
const root = await contained(this.ctx.root, ".apexrest/jobs/" + id);
|
|
1700
|
-
const deadline = Date.now() + waitSeconds * 1e3;
|
|
1701
|
-
for (; ; ) {
|
|
1702
|
-
const state = await readJson(path.join(root, "state.json"));
|
|
1703
|
-
if (!["queued", "running"].includes(state.status)) return state;
|
|
1704
|
-
if (Date.parse(state.updatedAt) + 6e4 < Date.now())
|
|
1705
|
-
return {
|
|
1706
|
-
...state,
|
|
1707
|
-
status: "outcome_unknown",
|
|
1708
|
-
nextAction: "Worker heartbeat expired. Reconcile target before retrying."
|
|
1709
|
-
};
|
|
1710
|
-
const remaining = deadline - Date.now();
|
|
1711
|
-
if (remaining <= 0 || signal?.aborted) return state;
|
|
1712
|
-
await delay(Math.min(250, remaining), void 0, { signal }).catch((error) => {
|
|
1713
|
-
if (!signal?.aborted) throw error;
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
async cancel(id) {
|
|
1718
|
-
await requireTrust(this.ctx.root);
|
|
1719
|
-
parse(external_exports.uuid(), id);
|
|
1720
|
-
const state = await this.status(id);
|
|
1721
|
-
if (!["queued", "running"].includes(state.status)) return state;
|
|
1722
|
-
await writeJson(await contained(this.ctx.root, ".apexrest/jobs/" + id + "/cancel.json"), {
|
|
1723
|
-
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1724
|
-
});
|
|
1725
|
-
return { jobId: id, status: "cancellation_requested", rollbackConfirmed: false };
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1728
|
-
async function executeJob(ctx, id, execute) {
|
|
1729
|
-
await requireTrust(ctx.root);
|
|
1730
|
-
parse(external_exports.uuid(), id);
|
|
1731
|
-
const root = await contained(ctx.root, ".apexrest/jobs/" + id);
|
|
1732
|
-
const request = await readJson(path.join(root, "request.json"));
|
|
1733
|
-
const controller = new AbortController();
|
|
1734
|
-
let done = false;
|
|
1735
|
-
const pulse = async () => {
|
|
1736
|
-
if (done) return;
|
|
1737
|
-
if (await exists(path.join(root, "cancel.json"))) controller.abort();
|
|
1738
|
-
if (!done)
|
|
1739
|
-
await writeJson(path.join(root, "state.json"), {
|
|
1740
|
-
id,
|
|
1741
|
-
operation: request.operation,
|
|
1742
|
-
status: "running",
|
|
1743
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1744
|
-
});
|
|
1745
|
-
};
|
|
1746
|
-
await pulse();
|
|
1747
|
-
let pending = Promise.resolve();
|
|
1748
|
-
const timer = setInterval(() => {
|
|
1749
|
-
pending = pending.then(pulse).catch(() => {
|
|
1750
|
-
controller.abort();
|
|
1751
|
-
});
|
|
1752
|
-
}, 2e3), timeout = setTimeout(() => controller.abort(), 9e5);
|
|
1753
|
-
try {
|
|
1754
|
-
const result = await execute(request.operation, request.input, controller.signal);
|
|
1755
|
-
done = true;
|
|
1756
|
-
clearInterval(timer);
|
|
1757
|
-
clearTimeout(timeout);
|
|
1758
|
-
await pending;
|
|
1759
|
-
await writeJson(path.join(root, "state.json"), {
|
|
1760
|
-
id,
|
|
1761
|
-
status: "completed",
|
|
1762
|
-
result,
|
|
1763
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1764
|
-
});
|
|
1765
|
-
} finally {
|
|
1766
|
-
done = true;
|
|
1767
|
-
clearInterval(timer);
|
|
1768
|
-
clearTimeout(timeout);
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
// packages/core/src/panel.ts
|
|
1773
|
-
import path2 from "node:path";
|
|
1774
|
-
import { fileURLToPath } from "node:url";
|
|
1775
|
-
import { readdir as readdir2, realpath, stat } from "node:fs/promises";
|
|
1776
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1777
|
-
var safe = (value) => sanitized(value);
|
|
1778
|
-
var PanelService = class {
|
|
1779
|
-
constructor(root, oracle = new OracleAdapter()) {
|
|
1780
|
-
this.root = root;
|
|
1781
|
-
this.oracle = oracle;
|
|
1782
|
-
}
|
|
1783
|
-
root;
|
|
1784
|
-
oracle;
|
|
1785
|
-
async preferences() {
|
|
1786
|
-
return browserPreferences(this.root);
|
|
1787
|
-
}
|
|
1788
|
-
async records(folder) {
|
|
1789
|
-
const base = await contained(this.root, ".apexrest/" + folder);
|
|
1790
|
-
if (!await exists(base)) return [];
|
|
1791
|
-
const entries = (await readdir2(base, { withFileTypes: true })).filter(
|
|
1792
|
-
(e) => e.isDirectory() && external_exports.uuid().safeParse(e.name).success
|
|
1793
|
-
);
|
|
1794
|
-
if (entries.length > 2e3)
|
|
1795
|
-
throw new Fault(
|
|
1796
|
-
"PANEL_HISTORY_LIMIT",
|
|
1797
|
-
"Archive old operation records before loading more than 2000 runs.",
|
|
1798
|
-
2
|
|
1799
|
-
);
|
|
1800
|
-
const files = await Promise.all(
|
|
1801
|
-
entries.map(async (entry) => {
|
|
1802
|
-
const file = await contained(base, entry.name + "/state.json");
|
|
1803
|
-
const info = await stat(file).catch(() => null);
|
|
1804
|
-
return { id: entry.name, file, at: info?.mtimeMs ?? 0, size: info?.size ?? 0 };
|
|
1805
|
-
})
|
|
1806
|
-
);
|
|
1807
|
-
return Promise.all(
|
|
1808
|
-
files.filter((f) => f.size > 0).sort((a, b) => b.at - a.at).slice(0, 12).map(async (f) => {
|
|
1809
|
-
if (f.size > 2 * 1024 * 1024)
|
|
1810
|
-
return {
|
|
1811
|
-
id: f.id,
|
|
1812
|
-
status: "unavailable",
|
|
1813
|
-
diagnostics: ["Record exceeds the panel limit."]
|
|
1814
|
-
};
|
|
1815
|
-
try {
|
|
1816
|
-
return { ...await readJson(f.file), id: f.id };
|
|
1817
|
-
} catch {
|
|
1818
|
-
return {
|
|
1819
|
-
id: f.id,
|
|
1820
|
-
status: "unavailable",
|
|
1821
|
-
diagnostics: ["Cannot read this operation record."]
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
})
|
|
1825
|
-
);
|
|
1826
|
-
}
|
|
1827
|
-
async snapshot() {
|
|
1828
|
-
this.root = await realpath(this.root);
|
|
1829
|
-
const ctx = await loadProject(this.root).catch((error) => {
|
|
1830
|
-
if (error instanceof Fault && error.code === "PROJECT_NOT_CONFIGURED") return null;
|
|
1831
|
-
throw error;
|
|
1832
|
-
});
|
|
1833
|
-
const [prefs, sqlcl, refs, security, jobRecords, deploymentRecords] = await Promise.all([
|
|
1834
|
-
this.preferences(),
|
|
1835
|
-
sqlclConfig(),
|
|
1836
|
-
connections(),
|
|
1837
|
-
policy(),
|
|
1838
|
-
this.records("jobs"),
|
|
1839
|
-
this.records("deployments")
|
|
1840
|
-
]);
|
|
1841
|
-
const jobs = await Promise.all(
|
|
1842
|
-
jobRecords.map(async (row) => {
|
|
1843
|
-
const state = ctx ? await new JobService(ctx).status(String(row.id)) : row;
|
|
1844
|
-
const result = state.result ?? {};
|
|
1845
|
-
return {
|
|
1846
|
-
id: String(row.id),
|
|
1847
|
-
operation: String(row.operation ?? result.operation ?? "operation"),
|
|
1848
|
-
status: String(result.status ?? state.status),
|
|
1849
|
-
updatedAt: String(state.updatedAt ?? ""),
|
|
1850
|
-
summary: String(result.summary ?? "").slice(0, 1e3),
|
|
1851
|
-
diagnostics: Array.isArray(result.diagnostics) ? result.diagnostics.slice(0, 5) : [],
|
|
1852
|
-
artifacts: Array.isArray(result.artifacts) ? result.artifacts.slice(0, 10) : []
|
|
1853
|
-
};
|
|
1854
|
-
})
|
|
1855
|
-
);
|
|
1856
|
-
const deployments = deploymentRecords.map((row) => ({
|
|
1857
|
-
id: String(row.id),
|
|
1858
|
-
status: String(row.state ?? "unknown"),
|
|
1859
|
-
at: String(row.at ?? ""),
|
|
1860
|
-
details: JSON.stringify(safe(row.details ?? {})).slice(0, 1200)
|
|
1861
|
-
}));
|
|
1862
|
-
let changes = { status: "unavailable", files: [] };
|
|
1863
|
-
try {
|
|
1864
|
-
const git = await runProcess({
|
|
1865
|
-
executable: "git",
|
|
1866
|
-
args: ["status", "--porcelain=v1", "--untracked-files=normal"],
|
|
1867
|
-
cwd: this.root,
|
|
1868
|
-
timeoutMs: 3e3
|
|
1869
|
-
});
|
|
1870
|
-
if (git.code === 0)
|
|
1871
|
-
changes = { status: "available", files: git.stdout.split("\n").filter(Boolean).slice(0, 80) };
|
|
1872
|
-
} catch {
|
|
1873
|
-
}
|
|
1874
|
-
let toolchain = null;
|
|
1875
|
-
if (ctx) {
|
|
1876
|
-
const file = await contained(this.root, ctx.config.toolchain.lockFile);
|
|
1877
|
-
if (await exists(file)) {
|
|
1878
|
-
if ((await stat(file)).size <= 128e3) toolchain = await readJson(file);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
const sync = ctx ? await Promise.all(
|
|
1882
|
-
Object.entries(ctx.config.environments).map(async ([name, env]) => {
|
|
1883
|
-
try {
|
|
1884
|
-
return { environment: name, ...await new SyncStore(ctx, env, name).status() };
|
|
1885
|
-
} catch (error) {
|
|
1886
|
-
return {
|
|
1887
|
-
environment: name,
|
|
1888
|
-
status: "blocked",
|
|
1889
|
-
blocked: true,
|
|
1890
|
-
blockedReason: error instanceof Fault ? error.code : "SYNC_STATE_INVALID",
|
|
1891
|
-
serverFreshness: "not-checked"
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
})
|
|
1895
|
-
) : [];
|
|
1896
|
-
return safe({
|
|
1897
|
-
sync,
|
|
1898
|
-
version: VERSION,
|
|
1899
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1900
|
-
project: this.root,
|
|
1901
|
-
configured: !!ctx,
|
|
1902
|
-
trusted: security.trustedProjects.includes(this.root),
|
|
1903
|
-
configuration: ctx?.config ?? null,
|
|
1904
|
-
sqlcl,
|
|
1905
|
-
preferences: prefs,
|
|
1906
|
-
connections: refs,
|
|
1907
|
-
toolchain,
|
|
1908
|
-
jobs,
|
|
1909
|
-
deployments,
|
|
1910
|
-
changes,
|
|
1911
|
-
permissions: {
|
|
1912
|
-
activeGrants: security.grants.filter((g) => g.projectRoot === this.root && Date.parse(g.expiresAt) > Date.now()).map((g) => ({ operations: g.operations, expiresAt: g.expiresAt, exactPlan: !!g.planDigest }))
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
}
|
|
1916
|
-
async act(action) {
|
|
1917
|
-
action = parse(panelActionSchema, { action }).action;
|
|
1918
|
-
this.root = await realpath(this.root);
|
|
1919
|
-
await requireTrust(this.root);
|
|
1920
|
-
if (action.kind === "saved-connections") return this.oracle.savedConnections();
|
|
1921
|
-
if (action.kind === "preferences") {
|
|
1922
|
-
const settings = { ...await this.preferences(), ...action.settings };
|
|
1923
|
-
await writeJson(await contained(this.root, ".apexrest/panel/preferences.json"), settings);
|
|
1924
|
-
return { saved: true, appliesTo: "browser-verification" };
|
|
1925
|
-
}
|
|
1926
|
-
if (action.kind === "sqlcl") {
|
|
1927
|
-
return configureSqlcl(
|
|
1928
|
-
action.settings.mode,
|
|
1929
|
-
action.settings.mcpRestrictLevel,
|
|
1930
|
-
action.settings.databaseTransport
|
|
1931
|
-
);
|
|
1932
|
-
}
|
|
1933
|
-
if (action.kind === "connection") return configureConnection(action.name, action);
|
|
1934
|
-
if (action.kind === "catalog-search")
|
|
1935
|
-
return catalogSearch(action.query, {
|
|
1936
|
-
project: this.root,
|
|
1937
|
-
limit: 8,
|
|
1938
|
-
...action.profile ? { profile: action.profile } : {}
|
|
1939
|
-
});
|
|
1940
|
-
if (action.kind === "catalog-read") return catalogRead(action.id, action.offset, 8192, this.root);
|
|
1941
|
-
const ctx = await loadProject(this.root);
|
|
1942
|
-
if (action.kind === "blueprint-read") {
|
|
1943
|
-
const blueprint = await readDocument(this.root, action.blueprint, blueprintSchema);
|
|
1944
|
-
return { blueprint, digest: semanticDigest(blueprint) };
|
|
1945
|
-
}
|
|
1946
|
-
if (action.kind === "blueprint-add")
|
|
1947
|
-
return blueprintAdd(
|
|
1948
|
-
ctx,
|
|
1949
|
-
action.blueprint,
|
|
1950
|
-
action.instanceId,
|
|
1951
|
-
action.instance,
|
|
1952
|
-
action.expectedDigest,
|
|
1953
|
-
action.apply
|
|
1954
|
-
);
|
|
1955
|
-
if (action.kind === "compose-status") {
|
|
1956
|
-
const job = await new JobService(ctx).status(action.id);
|
|
1957
|
-
const result = "result" in job ? job.result : null;
|
|
1958
|
-
let materializable = false;
|
|
1959
|
-
if (result?.data?.plan && result.data.planDigest)
|
|
1960
|
-
materializable = await assessPlan(ctx, result.data.plan, result.data.planDigest);
|
|
1961
|
-
return { job, materializable };
|
|
1962
|
-
}
|
|
1963
|
-
if (action.kind === "compose-plan" || action.kind === "compose-materialize") {
|
|
1964
|
-
const { kind, ...input2 } = action;
|
|
1965
|
-
return new JobService(ctx).start(
|
|
1966
|
-
kind === "compose-plan" ? "compose.plan" : "compose.materialize",
|
|
1967
|
-
input2,
|
|
1968
|
-
path2.join(path2.dirname(fileURLToPath(import.meta.url)), "apexrest.mjs")
|
|
1969
|
-
);
|
|
1970
|
-
}
|
|
1971
|
-
if (action.kind === "browser") return openVerificationBrowser(ctx, action.env);
|
|
1972
|
-
if (action.kind === "cancel-job") return new JobService(ctx).cancel(action.id);
|
|
1973
|
-
const operation = action.kind === "validate" ? "apex.validate" : action.kind === "plan" ? "deploy.plan" : "test.run";
|
|
1974
|
-
const input = action.kind === "plan" ? { env: action.env, out: ".apexrest/plans/" + randomUUID2() + ".json" } : action.kind === "test" ? { suite: action.suite, ...action.env ? { env: action.env } : {} } : {};
|
|
1975
|
-
return new JobService(ctx).start(
|
|
1976
|
-
operation,
|
|
1977
|
-
input,
|
|
1978
|
-
path2.join(path2.dirname(fileURLToPath(import.meta.url)), "apexrest.mjs")
|
|
1979
|
-
);
|
|
1980
|
-
}
|
|
1981
|
-
};
|
|
1982
|
-
|
|
1983
|
-
export {
|
|
1984
|
-
metadataInputSchema,
|
|
1985
|
-
metadataRead,
|
|
1986
|
-
composePlanInput,
|
|
1987
|
-
composeMaterializeInput,
|
|
1988
|
-
composePlan,
|
|
1989
|
-
composeMaterialize,
|
|
1990
|
-
panelReadSchema,
|
|
1991
|
-
panelActionSchema,
|
|
1992
|
-
publicPanelActionSchema,
|
|
1993
|
-
JobService,
|
|
1994
|
-
executeJob,
|
|
1995
|
-
PanelService
|
|
1996
|
-
};
|