@reventlessdev/reventless-aws 3.0.0-alpha.175 → 3.0.0-alpha.177

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 (76) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +10 -10
  3. package/src/Platform.res +127 -31
  4. package/src/Platform.res.mjs +226 -44
  5. package/src/adapter/DcbEventLog/DcbBackend.res +2 -2
  6. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +4 -1
  7. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +1 -1
  8. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res +14 -4
  9. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs +19 -14
  10. package/src/adapter/EventLog/EventLogBackend.res +82 -0
  11. package/src/adapter/EventLog/EventLogBackend.res.mjs +53 -0
  12. package/src/adapter/EventLog/EventLogStorage.res +15 -0
  13. package/src/adapter/EventLog/EventLogStorage.res.mjs +20 -1
  14. package/src/adapter/EventLog/EventLogStorage_Postgres.res +37 -0
  15. package/src/adapter/EventLog/EventLogStorage_Postgres.res.mjs +21 -0
  16. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +26 -11
  17. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +15 -6
  18. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +54 -0
  19. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +23 -0
  20. package/src/adapter/Postgres/PgProjectionFeed.res +100 -0
  21. package/src/adapter/Postgres/PgProjectionFeed.res.mjs +54 -0
  22. package/src/adapter/QueryDb/PgQueryResolver_Builder.res +281 -0
  23. package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +189 -0
  24. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +335 -0
  25. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +252 -0
  26. package/src/adapter/QueryDb/QueryDbBackend.res +52 -0
  27. package/src/adapter/QueryDb/QueryDbBackend.res.mjs +44 -0
  28. package/src/adapter/QueryDb/QueryDbResolvers.res +49 -0
  29. package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +20 -1
  30. package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +264 -0
  31. package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +164 -0
  32. package/src/adapter/QueryDb/QueryDbStorage.res +44 -0
  33. package/src/adapter/QueryDb/QueryDbStorage.res.mjs +34 -1
  34. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +47 -0
  35. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +21 -0
  36. package/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res +19 -0
  37. package/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs +15 -0
  38. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +12 -1
  39. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs +2 -1
  40. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +83 -3
  41. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +46 -3
  42. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +83 -3
  43. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +48 -3
  44. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +130 -5
  45. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +66 -6
  46. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +15 -10
  47. package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +88 -0
  48. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +32 -15
  49. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +26 -12
  50. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +138 -8
  51. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +76 -11
  52. package/src/components/Aggregate_Builder_Single.res +12 -5
  53. package/src/components/Aggregate_Builder_Single.res.mjs +12 -6
  54. package/src/components/Aggregate_Builder_Single_Async.res +12 -5
  55. package/src/components/Aggregate_Builder_Single_Async.res.mjs +12 -6
  56. package/src/components/ReadModel_Builder_NoResolver.res.mjs +1 -1
  57. package/src/components/ReadModel_Builder_NoResolver_Stream.res +13 -4
  58. package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +14 -9
  59. package/src/components/ReadModel_Builder_Single.res +14 -5
  60. package/src/components/ReadModel_Builder_Single.res.mjs +15 -12
  61. package/src/components/ReadModel_Builder_Single_Stream.res +14 -5
  62. package/src/components/ReadModel_Builder_Single_Stream.res.mjs +15 -12
  63. package/src/components/StateViewSlice_Builder.res +10 -4
  64. package/src/components/StateViewSlice_Builder.res.mjs +8 -10
  65. package/src/components/StateViewSlice_Builder_Stream.res +10 -4
  66. package/src/components/StateViewSlice_Builder_Stream.res.mjs +8 -10
  67. package/src/plugin/runtime/PluginRuntime_Builder.res +12 -0
  68. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +4 -0
  69. package/tests/EventCollectorChannelStreamRuntimeTest.res +55 -0
  70. package/tests/EventCollectorChannelStreamRuntimeTest.res.mjs +78 -0
  71. package/tests/PgChangeFeedRelay_IntegrationTest.res +115 -4
  72. package/tests/PgChangeFeedRelay_IntegrationTest.res.mjs +84 -0
  73. package/tests/PgPipeline_IntegrationTest.res +208 -0
  74. package/tests/PgPipeline_IntegrationTest.res.mjs +260 -0
  75. package/tests/PgQueryResolver_LambdaTest.res +308 -0
  76. package/tests/PgQueryResolver_LambdaTest.res.mjs +392 -0
@@ -1,2 +1,51 @@
1
1
  module AppSync = QueryDbResolvers_AppSync
2
2
  module NoOp = QueryDbResolvers_NoOp
