@reventlessdev/reventless-aws 3.0.0-alpha.381 → 3.0.0-alpha.383

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,
@@ -42,7 +42,15 @@ let make: ReventlessCore.Scheduler_Adapter.scheduledPublisherMaker = (~name, ~op
42
42
  ~name=name ++ "ScheduleGroup",
43
43
  ~args={
44
44
  name: role.name->Pulumi.Output.asInput,
45
- tags: tags(name ++ "ScheduleGroup"),
45
+ // A schedule group refuses the empty values platform-scoped tags carry.
46
+ tags: AWS.Tags.makeDict(
47
+ ~name=name ++ "ScheduleGroup",
48
+ ~kind=ReventlessCore.ComponentType.Platform,
49
+ ~role=Identity,
50
+ ~scope=Platform,
51
+ )
52
+ ->AWS.Tags.withoutEmpty
53
+ ->Pulumi.Input.make,
46
54
  },
47
55
  ~opts,
48
56
  )
@@ -23,7 +23,7 @@ function make(name, opts) {
23
23
  }, opts);
24
24
  new (Aws.scheduler.ScheduleGroup)(name + "ScheduleGroup", {
25
25
  name: role.name,
26
- tags: tags(name + "ScheduleGroup")
26
+ tags: AWS_Tags$ReventlessAws.withoutEmpty(AWS_Tags$ReventlessAws.makeDict(name + "ScheduleGroup", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined))
27
27
  }, opts);
28
28
  return {
29
29
  resource: Adapter$ReventlessInfra.make(role.name, role.id, role.arn, Pulumi.output("EventBridgeScheduler"), undefined, undefined, undefined, Pulumi.output("aws:scheduler:ScheduleGroup"), undefined, undefined),
@@ -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 */
@@ -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_
@@ -1,6 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
3
4
  import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
5
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
4
6
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
5
7
  import * as EventTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventTopic/EventTopic.res.mjs";
6
8
  import * as Util_Bundle$ReventlessAws from "../util/Util_Bundle.res.mjs";
@@ -11,6 +13,20 @@ import * as RuntimeEnvironment_Lambda$ReventlessAws from "../adapter/Runtime/Run
11
13
  import * as EventCollectorChannel_DynamoDbStream$ReventlessAws from "../adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs";
12
14
  import * as AutomationSliceRuntime_Builder_Single$ReventlessAws from "../adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs";
13
15
 
16
+ function platformFor(slice) {
17
+ let refuse = () => Stdlib_JsError.throwWithMessage(`AutomationSlice(` + slice + `): schedules are set by its Lambda, not at deploy`);
18
+ return {
19
+ put: async (param, param$1) => refuse(),
20
+ removeName: async param => refuse(),
21
+ names: async () => refuse(),
22
+ nameOf: key => key
23
+ };
24
+ }
25
+
26
+ let DeployTimeSchedules = {
27
+ platformFor: platformFor
28
+ };
29
+
14
30
  function Make(Api) {
15
31
  let Inner = AutomationSlice_Builder$ReventlessCore.Make({
16
32
  make: RuntimeEnvironment_Lambda$ReventlessAws.make,
@@ -35,7 +51,7 @@ function Make(Api) {
35
51
  },
36
52
  forEventCollector: AutomationSliceRuntime_Builder_Single$ReventlessAws.forEventCollector,
37
53
  finish: AutomationSliceRuntime_Builder_Single$ReventlessAws.finish
38
- })(Api);
54
+ })(Api)(DeployTimeSchedules);
39
55
  let Make$1 = Spec => (Automation => {
40
56
  let InnerMake = Inner.Make(Spec)(Automation);
41
57
  let sourceNames = InnerMake.sourceNames;
@@ -46,13 +62,14 @@ function Make(Api) {
46
62
  let queryDbTableName = tableResource.name;
47
63
  let sourceTopics = EventTopic$ReventlessCore.filter(allEventTopics, Belt_SetString.fromArray(sourceNames));
48
64
  let consumesDcbLog = sourceNames.length === 0 || sourceNames.some(name => !(name in allEventTopics));
49
- AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Automation.moduleUrl), "automation", queryDbTableName, queryDbOutputs.resources, context, sourceTopics, consumesDcbLog, undefined);
65
+ AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Automation.moduleUrl), "automation", queryDbTableName, queryDbOutputs.resources, context, sourceTopics, consumesDcbLog, undefined, Spec.schedule, Stdlib_Option.mapOr(Component$ReventlessCore.outputs(as_).schedulesDb, [], db => db.resources));
50
66
  return as_;
51
67
  };
52
68
  return {
53
69
  Spec: Spec,
54
70
  Automation: Automation,
55
71
  queryDbName: InnerMake.queryDbName,
72
+ schedulesDbName: InnerMake.schedulesDbName,
56
73
  sourceNames: sourceNames,
57
74
  make: make
58
75
  };
@@ -75,6 +92,7 @@ export {
75
92
  EventCollectorChannel,
76
93
  RuntimeEnvironment,
77
94
  EventCollectorRuntimeBuilder,
95
+ DeployTimeSchedules,
78
96
  Make,
79
97
  }
80
98
  /* Component-ReventlessCore Not a pure module */
@@ -63,7 +63,7 @@ function Make(Api) {
63
63
  let declaredSources = Spec.sourceNames;
64
64
  let sourceTopics = EventTopic$ReventlessCore.filter(allEventTopics, Belt_SetString.fromArray(declaredSources));
65
65
  let consumesDcbLog = declaredSources.length === 0 || declaredSources.some(name => !(name in allEventTopics));
66
- AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl), "outbound", queryDbTableName, queryDbOutputs.resources, undefined, sourceTopics, consumesDcbLog, Spec.targetName);
66
+ AutomationSliceRuntime_Builder_Single$ReventlessAws.registerAutomationSlice(Spec.name, Util_Bundle$ReventlessAws.getModuleSpecifier(Spec.moduleUrl), Util_Bundle$ReventlessAws.getModuleSpecifier(Translation.moduleUrl), "outbound", queryDbTableName, queryDbOutputs.resources, undefined, sourceTopics, consumesDcbLog, Spec.targetName, undefined, undefined);
67
67
  return ots;
68
68
  };
69
69
  return {