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

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.
@@ -10,15 +10,12 @@ import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
10
10
  import * as Effect$1 from "effect/Effect";
11
11
  import * as Pulumi from "@pulumi/pulumi";
12
12
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
13
- import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
14
13
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
15
14
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
16
- import * as LibDynamodb from "@aws-sdk/lib-dynamodb";
17
15
  import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
18
16
  import * as ClientAppsync from "@aws-sdk/client-appsync";
19
17
  import * as Auth_Cognito$ReventlessAws from "../../adapter/Auth/Auth_Cognito.res.mjs";
20
18
  import * as AppSync_Error$ReventlessAws from "../../errors/AppSync_Error.res.mjs";
21
- import * as DynamoDb_DocumentClient$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DocumentClient.res.mjs";
22
19
  import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
23
20
  import * as AppSync_SdlDecorate$ReventlessAws from "./AppSync_SdlDecorate.res.mjs";
24
21
  import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
@@ -90,84 +87,6 @@ function deploySchemaWithRetry(client, apiId, definition) {
90
87
  })).then(param => Promise.resolve())), AppSync_Error$ReventlessAws.retrySchedule);
91
88
  }
92
89
 
93
- function schemaLockSleep(ms) {
94
- return new Promise((resolve, param) => {
95
- setTimeout(() => resolve(), ms);
96
- });
97
- }
98
-
99
- let _schemaLockCounter = {
100
- contents: 0
101
- };
102
-
103
- async function withSchemaPushLock(tableName, apiId, leaseMsOpt, maxWaitMsOpt, fn) {
104
- let leaseMs = leaseMsOpt !== undefined ? leaseMsOpt : 120000;
105
- let maxWaitMs = maxWaitMsOpt !== undefined ? maxWaitMsOpt : 180000;
106
- let lockId = `schema-push-lock:` + apiId;
107
- _schemaLockCounter.contents = _schemaLockCounter.contents + 1 | 0;
108
- let holder = apiId + `#` + Date.now().toString() + `#` + _schemaLockCounter.contents.toString();
109
- let acquire = async () => {
110
- let deadline = Date.now() + maxWaitMs;
111
- let acquired = false;
112
- while (!acquired) {
113
- let now = Date.now();
114
- let ok = await Stdlib_Promise.$$catch(DynamoDb_DocumentClient$AwsSdk.PutCommand.send(new LibDynamodb.PutCommand({
115
- Item: Object.fromEntries([
116
- [
117
- "id",
118
- lockId
119
- ],
120
- [
121
- "holder",
122
- holder
123
- ],
124
- [
125
- "expiresAt",
126
- now + leaseMs
127
- ]
128
- ]),
129
- TableName: tableName,
130
- ConditionExpression: "attribute_not_exists(id) OR expiresAt < :now",
131
- ExpressionAttributeValues: Object.fromEntries([[
132
- ":now",
133
- now
134
- ]])
135
- })).then(param => true), param => Promise.resolve(false));
136
- if (ok) {
137
- acquired = true;
138
- } else if (Date.now() > deadline) {
139
- log.warn("AppSync_Adapter", undefined, `schema-push lock for ` + apiId + ` not acquired within ` + maxWaitMs.toString() + `ms — proceeding best-effort`);
140
- acquired = true;
141
- } else {
142
- await schemaLockSleep(1000);
143
- }
144
- };
145
- };
146
- let release = async () => {
147
- await Stdlib_Promise.$$catch(DynamoDb_DocumentClient$AwsSdk.DeleteCommand.send(new LibDynamodb.DeleteCommand({
148
- TableName: tableName,
149
- Key: Object.fromEntries([[
150
- "id",
151
- lockId
152
- ]]),
153
- ConditionExpression: "holder = :holder",
154
- ExpressionAttributeValues: Object.fromEntries([[
155
- ":holder",
156
- holder
157
- ]])
158
- })).then(param => {}), param => Promise.resolve());
159
- };
160
- await acquire();
161
- try {
162
- let r = await fn();
163
- await release();
164
- return r;
165
- } catch (exn) {
166
- await release();
167
- throw exn;
168
- }
169
- }
170
-
171
90
  let _client = {
172
91
  contents: undefined
173
92
  };