3
+ module Lambda = QueryDbResolvers_Lambda
4
+
5
+ // B3.2b: Postgres-backed read models have no DynamoDB data source — their
6
+ // GraphQL Query fields are served by the shared PgQueryResolver Lambda data
7
+ // source (`QueryDbResolvers_Lambda`, Invoke templates → PgQueryResolver_Lambda).
8
+ // DynamoDB-backed (incl. admin-exempt) read models keep the direct resolver set.
9
+ module Selectable = {
10
+ type api = QueryDbResolvers_AppSync_NoOp.api
11
+ type role = QueryDbResolvers_AppSync_NoOp.role
12
+ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
13
+ ~name,
14
+ ~api,
15
+ ~apiRole,
16
+ ~dataSourceName,
17
+ ~indexes,
18
+ ~subIdField,
19
+ ~idResolverConfigs,
20
+ ~idsResolverConfigs,
21
+ ~authorization,
22
+ ~opts,
23
+ ) =>
24
+ if QueryDbBackend.isPostgresFor(name) {
25
+ QueryDbResolvers_Lambda.make(
26
+ ~name,
27
+ ~api,
28
+ ~apiRole,
29
+ ~dataSourceName,
30
+ ~indexes,
31
+ ~subIdField,
32
+ ~idResolverConfigs,
33
+ ~idsResolverConfigs,
34
+ ~authorization,
35
+ ~opts,
36
+ )
37
+ } else {
38
+ QueryDbResolvers_AppSync.make(
39
+ ~name,
40
+ ~api,
41
+ ~apiRole,
42
+ ~dataSourceName,
43
+ ~indexes,
44
+ ~subIdField,
45
+ ~idResolverConfigs,
46
+ ~idsResolverConfigs,
47
+ ~authorization,
48
+ ~opts,
49
+ )
50
+ }
51
+ }
@@ -1,12 +1,31 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as QueryDbBackend$ReventlessAws from "./QueryDbBackend.res.mjs";
4
+ import * as QueryDbResolvers_Lambda$ReventlessAws from "./QueryDbResolvers_Lambda.res.mjs";
5
+ import * as QueryDbResolvers_AppSync$ReventlessAws from "./QueryDbResolvers_AppSync.res.mjs";
6
+
7
+ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, authorization, opts) {
8
+ if (QueryDbBackend$ReventlessAws.isPostgresFor(name)) {
9
+ return QueryDbResolvers_Lambda$ReventlessAws.make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, authorization, opts);
10
+ } else {
11
+ return QueryDbResolvers_AppSync$ReventlessAws.make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, authorization, opts);
12
+ }
13
+ }
14
+
15
+ let Selectable = {
16
+ make: make
17
+ };
3
18
 
4
19
  let AppSync;
5
20
 
6
21
  let NoOp;
7
22
 
23
+ let Lambda;
24
+
8
25
  export {
9
26
  AppSync,
10
27
  NoOp,
28
+ Lambda,
29
+ Selectable,
11
30
  }
