@reventlessdev/reventless-aws 3.0.0-alpha.382 → 3.0.0-alpha.384

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.
@@ -8,7 +8,10 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
8
  import * as Pulumi from "@pulumi/pulumi";
9
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11
+ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
11
12
  import * as AWS$ReventlessAws from "../AWS.res.mjs";
13
+ import * as Schedule$Reventless from "@reventlessdev/reventless-spec/src/types/Schedule.res.mjs";
14
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
12
15
  import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
13
16
  import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
14
17
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
@@ -22,6 +25,7 @@ import * as Util_LambdaEnvBudget$ReventlessAws from "../../util/Util_LambdaEnvBu
22
25
  import * as PluginRuntime_Builder$ReventlessAws from "../../plugin/runtime/PluginRuntime_Builder.res.mjs";
23
26
  import * as RuntimeEnvironment_Lambda$ReventlessAws from "./RuntimeEnvironment_Lambda.res.mjs";
24
27
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
28
+ import * as ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws from "../ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs";
25
29
 
26
30
  let log = Logger$ReventlessCore.fromEnv();
27
31
 
@@ -52,11 +56,12 @@ function getDcbQueueUrl() {
52
56
  return dcbQueueUrlRef.contents;
53
57
  }
54
58
 
55
- function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackTypeOpt, queryDbTableName, queryDbResourcesOpt, context, sourceTopicsOpt, consumesDcbLogOpt, commandTargetName) {
59
+ function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackTypeOpt, queryDbTableName, queryDbResourcesOpt, context, sourceTopicsOpt, consumesDcbLogOpt, commandTargetName, schedule, schedulesResourcesOpt) {
56
60
  let callbackType = callbackTypeOpt !== undefined ? callbackTypeOpt : "automation";
57
61
  let queryDbResources = queryDbResourcesOpt !== undefined ? queryDbResourcesOpt : [];
58
62
  let sourceTopics = sourceTopicsOpt !== undefined ? sourceTopicsOpt : ({});
59
63
  let consumesDcbLog = consumesDcbLogOpt !== undefined ? consumesDcbLogOpt : true;
64
+ let schedulesResources = schedulesResourcesOpt !== undefined ? schedulesResourcesOpt : [];
60
65
  bundledInfos[name] = {
61
66
  specModulePath: specModulePath,
62
67
  bodyModulePath: bodyModulePath,
@@ -66,7 +71,9 @@ function registerAutomationSlice(name, specModulePath, bodyModulePath, callbackT
66
71
  context: context,
67
72
  sourceTopics: sourceTopics,
68
73
  consumesDcbLog: consumesDcbLog,
69
- commandTargetName: commandTargetName
74
+ commandTargetName: commandTargetName,
75
+ schedule: schedule,
76
+ schedulesResources: schedulesResources
70
77
  };
71
78
  }
72
79
 
@@ -168,6 +175,73 @@ function makeMessagingGrant(runtime, opts) {
168
175
  }, customOpts);
169
176
  }
170
177
 
