@reventlessdev/reventless-aws 3.0.0-alpha.210 → 3.0.0-alpha.212
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/CHANGELOG.md +20 -0
- package/package.json +6 -6
- package/src/Platform.res +116 -849
- package/src/Platform.res.mjs +75 -710
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +16 -3
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +11 -1
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +4 -47
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +1 -30
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +0 -11
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +0 -5
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +6 -7
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +4 -1
- package/src/components/Api/AppSync_Adapter.res +32 -105
- package/src/components/Api/AppSync_Adapter.res.mjs +12 -53
- package/src/components/Api/AppSync_MergedApi.res +1 -1
- package/src/components/Api/AppSync_SdlDecorate.res +6 -65
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +1 -31
- package/src/components/Plugin.res.mjs +1 -2
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +40 -35
- package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +14 -15
- package/src/plugin/runtime/PluginRuntime_Builder.res +58 -69
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +31 -29
- package/tests/AppSync_AdapterTest.res +23 -15
- package/tests/AppSync_AdapterTest.res.mjs +18 -16
- package/tests/AppSync_SdlDecorateTest.res +9 -89
- package/tests/AppSync_SdlDecorateTest.res.mjs +7 -72
- package/tests/MCP_LambdaTest.res +4 -6
- package/tests/MCP_LambdaTest.res.mjs +2 -2
- package/src/adapter/Api/ApiFragmentDeregistration.res +0 -138
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +0 -108
- package/src/adapter/Api/ApiSchemaPush.res +0 -79
- package/src/adapter/Api/ApiSchemaPush.res.mjs +0 -64
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res +0 -222
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +0 -202
- package/src/adapter/QueryDb/NodeResolver_AppSync.res +0 -71
- package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +0 -44
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +0 -204
- package/tests/ApiSchemaPushTest.res +0 -32
- package/tests/ApiSchemaPushTest.res.mjs +0 -20
|
@@ -67,10 +67,10 @@ describe("AppSync_SdlDecorate.injectAwsSubscribe", () => {
|
|
|
67
67
|
})
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
describe("AppSync_Adapter.
|
|
71
|
-
testSync("assembles neutral
|
|
72
|
-
let
|
|
73
|
-
types: [],
|
|
70
|
+
describe("AppSync_Adapter.stitchStandaloneWithAwsDirectives", () => {
|
|
71
|
+
testSync("assembles a neutral fragment into an AWS-dialect standalone document", () => {
|
|
72
|
+
let fragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
73
|
+
types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
|
|
74
74
|
mutations: [` Platform_PluginStatusChanged(pluginId: ID!, status: PluginStatus!): PluginStatusChangeEvent`],
|
|
75
75
|
queries: [],
|
|
76
76
|
subscriptions: [` onPluginStatusChange: PluginStatusChangeEvent`],
|
|
@@ -78,19 +78,11 @@ describe("AppSync_Adapter.stitchWithAwsDirectives", () => {
|
|
|
78
78
|
{field: "onPluginStatusChange", mutations: ["Platform_PluginStatusChanged"]},
|
|
79
79
|
],
|
|
80
80
|
})
|
|
81
|
-
let
|
|
82
|
-
types: [`type PluginStatusChangeEvent {\n pluginId: ID!\n}`],
|
|
83
|
-
mutations: [` Catalog_AddProduct(input: AddInput): CommandResult`],
|
|
84
|
-
queries: [],
|
|
85
|
-
subscriptions: [` onCatalog_AddProduct(id: ID): CommandResult`],
|
|
86
|
-
subscriptionSources: [{field: "onCatalog_AddProduct", mutations: ["Catalog_AddProduct"]}],
|
|
87
|
-
})
|
|
88
|
-
let sdl = AppSync_Adapter.stitchWithAwsDirectives(
|
|
89
|
-
~baseFragment,
|
|
90
|
-
~pluginFragments=[pluginFragment],
|
|
91
|
-
)
|
|
81
|
+
let sdl = AppSync_Adapter.stitchStandaloneWithAwsDirectives(~fragment)
|
|
92
82
|
expect(sdl)->toContain(`@aws_subscribe(mutations: ["Platform_PluginStatusChanged"])`)
|
|
93
|
-
|
|
83
|
+
// Standalone documents never carry the global node query (dropped on AWS —
|
|
84
|
+
// see the merged-api plan's Relay node resolution).
|
|
85
|
+
expect(sdl)->not_->toContain("node(id: ID!): Node")
|
|
94
86
|
})
|
|
95
87
|
})
|
|
96
88
|
|
|
@@ -126,81 +118,9 @@ describe("AppSync_SdlDecorate.injectAwsAuthAll", () => {
|
|
|
126
118
|
})
|
|
127
119
|
})
|
|
128
120
|
|
|
129
|
-
describe("AppSync_SdlDecorate.planAwsPushes", () => {
|
|
130
|
-
// Neutral admin base: a system-callable mutation + a shared traversal type.
|
|
131
|
-
let rawAdminBase = ReventlessCore.GraphQL_Stitcher.encode({
|
|
132
|
-
types: [`type CommandAccepted {\n id: ID!\n}`],
|
|
133
|
-
mutations: [` Platform_RegisterApiFragment(input: ApiFragmentInput): CommandResult`],
|
|
134
|
-
queries: [],
|
|
135
|
-
subscriptions: [],
|
|
136
|
-
subscriptionSources: [],
|
|
137
|
-
})
|
|
138
|
-
let iamFieldNames = ["Platform_RegisterApiFragment"]
|
|
139
|
-
let mkFrag = (~mutation, ~target): AppSync_SdlDecorate.targetedFragmentInput => {
|
|
140
|
-
encoded: ReventlessCore.GraphQL_Stitcher.encode({
|
|
141
|
-
types: [],
|
|
142
|
-
mutations: [mutation],
|
|
143
|
-
queries: [],
|
|
144
|
-
subscriptions: [],
|
|
145
|
-
subscriptionSources: [],
|
|
146
|
-
}).encoded,
|
|
147
|
-
protocol: "graphql",
|
|
148
|
-
target,
|
|
149
|
-
}
|
|
150
|
-
let platformFrag = mkFrag(~mutation=` Inspector_Ping: CommandResult`, ~target="Platform")
|
|
151
|
-
let domainFrag = mkFrag(~mutation=` Catalog_AddProduct(input: AddInput): CommandResult`, ~target="Domain")
|
|
152
|
-
|
|
153
|
-
testSync("split mode: Platform API carries admin base (dual-auth) + Platform frags; Domain API excludes the admin base", () => {
|
|
154
|
-
let plans = AppSync_SdlDecorate.planAwsPushes(
|
|
155
|
-
~rawAdminBase,
|
|
156
|
-
~iamFieldNames,
|
|
157
|
-
~fragments=[platformFrag, domainFrag],
|
|
158
|
-
~splitApi=true,
|
|
159
|
-
)
|
|
160
|
-
expect(plans->Array.length)->toBe(2)
|
|
161
|
-
let platformPlan = plans->Array.find(p => p.api == "PlatformApi")->Option.getOrThrow
|
|
162
|
-
let domainPlan = plans->Array.find(p => p.api == "DomainApi")->Option.getOrThrow
|
|
163
|
-
// Admin base mutation lands on the Platform API with dual-auth (system caller).
|
|
164
|
-
expect(platformPlan.sdl)->toContain("Platform_RegisterApiFragment")
|
|
165
|
-
expect(platformPlan.sdl)->toContain(`@aws_cognito_user_pools(cognito_groups: ["Admin"]) @aws_iam`)
|
|
166
|
-
// Platform-target plugin field present on the Platform API.
|
|
167
|
-
expect(platformPlan.sdl)->toContain("Inspector_Ping")
|
|
168
|
-
// Shared traversal type stamped once on the assembled SDL.
|
|
169
|
-
expect(platformPlan.sdl)->toContain(`type CommandAccepted @aws_cognito_user_pools @aws_iam {`)
|
|
170
|
-
// Domain-target field on the Domain API; admin base absent (empty base in split mode).
|
|
171
|
-
expect(domainPlan.sdl)->toContain("Catalog_AddProduct")
|
|
172
|
-
expect(domainPlan.sdl->String.includes("Platform_RegisterApiFragment"))->toBe(false)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
testSync("unified mode: a single Domain API carries admin base + all frags", () => {
|
|
176
|
-
let plans = AppSync_SdlDecorate.planAwsPushes(
|
|
177
|
-
~rawAdminBase,
|
|
178
|
-
~iamFieldNames,
|
|
179
|
-
~fragments=[platformFrag, domainFrag],
|
|
180
|
-
~splitApi=false,
|
|
181
|
-
)
|
|
182
|
-
expect(plans->Array.length)->toBe(1)
|
|
183
|
-
let plan = plans->Array.getUnsafe(0)
|
|
184
|
-
expect(plan.api)->toBe("DomainApi")
|
|
185
|
-
expect(plan.sdl)->toContain("Platform_RegisterApiFragment")
|
|
186
|
-
expect(plan.sdl)->toContain("Inspector_Ping")
|
|
187
|
-
expect(plan.sdl)->toContain("Catalog_AddProduct")
|
|
188
|
-
})
|
|
189
|
-
})
|
|
190
121
|
|
|
191
122
|
describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
|
|
192
|
-
|
|
193
|
-
// SigV4; its return type Platform_ApiFragmentEntry must carry the type-level @aws_iam or
|
|
194
|
-
// the SigV4 caller reaches the query field but gets "Not Authorized to access <field> on
|
|
195
|
-
// type Platform_ApiFragmentEntry" (deploy validation #7).
|
|
196
|
-
testSync("stamps Platform_ApiFragmentEntry with dual-auth", () => {
|
|
197
|
-
let sdl = AppSync_SdlDecorate.stampSharedIamTypes(
|
|
198
|
-
"type Platform_ApiFragmentEntry {\n pluginId: String!\n pushStatus: String!\n}",
|
|
199
|
-
)
|
|
200
|
-
expect(sdl)->toContain("type Platform_ApiFragmentEntry @aws_cognito_user_pools @aws_iam {")
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
testSync("still stamps the CommandResult members", () => {
|
|
123
|
+
testSync("stamps the CommandResult members", () => {
|
|
204
124
|
let sdl = AppSync_SdlDecorate.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}")
|
|
205
125
|
expect(sdl)->toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {")
|
|
206
126
|
})
|
|
@@ -61,10 +61,10 @@ globalThis.describe("AppSync_SdlDecorate.injectAwsSubscribe", () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
globalThis.describe("AppSync_Adapter.
|
|
65
|
-
globalThis.test("assembles neutral
|
|
66
|
-
let
|
|
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
|
|
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(
|
|
78
|
+
globalThis.expect(sdl).not.toContain("node(id: ID!): Node");
|
|
89
79
|
});
|
|
90
80
|
});
|
|
91
81
|
|
|
@@ -115,63 +105,8 @@ 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");
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
108
|
globalThis.describe("AppSync_SdlDecorate.stampSharedIamTypes", () => {
|
|
170
|
-
globalThis.test("stamps
|
|
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 {");
|
|
173
|
-
});
|
|
174
|
-
globalThis.test("still stamps the CommandResult members", () => {
|
|
109
|
+
globalThis.test("stamps the CommandResult members", () => {
|
|
175
110
|
let sdl = AppSync_SdlDecorate$ReventlessAws.stampSharedIamTypes("type CommandAccepted {\n id: ID!\n}");
|
|
176
111
|
globalThis.expect(sdl).toContain("type CommandAccepted @aws_cognito_user_pools @aws_iam {");
|
|
177
112
|
});
|
package/tests/MCP_LambdaTest.res
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 */
|