12
- /* No side effect */
31
+ /* QueryDbResolvers_Lambda-ReventlessAws Not a pure module */
@@ -0,0 +1,264 @@
1
+ // AppSync resolvers for Postgres-backed read models (B3.2b) — the Lambda-data-
2
+ // source parallel to QueryDbResolvers_AppSync's direct DynamoDB resolvers.
3
+ //
4
+ // Every Query field becomes a thin APPSYNC_JS unit resolver whose `Invoke`
5
+ // template carries { readModelName, kind, index?, arguments, identity } to the
6
+ // ONE shared PgQueryResolver Lambda (PgQueryResolver_Builder), which dispatches
7
+ // via PgQueryResolver_Lambda.dispatch. `dataSourceName` is the shared data
8
+ // source's (deferred) name, the same for every Postgres read model.
9
+ //
10
+ // Field names / includeIdParam / connectionSpec come from the same registry the
11
+ // AppSync path reads, so the SDL emitted by GraphQL_FragmentGenerator stays in
12
+ // lockstep. Kinds covered: getById, list (connection), index, byIds. Deferred to
13
+ // B3.2c: items (sub-id connection), @resolves/@resolvesMany, node, auth-table.
14
+
15
+ module Resolver = AppSync_Resolver_Retrying
16
+ open Reventless.ReadModel
17
+
18
+ type api = Types.AppSync.api
19
+ type role = Types.AppSync.role
20
+
21
+ // Identity block shared by every Invoke template (Cognito vs IAM), matching the
22
+ // shape PgQueryResolver_Lambda decodes (Reventless.Identity.t).
23
+ let identityBlock = `id != null && id.sub != null
24
+ ? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
25
+ : id != null
26
+ ? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
27
+ : null`
28
+
29
+ let invokeTemplate = (~readModelName: string, ~kind: string, ~index: option<string>=?) => {
30
+ let indexFrag = switch index {
31
+ | Some(ix) => `\n index: '${ix}',`
32
+ | None => ""
33
+ }
34
+ `import { util } from '@aws-appsync/utils';
35
+ export function request(ctx) {
36
+ const id = ctx.identity;
37
+ return {
38
+ operation: 'Invoke',
39
+ payload: {
40
+ readModelName: '${readModelName}',
41
+ kind: '${kind}',${indexFrag}
42
+ arguments: ctx.args,
43
+ identity: ${identityBlock}
44
+ }
45
+ };
46
+ }
47
+ export function response(ctx) {
48
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
49
+ return ctx.result;
50
+ }
51
+ `->Pulumi.Input.make
52
+ }
53
+
54
+ // Cross-table field resolver (@resolves/@resolvesMany, B3.2c): on a parent
55
+ // entity type's field, carrying the parent object (ctx.source) + baked target
56
+ // metadata (`extra`). `sourceType` is the parent read model (used only for auth
57
+ // scoping in dispatch); `target` is the target read model's binding key.
58
+ let invokeFieldTemplate = (~sourceType: string, ~kind: string, ~target: string, ~extra: string) =>
59
+ `import { util } from '@aws-appsync/utils';
60
+ export function request(ctx) {
61
+ const id = ctx.identity;
62
+ return {
63
+ operation: 'Invoke',
64
+ payload: {
65
+ readModelName: '${sourceType}',
66
+ kind: '${kind}',
67
+ target: '${target}',
68
+ source: ctx.source,
69
+ arguments: ctx.args,${extra}
70
+ identity: ${identityBlock}
71
+ }
72
+ };
73
+ }
74
+ export function response(ctx) {
75
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
76
+ return ctx.result;
77
+ }
78
+ `->Pulumi.Input.make
79
+
80
+ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
81
+ ~name: string,
82
+ ~api: api,
83
+ ~apiRole as _: role,
84
+ ~dataSourceName,
85
+ ~indexes: array<indexConfig>,
86
+ ~subIdField,
87
+ ~idResolverConfigs: array<idResolverConfig>,
88
+ ~idsResolverConfigs: array<idsResolverConfig>,
89
+ ~authorization as _: Reventless.Authorization.permission,
90
+ ~opts,
91
+ ) => {
92
+ let dataSourceName = dataSourceName->Pulumi.Output.asInput
93
+ let name = name->String.capitalize
94
+ let registryEntry = ReventlessCore.Plugin_Helpers.queryFieldNamesRegistry->Dict.get(name)
95
+
96
+ let fieldNameForSingle = switch registryEntry {
97
+ | Some({singleFieldName}) => singleFieldName
98
+ | None => name->Resolver.Functions.uncapitalize
99
+ }
100
+ let includeIdParam = switch registryEntry {
101
+ | Some({includeIdParam}) => includeIdParam
102
+ | None => true
103
+ }
104
+ let connectionSpec = switch registryEntry {
105
+ | Some({connectionSpec}) => connectionSpec
106
+ | None => true
107
+ }
108
+ let fieldNameForAll = switch registryEntry {
109
+ | Some({listFieldName}) => listFieldName
110
+ | None => name ++ "s"
111
+ }
112
+ let labelField = switch registryEntry {
113
+ | Some({labelField: ?lf}) => lf->Option.getOr("id")
114
+ | None => "id"
115
+ }
116
+ let returnTypeName = switch registryEntry {
117
+ | Some({returnTypeName: rt}) => rt
118
+ | None => name
119
+ }
120
+
121
+ // Register the binding info the shared Lambda's env config needs (the entry
122
+ // point gets indexes/subIdField/schema from the spec module; labelField and
123
+ // includeIdParam come from this deploy-time registry). Keyed by spec name.
124
+ PgQueryResolver_Builder.register({readModelName: name, labelField, includeIdParam})
125
+
126
+ // Relay node type → this read model (for the shared node(id) resolver, B3.2c).
127
+ // Only entities addressable by id participate in node resolution.
128
+ if includeIdParam {
129
+ PgQueryResolver_Builder.registerNodeType(~typeName=returnTypeName, ~readModelName=name)
130
+ }
131
+
132
+ let mkResolver = (~resolverName, ~field, ~kind, ~index=?) =>
133
+ Resolver.makeUnitJsResolver(
134
+ ~name=resolverName,
135
+ ~api,
136
+ ~dataSourceName,
137
+ ~type_="Query"->Pulumi.Input.make,
138
+ ~field=field->Pulumi.Input.make,
139
+ ~code=invokeTemplate(~readModelName=name, ~kind, ~index?),
140
+ ~opts,
141
+ )
142
+
143
+ let stripLeadingBy = s =>
144
+ if s->String.startsWith("by") && s->String.length > 2 {
145
+ s->String.slice(~start=2, ~end=s->String.length)
146
+ } else {
147
+ s
148
+ }
149
+
150
+ // Resolvers are deferred into resourcesMaker (created inside the schema-pushed
151
+ // builderOutputs.apply, like the AppSync path) — so they only exist after the
152
+ // fields are ACTIVE and after PgQueryResolver_Builder.provision has resolved
153
+ // the shared data source name.
154
+ let resourcesMaker: ReventlessCore.QueryDb.resolversResourcesMaker = _allQueryDbs => {
155
+ // getById (or listAll when the read model has no id param, e.g. singletons).
156
+ let byId = mkResolver(
157
+ ~resolverName=fieldNameForSingle->String.capitalize,
158
+ ~field=fieldNameForSingle,
159
+ ~kind=includeIdParam ? "getById" : "list",
160
+ )
161
+
162
+ // Main list — Relay connection (or legacy list when connectionSpec=false).
163
+ let all = mkResolver(
164
+ ~resolverName=fieldNameForAll->String.capitalize,
165
+ ~field=fieldNameForAll,
166
+ ~kind=connectionSpec ? "list" : "list",
167
+ )
168
+
169
+ // Sub-id connection — {single}Items(id, filter, …); only when subId configured.
170
+ let items = switch subIdField {
171
+ | Some(_) => [
172
+ mkResolver(
173
+ ~resolverName=fieldNameForSingle->String.capitalize ++ "Items",
174
+ ~field=fieldNameForSingle ++ "Items",
175
+ ~kind="items",
176
+ ),
177
+ ]
178
+ | None => []
179
+ }
180
+
181
+ // Batched-by-ids — single-key projections only (mirrors the SDL).
182
+ let byIds = if includeIdParam && subIdField === None {
183
+ let byIdsField = fieldNameForAll ++ "ByIds"
184
+ [
185
+ mkResolver(
186
+ ~resolverName=byIdsField->String.capitalize,
187
+ ~field=byIdsField,
188
+ ~kind="byIds",
189
+ ),
190
+ ]
191
+ } else {
192
+ []
193
+ }
194
+
195
+ // Per-index equality queries: {single}By{Index}.
196
+ let byIndex = indexes->Array.map(({index}) => {
197
+ let resolverName =
198
+ fieldNameForSingle->String.capitalize ++ "By" ++ index->stripLeadingBy->String.capitalize
199
+ let field = fieldNameForSingle ++ "By" ++ index->stripLeadingBy->String.capitalize
200
+ mkResolver(~resolverName, ~field, ~kind="index", ~index)
201
+ })
202
+
203
+ // @resolves — single cross-table field resolver on this (parent) type. The
204
+ // target's binding key is its capitalized spec name (mirrors how the binding
205
+ // registry is keyed); the parent object flows via ctx.source.
206
+ let idResolvers = idResolverConfigs->Array.map(config => {
207
+ let {source: {idField, subId, resolvedField}, target} = config
208
+ let targetKey = target.tableName->String.capitalize
209
+ let (field, multi) = switch resolvedField {
210
+ | Single(f) => (f, false)
211
+ | Multi(f) => (f, true)
212
+ }
213
+ let targetFrag = switch target.idField {
214
+ | Index(ix) => `\n targetIndex: '${ix}',\n targetIndexIdField: '${ix}',`
215
+ | IndexWithId(ix, idf) => `\n targetIndex: '${ix}',\n targetIndexIdField: '${idf}',`
216
+ | Id => ""
217
+ }
218
+ let subIdFrag = switch subId {
219
+ | Field(f) => `\n sourceSubId: { kind: 'field', name: '${f}' },`
220
+ | Argument(a) => `\n sourceSubId: { kind: 'arg', name: '${a}' },`
221
+ | NoSubId => ""
222
+ }
223
+ let extra =
224
+ `\n sourceIdField: '${idField}',\n multi: ${multi ? "true" : "false"},` ++
225
+ targetFrag ++
226
+ subIdFrag
227
+ Resolver.makeUnitJsResolver(
228
+ ~name=name ++ field->String.capitalize,
229
+ ~api,
230
+ ~dataSourceName,
231
+ ~type_=name->Pulumi.Input.make,
232
+ ~field=field->Pulumi.Input.make,
233
+ ~code=invokeFieldTemplate(~sourceType=name, ~kind="resolveOne", ~target=targetKey, ~extra),
234
+ ~opts,
235
+ )
236
+ })
237
+
238
+ // @resolvesMany — batch cross-table field resolver (BatchGet by ids).
239
+ let idsResolvers = idsResolverConfigs->Array.map(config => {
240
+ let {source: {idsField, resolvedField}, target} = config
241
+ let targetKey = target.tableName->String.capitalize
242
+ let extra = `\n sourceIdsField: '${idsField}',`
243
+ Resolver.makeUnitJsResolver(
244
+ ~name=name ++ resolvedField->String.capitalize,
245
+ ~api,
246
+ ~dataSourceName,
247
+ ~type_=name->Pulumi.Input.make,
248
+ ~field=resolvedField->Pulumi.Input.make,
249
+ ~code=invokeFieldTemplate(~sourceType=name, ~kind="resolveMany", ~target=targetKey, ~extra),
250
+ ~opts,
251
+ )
252
+ })
253
+
254
+ [byId, all]
255
+ ->Array.concat(items)
256
+ ->Array.concat(byIds)
257
+ ->Array.concat(byIndex)
258
+ ->Array.concat(idResolvers)
259
+ ->Array.concat(idsResolvers)
260
+ ->Array.map(Util.AppSync.toResourceNative)
261
+ }
262
+
263
+ {ReventlessCore.QueryDb_Adapter.resources: [], resourcesMaker}
264
+ }
@@ -0,0 +1,164 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
5
+ import * as Util_AppSync$ReventlessAws from "../../util/Util_AppSync.res.mjs";
6
+ import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
7
+ import * as AppSync_Resolver_Functions$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/AppSync/AppSync_Resolver_Functions.res.mjs";
8
+ import * as PgQueryResolver_Builder$ReventlessAws from "./PgQueryResolver_Builder.res.mjs";
9
+ import * as AppSync_Resolver_Retrying$ReventlessAws from "../Api/AppSync_Resolver_Retrying.res.mjs";
10
+
11
+ let identityBlock = `id != null && id.sub != null
12
+ ? { userId: id.sub, username: id.username, groups: id.claims?.['cognito:groups'] ?? [], claims: id.claims, provider: 'Cognito' }
13
+ : id != null
14
+ ? { userArn: id.userArn ?? null, accountId: id.accountId ?? null, username: id.username ?? null, provider: 'IAM' }
15
+ : null`;
16
+
17
+ function invokeTemplate(readModelName, kind, index) {
18
+ let indexFrag = index !== undefined ? `\n index: '` + index + `',` : "";
19
+ return `import { util } from '@aws-appsync/utils';
20
+ export function request(ctx) {
21
+ const id = ctx.identity;
22
+ return {
23
+ operation: 'Invoke',
24
+ payload: {
25
+ readModelName: '` + readModelName + `',
26
+ kind: '` + kind + `',` + indexFrag + `
27
+ arguments: ctx.args,
28
+ identity: ` + identityBlock + `
29
+ }
30
+ };
31
+ }
32
+ export function response(ctx) {
33
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
34
+ return ctx.result;
35
+ }
36
+ `;
37
+ }
38
+
39
+ function invokeFieldTemplate(sourceType, kind, target, extra) {
40
+ return `import { util } from '@aws-appsync/utils';
41
+ export function request(ctx) {
42
+ const id = ctx.identity;
43
+ return {
44
+ operation: 'Invoke',
45
+ payload: {
46
+ readModelName: '` + sourceType + `',
47
+ kind: '` + kind + `',
48
+ target: '` + target + `',
49
+ source: ctx.source,
50
+ arguments: ctx.args,` + extra + `
51
+ identity: ` + identityBlock + `
52
+ }
53
+ };
54
+ }
55
+ export function response(ctx) {
56
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
57
+ return ctx.result;
58
+ }
59
+ `;
60
+ }
61
+
62
+ function make(name, api, param, dataSourceName, indexes, subIdField, idResolverConfigs, idsResolverConfigs, param$1, opts) {
63
+ let name$1 = Stdlib_String.capitalize(name);
64
+ let registryEntry = Plugin_Helpers$ReventlessCore.queryFieldNamesRegistry[name$1];
65
+ let fieldNameForSingle = registryEntry !== undefined ? registryEntry.singleFieldName : AppSync_Resolver_Functions$PulumiAws.uncapitalize(name$1);
66
+ let includeIdParam = registryEntry !== undefined ? registryEntry.includeIdParam : true;
67
+ let fieldNameForAll = registryEntry !== undefined ? registryEntry.listFieldName : name$1 + "s";
68
+ let labelField = registryEntry !== undefined ? Stdlib_Option.getOr(registryEntry.labelField, "id") : "id";
69
+ let returnTypeName = registryEntry !== undefined ? registryEntry.returnTypeName : name$1;
70
+ PgQueryResolver_Builder$ReventlessAws.register({
71
+ readModelName: name$1,
72
+ labelField: labelField,
73
+ includeIdParam: includeIdParam
74
+ });
75
+ if (includeIdParam) {
76
+ PgQueryResolver_Builder$ReventlessAws.registerNodeType(returnTypeName, name$1);
77
+ }
78
+ let mkResolver = (resolverName, field, kind, index) => AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(resolverName, api, dataSourceName, "Query", field, invokeTemplate(name$1, kind, index), opts);
79
+ let stripLeadingBy = s => {
80
+ if (s.startsWith("by") && s.length > 2) {
81
+ return s.slice(2, s.length);
82
+ } else {
83
+ return s;
84
+ }
85
+ };
86
+ let resourcesMaker = _allQueryDbs => {
87
+ let byId = mkResolver(Stdlib_String.capitalize(fieldNameForSingle), fieldNameForSingle, includeIdParam ? "getById" : "list", undefined);
88
+ let all = mkResolver(Stdlib_String.capitalize(fieldNameForAll), fieldNameForAll, "list", undefined);
89
+ let items = subIdField !== undefined ? [mkResolver(Stdlib_String.capitalize(fieldNameForSingle) + "Items", fieldNameForSingle + "Items", "items", undefined)] : [];
90
+ let byIds;
91
+ if (includeIdParam && subIdField === undefined) {
92
+ let byIdsField = fieldNameForAll + "ByIds";
93
+ byIds = [mkResolver(Stdlib_String.capitalize(byIdsField), byIdsField, "byIds", undefined)];
94
+ } else {
95
+ byIds = [];
96
+ }
97
+ let byIndex = indexes.map(param => {
98
+ let index = param.index;
99
+ let resolverName = Stdlib_String.capitalize(fieldNameForSingle) + "By" + Stdlib_String.capitalize(stripLeadingBy(index));
100
+ let field = fieldNameForSingle + "By" + Stdlib_String.capitalize(stripLeadingBy(index));
101
+ return mkResolver(resolverName, field, "index", index);
102
+ });
103
+ let idResolvers = idResolverConfigs.map(config => {
104
+ let target = config.target;
105
+ let match = config.source;
106
+ let resolvedField = match.resolvedField;
107
+ let subId = match.subId;
108
+ let targetKey = Stdlib_String.capitalize(target.tableName);
109
+ let match$1;
110
+ match$1 = resolvedField.TAG === "Single" ? [
111
+ resolvedField._0,
112
+ false
113
+ ] : [
114
+ resolvedField._0,
115
+ true
116
+ ];
117
+ let field = match$1[0];
118
+ let ix = target.idField;
119
+ let targetFrag;
120
+ if (typeof ix !== "object") {
121
+ targetFrag = "";
122
+ } else if (ix.TAG === "Index") {
123
+ let ix$1 = ix._0;
124
+ targetFrag = `\n targetIndex: '` + ix$1 + `',\n targetIndexIdField: '` + ix$1 + `',`;
125
+ } else {
126
+ targetFrag = `\n targetIndex: '` + ix._0 + `',\n targetIndexIdField: '` + ix._1 + `',`;
127
+ }
128
+ let subIdFrag;
129
+ subIdFrag = typeof subId !== "object" ? "" : (
130
+ subId.TAG === "Field" ? `\n sourceSubId: { kind: 'field', name: '` + subId._0 + `' },` : `\n sourceSubId: { kind: 'arg', name: '` + subId._0 + `' },`
131
+ );
132
+ let extra = `\n sourceIdField: '` + match.idField + `',\n multi: ` + (
133
+ match$1[1] ? "true" : "false"
134
+ ) + `,` + targetFrag + subIdFrag;
135
+ return AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(name$1 + Stdlib_String.capitalize(field), api, dataSourceName, name$1, field, invokeFieldTemplate(name$1, "resolveOne", targetKey, extra), opts);
136
+ });
137
+ let idsResolvers = idsResolverConfigs.map(config => {
138
+ let match = config.source;
139
+ let resolvedField = match.resolvedField;
140
+ let targetKey = Stdlib_String.capitalize(config.target.tableName);
141
+ let extra = `\n sourceIdsField: '` + match.idsField + `',`;
142
+ return AppSync_Resolver_Retrying$ReventlessAws.makeUnitJsResolver(name$1 + Stdlib_String.capitalize(resolvedField), api, dataSourceName, name$1, resolvedField, invokeFieldTemplate(name$1, "resolveMany", targetKey, extra), opts);
143
+ });
144
+ return [
145
+ byId,
146
+ all
147
+ ].concat(items).concat(byIds).concat(byIndex).concat(idResolvers).concat(idsResolvers).map(Util_AppSync$ReventlessAws.toResourceNative);
148
+ };
149
+ return {
150
+ resources: [],
151
+ resourcesMaker: resourcesMaker
152
+ };
153
+ }
154
+
155
+ let Resolver;
156
+
157
+ export {
158
+ Resolver,
159
+ identityBlock,
160
+ invokeTemplate,
161
+ invokeFieldTemplate,
162
+ make,
163
+ }
164
+ /* Util_AppSync-ReventlessAws Not a pure module */
@@ -3,3 +3,47 @@ let name = "QueryDbStorage"
3
3
  module DynamoDb = QueryDbStorage_DynamoDb
