@reventlessdev/reventless-aws 3.0.0-alpha.266 → 3.0.0-alpha.267

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 CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.267 (2026-08-04)
7
+
8
+ ### Features
9
+
10
+ * **admin:** serve the complete plugin structure to developer tooling ([e99cda0](https://github.com/ReventlessDev/reventless-core/commit/e99cda02a2f862b36bcfdb018a0fb34bb7426a2b))
11
+
12
+
6
13
  # 3.0.0-alpha.266 (2026-08-04)
7
14
 
8
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.266",
3
+ "version": "3.0.0-alpha.267",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -13,16 +13,16 @@
13
13
  "uuid": "^13.0.0",
14
14
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.3",
15
15
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
16
- "@reventlessdev/rescript-node": "2.0.0-alpha.1",
17
16
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
18
17
  "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.65",
18
+ "@reventlessdev/rescript-node": "2.0.0-alpha.1",
19
19
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
20
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
21
- "@reventlessdev/reventless-core": "3.0.0-alpha.211",
22
20
  "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
23
- "@reventlessdev/reventless-spec": "3.0.0-alpha.100",
21
+ "@reventlessdev/reventless-core": "3.0.0-alpha.212",
24
22
  "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
25
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.75"
23
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.76",
24
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
25
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.100"
26
26
  },
27
27
  "devDependencies": {
28
28
  "rescript": "12.3.0",
package/src/Platform.res CHANGED
@@ -1452,6 +1452,7 @@ module MakeWithConfig = (
1452
1452
  ~api=platformApi,
1453
1453
  ~pluginReadModelTableName=tableName,
1454
1454
  ~offloadBucketName,
1455
+ ~schemaReady=admin.adminSchemaPushed,
1455
1456
  ~opts={},
1456
1457
  )
1457
1458
  | None => ()
@@ -861,7 +861,7 @@ function MakeWithConfig(Config) {
861
861
  Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
862
862
  if (pluginReadModelTableName !== undefined) {
863
863
  AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
864
- Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, {});
864
+ Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, {});
865
865
  }
866
866
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
867
867
  if (rm !== undefined) {
@@ -2096,7 +2096,7 @@ function Make($star) {
2096
2096
  Pulumi$Pulumi.$$export("offloadBucket", offloadBucketName);
2097
2097
  if (pluginReadModelTableName !== undefined) {
2098
2098
  AggregateRuntime_Builder_Single$ReventlessAws.setPluginReadModelTable(pluginReadModelTableName);
2099
- Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, {});
2099
+ Platform_ComponentDefinitions_Lambda$ReventlessAws.make(platformApi, pluginReadModelTableName, offloadBucketName, admin.adminSchemaPushed, {});
2100
2100
  }
2101
2101
  let rm = admin.stateViewSlicesOutputs["UiFragments"];
2102
2102
  if (rm !== undefined) {
@@ -24,10 +24,29 @@ export function response(ctx) {
24
24
  }
25
25
  `->Pulumi.Input.make
26
26
 
27
+ // `Platform_PluginStructures` — the same scan, unfiltered. The `complete` flag is
28
+ // the whole difference; see the handler's `toEntryWith`.
29
+ let completeResolverCode = `
30
+ import { util } from '@aws-appsync/utils';
31
+ export function request(ctx) {
32
+ return { operation: 'Invoke', payload: { complete: true } };
33
+ }
34
+ export function response(ctx) {
35
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
36
+ return ctx.result;
37
+ }
38
+ `->Pulumi.Input.make
39
+
27
40
  let make = (
28
41
  ~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
29
42
  ~pluginReadModelTableName: Pulumi.Output.t<string>,
30
43
  ~offloadBucketName: Pulumi.Output.t<string>,
44
+ // Resolves once the admin schema push is ACTIVE (Platform_Admin.adminSchemaPushed).
45
+ // Only the `Platform_PluginStructures` resolver is gated on it: that field is new,
46
+ // so CreateResolver would otherwise race StartSchemaCreation on the deploy that
47
+ // first ships it. `Platform_ComponentDefinitions` predates the gate and is left
48
+ // ungated so this change adds no dependency edge to an already-deployed resource.
49
+ ~schemaReady: Pulumi.Output.t<unit>,
31
50
  ~opts: Pulumi.ComponentResource.options,
32
51
  ) => {
33
52
  let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
@@ -215,4 +234,21 @@ let make = (
215
234
  ~code=resolverCode,
216
235
  ~opts,
217
236
  )
237
+
238
+ // Second field, same DataSource and same Lambda — the developer-tooling read of
239
+ // the identical scan. Sharing the data source is what keeps the two answers from
240
+ // drifting: there is one place that decides what a deployed plugin's structure is.
241
+ // Created only after the schema carrying `Platform_PluginStructures` is ACTIVE.
242
+ let _structuresResolver =
243
+ schemaReady->Pulumi.Output.apply(() =>
244
+ AppSync_Resolver_Native.makeUnitJsResolver(
245
+ ~name=name ++ "StructuresResolver",
246
+ ~api,
247
+ ~dataSourceName=dataSource.name->Pulumi.Output.asInput,
248
+ ~type_="Query"->Pulumi.Input.make,
249
+ ~field="Platform_PluginStructures"->Pulumi.Input.make,
250
+ ~code=completeResolverCode,
251
+ ~opts,
252
+ )
253
+ )
218
254
  }
@@ -27,7 +27,18 @@ export function response(ctx) {
27
27
  }
28
28
  `;
29
29
 
30
- function make(api, pluginReadModelTableName, offloadBucketName, opts) {
30
+ let completeResolverCode = `
31
+ import { util } from '@aws-appsync/utils';
32
+ export function request(ctx) {
33
+ return { operation: 'Invoke', payload: { complete: true } };
34
+ }
35
+ export function response(ctx) {
36
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
37
+ return ctx.result;
38
+ }
39
+ `;
40
+
41
+ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, opts) {
31
42
  let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
32
43
  let name = "PlatformUIDefinitions";
33
44
  let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "Lambda", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "Lambda", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
@@ -131,10 +142,12 @@ function make(api, pluginReadModelTableName, offloadBucketName, opts) {
131
142
  serviceRoleArn: dataSourceRole.arn
132
143
  }, opts$1);
133
144
  AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "Resolver", api, dataSource.name, "Query", "Platform_ComponentDefinitions", resolverCode, opts$1);
145
+ schemaReady.apply(() => AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "StructuresResolver", api, dataSource.name, "Query", "Platform_PluginStructures", completeResolverCode, opts$1));
134
146
  }
