@reventlessdev/reventless-aws 3.0.0-alpha.209 → 3.0.0-alpha.211

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +8 -8
  3. package/src/Platform.res +348 -707
  4. package/src/Platform.res.mjs +271 -739
  5. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +4 -47
  6. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -30
  7. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +0 -11
  8. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +0 -5
  9. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +6 -7
  10. package/src/components/Api/AppSync_Adapter.res +143 -101
  11. package/src/components/Api/AppSync_Adapter.res.mjs +60 -57
  12. package/src/components/Api/AppSync_MergedApi.res +218 -0
  13. package/src/components/Api/AppSync_MergedApi.res.mjs +122 -0
  14. package/src/components/Api/AppSync_SdlDecorate.res +49 -61
  15. package/src/components/Api/AppSync_SdlDecorate.res.mjs +38 -29
  16. package/src/components/Plugin.res.mjs +1 -2
  17. package/tests/AppSync_AdapterTest.res +184 -0
  18. package/tests/AppSync_AdapterTest.res.mjs +136 -0
  19. package/tests/AppSync_SdlDecorateTest.res +33 -86
  20. package/tests/AppSync_SdlDecorateTest.res.mjs +26 -71
  21. package/tests/MCP_LambdaTest.res +4 -6
  22. package/tests/MCP_LambdaTest.res.mjs +2 -2
  23. package/src/adapter/Api/ApiFragmentDeregistration.res +0 -138
  24. package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +0 -108
  25. package/src/adapter/Api/ApiSchemaPush.res +0 -79
  26. package/src/adapter/Api/ApiSchemaPush.res.mjs +0 -64
  27. package/src/adapter/Api/Platform_ApiFragments_Lambda.res +0 -222
  28. package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +0 -202
  29. package/src/adapter/QueryDb/NodeResolver_AppSync.res +0 -71
  30. package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +0 -44
  31. package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +0 -204
  32. package/tests/ApiSchemaPushTest.res +0 -32
  33. package/tests/ApiSchemaPushTest.res.mjs +0 -20
@@ -61,10 +61,10 @@ globalThis.describe("AppSync_SdlDecorate.injectAwsSubscribe", () => {
61
61
  });
62
62
  });
63
63
 
64
- globalThis.describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
65
- globalThis.test("assembles neutral fragments into AWS-dialect SDL", () => {
66
- let baseFragment = GraphQL_Stitcher$ReventlessCore.encode({
67
- types: [],
64
+ globalThis.describe("AppSync_Adapter.stitchStandaloneWithAwsDirectives", () => {
65
+ globalThis.test("assembles a neutral fragment into an AWS-dialect standalone document", () => {
66
+ let fragment = GraphQL_Stitcher$ReventlessCore.encode({
67
+ types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
68
68
  mutations: [` Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent`],
69
69
  queries: [],
70
70
  subscriptions: [` onPluginStatusChange: PluginStatusChangeEvent`],
@@ -73,19 +73,9 @@ globalThis.describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
73
73
  mutations: ["Platform_PluginStatusChanged"]
74
74
  }]
75
75
  });
76
- let pluginFragment = GraphQL_Stitcher$ReventlessCore.encode({
77
- types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
78
- mutations: [` Catalog_AddProduct(input: AddInput): CommandResult`],
79
- queries: [],
80
- subscriptions: [` onCatalog_AddProduct(id: ID): CommandResult`],
81
- subscriptionSources: [{
82
- field: "onCatalog_AddProduct",
83
- mutations: ["Catalog_AddProduct"]
84
- }]
85
- });
86
- let sdl = AppSync_Adapter$ReventlessAws.stitchWithAwsDirectives(baseFragment, [pluginFragment]);
76
+ let sdl = AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(fragment);
87
77
  globalThis.expect(sdl).toContain(`@aws_subscribe(mutations: ["Platform_PluginStatusChanged"])`);
88
- globalThis.expect(sdl).toContain(`@aws_subscribe(mutations: ["Catalog_AddProduct"])`);
78
+ globalThis.expect(sdl).not.toContain("node(id: ID!): Node");
89
79
  });
90
80
  });
91
81
 
@@ -115,65 +105,30 @@ globalThis.describe("AppSync_SdlDecorate.injectAwsAuthAll", () => {
115
105
  });
116
106
  });