4
4
  module DynamoDbStream = QueryDbStorage_DynamoDbStream
5
5
  module DynamoDb_Runtime = QueryDbStorage_DynamoDb_Runtime
6
+ module Postgres = QueryDbStorage_Postgres
7
+
8
+ // Build-time QueryDb storage selectors (B3.1). Read `QueryDbBackend` — set by the
9
+ // platform when a `PgConnection` is supplied — and dispatch per read model:
10
+ // Postgres (no table, no data source) unless the name is admin-exempt. The Stream
11
+ // selector falls back to the streamed DynamoDB maker; on Postgres there is no
12
+ // stream — live updates for Postgres-backed read models are deferred (B3.3).
13
+ module Selectable = {
14
+ type api = QueryDbStorage_Postgres.api
15
+ type role = QueryDbStorage_Postgres.role
16
+ let make: ReventlessCore.QueryDb_Adapter.storageMaker<api, role> = (
17
+ ~name,
18
+ ~indexes,
19
+ ~subIdField=?,
20
+ ~ttl=?,
21
+ ~api,
22
+ ~apiRole,
23
+ ~opts,
24
+ ) =>
25
+ if QueryDbBackend.isPostgresFor(name) {
26
+ Postgres.make(~name, ~indexes, ~subIdField?, ~ttl?, ~api, ~apiRole, ~opts)
27
+ } else {
28
+ DynamoDb.make(~name, ~indexes, ~subIdField?, ~ttl?, ~api, ~apiRole, ~opts)
29
+ }
30
+ }
31
+
32
+ module SelectableStream = {
33
+ type api = QueryDbStorage_Postgres.api
34
+ type role = QueryDbStorage_Postgres.role
35
+ let make: ReventlessCore.QueryDb_Adapter.storageMaker<api, role> = (
36
+ ~name,
37
+ ~indexes,
38
+ ~subIdField=?,
39
+ ~ttl=?,
40
+ ~api,
41
+ ~apiRole,
42
+ ~opts,
43
+ ) =>
44
+ if QueryDbBackend.isPostgresFor(name) {
45
+ Postgres.make(~name, ~indexes, ~subIdField?, ~ttl?, ~api, ~apiRole, ~opts)
46
+ } else {
47
+ DynamoDbStream.make(~name, ~indexes, ~subIdField?, ~ttl?, ~api, ~apiRole, ~opts)
48
+ }
49
+ }
@@ -1,5 +1,33 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as QueryDbBackend$ReventlessAws from "./QueryDbBackend.res.mjs";
4
+ import * as QueryDbStorage_DynamoDb$ReventlessAws from "./QueryDbStorage_DynamoDb.res.mjs";
5
+ import * as QueryDbStorage_Postgres$ReventlessAws from "./QueryDbStorage_Postgres.res.mjs";
6
+ import * as QueryDbStorage_DynamoDbStream$ReventlessAws from "./QueryDbStorage_DynamoDbStream.res.mjs";
7
+
8
+ function make(name, indexes, subIdField, ttl, api, apiRole, opts) {
9
+ if (QueryDbBackend$ReventlessAws.isPostgresFor(name)) {
10
+ return QueryDbStorage_Postgres$ReventlessAws.make(name, indexes, subIdField, ttl, api, apiRole, opts);
11
+ } else {
12
+ return QueryDbStorage_DynamoDb$ReventlessAws.make(name, indexes, subIdField, ttl, api, apiRole, opts);
13
+ }
14
+ }
15
+
16
+ let Selectable = {
17
+ make: make
18
+ };
19
+
20
+ function make$1(name, indexes, subIdField, ttl, api, apiRole, opts) {
21
+ if (QueryDbBackend$ReventlessAws.isPostgresFor(name)) {
22
+ return QueryDbStorage_Postgres$ReventlessAws.make(name, indexes, subIdField, ttl, api, apiRole, opts);
23
+ } else {
24
+ return QueryDbStorage_DynamoDbStream$ReventlessAws.make(name, indexes, subIdField, ttl, api, apiRole, opts);
25
+ }
26
+ }
27
+
28
+ let SelectableStream = {
29
+ make: make$1
30
+ };
3
31
 