178
+ function makeAutomationSchedules(runtime, schedulerRoleArn, sourceCodeHash, opts) {
179
+ let name = "AllAutomationSlices";
180
+ let customOpts = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
181
+ let lambdaArn = Output$Pulumi.flatMap(runtime.parts.lambda, l => l.arn);
182
+ let manage = new (Aws.iam.RolePolicy)(name + `SchedulerPolicy`, {
183
+ policy: schedulerRoleArn.apply(arn => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `SchedulerPolicy`, [
184
+ {
185
+ Sid: "AllowManageSchedules",
186
+ Effect: "Allow",
187
+ Action: ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.manageActions,
188
+ Resource: ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.schedulesOfRoleArn(arn)
189
+ },
190
+ {
191
+ Sid: "AllowPassSchedulerRole",
192
+ Effect: "Allow",
193
+ Action: "iam:PassRole",
194
+ Resource: arn
195
+ },
196
+ {
197
+ Sid: "AllowListSchedules",
198
+ Effect: "Allow",
199
+ Action: ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.listActions,
200
+ Resource: "*"
201
+ }
202
+ ]))),
203
+ role: runtime.parts.lambdaRole.id
204
+ }, customOpts);
205
+ let invoke = new (Aws.iam.RolePolicy)(name + `SchedulerInvokePolicy`, {
206
+ policy: lambdaArn.apply(arn => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `SchedulerInvokePolicy`, [{
207
+ Sid: "AllowSchedulerToInvokeAutomations",
208
+ Effect: "Allow",
209
+ Action: "lambda:InvokeFunction",
210
+ Resource: arn
211
+ }]))),
212
+ role: schedulerRoleArn.apply(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.groupOfRoleArn)
213
+ }, customOpts);
214
+ let declared = Object.fromEntries(Stdlib_Array.filterMap(Object.entries(bundledInfos), param => {
215
+ let slice = param[0];
216
+ return Stdlib_Option.map(param[1].schedule, time => [
217
+ slice,
218
+ Util_Sury$Reventless.toJson(time, Schedule$Reventless.timeSchema)
219
+ ]);
220
+ }).toSorted((param, param$1) => Primitive_string.compare(param[0], param$1[0])));
221
+ let input = JSON.stringify(Object.fromEntries([
222
+ [
223
+ "reventlessReconcile",
224
+ true
225
+ ],
226
+ [
227
+ "declared",
228
+ declared
229
+ ],
230
+ [
231
+ "code",
232
+ sourceCodeHash
233
+ ]
234
+ ]));
235
+ let newrecord = {...customOpts};
236
+ new (Aws.lambda.Invocation)(name + `ReconcileSchedules`, {
237
+ functionName: Output$Pulumi.flatMap(runtime.parts.lambda, l => l.name),
238
+ input: input
239
+ }, (newrecord.dependsOn = [
240
+ manage,
241
+ invoke
242
+ ], newrecord));
243
+ }
244
+
171
245
  let finished = {
172
246
  contents: false
173
247
  };
@@ -235,7 +309,7 @@ function finish() {
235
309
  finished.contents = true;
236
310
  }
237
311
 