117
107
 
118
- globalThis.describe("AppSync_SdlDecorate.planAwsPushes", () => {
119
- let rawAdminBase = GraphQL_Stitcher$ReventlessCore.encode({
120
- types: [`type CommandAccepted {\n id: ID!\n}`],
121
- mutations: [` Platform_RegisterApiFragment(input: ApiFragmentInput): CommandResult`],
122
- queries: [],
123
- subscriptions: [],
124
- subscriptionSources: []
125
- });
126
- let iamFieldNames = ["Platform_RegisterApiFragment"];
127
- let mkFrag = (mutation, target) => ({
128
- encoded: GraphQL_Stitcher$ReventlessCore.encode({
129
- types: [],
130
- mutations: [mutation],
131
- queries: [],
132
- subscriptions: [],
133
- subscriptionSources: []
134
- }).encoded,
135
- protocol: "graphql",
136
- target: target
137
- });
138
- let platformFrag = mkFrag(` Inspector_Ping: CommandResult`, "Platform");
139
- let domainFrag = mkFrag(` Catalog_AddProduct(input: AddInput): CommandResult`, "Domain");
140
- globalThis.test("split mode: Platform API carries admin base (dual-auth) + Platform frags; Domain API excludes the admin base", () => {
141
- let plans = AppSync_SdlDecorate$ReventlessAws.planAwsPushes(rawAdminBase, iamFieldNames, [
142
- platformFrag,
143
- domainFrag
144
- ], true);
145
- globalThis.expect(plans.length).toBe(2);
146
- let platformPlan = Stdlib_Option.getOrThrow(plans.find(p => p.api === "PlatformApi"), undefined);
147
- let domainPlan = Stdlib_Option.getOrThrow(plans.find(p => p.api === "DomainApi"), undefined);
148
- globalThis.expect(platformPlan.sdl).toContain("Platform_RegisterApiFragment");
149
- globalThis.expect(platformPlan.sdl).toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin"]) @aws_iam`);
150
- globalThis.expect(platformPlan.sdl).toContain("Inspector_Ping");
151
- globalThis.expect(platformPlan.sdl).toContain(`type CommandAccepted @aws_cognito_user_pools @aws_iam {`);
152
- globalThis.expect(domainPlan.sdl).toContain("Catalog_AddProduct");
153
- globalThis.expect(domainPlan.sdl.includes("Platform_RegisterApiFragment")).toBe(false);
154
- });
155
- globalThis.test("unified mode: a single Domain API carries admin base + all frags", () => {
156
- let plans = AppSync_SdlDecorate$ReventlessAws.planAwsPushes(rawAdminBase, iamFieldNames, [
157
- platformFrag,
158
- domainFrag
159
- ], false);
160
- globalThis.expect(plans.length).toBe(1);
161
- let plan = plans[0];
162
- globalThis.expect(plan.api).toBe("DomainApi");
163
- globalThis.expect(plan.sdl).toContain("Platform_RegisterApiFragment");
164
- globalThis.expect(plan.sdl).toContain("Inspector_Ping");
165
- globalThis.expect(plan.sdl).toContain("Catalog_AddProduct");
108
+ globalThis.describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
109
+ globalThis.test("stamps the CommandResult members", () => {
110
+ let sdl = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}");
111
+ globalThis.expect(sdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {");
166
112
  });
167
113
  });
168
114
 
169
- globalThis.describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
170
- globalThis.test("stamps Platform_ApiFragmentEntry with dual-auth", () => {
171
- let sdl = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type Platform_ApiFragmentEntry {\n pluginId: String!\n pushStatus: String!\n}");
172
- globalThis.expect(sdl).toContain("type Platform_ApiFragmentEntry @aws_cognito_user_pools @aws_iam {");
115
+ globalThis.describe("AppSync_SdlDecorate.stampCanonicalTypes", () => {
116
+ globalThis.test("stamps shared object types, the Node interface, and the CommandResult union", () => {
117
+ let sdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes("interface Node {\n id: ID!\n}\n\ntype PageInfo {\n hasNextPage: Boolean!\n}\n\nunion CommandResult = CommandAccepted | CommandRejected | CommandPending\n\ntype CommandAccepted {\n msgId: ID!\n}");
118
+ globalThis.expect(sdl).toContain("interface Node @canonical {");
119
+ globalThis.expect(sdl).toContain("type PageInfo @canonical {");
120
+ globalThis.expect(sdl).toContain("union CommandResult @canonical = CommandAccepted");
121
+ globalThis.expect(sdl).toContain("type CommandAccepted @canonical {");
173
122
  });
174
- globalThis.test("still stamps the CommandResult members", () => {
175
- let sdl = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}");
176
- globalThis.expect(sdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {");
123
+ globalThis.test("composes after stampSharedIamTypes and leaves other types alone", () => {
124
+ let sdl = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type PageInfo {\n hasNextPage: Boolean!\n}\n\ntype Product {\n id: ID!\n}"));
125
+ globalThis.expect(sdl).toContain("type PageInfo @aws_cognito_user_pools @aws_iam @canonical {");
126
+ globalThis.expect(sdl).toContain("type Product {");
127
+ globalThis.expect(sdl.includes("type Product @canonical")).toBe(false);
128
+ });
129
+ globalThis.test("is idempotent", () => {
130
+ let once = AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes("type PageInfo {\n x: Int\n}");
131
+ globalThis.expect(AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(once)).toBe(once);
177
132
  });
178
133
  });
179
134
 
@@ -7,9 +7,8 @@ describe("MCP_Lambda.generateAdminConfig", () => {
7
7
  ~serverVersion="1.0.0",
8
8
  ~cloner=false,
9
9
  )
10
- // Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire +
11
- // ApiFragmentRegistry_Register/DeregisterApiFragment mutations
12
- expect(config.tools)->toHaveLength(5)
10
+ // Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire
11
+ expect(config.tools)->toHaveLength(3)
13
12
  })
14
13
 
15
14
  testSync("produces additional clone tool when cloner=true", () => {
@@ -18,9 +17,8 @@ describe("MCP_Lambda.generateAdminConfig", () => {
18
17
  ~serverVersion="1.0.0",
19
18
  ~cloner=true,
20
19
  )
21
- // Plugin_Activate, Plugin_Deactivate, Plugin_Retire,
22
- // ApiFragmentRegistry_Register/DeregisterApiFragment, Clone
23
- expect(config.tools)->toHaveLength(6)
20
+ // Plugin_Activate, Plugin_Deactivate, Plugin_Retire, Clone
21
+ expect(config.tools)->toHaveLength(4)
24
22
  })
25
23
 
26
24
  testSync("produces resources from admin query entries", () => {
@@ -6,11 +6,11 @@ import * as MCP_Lambda$ReventlessAws from "../src/adapter/Mcp/MCP_Lambda.res.mjs
6
6
  globalThis.describe("MCP_Lambda.generateAdminConfig", () => {
7
7
  globalThis.test("produces tools from admin mutation entries (no cloner)", () => {
8
8
  let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", false, undefined, undefined);
9
- globalThis.expect(config.tools).toHaveLength(5);
9
+ globalThis.expect(config.tools).toHaveLength(3);
10
10
  });
11
11
  globalThis.test("produces additional clone tool when cloner=true", () => {
12
12
  let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", true, undefined, undefined);
13
- globalThis.expect(config.tools).toHaveLength(6);
13
+ globalThis.expect(config.tools).toHaveLength(4);
14
14
  });
15
15
  globalThis.test("produces resources from admin query entries", () => {
16
16
  let config = MCP_Lambda$ReventlessAws.generateAdminConfig("test", "1.0.0", undefined, undefined, undefined);
@@ -1,138 +0,0 @@
1
- /** Pulumi dynamic resource whose sole purpose is to deregister a plugin's
2
- API-schema fragment on `pulumi destroy` (final retirement of the plugin
3
- stack). `create` / `update` are no-ops — registration is done by
4
- `Platform.registerFragmentViaApi` at deploy time; only `delete` matters,
5
- and it calls `Platform_DeregisterApiFragment` on the platform's Platform API
6
- (SigV4), whose event triggers the platform-side reactive single writer (2e)
7
- to re-stitch the schema WITHOUT this plugin's fields.
8
-
9
- Serialization: Pulumi serialises the provider object (and its captured import
10
- closure) into stack state, so it must NOT statically capture the AWS SDK —
11
- that breaks Pulumi's closure serialiser (the CJS/ESM dual-export confusion
12
- documented in AppSync_Resolver_Retrying). The delete handler therefore
13
- DYNAMICALLY imports the signed caller at invocation time; only the string
14
- specifier lands in state.
15
-
16
- Version supersession must NOT deregister: `diff` never reports changes/replaces
17
- (pluginId, endpoint, and region are all version-stable), so a version bump is
18
- an in-place no-op and the `delete` handler fires only on a genuine stack
19
- destroy — the "final retirement, resolvers gone" trigger. */
20
-
21
- let log = ReventlessCore.Logger.fromEnv()
22
-
23
- type providerInputs = {
24
- pluginId: string,
25
- endpoint: string,
26
- region: string,
27
- }
28
-
29
- // Pulumi passes OUTPUTS (possibly undefined) to delete/read handlers, so encode
30
- // the carrier fields into the resource id — always available. "|" cannot appear
31
- // in a plugin name, an AWS region, or an AppSync GraphQL endpoint URL.
32
- let encodeId = (i: providerInputs): string => `${i.pluginId}|${i.region}|${i.endpoint}`
33
- let decodeId = (id: string): option<providerInputs> =>
34
- switch id->String.split("|") {
35
- | [pluginId, region, endpoint] => Some({pluginId, region, endpoint})
36
- | _ => None
37
- }
38
-
39
- // Dynamically import the signed caller and send Platform_DeregisterApiFragment.
40
- // The `import(...)` keeps Util_AppSync_Caller (and its transitive @aws-sdk/@smithy
41
- // deps) OUT of the serialised provider closure — only this string is captured.
42
- // Positional call matches the compiled signature
43
- // `sendMutation(endpoint, region, mutation, selection, variables)`.
44
- let sendDeregister: (JSON.t, string, string) => promise<unit> = %raw(`
45
- async function (variables, endpoint, region) {
46
- const mod = await import("@reventlessdev/reventless-aws/src/util/Util_AppSync_Caller.res.mjs");
47
- await mod.sendMutation(endpoint, region, "Platform_ApiFragmentRegistry_DeregisterApiFragment", "{ __typename }", variables);
48
- }
49
- `)
50
-
51
- let errMessage: 'a => string = %raw(`(e) => (e && e.message) ? String(e.message) : String(e)`)
52
-
53
- type createResult = {id: string, outs: providerInputs}
54
- type updateResult = {outs: providerInputs}
55
- type diffResult = {changes: bool, replaces: array<string>, deleteBeforeReplace: bool}
56
-
57
- let create = async (inputs: providerInputs): createResult => {id: encodeId(inputs), outs: inputs}
58
-
59
- let update = async (_id: string, _olds: providerInputs, news: providerInputs): updateResult => {
60
- outs: news,
61
- }
62
-
63
- // Never change/replace — see the module note: keeps version bumps from deleting
64
- // (and therefore deregistering) the fragment.
65
- let diff = (_id: string, _olds: providerInputs, _news: providerInputs): diffResult => {
66
- changes: false,
67
- replaces: [],
68
- deleteBeforeReplace: false,
69
- }
70
-
71
- let delete_ = async (id: string, props: providerInputs): unit => {
72
- let carrier = switch decodeId(id) {
73
- | Some(c) => c
74
- | None => props
75
- }
76
- let variables =
77
- Dict.fromArray([
78
- // Platform_ApiFragmentRegistry_DeregisterApiFragment(id: ID!, pluginId: ID!) — the
79
- // registry is a SINGLETON aggregate, so `id` is the fixed constant; `pluginId`
80
- // (payload) names the plugin whose fragment is removed.
81
- ("id", JSON.Encode.string("registry")),
82
- ("pluginId", JSON.Encode.string(carrier.pluginId)),
83
- ])->JSON.Encode.object
84
- // Best-effort: on a full teardown the platform API may already be gone, and a
85
- // destroy must not fail because deregistration could not reach it.
86
- try {
87
- await sendDeregister(variables, carrier.endpoint, carrier.region)
88
- log.info(~comp="ApiFragmentDeregistration", `Deregistered API fragment for ${carrier.pluginId}`)
89
- } catch {
90
- | exn =>
91
- log.warn(
92
- ~comp="ApiFragmentDeregistration",
93
- `deregister for ${carrier.pluginId} failed (best-effort; platform may be gone): ${errMessage(
94
- exn,
95
- )}`,
96
- )
97
- }
98
- }
99
-
100
- // Provider as a plain JS object — no Pulumi Output captures; all state flows
101
- // through inputs / id.
102
- let provider = {
103
- "create": create,
104
- "update": update,
105
- "delete": delete_,
106
- "diff": diff,
107
- }
108
-
109
- // ── Pulumi dynamic resource binding ──────────────────────────────────────────
110
-
111
- type t
112
-
113
- type constructorProps = {
114
- pluginId: Pulumi.Input.t<string>,
115
- endpoint: Pulumi.Input.t<string>,
116
- region: Pulumi.Input.t<string>,
117
- }
118
-
119
- // pulumi.dynamic.Resource constructor: (provider, name, props, opts). Explicit
120
- // /index.js path because @pulumi/pulumi/dynamic is a directory import not
121
- // resolvable in ESM mode.
122
- @module("@pulumi/pulumi/dynamic/index.js") @new
123
- external _newResource: ('provider, string, 'props, Pulumi.CustomResourceOptions.t) => t = "Resource"
124
-
125
- let make = (
126
- ~name: string,
127
- ~pluginId: string,
128
- ~endpoint: Pulumi.Input.t<string>,
129
- ~region: string,
130
- ~opts: Pulumi.CustomResourceOptions.t={},
131
- ): t => {
132
- let props: constructorProps = {
133
- pluginId: pluginId->Pulumi.Input.make,
134
- endpoint,
135
- region: region->Pulumi.Input.make,
136
- }
137
- _newResource(provider, name, props, opts)
138
- }
@@ -1,108 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
- import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
4
- import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
5
- import * as IndexJs from "@pulumi/pulumi/dynamic/index.js";
6
-
7
- let log = Logger$ReventlessCore.fromEnv();
8
-
9
- function encodeId(i) {
10
- return i.pluginId + `|` + i.region + `|` + i.endpoint;
11
- }
12
-
13
- function decodeId(id) {
14
- let match = id.split("|");
15
- if (match.length !== 3) {
16
- return;
17
- }
18
- let pluginId = match[0];
19
- let region = match[1];
20
- let endpoint = match[2];
21
- return {
22
- pluginId: pluginId,
23
- endpoint: endpoint,
24
- region: region
25
- };
26
- }
27
-
28
- let sendDeregister = (async function (variables, endpoint, region) {
29
- const mod = await import("@reventlessdev/reventless-aws/src/util/Util_AppSync_Caller.res.mjs");
30
- await mod.sendMutation(endpoint, region, "Platform_ApiFragmentRegistry_DeregisterApiFragment", "{ __typename }", variables);
31
- });
32
-
33
- let errMessage = ((e) => (e && e.message) ? String(e.message) : String(e));
34
-
35
- async function create(inputs) {
36
- return {
37
- id: encodeId(inputs),
38
- outs: inputs
39
- };
40
- }
41
-
42
- async function update(_id, _olds, news) {
43
- return {
44
- outs: news
45
- };
46
- }
47
-
48
- function diff(_id, _olds, _news) {
49
- return {
50
- changes: false,
51
- replaces: [],
52
- deleteBeforeReplace: false
53
- };
54
- }
55
-
56
- async function delete_(id, props) {
57
- let c = decodeId(id);
58
- let carrier = c !== undefined ? c : props;
59
- let variables = Object.fromEntries([
60
- [
61
- "id",
62
- "registry"
63
- ],
64
- [
65
- "pluginId",
66
- carrier.pluginId
67
- ]
68
- ]);
69
- try {
70
- await sendDeregister(variables, carrier.endpoint, carrier.region);
71
- return log.info("ApiFragmentDeregistration", undefined, `Deregistered API fragment for ` + carrier.pluginId);
72
- } catch (raw_exn) {
73
- let exn = Primitive_exceptions.internalToException(raw_exn);
74
- return log.warn("ApiFragmentDeregistration", undefined, `deregister for ` + carrier.pluginId + ` failed (best-effort; platform may be gone): ` + errMessage(exn));
75
- }
76
- }
77
-
78
- let provider = {
79
- create: create,
80
- update: update,
81
- delete: delete_,
82
- diff: diff
83
- };
84
-
85
- function make(name, pluginId, endpoint, region, optsOpt) {
86
- let opts = optsOpt !== undefined ? optsOpt : ({});
87
- let props = {
88
- pluginId: pluginId,
89
- endpoint: endpoint,
90
- region: region
91
- };
92
- return new IndexJs.Resource(provider, name, props, opts);
93
- }
94
-
95
- export {
96
- log,
97
- encodeId,
98
- decodeId,
99
- sendDeregister,
100
- errMessage,
101
- create,
102
- update,
103
- diff,
104
- delete_,
105
- provider,
106
- make,
107
- }
108
- /* log Not a pure module */
@@ -1,79 +0,0 @@
1
- // ApiSchemaPush — the admin reactive schema-push SideEffect
2
- // (docs/plans/event-sourced-fragment-registries.md § Reactive writer design).
3
- //
4
- // Source = the ApiFragmentRegistry singleton aggregate. On every ApiSchemaComputed
5
- // { snapshot } (emitted by the aggregate behaviour alongside each ApiFragment* fact,
6
- // carrying the WHOLE consistent per-plugin fragment set), this hands the snapshot to
7
- // the runtime-pure push engine (ApiSchemaPush_Runtime.mjs), which stitches one
8
- // AWS-decorated schema per target API, pushes each behind the shrink guard, and writes
9
- // the outcome back via RecordApiFragmentPush.
10
- //
11
- // The generic SideEffect.T context (`queryEngine`) is unused: this bespoke platform
12
- // side effect self-acquires its config (API ids, split flag, command-topic URL) from
13
- // Lambda env injected by the admin SideEffectHandler (~extraEnvVars). The push module
14
- // is loaded via dynamic import so no @aws-sdk / provider code is statically captured
15
- // beyond what the SideEffectHandler Lambda already bundles (runtime-purity — see
16
- // reference_pulumi_leaks_into_lambda_runtime_graph).
17
- module Spec = ReventlessCore.ApiFragmentRegistrySpec
18
-
19
- // Source is built EXPLICITLY rather than aliasing/including the spec. SideEffectHandler_Callback
20
- // reads Source.{name,eventSchema,Id.schema} reflectively off this module's compiled export at
21
- // Lambda cold start, but the spec uses these only at the TYPE level, so ReScript dead-shakes
22
- // them: a bare `module Source = Spec` alias erases the whole binding to `let Source;`, and even
23
- // the spec's own @@reventless.spec-injected `module Id` compiles to `let Id;` (undefined) — the
24
- // cross-module runtime reflection is invisible to ReScript's optimiser (deploy-time is fine
25
- // because Platform.res hand-wires these values). Both surfaced on deploy as "Cannot read
26
- // properties of undefined (reading 'eventSchema' / 'schema')". Forwarding name/eventSchema (real
27
- // spec exports) and re-binding Id (String — the singleton registry id, matching Platform.res's
28
- // hand-wiring) materialises all three, while the type aliases keep full transparency for the
29
- // pattern match below.
30
- module Source = {
31
- type event = Spec.event
32
- type fragmentSnapshotEntry = Spec.fragmentSnapshotEntry
33
- // `include` (not `module Id = Reventless.Id.String`) — a bare module alias compiles to
34
- // `Id: undefined` in the Source record (Id is only type-projected via Source.Id.t), and
35
- // SideEffectHandler_Callback's `Source.Id.schema` reflective read crashes at cold start.
36
- // `include` materialises the Id module's runtime values into the record.
37
- module Id = {
38
- include Reventless.Id.String
39
- }
40
- let name = Spec.name
41
- let eventSchema = Spec.eventSchema
42
- }
43
-
44
- let moduleUrl: string = %raw(`import.meta.url`)
45
-
46
- type jsEntry = {pluginId: string, encoded: string, protocol: string, apiTarget: string}
47
-
48
- let pushApiSchema: array<jsEntry> => promise<unit> = %raw(`
49
- async function (snapshot) {
50
- const mod = await import("@reventlessdev/reventless-aws/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs");
51
- await mod.pushApiSchema(snapshot);
52
- }
53
- `)
54
-
55
- let execute = async (
56
- _id: Source.Id.t,
57
- _meta: Reventless.Message.meta,
58
- event: Source.event,
59
- _queryEngine: Reventless.QueryEngine.operations,
60
- ) =>
61
- switch event {
62
- | ApiSchemaComputed({snapshot}) =>
63
- let jsSnapshot = snapshot->Array.map((e: Source.fragmentSnapshotEntry) => {
64
- pluginId: e.pluginId,
65
- encoded: e.encoded,
66
- protocol: e.protocol,
67
- apiTarget: switch e.apiTarget {
68
- | Reventless.Plugin.Domain => "Domain"
69
- | Reventless.Plugin.Platform => "Platform"
70
- },
71
- })
72
- await pushApiSchema(jsSnapshot)
73
- // Fact events are handled via the ApiSchemaComputed echo (which carries the
74
- // consistent snapshot); the write-back and lifecycle facts are no-ops here.
75
- | ApiFragmentRegistered(_)
76
- | ApiFragmentUpdated(_)
77
- | ApiFragmentDeregistered(_)
78
- | ApiFragmentPushRecorded(_) => ()
79
- }
@@ -1,64 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
- import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
4
- import * as ApiFragmentRegistrySpec$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs";
5
-
6
- let Id_schema = Id$Reventless.$$String.schema;
7
-
8
- let Id_make = Id$Reventless.$$String.make;
9
-
10
- let Id_makeFromString = Id$Reventless.$$String.makeFromString;
11
-
12
- let Id_toString = Id$Reventless.$$String.toString;
13
-
14
- let Id_cmp = Id$Reventless.$$String.cmp;
15
-
16
- let Id = {
17
- schema: Id_schema,
18
- make: Id_make,
19
- makeFromString: Id_makeFromString,
20
- toString: Id_toString,
21
- cmp: Id_cmp
22
- };
23
-
24
- let Source = {
25
- Id: Id,
26
- name: ApiFragmentRegistrySpec$ReventlessCore.name,
27
- eventSchema: ApiFragmentRegistrySpec$ReventlessCore.eventSchema
28
- };
29
-
30
- let moduleUrl = import.meta.url;
31
-
32
- let pushApiSchema = (async function (snapshot) {
33
- const mod = await import("@reventlessdev/reventless-aws/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs");
34
- await mod.pushApiSchema(snapshot);
35
- });
36
-
37
- async function execute(_id, _meta, event, _queryEngine) {
38
- if (event.TAG !== "ApiSchemaComputed") {
39
- return;
40
- }
41
- let jsSnapshot = event.snapshot.map(e => {
42
- let match = e.apiTarget;
43
- let tmp;
44
- tmp = match === "Domain" ? "Domain" : "Platform";
45
- return {
46
- pluginId: e.pluginId,
47
- encoded: e.encoded,
48
- protocol: e.protocol,
49
- apiTarget: tmp
50
- };
51
- });
52
- return await pushApiSchema(jsSnapshot);
53
- }
54
-
55
- let Spec;
56
-
57
- export {
58
- Spec,
59
- Source,
60
- moduleUrl,
61
- pushApiSchema,
62
- execute,
63
- }
64
- /* moduleUrl Not a pure module */