@@ -408,9 +327,6 @@ export {
408
327
  waitForSchemaActive,
409
328
  getIntrospectionSdl,
410
329
  deploySchemaWithRetry,
411
- schemaLockSleep,
412
- _schemaLockCounter,
413
- withSchemaPushLock,
414
330
  _client,
415
331
  getClient,
416
332
  _permissionToCognitoGroups,
@@ -3,38 +3,21 @@ let log = ReventlessCore.Logger.fromEnv()
3
3
  type adminConfig = {
4
4
  eventTopicArn: option<Pulumi.Output.t<string>>,
5
5
  pluginReadModelTableName: option<Pulumi.Output.t<string>>,
6
- // Dedicated PluginSchemaPersistence table holding deploy-time SDL fragments
7
- // (rows keyed "deploy-schema:<name>"). The runtime schema stitch reads this
8
- // durable source instead of the lifecycle-volatile Plugin RM Connected rows.
9
- pluginSchemaPersistenceTableName: option<Pulumi.Output.t<string>>,
10
6
  schedulerRoleArn: option<Pulumi.Output.t<string>>,
11
7
  schedulerQueueArn: option<Pulumi.Output.t<string>>,
12
8
  schedulerQueueName: option<Pulumi.Output.t<string>>,
13
9
  appSyncApiId: option<Pulumi.Output.t<string>>,
14
10
  clonerEnabled: bool,
15
- // Reactive ApiFragmentRegistry single writer (2e), admin EventCollector only:
16
- // the Platform AppSync id (split mode; unified → same as appSyncApiId), the
17
- // ApiFragments StateViewSlice table it scans, the admin DCB command-topic FIFO
18
- // URL it dispatches RecordApiFragmentPush to, and the split/unified mode flag.
19
- platformApiId: option<Pulumi.Output.t<string>>,
20
- apiFragmentRegistryTableName: option<Pulumi.Output.t<string>>,
21
- adminDcbCmdTopicUrl: option<Pulumi.Output.t<string>>,
22
- splitApi: bool,
23
11
  }
24
12
 
25
13
  let configRef: ref<adminConfig> = ref({
26
14
  eventTopicArn: None,
27
15
  pluginReadModelTableName: None,
28
- pluginSchemaPersistenceTableName: None,
29
16
  schedulerRoleArn: None,
30
17
  schedulerQueueArn: None,
31
18
  schedulerQueueName: None,
32
19
  appSyncApiId: None,
33
20
  clonerEnabled: false,
34
- platformApiId: None,
35
- apiFragmentRegistryTableName: None,
36
- adminDcbCmdTopicUrl: None,
37
- splitApi: false,
38
21
  })
39
22
 
40
23
  type sliceModulePaths = {
@@ -111,31 +94,21 @@ let setStateChangesConfig = (~sync=?, ~async=?, ()) => {
111
94
  let registerConfig = (
112
95
  ~eventTopicArn=?,
113
96
  ~pluginReadModelTableName=?,
114
- ~pluginSchemaPersistenceTableName=?,
115
97
  ~schedulerRoleArn=?,
116
98
  ~schedulerQueueArn=?,
117
99
  ~schedulerQueueName=?,
118
100
  ~appSyncApiId=?,
119
101
  ~clonerEnabled=false,
120
- ~platformApiId=?,
121
- ~apiFragmentRegistryTableName=?,
122
- ~adminDcbCmdTopicUrl=?,
123
- ~splitApi=false,
124
102
  (),
125
103
  ) =>
126
104
  configRef := {
127
105
  eventTopicArn,
128
106
  pluginReadModelTableName,
129
- pluginSchemaPersistenceTableName,
130
107
  schedulerRoleArn,
131
108
  schedulerQueueArn,
132
109
  schedulerQueueName,
133
110
  appSyncApiId,
134
111
  clonerEnabled,
135
- platformApiId,
136
- apiFragmentRegistryTableName,
137
- adminDcbCmdTopicUrl,
138
- splitApi,
139
112
  }
140
113
 
141
114
  // PluginRuntime_Builder is a functor so that the caller can inject the EventCollectorChannel
@@ -318,10 +291,6 @@ module Make = (
318
291
  config.schedulerQueueName->outputOrPlaceholder->Obj.magic,
319
292
  config.appSyncApiId->outputOrPlaceholder->Obj.magic,
320
293
  epEventTopicArnsOutput->Pulumi.Output.asInput->Obj.magic,
321
- config.pluginSchemaPersistenceTableName->outputOrPlaceholder->Obj.magic,
322
- config.platformApiId->outputOrPlaceholder->Obj.magic,
323
- config.apiFragmentRegistryTableName->outputOrPlaceholder->Obj.magic,
324
- config.adminDcbCmdTopicUrl->outputOrPlaceholder->Obj.magic,
325
294
  ])
326
295
  ->Pulumi.Output.apply(values => {
327
296
  let queueUrl = values->Array.getUnsafe(0)
@@ -333,27 +302,13 @@ module Make = (
333
302
  let schedQueueName = values->Array.getUnsafe(6)
334
303
  let appSyncApiId = values->Array.getUnsafe(7)
335
304
  let epEventTopicArns: array<string> = Obj.magic(values->Array.getUnsafe(8))
336
- let schemaPersistenceTable = values->Array.getUnsafe(9)
337
- let platformApiId = values->Array.getUnsafe(10)
338
- let apiFragmentRegistryTable = values->Array.getUnsafe(11)
339
- let adminDcbCmdTopicUrl = values->Array.getUnsafe(12)
340
305
 
341
306
  let dict = Dict.make()
342
307
  dict->Dict.set("queueUrl", JSON.Encode.string(queueUrl))
343
308
  dict->Dict.set("pluginExtensionPointCmdTopicUrl", JSON.Encode.string(pluginEpCmdTopicUrl))
344
309
  dict->Dict.set("eventTopicArn", JSON.Encode.string(topLevelEventTopicArn))
345
310
  dict->Dict.set("pluginReadModelTableName", JSON.Encode.string(rmTable))
346
- dict->Dict.set(
347
- "pluginSchemaPersistenceTableName",
348
- JSON.Encode.string(schemaPersistenceTable),
349
- )
350
311
  dict->Dict.set("appSyncApiId", JSON.Encode.string(appSyncApiId))
351
- // Reactive ApiFragmentRegistry single writer (2e) — placeholders for
352
- // plugin ECs / all-at-once platforms disable it in the mjs.
353
- dict->Dict.set("platformApiId", JSON.Encode.string(platformApiId))
354
- dict->Dict.set("apiFragmentRegistryTableName", JSON.Encode.string(apiFragmentRegistryTable))
355
- dict->Dict.set("adminDcbCmdTopicUrl", JSON.Encode.string(adminDcbCmdTopicUrl))
356
- dict->Dict.set("splitApi", JSON.Encode.bool(config.splitApi))
357
312
  dict->Dict.set("clonerEnabled", JSON.Encode.bool(config.clonerEnabled))
358
313
  dict->Dict.set("schedulerRoleArn", JSON.Encode.string(schedRoleArn))
359
314
  dict->Dict.set("schedulerQueueArn", JSON.Encode.string(schedQueueArn))
@@ -643,100 +598,6 @@ module Make = (
643
598
  | None => ()
644
599
  }
645
600
 
646
- // mkUpdateApiSchema reads each plugin's deploy-time SDL fragment from the
647
- // dedicated PluginSchemaPersistence table (deploy-schema:<name> rows) to
648
- // re-stitch the live schema. Grant Scan on that table too — it is owned by
649
- // Platform.res, so the admin EC's default policy includes no perms on it.
650
- switch config.pluginSchemaPersistenceTableName {
651
- | Some(schemaTableOutput) =>
652
- let policyJson =
653
- schemaTableOutput->Pulumi.Output.apply(tableName =>
654
- PulumiAws.PolicyDocument.make(
655
- ~id=`${name}PluginSchemaScanPolicy`,
656
- ~statements=[
657
- {
658
- sid: "AllowAdminScanPluginSchemaPersistence",
659
- effect: Allow,
660
- actions: Actions(["dynamodb:Scan"]),
661
- resources: Resource("arn:aws:dynamodb:*:*:table/" ++ tableName),
662
- },
663
- ],
664
- )->PulumiAws.PolicyDocument.toJsonString
665
- )
666
- let _ = PulumiAws.IAM.RolePolicy.make(
667
- ~name=`${name}-pluginSchemaScan`,
668
- ~args={
669
- policy: policyJson->Pulumi.Output.asInput,
670
- role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
671
- },
672
- )
673
- | None => ()
674
- }
675
-
676
- // 2e: the reactive ApiFragmentRegistry single writer scans the ApiFragments
677
- // StateViewSlice table to re-fold the registry on each ApiFragment* event.
678
- // The table is owned by the admin DcbBuilder, so grant Scan explicitly.
679
- switch config.apiFragmentRegistryTableName {
680
- | Some(tableOutput) =>
681
- let policyJson =
682
- tableOutput->Pulumi.Output.apply(tableName =>
683
- PulumiAws.PolicyDocument.make(
684
- ~id=`${name}ApiFragmentsScanPolicy`,
685
- ~statements=[
686
- {
687
- sid: "AllowAdminScanApiFragments",
688
- effect: Allow,
689
- actions: Actions(["dynamodb:Scan"]),
690
- resources: Resource("arn:aws:dynamodb:*:*:table/" ++ tableName),
691
- },
692
- ],
693
- )->PulumiAws.PolicyDocument.toJsonString
694
- )
695
- let _ = PulumiAws.IAM.RolePolicy.make(
696
- ~name=`${name}-apiFragmentsScan`,
697
- ~args={
698
- policy: policyJson->Pulumi.Output.asInput,
699
- role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
700
- },
701
- )
702
- | None => ()
703
- }
704
-
705
- // 2e: the reactive push writes the outcome back with RecordApiFragmentPush,
706
- // dispatched onto the admin DCB command-topic FIFO queue — grant
707
- // sqs:SendMessage on it (queue URL → ARN, same derivation as
708
- // publishToAggregates below).
709
- switch config.adminDcbCmdTopicUrl {
710
- | Some(urlOutput) =>
711
- let policyJson =
712
- urlOutput->Pulumi.Output.apply(url => {
713
- let arn = switch url->String.split("/") {
714
- | [_, _, host, acct, qn] =>
715
- let region = host->String.split(".")->Array.get(1)->Option.getOr("eu-west-1")
716
- `arn:aws:sqs:${region}:${acct}:${qn}`
717
- | _ => url
718
- }
719
- PulumiAws.PolicyDocument.make(
720
- ~id=`${name}AdminDcbSendPolicy`,
721
- ~statements=[
722
- {
723
- sid: "AllowAdminDispatchRecordApiFragmentPush",
724
- effect: Allow,
725
- actions: Actions(["sqs:SendMessage"]),
726
- resources: Resource(arn),
727
- },
728
- ],
729
- )->PulumiAws.PolicyDocument.toJsonString
730
- })
731
- let _ = PulumiAws.IAM.RolePolicy.make(
732
- ~name=`${name}-adminDcbSend`,
733
- ~args={
734
- policy: policyJson->Pulumi.Output.asInput,
735
- role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
736
- },
737
- )
738
- | None => ()
739
- }
740
601
  }
741
602
 
742
603
  // Plugin EC sqs:SendMessage grants on the aggregate / StateChangeSlice
@@ -25,16 +25,11 @@ let configRef = {
25
25
  contents: {
26
26
  eventTopicArn: undefined,
27
27
  pluginReadModelTableName: undefined,
28
- pluginSchemaPersistenceTableName: undefined,
29
28
  schedulerRoleArn: undefined,
30
29
  schedulerQueueArn: undefined,
31
30
  schedulerQueueName: undefined,
32
31
  appSyncApiId: undefined,
33
- clonerEnabled: false,
34
- platformApiId: undefined,
35
- apiFragmentRegistryTableName: undefined,
36
- adminDcbCmdTopicUrl: undefined,
37
- splitApi: false
32
+ clonerEnabled: false
38
33
  }
39
34
  };
40
35
 
@@ -108,22 +103,16 @@ function setStateChangesConfig(sync, async, param) {
108
103
  });
109
104
  }
110
105
 
111
- function registerConfig(eventTopicArn, pluginReadModelTableName, pluginSchemaPersistenceTableName, schedulerRoleArn, schedulerQueueArn, schedulerQueueName, appSyncApiId, clonerEnabledOpt, platformApiId, apiFragmentRegistryTableName, adminDcbCmdTopicUrl, splitApiOpt, param) {
106
+ function registerConfig(eventTopicArn, pluginReadModelTableName, schedulerRoleArn, schedulerQueueArn, schedulerQueueName, appSyncApiId, clonerEnabledOpt, param) {
112
107
  let clonerEnabled = clonerEnabledOpt !== undefined ? clonerEnabledOpt : false;
113
- let splitApi = splitApiOpt !== undefined ? splitApiOpt : false;
114
108
  configRef.contents = {
115
109
  eventTopicArn: eventTopicArn,
116
110
  pluginReadModelTableName: pluginReadModelTableName,
117
- pluginSchemaPersistenceTableName: pluginSchemaPersistenceTableName,
118
111
  schedulerRoleArn: schedulerRoleArn,
119
112
  schedulerQueueArn: schedulerQueueArn,
120
113
  schedulerQueueName: schedulerQueueName,
121
114
  appSyncApiId: appSyncApiId,
122
- clonerEnabled: clonerEnabled,
123
- platformApiId: platformApiId,
124
- apiFragmentRegistryTableName: apiFragmentRegistryTableName,
125
- adminDcbCmdTopicUrl: adminDcbCmdTopicUrl,
126
- splitApi: splitApi
115
+ clonerEnabled: clonerEnabled
127
116
  };
128
117
  }
129
118
 
@@ -209,11 +198,7 @@ function Make(EventCollectorChannel) {
209
198
  outputOrPlaceholder(config.schedulerQueueArn),
210
199
  outputOrPlaceholder(config.schedulerQueueName),
211
200
  outputOrPlaceholder(config.appSyncApiId),
212
- epEventTopicArnsOutput,
213
- outputOrPlaceholder(config.pluginSchemaPersistenceTableName),
214
- outputOrPlaceholder(config.platformApiId),
215
- outputOrPlaceholder(config.apiFragmentRegistryTableName),
216
- outputOrPlaceholder(config.adminDcbCmdTopicUrl)
201
+ epEventTopicArnsOutput
217
202
  ]).apply(values => {
218
203
  let queueUrl = values[0];
219
204
  let pluginEpCmdTopicUrl = values[1];
@@ -224,21 +209,12 @@ function Make(EventCollectorChannel) {
224
209
  let schedQueueName = values[6];
225
210
  let appSyncApiId = values[7];
226
211
  let epEventTopicArns = values[8];
227
- let schemaPersistenceTable = values[9];
228
- let platformApiId = values[10];
229
- let apiFragmentRegistryTable = values[11];
230
- let adminDcbCmdTopicUrl = values[12];
231
212
  let dict = {};
232
213
  dict["queueUrl"] = queueUrl;
233
214
  dict["pluginExtensionPointCmdTopicUrl"] = pluginEpCmdTopicUrl;
234
215
  dict["eventTopicArn"] = topLevelEventTopicArn;
235
216
  dict["pluginReadModelTableName"] = rmTable;
236
- dict["pluginSchemaPersistenceTableName"] = schemaPersistenceTable;
237
217
  dict["appSyncApiId"] = appSyncApiId;
238
- dict["platformApiId"] = platformApiId;
239
- dict["apiFragmentRegistryTableName"] = apiFragmentRegistryTable;
240
- dict["adminDcbCmdTopicUrl"] = adminDcbCmdTopicUrl;
241
- dict["splitApi"] = config.splitApi;
242
218
  dict["clonerEnabled"] = config.clonerEnabled;
243
219
  dict["schedulerRoleArn"] = schedRoleArn;
244
220
  dict["schedulerQueueArn"] = schedQueueArn;
@@ -383,58 +359,6 @@ function Make(EventCollectorChannel) {
383
359
  role: runtime.parts.lambdaRole.id
384
360
  });
385
361
  }
386
- let schemaTableOutput = config.pluginSchemaPersistenceTableName;
387
- if (schemaTableOutput !== undefined) {
388
- let policyJson$1 = schemaTableOutput.apply(tableName => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `PluginSchemaScanPolicy`, [{
389
- Sid: "AllowAdminScanPluginSchemaPersistence",
390
- Effect: "Allow",
391
- Action: ["dynamodb:Scan"],
392
- Resource: "arn:aws:dynamodb:*:*:table/" + tableName
393
- }])));
394
- new (Aws.iam.RolePolicy)(name + `-pluginSchemaScan`, {
395
- policy: policyJson$1,
396
- role: runtime.parts.lambdaRole.id
397
- });
398
- }
399
- let tableOutput = config.apiFragmentRegistryTableName;
400
- if (tableOutput !== undefined) {
401
- let policyJson$2 = tableOutput.apply(tableName => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `ApiFragmentsScanPolicy`, [{
402
- Sid: "AllowAdminScanApiFragments",
403
- Effect: "Allow",
404
- Action: ["dynamodb:Scan"],
405
- Resource: "arn:aws:dynamodb:*:*:table/" + tableName
406
- }])));
407
- new (Aws.iam.RolePolicy)(name + `-apiFragmentsScan`, {
408
- policy: policyJson$2,
409
- role: runtime.parts.lambdaRole.id
410
- });
411
- }
412
- let urlOutput = config.adminDcbCmdTopicUrl;
413
- if (urlOutput !== undefined) {
414
- let policyJson$3 = urlOutput.apply(url => {
415
- let match = url.split("/");
416
- let arn;
417
- if (match.length !== 5) {
418
- arn = url;
419
- } else {
420
- let host = match[2];
421
- let acct = match[3];
422
- let qn = match[4];
423
- let region = Stdlib_Option.getOr(host.split(".")[1], "eu-west-1");
424
- arn = `arn:aws:sqs:` + region + `:` + acct + `:` + qn;
425
- }
426
- return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `AdminDcbSendPolicy`, [{
427
- Sid: "AllowAdminDispatchRecordApiFragmentPush",
428
- Effect: "Allow",
429
- Action: ["sqs:SendMessage"],
430
- Resource: arn
431
- }]));
432
- });
433
- new (Aws.iam.RolePolicy)(name + `-adminDcbSend`, {
434
- policy: policyJson$3,
435
- role: runtime.parts.lambdaRole.id
436
- });
437
- }
438
362
  }