4
32
  let name = "QueryDbStorage";
5
33
 
@@ -9,10 +37,15 @@ let DynamoDbStream;
9
37
 
10
38
  let DynamoDb_Runtime;
11
39
 
40
+ let Postgres;
41
+
12
42
  export {
13
43
  name,
14
44
  DynamoDb,
15
45
  DynamoDbStream,
16
46
  DynamoDb_Runtime,
47
+ Postgres,
48
+ Selectable,
49
+ SelectableStream,
17
50
  }
18
- /* No side effect */
51
+ /* QueryDbStorage_DynamoDb-ReventlessAws Not a pure module */
@@ -0,0 +1,47 @@
1
+ // Deploy-time QueryDb storage for the Postgres backend (B3.1) — the read-model
2
+ // analogue of `EventLogStorage_Postgres` / `DcbEventLogStorage_Postgres`.
3
+ //
4
+ // Creates NO DynamoDB table and NO AppSync data source: read-model state lives in
5
+ // Postgres (`qdb_<name>`, JSONB) and is written by the projection Lambdas via the
6
+ // runtime ops. Returns:
7
+ // - `resources: []` — no stream, no direct AppSync resolvers (suppressed by
8
+ // `QueryDbResolvers.Selectable` until B3.2's Lambda data source);
9
+ // - `dataSourceName: ""` — nothing to attach resolvers to;
10
+ // - `operations` — the Postgres op set bound to the shared `PgConnection` pool.
11
+ // Schema setup is lazy (first operation), so nothing connects during
12
+ // `pulumi up` — a real query only runs inside a Lambda at runtime.
13
+ //
14
+ // Selected by `QueryDbStorage.Selectable(Stream)` when `QueryDbBackend` holds a
15
+ // selection and the read model is not admin-exempt.
16
+
17
+ type api = Types.AppSync.api
18
+ type role = Types.AppSync.role
19
+
20
+ let make: ReventlessCore.QueryDb_Adapter.storageMaker<api, role> = (
21
+ ~name,
22
+ ~indexes,
23
+ ~subIdField=?,
24
+ ~ttl as _=?,
25
+ ~api as _,
26
+ ~apiRole as _,
27
+ ~opts as _,
28
+ ) => {
29
+ let operations = switch QueryDbBackend.get() {
30
+ | Some({connectionConfig}) =>
31
+ connectionConfig->Pulumi.Output.apply(config =>
32
+ QueryDbStorage_Postgres_Runtime.opsFor(config, ~name, ~indexes, ~subIdField?)
33
+ )
34
+ | None =>
35
+ // Selectable only routes here when a selection is set; guard defensively.
36
+ JsError.throwWithMessage("QueryDbStorage_Postgres.make called without a QueryDbBackend selection")
37
+ }
38
+ {
39
+ ReventlessCore.QueryDb_Adapter.resources: [],
40
+ // B3.2b: the shared PgQueryResolver Lambda data source. Deferred — resolved
41
+ // by PgQueryResolver_Builder.provision after every plugin is built (the
42
+ // resolvers that read it are created even later, inside the schema-pushed
43
+ // resourcesMaker). Same value for every Postgres read model.
44
+ dataSourceName: PgQueryResolver_Builder.dataSourceName,
45
+ operations,
46
+ }
47
+ }
@@ -0,0 +1,21 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
4
+ import * as QueryDbBackend$ReventlessAws from "./QueryDbBackend.res.mjs";
5
+ import * as PgQueryResolver_Builder$ReventlessAws from "./PgQueryResolver_Builder.res.mjs";
6
+ import * as QueryDbStorage_Postgres_Runtime$ReventlessAws from "./QueryDbStorage_Postgres_Runtime.res.mjs";
7
+
8
+ function make(name, indexes, subIdField, param, param$1, param$2, param$3) {
9
+ let match = QueryDbBackend$ReventlessAws.get();
10
+ let operations = match !== undefined ? match.connectionConfig.apply(config => QueryDbStorage_Postgres_Runtime$ReventlessAws.opsFor(config, name, indexes, subIdField)) : Stdlib_JsError.throwWithMessage("QueryDbStorage_Postgres.make called without a QueryDbBackend selection");
11
+ return {
12
+ resources: [],
13
+ dataSourceName: PgQueryResolver_Builder$ReventlessAws.dataSourceName,
14
+ operations: operations
15
+ };
16
+ }
17
+
18
+ export {
19
+ make,
20
+ }
21
+ /* PgQueryResolver_Builder-ReventlessAws Not a pure module */