135
147
 
136
148
  export {
137
149
  resolverCode,
150
+ completeResolverCode,
138
151
  make,
139
152
  }
140
153
  /* @pulumi/aws Not a pure module */
@@ -43,11 +43,21 @@ let filterStructure = (structure: JSON.t): option<dict<JSON.t>> =>
43
43
  Some(out)
44
44
  }
45
45
 
46
- // `{pluginId: name, ...filterStructure(structure)}` — pluginId first so the
47
- // structure's fields win on the (never-expected) key collision, matching the JS
48
- // spread. Drops a row with no decodable structure (former `!item.structure`).
49
- let toEntry = (item: dict<JSON.t>, ~name: string): option<JSON.t> =>
50
- switch item->Dict.get("structure")->Option.flatMap(filterStructure) {
46
+ // `{pluginId: name, ...structure}` — pluginId first so the structure's fields win
47
+ // on the (never-expected) key collision, matching the JS spread. Drops a row with
48
+ // no decodable structure (former `!item.structure`).
49
+ //
50
+ // `~filter` is what separates the two fields this handler serves: AutoUI
51
+ // (`Platform_ComponentDefinitions`) gets the public-queryable subset, developer
52
+ // tooling (`Platform_PluginStructures`) gets the structure exactly as persisted,
53
+ // including Internal components and the producer-side `extensionPoints` that the
54
+ // AutoUI entry type does not declare.
55
+ let toEntryWith = (~filter: bool, item: dict<JSON.t>, ~name: string): option<JSON.t> => {
56
+ let structure =
57
+ item
58
+ ->Dict.get("structure")
59
+ ->Option.flatMap(s => filter ? filterStructure(s) : s->JSON.Decode.object)
60
+ switch structure {
51
61
  | None => None
52
62
  | Some(structureObj) =>
53
63
  let entry = Dict.make()
@@ -55,10 +65,17 @@ let toEntry = (item: dict<JSON.t>, ~name: string): option<JSON.t> =>
55
65
  structureObj->Dict.toArray->Array.forEach(((k, v)) => entry->Dict.set(k, v))
56
66
  Some(JSON.Encode.object(entry))
57
67
  }
68
+ }
69
+
58
70
 
59
71
  // The built-in Platform_Admin entry, injected at deploy time as the
60
72
  // ADMIN_ENTRY_JSON env var (the admin never Connects to itself, so its structure
61
73
  // never enters the Plugin read model).
74
+ //
75
+ // Shared by both fields. The admin structure has no Internal components and no
76
+ // extension points, so its filtered and complete encodings carry the same
77
+ // components; the structure-level fields the complete entry adds are absent from
78
+ // this JSON and resolve to null, which is what `extensionPoints: None` means.
62
79
  let adminEntry: option<JSON.t> =
63
80
  switch NodeProcess.env->Dict.get("ADMIN_ENTRY_JSON") {
64
81
  | Some(s) if s != "" => Some(JSON.parseOrThrow(s))
@@ -94,7 +111,21 @@ let resolveStructure = (
94
111
  }
95
112
  }
96
113
 
97
- let handler = async (_event: JSON.t): array<JSON.t> => {
114
+ // One Lambda serves both fields the scan, the offload resolution and the
115
+ // latest-version-per-plugin collapse are identical work, and duplicating them into
116
+ // a second function would mean a second cold start and a second chance for the two
117
+ // to disagree about what "the deployed structure" is. The resolver for
118
+ // `Platform_PluginStructures` invokes with `{complete: true}`.
119
+ let isComplete = (event: JSON.t): bool =>
120
+ event
121
+ ->JSON.Decode.object
122
+ ->Option.flatMap(o => o->Dict.get("complete"))
123
+ ->Option.flatMap(JSON.Decode.bool)
124
+ ->Option.getOr(false)
125
+
126
+ let handler = async (event: JSON.t): array<JSON.t> => {
127
+ let complete = isComplete(event)
128
+ let toEntry = (item, ~name) => toEntryWith(~filter=!complete, item, ~name)
98
129
  let admin = adminEntry->Option.mapOr([], e => [e])
99
130
  switch NodeProcess.env->Dict.get("PLUGIN_RM_TABLE") {
100
131
  | None | Some("") =>
@@ -31,14 +31,20 @@ function filterStructure(structure) {
31
31
  return out;
32
32
  }
33
33
 
34
- function toEntry(item, name) {
35
- let structureObj = Stdlib_Option.flatMap(item["structure"], filterStructure);
36
- if (structureObj === undefined) {
34
+ function toEntryWith(filter, item, name) {
35
+ let structure = Stdlib_Option.flatMap(item["structure"], s => {
36
+ if (filter) {
37
+ return filterStructure(s);
38
+ } else {
39
+ return Stdlib_JSON.Decode.object(s);
40
+ }
41
+ });
42
+ if (structure === undefined) {
37
43
  return;
38
44
  }
39
45
  let entry = {};
40
46
  entry["pluginId"] = name;
41
- Object.entries(structureObj).forEach(param => {
47
+ Object.entries(structure).forEach(param => {
42
48
  entry[param[0]] = param[1];
43
49
  });
44
50
  return entry;
@@ -65,7 +71,13 @@ function resolveStructure(fetch, item) {
65
71
  }
66
72
  }
67
73
 
68
- async function handler(_event) {
74
+ function isComplete(event) {
75
+ return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(event), o => o["complete"]), Stdlib_JSON.Decode.bool), false);
76
+ }
77
+
78
+ async function handler(event) {
79
+ let complete = isComplete(event);
80
+ let toEntry = (item, name) => toEntryWith(!complete, item, name);
69
81
  let admin = Stdlib_Option.mapOr(adminEntry, [], e => [e]);
70
82
  let table = process.env["PLUGIN_RM_TABLE"];
71
83
  if (table !== undefined) {
@@ -94,9 +106,10 @@ export {
94
106
  str,
95
107
  isPublicQueryable,
96
108
  filterStructure,
97
- toEntry,
109
+ toEntryWith,
98
110
  adminEntry,
99
111
  resolveStructure,
112
+ isComplete,
100
113
  handler,
101
114
  }
102
115
  /* s Not a pure module */