238
- function finishWithDcbEventLog(dcbEventLog) {
312
+ function finishWithDcbEventLog(schedulerRoleArn, dcbEventLog) {
239
313
  if (finished.contents) {
240
314
  return;
241
315
  }
@@ -290,6 +364,9 @@ function finishWithDcbEventLog(dcbEventLog) {
290
364
  info.queryDbResources.forEach(r => {
291
365
  allQueryDbResources.push(r);
292
366
  });
367
+ info.schedulesResources.forEach(r => {
368
+ allQueryDbResources.push(r);
369
+ });
293
370
  let target = Stdlib_Option.flatMap(info.commandTargetName, CommandTopicRegistry$ReventlessAws.get);
294
371
  if (target !== undefined) {
295
372
  allQueryDbResources.push(target.resource);
@@ -316,16 +393,21 @@ function finishWithDcbEventLog(dcbEventLog) {
316
393
  ];
317
394
  let commandQueueIsFifo = match[1];
318
395
  let hasOwnUrns = !info.consumesDcbLog || Object.keys(info.sourceTopics).length !== 0;
396
+ let resource = info.schedulesResources[0];
397
+ let schedulesTableName = resource !== undefined ? resource.name : Pulumi.output("");
319
398
  let handlerJson = Pulumi.all([
320
399
  info.queryDbTableName,
321
400
  match[0],
322
- sourceUrnsFor(info)
401
+ sourceUrnsFor(info),
402
+ schedulesTableName
323
403
  ]).apply(param => {
404
+ let schedulesTable = param[3];
324
405
  let urnsFragment = hasOwnUrns ? `,"u":` + JSON.stringify(param[2].map(prim => prim)) : "";
325
406
  let queueFragment = target !== undefined ? `,"k":"` + param[1] + `","f":` + (
326
407
  commandQueueIsFifo ? "true" : "false"
327
408
  ) : "";
328
- return `{"n":` + nameJson + `,"q":"` + param[0] + `"` + urnsFragment + queueFragment + `}`;
409
+ let schedulesFragment = schedulesTable === "" ? "" : `,"s":"` + schedulesTable + `"`;
410
+ return `{"n":` + nameJson + `,"q":"` + param[0] + `"` + urnsFragment + queueFragment + schedulesFragment + `}`;
329
411
  });
330
412
  handlerOutputs.push(handlerJson);
331
413
  });
@@ -355,16 +437,19 @@ function finishWithDcbEventLog(dcbEventLog) {
355
437
  PluginRuntime_Builder$ReventlessAws.capabilityEnv().forEach(param => {
356
438
  envVars[param[0]] = param[1];
357
439
  });
440
+ envVars["SCHEDULER_ROLE_ARN"] = schedulerRoleArn;
358
441
  let extraStringAssets = {};
359
442
  extraStringAssets["automationSliceModules.json"] = `{` + sliceModuleEntries.join(",") + `}`;
360
443
  let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Runtime/AutomationSliceEntryPoint.mjs", packageDirs, extraStringAssets, undefined);
361
- let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", "Reactor", "AutomationSlice", match.code, match.sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, opts);
444
+ let sourceCodeHash = match.sourceCodeHash;
445
+ let runtime = RuntimeEnvironment_Lambda$ReventlessAws.makeFromCodeAsset("AllAutomationSlices", "Reactor", "AutomationSlice", match.code, sourceCodeHash, envVars, 1024, 30, undefined, undefined, undefined, undefined, undefined, opts);
362
446
  EventCollectorChannel_DynamoDbStream$ReventlessAws.connect("AllAutomationSlices", [{
363
447
  channel: channel,
364
448
  eventTopics: eventTopics,
365
449
  resources: allQueryDbResources
366
450
  }], runtime, opts);
367
451
  makeSweepSchedule(runtime, opts);
452
+ makeAutomationSchedules(runtime, schedulerRoleArn, sourceCodeHash, opts);
368
453
  makeGeocoderGrant(runtime, opts);
369
454
  makeMessagingGrant(runtime, opts);
370
455
  } else {
@@ -398,6 +483,7 @@ export {
398
483
  makeSweepSchedule,
399
484
  makeGeocoderGrant,
400
485
  makeMessagingGrant,
486
+ makeAutomationSchedules,
401
487
  finished,
402
488
  finish,
403
489
  finishWithDcbEventLog,
@@ -57,20 +57,33 @@ let failWithoutQueue = action => {
57
57
  ->Effect.runPromise
58
58
  }
59
59
 
60
+ let putSchedule = async (~roleArn, ~targetArn, schedule: Reventless.Schedule.schedule) => {
61
+ ReventlessCore.ScheduleTime.validate(schedule.time)
62
+ let input = schedule->scheduleInput(~roleArn, ~targetArn, ~setAt=Date.now())
63
+ switch await CreateScheduleCommand.make(input)->CreateScheduleCommand.send {
64
+ | _ => ()
65
+ | exception JsExn(err) if err->ErrorName.is(ErrorName.conflict) =>
66
+ let _ = await UpdateScheduleCommand.make(input)->UpdateScheduleCommand.send
67
+ }
68
+ }
69
+
70
+ let removeSchedule = async (~roleArn, name) =>
71
+ switch await DeleteScheduleCommand.make({
72
+ name,
73
+ groupName: roleArn->groupOfRoleArn,
74
+ })->DeleteScheduleCommand.send {
75
+ | _ => ()
76
+ | exception JsExn(err) if err->ErrorName.is(ErrorName.resourceNotFound) => ()
77
+ }
78
+
60
79
  /** Creates the schedule, or replaces the one of that name, as a classic rule's `PutRule` did. */
61
80
  let createSchedule = (~roleArn: string): ReventlessCore.Scheduler.createSchedule =>
62
81
  async (queueResources, schedule) =>
63
82
  switch queueResources {
64
83
  | [] => await failWithoutQueue("createSchedule")
65
84
  | resources =>
66
- ReventlessCore.ScheduleTime.validate(schedule.time)
67
85
  let resource = resources->Array.getUnsafe(0) // FIXME
68
- let input = schedule->scheduleInput(~roleArn, ~targetArn=resource.urn, ~setAt=Date.now())
69
- switch await CreateScheduleCommand.make(input)->CreateScheduleCommand.send {
70
- | _ => ()
71
- | exception JsExn(err) if err->ErrorName.is(ErrorName.conflict) =>
72
- let _ = await UpdateScheduleCommand.make(input)->UpdateScheduleCommand.send
73
- }
86
+ await putSchedule(~roleArn, ~targetArn=resource.urn, schedule)
74
87
  }
75
88
 
76
89
  /** Deletes the schedule; one already gone, such as a once-only time that fired, is no error. */
@@ -78,12 +91,88 @@ let deleteSchedule = (~roleArn: string): ReventlessCore.Scheduler.deleteSchedule
78
91
  async (queueResources, name) =>
79
92
  switch queueResources {
80
93
  | [] => await failWithoutQueue("deleteSchedule")
81
- | _ =>
82
- switch await DeleteScheduleCommand.make({
83
- name,
84
- groupName: roleArn->groupOfRoleArn,
85
- })->DeleteScheduleCommand.send {
86
- | _ => ()
87
- | exception JsExn(err) if err->ErrorName.is(ErrorName.resourceNotFound) => ()
88
- }
94
+ | _ => await removeSchedule(~roleArn, name)
89
95
  }
96
+
97
+ // ── An automation's schedules ───────────────────────────────────────────────
98
+
99
+ let validName = /^[0-9a-zA-Z\-_.]{1,64}$/
100
+
101
+ /**
102
+ The platform name of an automation's schedule: `<slice>.<key>`, or, when that is too
103
+ long or holds a character EventBridge refuses, the slice's name cut to 40 characters
104
+ and a hash of the key.
105
+ */
106
+ let tickScheduleName = (~slice, ~key) => {
107
+ let name = `${slice}.${key}`
108
+ validName->RegExp.test(name)
109
+ ? name
110
+ : {
111
+ let hash =
112
+ NodeCrypto.createHash("sha256")
113
+ ->NodeCrypto.hashUpdate(key)
114
+ ->NodeCrypto.hashDigest("hex")
115
+ ->String.slice(~start=0, ~end=16)
116
+ `${slice->String.slice(~start=0, ~end=40)}.${hash}`
117
+ }
118
+ }
119
+
120
+ /** What the Scheduler sends the automation Lambda: the slice, the key and the scheduled time. */
121
+ let tickInput = (~slice, ~key) =>
122
+ JSON.Encode.object(
123
+ Dict.fromArray([
124
+ (
125
+ "reventlessTick",
126
+ JSON.Encode.object(
127
+ Dict.fromArray([
128
+ ("slice", slice->JSON.Encode.string),
129
+ ("key", key->JSON.Encode.string),
130
+ ("at", "<aws.scheduler.scheduled-time>"->JSON.Encode.string),
131
+ ]),
132
+ ),
133
+ ),
134
+ ]),
135
+ )->JSON.stringify
136
+
137
+ /** Sets the slice's schedule of that key, invoking `lambdaArn` with `tickInput` at each firing. */
138
+ let putTickSchedule = (~roleArn, ~lambdaArn, ~slice, ~key, time) =>
139
+ putSchedule(
140
+ ~roleArn,
141
+ ~targetArn=lambdaArn,
142
+ {name: tickScheduleName(~slice, ~key), time, payload: tickInput(~slice, ~key)},
143
+ )
144
+
145
+ /** The actions a Lambda that reconciles an automation's schedules is granted on every
146
+ resource: listing schedules has no resource-level permission. */
147
+ let listActions = ["scheduler:ListSchedules"]
148
+
149
+ // Hashed names start with the slice's name cut to 40 characters.
150
+ let sliceNamePrefix = slice => slice->String.slice(~start=0, ~end=40)
151
+
152
+ let isSliceSchedule = (name, ~slice) => {
153
+ let cut = slice->sliceNamePrefix
154
+ name->String.startsWith(`${slice}.`) ||
155
+ (cut != slice &&
156
+ name->String.startsWith(`${cut}.`) &&
157
+ name->String.length == cut->String.length + 17)
158
+ }
159
+
160
+ /** The slice's schedules in the scheduler group of `roleArn`, invoking `lambdaArn`. */
161
+ let platformSchedules = (
162
+ ~roleArn,
163
+ ~lambdaArn,
164
+ ~slice,
165
+ ): ReventlessInfra.AutomationSlice.platformSchedules => {
166
+ put: (~key, time) => putTickSchedule(~roleArn, ~lambdaArn, ~slice, ~key, time),
167
+ removeName: name => removeSchedule(~roleArn, name),
168
+ names: async () =>
169
+ (
170
+ await ListSchedulesCommand.all({
171
+ groupName: roleArn->groupOfRoleArn,
172
+ namePrefix: slice->sliceNamePrefix,
173
+ })
174
+ )
175
+ ->Array.map(s => s.name)
176
+ ->Array.filter(isSliceSchedule(_, ~slice)),
177
+ nameOf: key => tickScheduleName(~slice, ~key),
178
+ }
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Nodecrypto from "node:crypto";
3
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
5
  import * as Effect from "effect/Effect";
5
6
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
@@ -62,52 +63,129 @@ function failWithoutQueue(action) {
62
63
  return Effect.runPromise(Effect.flatMap(EffectLogger$ReventlessCore.logError("ScheduledPublisher_EventBridgeScheduler_Runtime-ReventlessAws", undefined, err), () => Effect.sync(() => Stdlib_JsError.throwWithMessage(err))));
63
64
  }
64
65
 
66
+ async function putSchedule(roleArn, targetArn, schedule) {
67
+ ScheduleTime$ReventlessCore.validate(schedule.time);
68
+ let input = scheduleInput(roleArn, targetArn, Date.now(), schedule);
69
+ try {
70
+ await EventBridgeScheduler$AwsSdk.CreateScheduleCommand.send(new ClientScheduler.CreateScheduleCommand(input));
71
+ return;
72
+ } catch (raw_err) {
73
+ let err = Primitive_exceptions.internalToException(raw_err);
74
+ if (err.RE_EXN_ID === "JsExn") {
75
+ if (EventBridgeScheduler$AwsSdk.ErrorName.is(err._1, EventBridgeScheduler$AwsSdk.ErrorName.conflict)) {
76
+ await EventBridgeScheduler$AwsSdk.UpdateScheduleCommand.send(new ClientScheduler.UpdateScheduleCommand(input));
77
+ return;
78
+ }
79
+ throw err;
80
+ }
81
+ throw err;
82
+ }
83
+ }
84
+
85
+ async function removeSchedule(roleArn, name) {
86
+ try {
87
+ await EventBridgeScheduler$AwsSdk.DeleteScheduleCommand.send(new ClientScheduler.DeleteScheduleCommand({
88
+ Name: name,
89
+ GroupName: groupOfRoleArn(roleArn)
90
+ }));
91
+ return;
92
+ } catch (raw_err) {
93
+ let err = Primitive_exceptions.internalToException(raw_err);
94
+ if (err.RE_EXN_ID === "JsExn") {
95
+ if (EventBridgeScheduler$AwsSdk.ErrorName.is(err._1, EventBridgeScheduler$AwsSdk.ErrorName.resourceNotFound)) {
96
+ return;
97
+ }
98
+ throw err;
99
+ }
100
+ throw err;
101
+ }
102
+ }
103
+
65
104
  function createSchedule(roleArn) {
66
105
  return async (queueResources, schedule) => {
67
106
  if (queueResources.length === 0) {
68
107
  return await failWithoutQueue("createSchedule");
69
108
  }
70
- ScheduleTime$ReventlessCore.validate(schedule.time);
71
109
  let resource = queueResources[0];
72
- let input = scheduleInput(roleArn, resource.urn, Date.now(), schedule);
73
- try {
74
- await EventBridgeScheduler$AwsSdk.CreateScheduleCommand.send(new ClientScheduler.CreateScheduleCommand(input));
75
- return;
76
- } catch (raw_err) {
77
- let err = Primitive_exceptions.internalToException(raw_err);
78
- if (err.RE_EXN_ID === "JsExn") {
79
- if (EventBridgeScheduler$AwsSdk.ErrorName.is(err._1, EventBridgeScheduler$AwsSdk.ErrorName.conflict)) {
80
- await EventBridgeScheduler$AwsSdk.UpdateScheduleCommand.send(new ClientScheduler.UpdateScheduleCommand(input));
81
- return;
82
- }
83
- throw err;
84
- }
85
- throw err;
86
- }
110
+ return await putSchedule(roleArn, resource.urn, schedule);
87
111
  };
88
112
  }
89
113
 
90
114
  function deleteSchedule(roleArn) {
91
115
  return async (queueResources, name) => {
92
- if (queueResources.length === 0) {
116
+ if (queueResources.length !== 0) {
117
+ return await removeSchedule(roleArn, name);
118
+ } else {
93
119
  return await failWithoutQueue("deleteSchedule");
94
120
  }
95
- try {
96
- await EventBridgeScheduler$AwsSdk.DeleteScheduleCommand.send(new ClientScheduler.DeleteScheduleCommand({
97
- Name: name,
98
- GroupName: groupOfRoleArn(roleArn)
99
- }));
100
- return;
101
- } catch (raw_err) {
102
- let err = Primitive_exceptions.internalToException(raw_err);
103
- if (err.RE_EXN_ID === "JsExn") {
104
- if (EventBridgeScheduler$AwsSdk.ErrorName.is(err._1, EventBridgeScheduler$AwsSdk.ErrorName.resourceNotFound)) {
105
- return;
106
- }
107
- throw err;
108
- }
109
- throw err;
110
- }
121
+ };
122
+ }
123
+
124
+ let validName = /^[0-9a-zA-Z\-_.]{1,64}$/;
125
+
126
+ function tickScheduleName(slice, key) {
127
+ let name = slice + `.` + key;
128
+ if (validName.test(name)) {
129
+ return name;
130
+ }
131
+ let hash = Nodecrypto.createHash("sha256").update(key).digest("hex").slice(0, 16);
132
+ return slice.slice(0, 40) + `.` + hash;
133
+ }
134
+
135
+ function tickInput(slice, key) {
136
+ return JSON.stringify(Object.fromEntries([[
137
+ "reventlessTick",
138
+ Object.fromEntries([
139
+ [
140
+ "slice",
141
+ slice
142
+ ],
143
+ [
144
+ "key",
145
+ key
146
+ ],
147
+ [
148
+ "at",
149
+ "<aws.scheduler.scheduled-time>"
150
+ ]
151
+ ])
152
+ ]]));
153
+ }
154
+
155
+ function putTickSchedule(roleArn, lambdaArn, slice, key, time) {
156
+ return putSchedule(roleArn, lambdaArn, {
157
+ name: tickScheduleName(slice, key),
158
+ time: time,
159
+ payload: tickInput(slice, key)
160
+ });
161
+ }
162
+
163
+ let listActions = ["scheduler:ListSchedules"];
164
+
165
+ function sliceNamePrefix(slice) {
166
+ return slice.slice(0, 40);
167
+ }
168
+
169
+ function isSliceSchedule(name, slice) {
170
+ let cut = slice.slice(0, 40);
171
+ if (name.startsWith(slice + `.`)) {
172
+ return true;
173
+ } else if (cut !== slice && name.startsWith(cut + `.`)) {
174
+ return name.length === (cut.length + 17 | 0);
175
+ } else {
176
+ return false;
177
+ }
178
+ }
179
+
180
+ function platformSchedules(roleArn, lambdaArn, slice) {
181
+ return {
182
+ put: (key, time) => putTickSchedule(roleArn, lambdaArn, slice, key, time),
183
+ removeName: name => removeSchedule(roleArn, name),
184
+ names: async () => (await EventBridgeScheduler$AwsSdk.ListSchedulesCommand.all({
185
+ GroupName: groupOfRoleArn(roleArn),
186
+ NamePrefix: slice.slice(0, 40)
187
+ })).map(s => s.Name).filter(__x => isSliceSchedule(__x, slice)),
188
+ nameOf: key => tickScheduleName(slice, key)
111
189
  };
112
190
  }
113
191
 
@@ -120,7 +198,17 @@ export {
120
198
  isFifoQueue,
121
199
  scheduleInput,
122
200
  failWithoutQueue,
201
+ putSchedule,
202
+ removeSchedule,
123
203
  createSchedule,
124
204
  deleteSchedule,
205
+ validName,
206
+ tickScheduleName,
207
+ tickInput,
208
+ putTickSchedule,
209
+ listActions,
210
+ sliceNamePrefix,
211
+ isSliceSchedule,
212
+ platformSchedules,
125
213
  }
126
- /* effect/Effect Not a pure module */
214
+ /* node:crypto Not a pure module */
@@ -309,21 +309,18 @@ let injectAwsAuth = (
309
309
  iamQueryFieldPrefixes->Array.push(entry.listFieldName)
310
310
  iamTypePrefixes->Array.push(entry.returnTypeName)
311
311
  }
312
+ // Every door the entry emits: the resolvers here do not check the rule, so a
313
+ // by-ids or by-index field left to the open directive reads the gated rows.
314
+ let fieldNames = ReventlessCore.GraphQL_FragmentGenerator.queryFieldNamesOf(entry)
312
315
  switch entry.authorization {
313
- | Some({group}) =>
314
- queryAuthMap->Dict.set(entry.singleFieldName, [group])
315
- queryAuthMap->Dict.set(entry.listFieldName, [group])
316
+ | Some({group}) => fieldNames->Array.forEach(f => queryAuthMap->Dict.set(f, [group]))
316
317
  | None => ()
317
318
  }
318
319
  switch entry.permission {
319
320
  | Some(permission) =>
320
321
  switch AppSync_SdlDecorate.permissionToGate(permission) {
321
- | Groups(groups) =>
322
- queryAuthMap->Dict.set(entry.singleFieldName, groups)
323
- queryAuthMap->Dict.set(entry.listFieldName, groups)
324
- | AnyAuthenticated =>
325
- queryAuthMap->Dict.delete(entry.singleFieldName)
326
- queryAuthMap->Dict.delete(entry.listFieldName)
322
+ | Groups(groups) => fieldNames->Array.forEach(f => queryAuthMap->Dict.set(f, groups))
323
+ | AnyAuthenticated => fieldNames->Array.forEach(f => queryAuthMap->Dict.delete(f))
327
324
  | Anonymous =>
328
325
  // Both derived field names, because the spec declares one permission and
329
326
  // the generator emits two fields from it; naming only one would send the
@@ -154,11 +154,13 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
154
154
  iamQueryFieldPrefixes.push(entry.listFieldName);
155
155
  iamTypePrefixes.push(entry.returnTypeName);
156
156
  }
157
+ let fieldNames = GraphQL_FragmentGenerator$ReventlessCore.queryFieldNamesOf(entry);
157
158
  let match = entry.authorization;
158
159
  if (match !== undefined) {
159
160
  let group = match.group;
160
- queryAuthMap[entry.singleFieldName] = [group];
161
- queryAuthMap[entry.listFieldName] = [group];
161
+ fieldNames.forEach(f => {
162
+ queryAuthMap[f] = [group];
163
+ });
162
164
  }
163
165
  let permission = entry.permission;
164
166
  if (permission === undefined) {
@@ -167,16 +169,17 @@ function injectAwsAuth(fragment, mutationEntries, queryEntries) {
167
169
  let groups = AppSync_SdlDecorate$ReventlessAws.permissionToGate(permission);
168
170
  if (typeof groups !== "object") {
169
171
  if (groups === "AnyAuthenticated") {
170
- Stdlib_Dict.$$delete(queryAuthMap, entry.singleFieldName);
171
- return Stdlib_Dict.$$delete(queryAuthMap, entry.listFieldName);
172
+ fieldNames.forEach(f => Stdlib_Dict.$$delete(queryAuthMap, f));
173
+ return;
172
174
  }
173
175
  anonymousFields.push(entry.singleFieldName);
174
176
  anonymousFields.push(entry.listFieldName);
175
177
  return;
176
178
  } else {
177
179
  let groups$1 = groups._0;
178
- queryAuthMap[entry.singleFieldName] = groups$1;
179
- queryAuthMap[entry.listFieldName] = groups$1;
180
+ fieldNames.forEach(f => {
181
+ queryAuthMap[f] = groups$1;
182
+ });
180
183
  return;
181
184
  }
182
185
  });
@@ -5,6 +5,23 @@ module EventCollectorChannel = EventCollectorChannel.DynamoDbStream
5
5
  module RuntimeEnvironment = RuntimeEnvironment.Lambda
6
6
  module EventCollectorRuntimeBuilder = AutomationSliceRuntime_Builder_Single
7
7
 
8
+ // The core builder's operations never run on AWS: the automation Lambda drives the
9
+ // callback itself (AutomationSliceEntryPoint_Ops).
10
+ module DeployTimeSchedules = {
11
+ let platformFor = (~slice): ReventlessInfra.AutomationSlice.platformSchedules => {
12
+ let refuse = () =>
13
+ JsError.throwWithMessage(
14
+ `AutomationSlice(${slice}): schedules are set by its Lambda, not at deploy`,
15
+ )
16
+ {
17
+ put: async (~key as _, _) => refuse(),
18
+ removeName: async _ => refuse(),
19
+ names: async () => refuse(),
20
+ nameOf: key => key,
21
+ }
22
+ }
23
+ }
24
+
8
25
  module Make = (
9
26
  Api: {
10
27
  let api: unit => Types.AppSync.api
@@ -18,6 +35,7 @@ module Make = (
18
35
  EventCollectorChannel,
19
36
  EventCollectorRuntimeBuilder,
20
37
  Api,
38
+ DeployTimeSchedules,
21
39
  )
22
40
 
23
41
  module Make = (
@@ -30,6 +48,7 @@ module Make = (
30
48
  module Automation = Automation
31
49
  type component = InnerMake.component
32
50
  let queryDbName = InnerMake.queryDbName
51
+ let schedulesDbName = InnerMake.schedulesDbName
33
52
  let sourceNames = InnerMake.sourceNames
34
53
 
35
54
  let make = (~allEventTopics, ~publishJsons, ~context, ~runtime=?, ~opts=?): component => {
@@ -60,6 +79,11 @@ module Make = (
60
79
  ~context,
61
80
  ~sourceTopics,
62
81
  ~consumesDcbLog,
82
+ ~schedule=?Spec.schedule,
83
+ ~schedulesResources=(as_->ReventlessCore.Component.outputs).schedulesDb->Option.mapOr(
84
+ [],
85
+ db => db.resources,
86
+ ),
63
87
  )
64
88
 
65
89
  as_