@reventlessdev/reventless-aws 3.0.0-alpha.183 → 3.0.0-alpha.184

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.184 (2026-07-08)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **reventless-aws:** exclude internal rows from Platform_Plugins connection scan ([df14af2](https://github.com/ReventlessDev/reventless-core/commit/df14af2cbd70d49062f2afc03418cdbf18d151d6))
11
+
12
+
6
13
  # 3.0.0-alpha.183 (2026-07-08)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.183",
3
+ "version": "3.0.0-alpha.184",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "dependencies": {
@@ -11,7 +11,7 @@
11
11
  "@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.20",
12
12
  "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
13
13
  "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
14
- "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.46",
14
+ "@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.47",
15
15
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
16
16
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
17
17
  "@reventlessdev/reventless-core": "3.0.0-alpha.145",
@@ -57,6 +57,17 @@ export function response(ctx) {
57
57
  }
58
58
  `->Pulumi.Input.make
59
59
 
60
+ /** The discriminator attribute whose absence marks an internal bookkeeping row for a
61
+ read model whose physical DynamoDB table co-hosts rows written outside the projection.
62
+ Only the Plugins admin RM has this: its table also holds `deploy-schema:*` /
63
+ `plugin-info:*` / `deploy-schema-hash:*` rows (Platform.res preResolversSchemaHook)
64
+ that carry no `name`. Returning `Some(attr)` makes the Connection Scan emit an
65
+ `attribute_exists(#attr)` filter so those rows never reach the non-null GraphQL schema.
66
+ `name` is the already-capitalized read-model name (spec name, e.g. "Plugins").
67
+ See docs/plans/platform-plugins-admin-connection-null-rows.md. */
68
+ let internalRowRequiredAttr = (name: string): option<string> =>
69
+ name == "Plugins" ? Some("name") : None
70
+
60
71
  let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
61
72
  ~name: string,
62
73
  ~api: api,
@@ -217,6 +228,15 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
217
228
  )->Array.forEach(msg => log.warn(~comp="QueryDbResolvers_AppSync", msg))
218
229
  | None => ()
219
230
  }
231
+ // The Plugins admin RM's DynamoDB table co-hosts deploy-time infra rows
232
+ // (`deploy-schema:<name>`, `plugin-info:<name>`, `deploy-schema-hash:<apiId>`)
233
+ // written directly by the platform (Platform.res preResolversSchemaHook), not by
234
+ // the projection. Those rows carry no `name` attribute, so an unfiltered Scan
235
+ // resolves `name: String!` to null → non-null violation that nulls the entire
236
+ // Platform_PluginConnection. Exclude them with an `attribute_exists(#name)` filter
237
+ // (prefix-agnostic: real plugin rows always carry `name`, internal rows never do).
238
+ // See docs/plans/platform-plugins-admin-connection-null-rows.md.
239
+ let requireAttribute = internalRowRequiredAttr(name)
220
240
  let resolverAll = makeQueryResolver(
221
241
  ~resolverName=fieldNameForAll->String.capitalize,
222
242
  ~field=fieldNameForAll->Pulumi.Input.make,
@@ -226,6 +246,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
226
246
  ~filterFields=filterFieldNames,
227
247
  ~rangeFields=rangeFieldNames,
228
248
  ~sortFields=sortFieldNames,
249
+ ~requireAttribute?,
229
250
  )
230
251
  } else {
231
252
  Resolver.Functions.listAllItems
@@ -58,6 +58,12 @@ export function response(ctx) {
58
58
  `;
59
59
  }
60
60
 
61
+ function internalRowRequiredAttr(name) {
62
+ if (name === "Plugins") {
63
+ return "name";
64
+ }
65
+ }
66
+
61
67
  function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, param, opts) {
62
68
  let name$1 = Stdlib_String.capitalize(name);
63
69
  let registryEntry = Plugin_Helpers$ReventlessCore.queryFieldNamesRegistry[name$1];
@@ -100,7 +106,8 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
100
106
  ).concat(Stdlib_Array.filterMap(indexes, param => param.subIdField));
101
107
  GraphQL_FragmentGenerator$ReventlessCore.validateScanSortAlignment(stateSchemaOpt, name$1, knownSortFields).forEach(msg => log.warn("QueryDbResolvers_AppSync", undefined, msg));
102
108
  }
103
- let resolverAll = makeQueryResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, connectionSpec ? AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection(labelField, filterFieldNames, rangeFieldNames, sortFieldNames) : AppSync_Resolver_Functions$PulumiAws.listAllItems);
109
+ let requireAttribute = internalRowRequiredAttr(name$1);
110
+ let resolverAll = makeQueryResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, connectionSpec ? AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection(labelField, filterFieldNames, rangeFieldNames, sortFieldNames, requireAttribute) : AppSync_Resolver_Functions$PulumiAws.listAllItems);
104
111
  let resolversByIndex = indexes.map(indexConfig => {
105
112
  let index = indexConfig.index;
106
113
  let stripLeadingBy = s => {
@@ -238,6 +245,7 @@ export {
238
245
  log,
239
246
  queryInterceptorConfig,
240
247
  interceptorCode,
248
+ internalRowRequiredAttr,
241
249
  make,
242
250
  }
243
251
  /* log Not a pure module */
@@ -0,0 +1,40 @@
1
+ open JestGlobals
2
+
3
+ // Regression guard for docs/plans/platform-plugins-admin-connection-null-rows.md:
4
+ // the Plugins admin RM shares its DynamoDB table with internal bookkeeping rows
5
+ // (`deploy-schema:*`, `plugin-info:*`, `deploy-schema-hash:*`) that carry no `name`.
6
+ // The auto-generated Connection Scan must exclude them, or `name: String!` resolves
7
+ // to null and nulls the entire Platform_PluginConnection ("No data" on the page).
8
+
9
+ // `Pulumi.Input.make` is `%identity`, so a generated resolver's `Pulumi.Input.t<string>`
10
+ // IS the underlying JS string — recover it with Obj.magic for assertion.
11
+ let codeOf = (input: Pulumi.Input.t<string>): string => Obj.magic(input)
12
+
13
+ describe("QueryDbResolvers_AppSync.internalRowRequiredAttr", () => {
14
+ testSync("Plugins RM requires the `name` discriminator", () => {
15
+ expect(QueryDbResolvers_AppSync.internalRowRequiredAttr("Plugins"))->toEqual(Some("name"))
16
+ })
17
+
18
+ testSync("ordinary read models require nothing (unchanged behaviour)", () => {
19
+ expect(QueryDbResolvers_AppSync.internalRowRequiredAttr("Products"))->toEqual(None)
20
+ expect(QueryDbResolvers_AppSync.internalRowRequiredAttr("Orders"))->toEqual(None)
21
+ })
22
+ })
23
+
24
+ describe("AppSync_Resolver_Retrying.Functions.listAllItemsConnection", () => {
25
+ testSync("with ~requireAttribute emits an attribute_exists filter that excludes internal rows", () => {
26
+ let code =
27
+ AppSync_Resolver_Retrying.Functions.listAllItemsConnection(
28
+ ~labelField="name",
29
+ ~requireAttribute="name",
30
+ )->codeOf
31
+ expect(code->String.includes("attribute_exists(#name)"))->toBe(true)
32
+ expect(code->String.includes("names['#name'] = 'name'"))->toBe(true)
33
+ })
34
+
35
+ testSync("without ~requireAttribute emits no attribute_exists filter (default read models unchanged)", () => {
36
+ let code =
37
+ AppSync_Resolver_Retrying.Functions.listAllItemsConnection(~labelField="name")->codeOf
38
+ expect(code->String.includes("attribute_exists"))->toBe(false)
39
+ })
40
+ })
@@ -0,0 +1,35 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
4
+ import * as QueryDbResolvers_AppSync$ReventlessAws from "../src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs";
5
+
6
+ function codeOf(input) {
7
+ return input;
8
+ }
9
+
10
+ globalThis.describe("QueryDbResolvers_AppSync.internalRowRequiredAttr", () => {
11
+ globalThis.test("Plugins RM requires the `name` discriminator", () => {
12
+ globalThis.expect(QueryDbResolvers_AppSync$ReventlessAws.internalRowRequiredAttr("Plugins")).toEqual("name");
13
+ });
14
+ globalThis.test("ordinary read models require nothing (unchanged behaviour)", () => {
15
+ globalThis.expect(QueryDbResolvers_AppSync$ReventlessAws.internalRowRequiredAttr("Products")).toEqual(undefined);
16
+ globalThis.expect(QueryDbResolvers_AppSync$ReventlessAws.internalRowRequiredAttr("Orders")).toEqual(undefined);
17
+ });
18
+ });
19
+
20
+ globalThis.describe("AppSync_Resolver_Retrying.Functions.listAllItemsConnection", () => {
21
+ globalThis.test("with ~requireAttribute emits an attribute_exists filter that excludes internal rows", () => {
22
+ let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, "name");
23
+ globalThis.expect(code.includes("attribute_exists(#name)")).toBe(true);
24
+ globalThis.expect(code.includes("names['#name'] = 'name'")).toBe(true);
25
+ });
26
+ globalThis.test("without ~requireAttribute emits no attribute_exists filter (default read models unchanged)", () => {
27
+ let code = AppSync_Resolver_Functions$PulumiAws.listAllItemsConnection("name", undefined, undefined, undefined, undefined);
28
+ globalThis.expect(code.includes("attribute_exists")).toBe(false);
29
+ });
30
+ });
31
+
32
+ export {
33
+ codeOf,
34
+ }
35
+ /* Not a pure module */