439
363
  let aggregateNameSet = {};
440
364
  context.extensions.forEach(ext => {
@@ -21,4 +21,12 @@ describe("ApiSchemaPush SideEffect Source (runtime materialisation)", () => {
21
21
  let tags = Reventless.DcbTag.extractAllVariantNames(ApiSchemaPush.Source.eventSchema)
22
22
  expect(tags)->toContain("ApiSchemaComputed")
23
23
  })
24
+
25
+ testSync("Source.Id is a live module — the reflective id round-trip does not crash", () => {
26
+ // SideEffectHandler_Callback reads `Source.Id.schema` on the id-bearing registry events.
27
+ // A bare `module Id` alias compiles to `Id: undefined` in the Source record, so any access
28
+ // (makeFromString/toString/schema) throws "reading 'schema'"/undefined on a regression.
29
+ let id = "registry"->ApiSchemaPush.Source.Id.makeFromString
30
+ expect(id->ApiSchemaPush.Source.Id.toString)->toBe("registry")
31
+ })
24
32
  })
@@ -11,6 +11,10 @@ globalThis.describe("ApiSchemaPush SideEffect Source (runtime materialisation)",
11
11
  let tags = DcbTag$Reventless.extractAllVariantNames(ApiSchemaPush$ReventlessAws.Source.eventSchema);
12
12
  globalThis.expect(tags).toContain("ApiSchemaComputed");
13
13
  });
