@omega-flow/store-aws 0.1.0
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/LICENSE +21 -0
- package/dist/index.d.mts +125 -0
- package/dist/index.d.ts +125 -0
- package/dist/index.js +329 -0
- package/dist/index.mjs +318 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) omega-flow contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { WorkflowStore, WorkflowMemory, WorkflowScheduler } from '@omega-flow/engine';
|
|
3
|
+
import { Workflow, Context, Event } from '@omega-flow/types';
|
|
4
|
+
import { SchedulerClient } from '@aws-sdk/client-scheduler';
|
|
5
|
+
|
|
6
|
+
interface DynamoDBWorkflowStoreConfig {
|
|
7
|
+
client: DynamoDBClient;
|
|
8
|
+
tableName: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* DynamoDB-backed implementation of WorkflowStore.
|
|
12
|
+
*
|
|
13
|
+
* Table layout (dedicated workflows table):
|
|
14
|
+
* domain (pk) = tenant identifier (e.g. organization id, company id)
|
|
15
|
+
* workflowId (sk) = workflow id
|
|
16
|
+
* data = full Workflow JSON
|
|
17
|
+
* createdAt, updatedAt = epoch ms
|
|
18
|
+
*/
|
|
19
|
+
declare class DynamoDBWorkflowStore implements WorkflowStore {
|
|
20
|
+
private docClient;
|
|
21
|
+
private tableName;
|
|
22
|
+
constructor(config: DynamoDBWorkflowStoreConfig);
|
|
23
|
+
getWorkflow(domain: string, workflowId: string): Promise<Workflow | null>;
|
|
24
|
+
getAllWorkflows(domain: string): Promise<Workflow[]>;
|
|
25
|
+
setWorkflow(domain: string, workflow: Workflow): Promise<void>;
|
|
26
|
+
createWorkflow(domain: string, workflowData: Omit<Workflow, "id">): Promise<Workflow>;
|
|
27
|
+
deleteWorkflow(domain: string, workflowId: string): Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DynamoDBWorkflowMemoryConfig {
|
|
31
|
+
client: DynamoDBClient;
|
|
32
|
+
tableName: string;
|
|
33
|
+
/**
|
|
34
|
+
* Name of the GSI with `domain` as partition key and `subjectId` as sort key.
|
|
35
|
+
* Required for `getAllContexts` and `getAllContextsForSubject`.
|
|
36
|
+
* Defaults to `domain-subjectId-index`.
|
|
37
|
+
*/
|
|
38
|
+
gsiName?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* DynamoDB-backed implementation of WorkflowMemory.
|
|
42
|
+
*
|
|
43
|
+
* Table layout (dedicated contexts table):
|
|
44
|
+
* contextKey (pk) = `${domain}#${workflowId}#${subjectId}`
|
|
45
|
+
* instanceId (sk) = workflow instance id
|
|
46
|
+
* domain = tenant identifier (denormalised for GSI)
|
|
47
|
+
* subjectId = subject identifier (denormalised for GSI)
|
|
48
|
+
* data = full Context JSON
|
|
49
|
+
* isCompleted, startedAt = mirrored from Context for filtering/sorting
|
|
50
|
+
* updatedAt = epoch ms, set on every save
|
|
51
|
+
*
|
|
52
|
+
* GSI (domain-subjectId-index):
|
|
53
|
+
* domain (pk), subjectId (sk) — enables getAllContexts and getAllContextsForSubject queries.
|
|
54
|
+
*
|
|
55
|
+
* Assumes domain / workflowId / subjectId do not contain '#'.
|
|
56
|
+
*/
|
|
57
|
+
declare class DynamoDBWorkflowMemory implements WorkflowMemory {
|
|
58
|
+
private docClient;
|
|
59
|
+
private tableName;
|
|
60
|
+
private gsiName;
|
|
61
|
+
constructor(config: DynamoDBWorkflowMemoryConfig);
|
|
62
|
+
private buildContextKey;
|
|
63
|
+
getContexts(domain: string, workflowId: string, subjectId: string): Promise<Context[]>;
|
|
64
|
+
saveContext(domain: string, workflowId: string, subjectId: string, context: Context): Promise<void>;
|
|
65
|
+
deleteContext(domain: string, workflowId: string, subjectId: string, instanceId: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetch a single Context by instance id.
|
|
68
|
+
*/
|
|
69
|
+
getContext(domain: string, workflowId: string, subjectId: string, instanceId: string): Promise<Context | null>;
|
|
70
|
+
/**
|
|
71
|
+
* List all contexts for a given subject across all workflows in a domain.
|
|
72
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
73
|
+
*/
|
|
74
|
+
getAllContextsForSubject(domain: string, subjectId: string): Promise<Context[]>;
|
|
75
|
+
/**
|
|
76
|
+
* List all contexts across all subjects and workflows in a domain.
|
|
77
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
78
|
+
* Returns contexts annotated with `subjectId`.
|
|
79
|
+
*/
|
|
80
|
+
getAllContexts(domain: string): Promise<Array<Context & {
|
|
81
|
+
subjectId: string;
|
|
82
|
+
}>>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface EventBusWorkflowSchedulerConfig {
|
|
86
|
+
client: SchedulerClient;
|
|
87
|
+
/** ARN of the EventBridge bus that receives the scheduled event. */
|
|
88
|
+
eventBusArn: string;
|
|
89
|
+
/** IAM role assumed by EventBridge Scheduler to invoke the bus target. */
|
|
90
|
+
roleArn: string;
|
|
91
|
+
/** Schedule group name. Defaults to `default`. */
|
|
92
|
+
scheduleGroupName?: string;
|
|
93
|
+
/** EventBridge `Source` for the published event. Defaults to `omega-flow`. */
|
|
94
|
+
source?: string;
|
|
95
|
+
/** EventBridge `DetailType` for the published event. Defaults to `workflow.event`. */
|
|
96
|
+
detailType?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* EventBridge Scheduler-backed implementation of WorkflowScheduler.
|
|
100
|
+
*
|
|
101
|
+
* Creates one-time schedules that publish the workflow event to an EventBridge
|
|
102
|
+
* bus when they fire. A downstream consumer (e.g. a Lambda subscribed to the
|
|
103
|
+
* bus) is expected to deserialize the event and call `WorkflowManager.processEvent`.
|
|
104
|
+
*
|
|
105
|
+
* Schedules use `ActionAfterCompletion: DELETE` so AWS removes them once fired.
|
|
106
|
+
*/
|
|
107
|
+
declare class EventBusWorkflowScheduler implements WorkflowScheduler {
|
|
108
|
+
private client;
|
|
109
|
+
private eventBusArn;
|
|
110
|
+
private roleArn;
|
|
111
|
+
private scheduleGroupName;
|
|
112
|
+
private source;
|
|
113
|
+
private detailType;
|
|
114
|
+
constructor(config: EventBusWorkflowSchedulerConfig);
|
|
115
|
+
schedule(event: Event, delayMs: number): Promise<string>;
|
|
116
|
+
cancel(scheduleId: string): Promise<boolean>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare class WorkflowAlreadyExistsError extends Error {
|
|
120
|
+
readonly domain: string;
|
|
121
|
+
readonly workflowId: string;
|
|
122
|
+
constructor(domain: string, workflowId: string);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { DynamoDBWorkflowMemory, type DynamoDBWorkflowMemoryConfig, DynamoDBWorkflowStore, type DynamoDBWorkflowStoreConfig, EventBusWorkflowScheduler, type EventBusWorkflowSchedulerConfig, WorkflowAlreadyExistsError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { WorkflowStore, WorkflowMemory, WorkflowScheduler } from '@omega-flow/engine';
|
|
3
|
+
import { Workflow, Context, Event } from '@omega-flow/types';
|
|
4
|
+
import { SchedulerClient } from '@aws-sdk/client-scheduler';
|
|
5
|
+
|
|
6
|
+
interface DynamoDBWorkflowStoreConfig {
|
|
7
|
+
client: DynamoDBClient;
|
|
8
|
+
tableName: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* DynamoDB-backed implementation of WorkflowStore.
|
|
12
|
+
*
|
|
13
|
+
* Table layout (dedicated workflows table):
|
|
14
|
+
* domain (pk) = tenant identifier (e.g. organization id, company id)
|
|
15
|
+
* workflowId (sk) = workflow id
|
|
16
|
+
* data = full Workflow JSON
|
|
17
|
+
* createdAt, updatedAt = epoch ms
|
|
18
|
+
*/
|
|
19
|
+
declare class DynamoDBWorkflowStore implements WorkflowStore {
|
|
20
|
+
private docClient;
|
|
21
|
+
private tableName;
|
|
22
|
+
constructor(config: DynamoDBWorkflowStoreConfig);
|
|
23
|
+
getWorkflow(domain: string, workflowId: string): Promise<Workflow | null>;
|
|
24
|
+
getAllWorkflows(domain: string): Promise<Workflow[]>;
|
|
25
|
+
setWorkflow(domain: string, workflow: Workflow): Promise<void>;
|
|
26
|
+
createWorkflow(domain: string, workflowData: Omit<Workflow, "id">): Promise<Workflow>;
|
|
27
|
+
deleteWorkflow(domain: string, workflowId: string): Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DynamoDBWorkflowMemoryConfig {
|
|
31
|
+
client: DynamoDBClient;
|
|
32
|
+
tableName: string;
|
|
33
|
+
/**
|
|
34
|
+
* Name of the GSI with `domain` as partition key and `subjectId` as sort key.
|
|
35
|
+
* Required for `getAllContexts` and `getAllContextsForSubject`.
|
|
36
|
+
* Defaults to `domain-subjectId-index`.
|
|
37
|
+
*/
|
|
38
|
+
gsiName?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* DynamoDB-backed implementation of WorkflowMemory.
|
|
42
|
+
*
|
|
43
|
+
* Table layout (dedicated contexts table):
|
|
44
|
+
* contextKey (pk) = `${domain}#${workflowId}#${subjectId}`
|
|
45
|
+
* instanceId (sk) = workflow instance id
|
|
46
|
+
* domain = tenant identifier (denormalised for GSI)
|
|
47
|
+
* subjectId = subject identifier (denormalised for GSI)
|
|
48
|
+
* data = full Context JSON
|
|
49
|
+
* isCompleted, startedAt = mirrored from Context for filtering/sorting
|
|
50
|
+
* updatedAt = epoch ms, set on every save
|
|
51
|
+
*
|
|
52
|
+
* GSI (domain-subjectId-index):
|
|
53
|
+
* domain (pk), subjectId (sk) — enables getAllContexts and getAllContextsForSubject queries.
|
|
54
|
+
*
|
|
55
|
+
* Assumes domain / workflowId / subjectId do not contain '#'.
|
|
56
|
+
*/
|
|
57
|
+
declare class DynamoDBWorkflowMemory implements WorkflowMemory {
|
|
58
|
+
private docClient;
|
|
59
|
+
private tableName;
|
|
60
|
+
private gsiName;
|
|
61
|
+
constructor(config: DynamoDBWorkflowMemoryConfig);
|
|
62
|
+
private buildContextKey;
|
|
63
|
+
getContexts(domain: string, workflowId: string, subjectId: string): Promise<Context[]>;
|
|
64
|
+
saveContext(domain: string, workflowId: string, subjectId: string, context: Context): Promise<void>;
|
|
65
|
+
deleteContext(domain: string, workflowId: string, subjectId: string, instanceId: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetch a single Context by instance id.
|
|
68
|
+
*/
|
|
69
|
+
getContext(domain: string, workflowId: string, subjectId: string, instanceId: string): Promise<Context | null>;
|
|
70
|
+
/**
|
|
71
|
+
* List all contexts for a given subject across all workflows in a domain.
|
|
72
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
73
|
+
*/
|
|
74
|
+
getAllContextsForSubject(domain: string, subjectId: string): Promise<Context[]>;
|
|
75
|
+
/**
|
|
76
|
+
* List all contexts across all subjects and workflows in a domain.
|
|
77
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
78
|
+
* Returns contexts annotated with `subjectId`.
|
|
79
|
+
*/
|
|
80
|
+
getAllContexts(domain: string): Promise<Array<Context & {
|
|
81
|
+
subjectId: string;
|
|
82
|
+
}>>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface EventBusWorkflowSchedulerConfig {
|
|
86
|
+
client: SchedulerClient;
|
|
87
|
+
/** ARN of the EventBridge bus that receives the scheduled event. */
|
|
88
|
+
eventBusArn: string;
|
|
89
|
+
/** IAM role assumed by EventBridge Scheduler to invoke the bus target. */
|
|
90
|
+
roleArn: string;
|
|
91
|
+
/** Schedule group name. Defaults to `default`. */
|
|
92
|
+
scheduleGroupName?: string;
|
|
93
|
+
/** EventBridge `Source` for the published event. Defaults to `omega-flow`. */
|
|
94
|
+
source?: string;
|
|
95
|
+
/** EventBridge `DetailType` for the published event. Defaults to `workflow.event`. */
|
|
96
|
+
detailType?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* EventBridge Scheduler-backed implementation of WorkflowScheduler.
|
|
100
|
+
*
|
|
101
|
+
* Creates one-time schedules that publish the workflow event to an EventBridge
|
|
102
|
+
* bus when they fire. A downstream consumer (e.g. a Lambda subscribed to the
|
|
103
|
+
* bus) is expected to deserialize the event and call `WorkflowManager.processEvent`.
|
|
104
|
+
*
|
|
105
|
+
* Schedules use `ActionAfterCompletion: DELETE` so AWS removes them once fired.
|
|
106
|
+
*/
|
|
107
|
+
declare class EventBusWorkflowScheduler implements WorkflowScheduler {
|
|
108
|
+
private client;
|
|
109
|
+
private eventBusArn;
|
|
110
|
+
private roleArn;
|
|
111
|
+
private scheduleGroupName;
|
|
112
|
+
private source;
|
|
113
|
+
private detailType;
|
|
114
|
+
constructor(config: EventBusWorkflowSchedulerConfig);
|
|
115
|
+
schedule(event: Event, delayMs: number): Promise<string>;
|
|
116
|
+
cancel(scheduleId: string): Promise<boolean>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare class WorkflowAlreadyExistsError extends Error {
|
|
120
|
+
readonly domain: string;
|
|
121
|
+
readonly workflowId: string;
|
|
122
|
+
constructor(domain: string, workflowId: string);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { DynamoDBWorkflowMemory, type DynamoDBWorkflowMemoryConfig, DynamoDBWorkflowStore, type DynamoDBWorkflowStoreConfig, EventBusWorkflowScheduler, type EventBusWorkflowSchedulerConfig, WorkflowAlreadyExistsError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DynamoDBWorkflowMemory: () => DynamoDBWorkflowMemory,
|
|
24
|
+
DynamoDBWorkflowStore: () => DynamoDBWorkflowStore,
|
|
25
|
+
EventBusWorkflowScheduler: () => EventBusWorkflowScheduler,
|
|
26
|
+
WorkflowAlreadyExistsError: () => WorkflowAlreadyExistsError
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/stores/DynamoDBWorkflowStore.ts
|
|
31
|
+
var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
32
|
+
var import_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
33
|
+
var import_nanoid = require("nanoid");
|
|
34
|
+
|
|
35
|
+
// src/errors.ts
|
|
36
|
+
var WorkflowAlreadyExistsError = class extends Error {
|
|
37
|
+
constructor(domain, workflowId) {
|
|
38
|
+
super(
|
|
39
|
+
`Workflow already exists: domain=${domain} workflowId=${workflowId}`
|
|
40
|
+
);
|
|
41
|
+
this.domain = domain;
|
|
42
|
+
this.workflowId = workflowId;
|
|
43
|
+
this.name = "WorkflowAlreadyExistsError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/stores/DynamoDBWorkflowStore.ts
|
|
48
|
+
var DynamoDBWorkflowStore = class {
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.docClient = import_lib_dynamodb.DynamoDBDocumentClient.from(config.client);
|
|
51
|
+
this.tableName = config.tableName;
|
|
52
|
+
}
|
|
53
|
+
async getWorkflow(domain, workflowId) {
|
|
54
|
+
const result = await this.docClient.send(
|
|
55
|
+
new import_lib_dynamodb.GetCommand({
|
|
56
|
+
TableName: this.tableName,
|
|
57
|
+
Key: { domain, workflowId }
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
if (!result.Item) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return result.Item.data;
|
|
64
|
+
}
|
|
65
|
+
async getAllWorkflows(domain) {
|
|
66
|
+
const workflows = [];
|
|
67
|
+
let lastKey;
|
|
68
|
+
do {
|
|
69
|
+
const result = await this.docClient.send(
|
|
70
|
+
new import_lib_dynamodb.QueryCommand({
|
|
71
|
+
TableName: this.tableName,
|
|
72
|
+
KeyConditionExpression: "#domain = :domain",
|
|
73
|
+
ExpressionAttributeNames: { "#domain": "domain" },
|
|
74
|
+
ExpressionAttributeValues: { ":domain": domain },
|
|
75
|
+
ExclusiveStartKey: lastKey
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
for (const item of result.Items ?? []) {
|
|
79
|
+
workflows.push(item.data);
|
|
80
|
+
}
|
|
81
|
+
lastKey = result.LastEvaluatedKey;
|
|
82
|
+
} while (lastKey);
|
|
83
|
+
return workflows;
|
|
84
|
+
}
|
|
85
|
+
async setWorkflow(domain, workflow) {
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
await this.docClient.send(
|
|
88
|
+
new import_lib_dynamodb.UpdateCommand({
|
|
89
|
+
TableName: this.tableName,
|
|
90
|
+
Key: { domain, workflowId: workflow.id },
|
|
91
|
+
UpdateExpression: "SET #data = :data, createdAt = if_not_exists(createdAt, :now), updatedAt = :now",
|
|
92
|
+
ExpressionAttributeNames: { "#data": "data" },
|
|
93
|
+
ExpressionAttributeValues: { ":data": workflow, ":now": now }
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
async createWorkflow(domain, workflowData) {
|
|
98
|
+
const id = (0, import_nanoid.nanoid)(8);
|
|
99
|
+
const workflow = { ...workflowData, id };
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
const item = {
|
|
102
|
+
domain,
|
|
103
|
+
workflowId: id,
|
|
104
|
+
data: workflow,
|
|
105
|
+
createdAt: now,
|
|
106
|
+
updatedAt: now
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
await this.docClient.send(
|
|
110
|
+
new import_lib_dynamodb.PutCommand({
|
|
111
|
+
TableName: this.tableName,
|
|
112
|
+
Item: item,
|
|
113
|
+
ConditionExpression: "attribute_not_exists(#domain)",
|
|
114
|
+
ExpressionAttributeNames: { "#domain": "domain" }
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (err instanceof import_client_dynamodb.ConditionalCheckFailedException) {
|
|
119
|
+
throw new WorkflowAlreadyExistsError(domain, id);
|
|
120
|
+
}
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
return workflow;
|
|
124
|
+
}
|
|
125
|
+
async deleteWorkflow(domain, workflowId) {
|
|
126
|
+
const result = await this.docClient.send(
|
|
127
|
+
new import_lib_dynamodb.DeleteCommand({
|
|
128
|
+
TableName: this.tableName,
|
|
129
|
+
Key: { domain, workflowId },
|
|
130
|
+
ReturnValues: "ALL_OLD"
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
return result.Attributes !== void 0;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/memories/DynamoDBWorkflowMemory.ts
|
|
138
|
+
var import_lib_dynamodb2 = require("@aws-sdk/lib-dynamodb");
|
|
139
|
+
var DynamoDBWorkflowMemory = class {
|
|
140
|
+
constructor(config) {
|
|
141
|
+
this.docClient = import_lib_dynamodb2.DynamoDBDocumentClient.from(config.client);
|
|
142
|
+
this.tableName = config.tableName;
|
|
143
|
+
this.gsiName = config.gsiName ?? "domain-subjectId-index";
|
|
144
|
+
}
|
|
145
|
+
buildContextKey(domain, workflowId, subjectId) {
|
|
146
|
+
return `${domain}#${workflowId}#${subjectId}`;
|
|
147
|
+
}
|
|
148
|
+
async getContexts(domain, workflowId, subjectId) {
|
|
149
|
+
const contextKey = this.buildContextKey(domain, workflowId, subjectId);
|
|
150
|
+
const contexts = [];
|
|
151
|
+
let lastKey;
|
|
152
|
+
do {
|
|
153
|
+
const result = await this.docClient.send(
|
|
154
|
+
new import_lib_dynamodb2.QueryCommand({
|
|
155
|
+
TableName: this.tableName,
|
|
156
|
+
KeyConditionExpression: "contextKey = :contextKey",
|
|
157
|
+
ExpressionAttributeValues: { ":contextKey": contextKey },
|
|
158
|
+
ExclusiveStartKey: lastKey
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
for (const item of result.Items ?? []) {
|
|
162
|
+
contexts.push(item.data);
|
|
163
|
+
}
|
|
164
|
+
lastKey = result.LastEvaluatedKey;
|
|
165
|
+
} while (lastKey);
|
|
166
|
+
return contexts;
|
|
167
|
+
}
|
|
168
|
+
async saveContext(domain, workflowId, subjectId, context) {
|
|
169
|
+
const item = {
|
|
170
|
+
contextKey: this.buildContextKey(domain, workflowId, subjectId),
|
|
171
|
+
instanceId: context.instanceId,
|
|
172
|
+
domain,
|
|
173
|
+
subjectId,
|
|
174
|
+
data: context,
|
|
175
|
+
isCompleted: !!context.isCompleted,
|
|
176
|
+
startedAt: context.startedAt,
|
|
177
|
+
updatedAt: Date.now()
|
|
178
|
+
};
|
|
179
|
+
await this.docClient.send(
|
|
180
|
+
new import_lib_dynamodb2.PutCommand({
|
|
181
|
+
TableName: this.tableName,
|
|
182
|
+
Item: item
|
|
183
|
+
})
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
async deleteContext(domain, workflowId, subjectId, instanceId) {
|
|
187
|
+
await this.docClient.send(
|
|
188
|
+
new import_lib_dynamodb2.DeleteCommand({
|
|
189
|
+
TableName: this.tableName,
|
|
190
|
+
Key: {
|
|
191
|
+
contextKey: this.buildContextKey(domain, workflowId, subjectId),
|
|
192
|
+
instanceId
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Fetch a single Context by instance id.
|
|
199
|
+
*/
|
|
200
|
+
async getContext(domain, workflowId, subjectId, instanceId) {
|
|
201
|
+
const result = await this.docClient.send(
|
|
202
|
+
new import_lib_dynamodb2.GetCommand({
|
|
203
|
+
TableName: this.tableName,
|
|
204
|
+
Key: {
|
|
205
|
+
contextKey: this.buildContextKey(domain, workflowId, subjectId),
|
|
206
|
+
instanceId
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
if (!result.Item) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return result.Item.data;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* List all contexts for a given subject across all workflows in a domain.
|
|
217
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
218
|
+
*/
|
|
219
|
+
async getAllContextsForSubject(domain, subjectId) {
|
|
220
|
+
const contexts = [];
|
|
221
|
+
let lastKey;
|
|
222
|
+
do {
|
|
223
|
+
const result = await this.docClient.send(
|
|
224
|
+
new import_lib_dynamodb2.QueryCommand({
|
|
225
|
+
TableName: this.tableName,
|
|
226
|
+
IndexName: this.gsiName,
|
|
227
|
+
KeyConditionExpression: "#domain = :domain AND subjectId = :subjectId",
|
|
228
|
+
ExpressionAttributeNames: { "#domain": "domain" },
|
|
229
|
+
ExpressionAttributeValues: { ":domain": domain, ":subjectId": subjectId },
|
|
230
|
+
ExclusiveStartKey: lastKey
|
|
231
|
+
})
|
|
232
|
+
);
|
|
233
|
+
for (const item of result.Items ?? []) {
|
|
234
|
+
contexts.push(item.data);
|
|
235
|
+
}
|
|
236
|
+
lastKey = result.LastEvaluatedKey;
|
|
237
|
+
} while (lastKey);
|
|
238
|
+
return contexts;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* List all contexts across all subjects and workflows in a domain.
|
|
242
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
243
|
+
* Returns contexts annotated with `subjectId`.
|
|
244
|
+
*/
|
|
245
|
+
async getAllContexts(domain) {
|
|
246
|
+
const contexts = [];
|
|
247
|
+
let lastKey;
|
|
248
|
+
do {
|
|
249
|
+
const result = await this.docClient.send(
|
|
250
|
+
new import_lib_dynamodb2.QueryCommand({
|
|
251
|
+
TableName: this.tableName,
|
|
252
|
+
IndexName: this.gsiName,
|
|
253
|
+
KeyConditionExpression: "#domain = :domain",
|
|
254
|
+
ExpressionAttributeNames: { "#domain": "domain" },
|
|
255
|
+
ExpressionAttributeValues: { ":domain": domain },
|
|
256
|
+
ExclusiveStartKey: lastKey
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
for (const item of result.Items ?? []) {
|
|
260
|
+
const ci = item;
|
|
261
|
+
contexts.push({ ...ci.data, subjectId: ci.subjectId });
|
|
262
|
+
}
|
|
263
|
+
lastKey = result.LastEvaluatedKey;
|
|
264
|
+
} while (lastKey);
|
|
265
|
+
return contexts;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// src/schedulers/EventBusWorkflowScheduler.ts
|
|
270
|
+
var import_client_scheduler = require("@aws-sdk/client-scheduler");
|
|
271
|
+
var import_nanoid2 = require("nanoid");
|
|
272
|
+
var EventBusWorkflowScheduler = class {
|
|
273
|
+
constructor(config) {
|
|
274
|
+
this.client = config.client;
|
|
275
|
+
this.eventBusArn = config.eventBusArn;
|
|
276
|
+
this.roleArn = config.roleArn;
|
|
277
|
+
this.scheduleGroupName = config.scheduleGroupName ?? "default";
|
|
278
|
+
this.source = config.source ?? "omega-flow";
|
|
279
|
+
this.detailType = config.detailType ?? "workflow.event";
|
|
280
|
+
}
|
|
281
|
+
async schedule(event, delayMs) {
|
|
282
|
+
const fireAt = new Date(Date.now() + delayMs);
|
|
283
|
+
const scheduleExpression = `at(${fireAt.toISOString().slice(0, 19)})`;
|
|
284
|
+
const name = `omf-${(0, import_nanoid2.nanoid)(16)}`;
|
|
285
|
+
await this.client.send(
|
|
286
|
+
new import_client_scheduler.CreateScheduleCommand({
|
|
287
|
+
Name: name,
|
|
288
|
+
GroupName: this.scheduleGroupName,
|
|
289
|
+
ScheduleExpression: scheduleExpression,
|
|
290
|
+
ScheduleExpressionTimezone: "UTC",
|
|
291
|
+
FlexibleTimeWindow: { Mode: "OFF" },
|
|
292
|
+
ActionAfterCompletion: "DELETE",
|
|
293
|
+
Target: {
|
|
294
|
+
Arn: this.eventBusArn,
|
|
295
|
+
RoleArn: this.roleArn,
|
|
296
|
+
Input: JSON.stringify(event),
|
|
297
|
+
EventBridgeParameters: {
|
|
298
|
+
Source: this.source,
|
|
299
|
+
DetailType: this.detailType
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
return name;
|
|
305
|
+
}
|
|
306
|
+
async cancel(scheduleId) {
|
|
307
|
+
try {
|
|
308
|
+
await this.client.send(
|
|
309
|
+
new import_client_scheduler.DeleteScheduleCommand({
|
|
310
|
+
Name: scheduleId,
|
|
311
|
+
GroupName: this.scheduleGroupName
|
|
312
|
+
})
|
|
313
|
+
);
|
|
314
|
+
return true;
|
|
315
|
+
} catch (err) {
|
|
316
|
+
if (err instanceof import_client_scheduler.ResourceNotFoundException) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
324
|
+
0 && (module.exports = {
|
|
325
|
+
DynamoDBWorkflowMemory,
|
|
326
|
+
DynamoDBWorkflowStore,
|
|
327
|
+
EventBusWorkflowScheduler,
|
|
328
|
+
WorkflowAlreadyExistsError
|
|
329
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// src/stores/DynamoDBWorkflowStore.ts
|
|
2
|
+
import {
|
|
3
|
+
ConditionalCheckFailedException
|
|
4
|
+
} from "@aws-sdk/client-dynamodb";
|
|
5
|
+
import {
|
|
6
|
+
DeleteCommand,
|
|
7
|
+
DynamoDBDocumentClient,
|
|
8
|
+
GetCommand,
|
|
9
|
+
PutCommand,
|
|
10
|
+
QueryCommand,
|
|
11
|
+
UpdateCommand
|
|
12
|
+
} from "@aws-sdk/lib-dynamodb";
|
|
13
|
+
import { nanoid } from "nanoid";
|
|
14
|
+
|
|
15
|
+
// src/errors.ts
|
|
16
|
+
var WorkflowAlreadyExistsError = class extends Error {
|
|
17
|
+
constructor(domain, workflowId) {
|
|
18
|
+
super(
|
|
19
|
+
`Workflow already exists: domain=${domain} workflowId=${workflowId}`
|
|
20
|
+
);
|
|
21
|
+
this.domain = domain;
|
|
22
|
+
this.workflowId = workflowId;
|
|
23
|
+
this.name = "WorkflowAlreadyExistsError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/stores/DynamoDBWorkflowStore.ts
|
|
28
|
+
var DynamoDBWorkflowStore = class {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.docClient = DynamoDBDocumentClient.from(config.client);
|
|
31
|
+
this.tableName = config.tableName;
|
|
32
|
+
}
|
|
33
|
+
async getWorkflow(domain, workflowId) {
|
|
34
|
+
const result = await this.docClient.send(
|
|
35
|
+
new GetCommand({
|
|
36
|
+
TableName: this.tableName,
|
|
37
|
+
Key: { domain, workflowId }
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
if (!result.Item) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return result.Item.data;
|
|
44
|
+
}
|
|
45
|
+
async getAllWorkflows(domain) {
|
|
46
|
+
const workflows = [];
|
|
47
|
+
let lastKey;
|
|
48
|
+
do {
|
|
49
|
+
const result = await this.docClient.send(
|
|
50
|
+
new QueryCommand({
|
|
51
|
+
TableName: this.tableName,
|
|
52
|
+
KeyConditionExpression: "#domain = :domain",
|
|
53
|
+
ExpressionAttributeNames: { "#domain": "domain" },
|
|
54
|
+
ExpressionAttributeValues: { ":domain": domain },
|
|
55
|
+
ExclusiveStartKey: lastKey
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
for (const item of result.Items ?? []) {
|
|
59
|
+
workflows.push(item.data);
|
|
60
|
+
}
|
|
61
|
+
lastKey = result.LastEvaluatedKey;
|
|
62
|
+
} while (lastKey);
|
|
63
|
+
return workflows;
|
|
64
|
+
}
|
|
65
|
+
async setWorkflow(domain, workflow) {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
await this.docClient.send(
|
|
68
|
+
new UpdateCommand({
|
|
69
|
+
TableName: this.tableName,
|
|
70
|
+
Key: { domain, workflowId: workflow.id },
|
|
71
|
+
UpdateExpression: "SET #data = :data, createdAt = if_not_exists(createdAt, :now), updatedAt = :now",
|
|
72
|
+
ExpressionAttributeNames: { "#data": "data" },
|
|
73
|
+
ExpressionAttributeValues: { ":data": workflow, ":now": now }
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
async createWorkflow(domain, workflowData) {
|
|
78
|
+
const id = nanoid(8);
|
|
79
|
+
const workflow = { ...workflowData, id };
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
const item = {
|
|
82
|
+
domain,
|
|
83
|
+
workflowId: id,
|
|
84
|
+
data: workflow,
|
|
85
|
+
createdAt: now,
|
|
86
|
+
updatedAt: now
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
await this.docClient.send(
|
|
90
|
+
new PutCommand({
|
|
91
|
+
TableName: this.tableName,
|
|
92
|
+
Item: item,
|
|
93
|
+
ConditionExpression: "attribute_not_exists(#domain)",
|
|
94
|
+
ExpressionAttributeNames: { "#domain": "domain" }
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
if (err instanceof ConditionalCheckFailedException) {
|
|
99
|
+
throw new WorkflowAlreadyExistsError(domain, id);
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
return workflow;
|
|
104
|
+
}
|
|
105
|
+
async deleteWorkflow(domain, workflowId) {
|
|
106
|
+
const result = await this.docClient.send(
|
|
107
|
+
new DeleteCommand({
|
|
108
|
+
TableName: this.tableName,
|
|
109
|
+
Key: { domain, workflowId },
|
|
110
|
+
ReturnValues: "ALL_OLD"
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
return result.Attributes !== void 0;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/memories/DynamoDBWorkflowMemory.ts
|
|
118
|
+
import {
|
|
119
|
+
DeleteCommand as DeleteCommand2,
|
|
120
|
+
DynamoDBDocumentClient as DynamoDBDocumentClient2,
|
|
121
|
+
GetCommand as GetCommand2,
|
|
122
|
+
PutCommand as PutCommand2,
|
|
123
|
+
QueryCommand as QueryCommand2
|
|
124
|
+
} from "@aws-sdk/lib-dynamodb";
|
|
125
|
+
var DynamoDBWorkflowMemory = class {
|
|
126
|
+
constructor(config) {
|
|
127
|
+
this.docClient = DynamoDBDocumentClient2.from(config.client);
|
|
128
|
+
this.tableName = config.tableName;
|
|
129
|
+
this.gsiName = config.gsiName ?? "domain-subjectId-index";
|
|
130
|
+
}
|
|
131
|
+
buildContextKey(domain, workflowId, subjectId) {
|
|
132
|
+
return `${domain}#${workflowId}#${subjectId}`;
|
|
133
|
+
}
|
|
134
|
+
async getContexts(domain, workflowId, subjectId) {
|
|
135
|
+
const contextKey = this.buildContextKey(domain, workflowId, subjectId);
|
|
136
|
+
const contexts = [];
|
|
137
|
+
let lastKey;
|
|
138
|
+
do {
|
|
139
|
+
const result = await this.docClient.send(
|
|
140
|
+
new QueryCommand2({
|
|
141
|
+
TableName: this.tableName,
|
|
142
|
+
KeyConditionExpression: "contextKey = :contextKey",
|
|
143
|
+
ExpressionAttributeValues: { ":contextKey": contextKey },
|
|
144
|
+
ExclusiveStartKey: lastKey
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
for (const item of result.Items ?? []) {
|
|
148
|
+
contexts.push(item.data);
|
|
149
|
+
}
|
|
150
|
+
lastKey = result.LastEvaluatedKey;
|
|
151
|
+
} while (lastKey);
|
|
152
|
+
return contexts;
|
|
153
|
+
}
|
|
154
|
+
async saveContext(domain, workflowId, subjectId, context) {
|
|
155
|
+
const item = {
|
|
156
|
+
contextKey: this.buildContextKey(domain, workflowId, subjectId),
|
|
157
|
+
instanceId: context.instanceId,
|
|
158
|
+
domain,
|
|
159
|
+
subjectId,
|
|
160
|
+
data: context,
|
|
161
|
+
isCompleted: !!context.isCompleted,
|
|
162
|
+
startedAt: context.startedAt,
|
|
163
|
+
updatedAt: Date.now()
|
|
164
|
+
};
|
|
165
|
+
await this.docClient.send(
|
|
166
|
+
new PutCommand2({
|
|
167
|
+
TableName: this.tableName,
|
|
168
|
+
Item: item
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
async deleteContext(domain, workflowId, subjectId, instanceId) {
|
|
173
|
+
await this.docClient.send(
|
|
174
|
+
new DeleteCommand2({
|
|
175
|
+
TableName: this.tableName,
|
|
176
|
+
Key: {
|
|
177
|
+
contextKey: this.buildContextKey(domain, workflowId, subjectId),
|
|
178
|
+
instanceId
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fetch a single Context by instance id.
|
|
185
|
+
*/
|
|
186
|
+
async getContext(domain, workflowId, subjectId, instanceId) {
|
|
187
|
+
const result = await this.docClient.send(
|
|
188
|
+
new GetCommand2({
|
|
189
|
+
TableName: this.tableName,
|
|
190
|
+
Key: {
|
|
191
|
+
contextKey: this.buildContextKey(domain, workflowId, subjectId),
|
|
192
|
+
instanceId
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
if (!result.Item) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return result.Item.data;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* List all contexts for a given subject across all workflows in a domain.
|
|
203
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
204
|
+
*/
|
|
205
|
+
async getAllContextsForSubject(domain, subjectId) {
|
|
206
|
+
const contexts = [];
|
|
207
|
+
let lastKey;
|
|
208
|
+
do {
|
|
209
|
+
const result = await this.docClient.send(
|
|
210
|
+
new QueryCommand2({
|
|
211
|
+
TableName: this.tableName,
|
|
212
|
+
IndexName: this.gsiName,
|
|
213
|
+
KeyConditionExpression: "#domain = :domain AND subjectId = :subjectId",
|
|
214
|
+
ExpressionAttributeNames: { "#domain": "domain" },
|
|
215
|
+
ExpressionAttributeValues: { ":domain": domain, ":subjectId": subjectId },
|
|
216
|
+
ExclusiveStartKey: lastKey
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
for (const item of result.Items ?? []) {
|
|
220
|
+
contexts.push(item.data);
|
|
221
|
+
}
|
|
222
|
+
lastKey = result.LastEvaluatedKey;
|
|
223
|
+
} while (lastKey);
|
|
224
|
+
return contexts;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* List all contexts across all subjects and workflows in a domain.
|
|
228
|
+
* Uses the `domain-subjectId-index` GSI.
|
|
229
|
+
* Returns contexts annotated with `subjectId`.
|
|
230
|
+
*/
|
|
231
|
+
async getAllContexts(domain) {
|
|
232
|
+
const contexts = [];
|
|
233
|
+
let lastKey;
|
|
234
|
+
do {
|
|
235
|
+
const result = await this.docClient.send(
|
|
236
|
+
new QueryCommand2({
|
|
237
|
+
TableName: this.tableName,
|
|
238
|
+
IndexName: this.gsiName,
|
|
239
|
+
KeyConditionExpression: "#domain = :domain",
|
|
240
|
+
ExpressionAttributeNames: { "#domain": "domain" },
|
|
241
|
+
ExpressionAttributeValues: { ":domain": domain },
|
|
242
|
+
ExclusiveStartKey: lastKey
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
for (const item of result.Items ?? []) {
|
|
246
|
+
const ci = item;
|
|
247
|
+
contexts.push({ ...ci.data, subjectId: ci.subjectId });
|
|
248
|
+
}
|
|
249
|
+
lastKey = result.LastEvaluatedKey;
|
|
250
|
+
} while (lastKey);
|
|
251
|
+
return contexts;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/schedulers/EventBusWorkflowScheduler.ts
|
|
256
|
+
import {
|
|
257
|
+
CreateScheduleCommand,
|
|
258
|
+
DeleteScheduleCommand,
|
|
259
|
+
ResourceNotFoundException
|
|
260
|
+
} from "@aws-sdk/client-scheduler";
|
|
261
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
262
|
+
var EventBusWorkflowScheduler = class {
|
|
263
|
+
constructor(config) {
|
|
264
|
+
this.client = config.client;
|
|
265
|
+
this.eventBusArn = config.eventBusArn;
|
|
266
|
+
this.roleArn = config.roleArn;
|
|
267
|
+
this.scheduleGroupName = config.scheduleGroupName ?? "default";
|
|
268
|
+
this.source = config.source ?? "omega-flow";
|
|
269
|
+
this.detailType = config.detailType ?? "workflow.event";
|
|
270
|
+
}
|
|
271
|
+
async schedule(event, delayMs) {
|
|
272
|
+
const fireAt = new Date(Date.now() + delayMs);
|
|
273
|
+
const scheduleExpression = `at(${fireAt.toISOString().slice(0, 19)})`;
|
|
274
|
+
const name = `omf-${nanoid2(16)}`;
|
|
275
|
+
await this.client.send(
|
|
276
|
+
new CreateScheduleCommand({
|
|
277
|
+
Name: name,
|
|
278
|
+
GroupName: this.scheduleGroupName,
|
|
279
|
+
ScheduleExpression: scheduleExpression,
|
|
280
|
+
ScheduleExpressionTimezone: "UTC",
|
|
281
|
+
FlexibleTimeWindow: { Mode: "OFF" },
|
|
282
|
+
ActionAfterCompletion: "DELETE",
|
|
283
|
+
Target: {
|
|
284
|
+
Arn: this.eventBusArn,
|
|
285
|
+
RoleArn: this.roleArn,
|
|
286
|
+
Input: JSON.stringify(event),
|
|
287
|
+
EventBridgeParameters: {
|
|
288
|
+
Source: this.source,
|
|
289
|
+
DetailType: this.detailType
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
return name;
|
|
295
|
+
}
|
|
296
|
+
async cancel(scheduleId) {
|
|
297
|
+
try {
|
|
298
|
+
await this.client.send(
|
|
299
|
+
new DeleteScheduleCommand({
|
|
300
|
+
Name: scheduleId,
|
|
301
|
+
GroupName: this.scheduleGroupName
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
return true;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (err instanceof ResourceNotFoundException) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
export {
|
|
314
|
+
DynamoDBWorkflowMemory,
|
|
315
|
+
DynamoDBWorkflowStore,
|
|
316
|
+
EventBusWorkflowScheduler,
|
|
317
|
+
WorkflowAlreadyExistsError
|
|
318
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omega-flow/store-aws",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AWS-backed storage and scheduler implementations for Omega Flow",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/omega-flow/omega-flow.git",
|
|
12
|
+
"directory": "packages/store-aws"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://omega-flow.github.io/omega-flow/",
|
|
15
|
+
"bugs": "https://github.com/omega-flow/omega-flow/issues",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"aws-sdk-client-mock": "^4.1.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@aws-sdk/client-dynamodb": "^3.700.0",
|
|
27
|
+
"@aws-sdk/client-scheduler": "^3.1045.0",
|
|
28
|
+
"@aws-sdk/lib-dynamodb": "^3.700.0",
|
|
29
|
+
"nanoid": "^3.3.7",
|
|
30
|
+
"@omega-flow/types": "0.1.0",
|
|
31
|
+
"@omega-flow/engine": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
35
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
36
|
+
"test": "jest --verbose --passWithNoTests",
|
|
37
|
+
"test:watch": "jest --watchAll --verbose --passWithNoTests --detectOpenHandles",
|
|
38
|
+
"test:coverage": "jest --verbose --coverage"
|
|
39
|
+
}
|
|
40
|
+
}
|