14
+ globalThis.test("Source.Id is a live module — the reflective id round-trip does not crash", () => {
15
+ let id = ApiSchemaPush$ReventlessAws.Source.Id.makeFromString("registry");
16
+ globalThis.expect(ApiSchemaPush$ReventlessAws.Source.Id.toString(id)).toBe("registry");
17
+ });
14
18
  });
15
19
 
16
20
  /* Not a pure module */
@@ -7,8 +7,9 @@ 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 mutations
11
- expect(config.tools)->toHaveLength(3)
10
+ // Admin has Plugin_Activate, Plugin_Deactivate, Plugin_Retire +
11
+ // ApiFragmentRegistry_Register/DeregisterApiFragment mutations
12
+ expect(config.tools)->toHaveLength(5)
12
13
  })
13
14
 
14
15
  testSync("produces additional clone tool when cloner=true", () => {
@@ -17,8 +18,9 @@ describe("MCP_Lambda.generateAdminConfig", () => {
17
18
  ~serverVersion="1.0.0",
18
19
  ~cloner=true,
19
20
  )
20
- // Plugin_Activate, Plugin_Deactivate, Plugin_Retire, Clone
21
- expect(config.tools)->toHaveLength(4)
21
+ // Plugin_Activate, Plugin_Deactivate, Plugin_Retire,
22
+ // ApiFragmentRegistry_Register/DeregisterApiFragment, Clone
23
+ expect(config.tools)->toHaveLength(6)
22
24
  })
23
25
 
24
26
  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(3);
9
+ globalThis.expect(config.tools).toHaveLength(5);
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(4);
13
+ globalThis.expect(config.tools).toHaveLength(6);
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);