@primero.ai/temporal-graph-tools 1.0.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/README.md +203 -0
- package/dist/examples/bundle/activities.d.ts +34 -0
- package/dist/examples/bundle/activities.d.ts.map +1 -0
- package/dist/examples/bundle/activities.js +35 -0
- package/dist/examples/bundle/workflows.d.ts +3 -0
- package/dist/examples/bundle/workflows.d.ts.map +1 -0
- package/dist/examples/bundle/workflows.js +14 -0
- package/dist/examples/trigger-workflows.d.ts +2 -0
- package/dist/examples/trigger-workflows.d.ts.map +1 -0
- package/dist/examples/trigger-workflows.js +43 -0
- package/dist/examples/worker.d.ts +3 -0
- package/dist/examples/worker.d.ts.map +1 -0
- package/dist/examples/worker.js +29 -0
- package/dist/src/bundler.d.ts +25 -0
- package/dist/src/bundler.d.ts.map +1 -0
- package/dist/src/bundler.js +208 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/types.d.ts +45 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +64 -0
- package/dist/src/utils/deep-equal.d.ts +2 -0
- package/dist/src/utils/deep-equal.d.ts.map +1 -0
- package/dist/src/utils/deep-equal.js +54 -0
- package/dist/src/workflow/builder.d.ts +27 -0
- package/dist/src/workflow/builder.d.ts.map +1 -0
- package/dist/src/workflow/builder.js +230 -0
- package/dist/src/workflow/collection.d.ts +3 -0
- package/dist/src/workflow/collection.d.ts.map +1 -0
- package/dist/src/workflow/collection.js +39 -0
- package/dist/src/workflow-bundler.d.ts +6 -0
- package/dist/src/workflow-bundler.d.ts.map +1 -0
- package/dist/src/workflow-bundler.js +163 -0
- package/package.json +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# @primero.ai/temporal-graph-tools
|
|
2
|
+
|
|
3
|
+
TypeScript utilities for assembling Temporal workflows from plain activity
|
|
4
|
+
functions. Build a workflow plan, capture the generated source code, hydrate
|
|
5
|
+
activity implementations, and bundle everything for a worker without
|
|
6
|
+
hand-writing workflow files.
|
|
7
|
+
|
|
8
|
+
## Highlights
|
|
9
|
+
|
|
10
|
+
- Fluent builder for chaining sequential steps and running activity stages in
|
|
11
|
+
parallel with type-safe input/output propagation.
|
|
12
|
+
- Automatic activity key generation and optional per-activity configuration so
|
|
13
|
+
the generated workflow source stays deterministic.
|
|
14
|
+
- One-call bundler that validates multiple plans, hydrates activities, and
|
|
15
|
+
produces bundled workflow code ready for a worker.
|
|
16
|
+
- Emits Temporal workflow source that proxies activities and stitches staged
|
|
17
|
+
plans together.
|
|
18
|
+
- Low-level helpers remain available if you prefer to collect results, build
|
|
19
|
+
bundles, or hydrate activities manually.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @primero.ai/temporal-graph-tools
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @primero.ai/temporal-graph-tools
|
|
27
|
+
# or
|
|
28
|
+
bun add @primero.ai/temporal-graph-tools
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The package targets Node.js 18+ and ships ESM builds.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
bundleWorkflows,
|
|
38
|
+
createActivity,
|
|
39
|
+
createWorkflowBuilder,
|
|
40
|
+
} from '@primero.ai/temporal-graph-tools'
|
|
41
|
+
|
|
42
|
+
type FetchUserInput = { userId: string }
|
|
43
|
+
type FetchUserOutput = { profile: { id: string; name: string } }
|
|
44
|
+
|
|
45
|
+
const fetchUserProfile = createActivity(
|
|
46
|
+
async ({ userId }: FetchUserInput): Promise<FetchUserOutput> => {
|
|
47
|
+
return { profile: { id: userId, name: `User ${userId}` } }
|
|
48
|
+
},
|
|
49
|
+
{ id: 'fetchUserProfile' },
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const sendWelcomeEmail = createActivity(
|
|
53
|
+
async ({ profile }: FetchUserOutput) => {
|
|
54
|
+
return { sent: true, name: profile.name }
|
|
55
|
+
},
|
|
56
|
+
{ id: 'sendWelcomeEmail' },
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async function compile() {
|
|
60
|
+
const builder = createWorkflowBuilder<FetchUserInput>({
|
|
61
|
+
workflowName: 'customerOnboardingWorkflow',
|
|
62
|
+
proxyOptions: { startToCloseTimeout: '2 minutes' },
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const plan = builder.then(fetchUserProfile).then(sendWelcomeEmail).commit()
|
|
66
|
+
const { activities, workflowBundle } = await bundleWorkflows([plan])
|
|
67
|
+
|
|
68
|
+
// Use the emitted artifacts with a Temporal worker
|
|
69
|
+
return { workflowBundle, activities }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See the complete onboarding example in `examples/` for a richer flow that uses a
|
|
74
|
+
parallel stage and hooks a worker up to the generated artifacts.
|
|
75
|
+
|
|
76
|
+
### Example scripts
|
|
77
|
+
|
|
78
|
+
After installing dependencies you can explore the sample project:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
bun install
|
|
82
|
+
bun run worker # Starts a Temporal worker (needs a Temporal cluster)
|
|
83
|
+
bun run trigger-workflows # Launches the sample workflows through the client
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Set `TEMPORAL_ADDRESS`, `TEMPORAL_NAMESPACE`, and `TEMPORAL_TASK_QUEUE` in
|
|
87
|
+
`.env` to point the worker at your cluster. Use `bun run trigger-workflows` to
|
|
88
|
+
start the compiled workflows through the Temporal client once the worker is
|
|
89
|
+
running.
|
|
90
|
+
|
|
91
|
+
## Workflow builder API
|
|
92
|
+
|
|
93
|
+
### `createWorkflowBuilder<TInput>(options)`
|
|
94
|
+
|
|
95
|
+
Creates a `WorkflowBuilder` instance typed with the initial workflow input.
|
|
96
|
+
`options` must include:
|
|
97
|
+
|
|
98
|
+
- `workflowName`: Name of the exported workflow function. This value must be a
|
|
99
|
+
non-empty string and unique across the plans you later bundle.
|
|
100
|
+
|
|
101
|
+
Optional fields:
|
|
102
|
+
|
|
103
|
+
- `activitiesImportPath`: Module specifier used in the generated workflow import
|
|
104
|
+
(`'./activities'` by default).
|
|
105
|
+
- `proxyOptions`: Either a `@temporalio/workflow` `ActivityOptions` object or a
|
|
106
|
+
string literal dropped into the generated code. If omitted, a one-minute
|
|
107
|
+
`startToCloseTimeout` is emitted.
|
|
108
|
+
|
|
109
|
+
### `builder.then(activity, config?)`
|
|
110
|
+
|
|
111
|
+
Appends a sequential activity. The helper accepts either a bare activity
|
|
112
|
+
function or a value created with `createActivity`. When both inline and
|
|
113
|
+
preconfigured options are provided they are merged; `config.id` determines the
|
|
114
|
+
activity key.
|
|
115
|
+
|
|
116
|
+
### `builder.parallel([activityA, activityB, ...])`
|
|
117
|
+
|
|
118
|
+
Executes multiple activities against the current stage output and returns an
|
|
119
|
+
object keyed by each activity's `id`. A parallel stage can only be added after
|
|
120
|
+
at least one `then` call.
|
|
121
|
+
|
|
122
|
+
### `builder.commit(options?)`
|
|
123
|
+
|
|
124
|
+
Finalises the plan and returns:
|
|
125
|
+
|
|
126
|
+
- `workflowName`: The sanitized name of the exported workflow function.
|
|
127
|
+
- `workflowSource`: Generated TypeScript for the Temporal workflow function.
|
|
128
|
+
- `activities`: Map of activity keys to the original implementations and config
|
|
129
|
+
metadata. Implementations remain live references so any captured helpers stay
|
|
130
|
+
intact.
|
|
131
|
+
|
|
132
|
+
Additional `options` override the builder defaults for this invocation.
|
|
133
|
+
|
|
134
|
+
## Activity helpers
|
|
135
|
+
|
|
136
|
+
### `createActivity(activityFn, config?)`
|
|
137
|
+
|
|
138
|
+
Wraps an activity function so it can be reused with the builder. When a config
|
|
139
|
+
object is provided its `id` becomes the activity key; without options the
|
|
140
|
+
function name (or an auto-incremented fallback) is used. The helper is also
|
|
141
|
+
re-exported as `createActivity` for codebases that prefer plural naming.
|
|
142
|
+
|
|
143
|
+
## Workflow bundler utilities
|
|
144
|
+
|
|
145
|
+
### `bundleWorkflows(plans, options?)`
|
|
146
|
+
|
|
147
|
+
High-level helper that accepts one or more `WorkflowBuildResult` instances,
|
|
148
|
+
validates them, hydrates all activities, and bundles the generated workflow
|
|
149
|
+
sources. Returns:
|
|
150
|
+
|
|
151
|
+
- `activities`: Map of activity keys to runnable implementations.
|
|
152
|
+
- `workflowBundle`: Object containing the bundled JavaScript (in `code`) with an
|
|
153
|
+
inline source map.
|
|
154
|
+
|
|
155
|
+
Use this when you want a single call that prepares everything for a Temporal
|
|
156
|
+
worker. Under the hood it relies on the lower-level helpers documented below.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const plans = [onboardingPlan, greetingPlan]
|
|
160
|
+
const { activities, workflowBundle } = await bundleWorkflows(plans, {
|
|
161
|
+
filename: 'team-workflows.js',
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `collectWorkflowBuildResults(results)`
|
|
166
|
+
|
|
167
|
+
Merges the output of multiple `builder.commit()` calls into a single object.
|
|
168
|
+
Workflow names must be unique (duplicates are rejected), and activity IDs either
|
|
169
|
+
unique or guaranteed to have identical implementation/config pairs. The result
|
|
170
|
+
can feed directly into `instantiateActivities` or `buildWorkflowBundleCode`.
|
|
171
|
+
|
|
172
|
+
### `instantiateActivities(bundles)`
|
|
173
|
+
|
|
174
|
+
Accepts the `activities` map returned by `builder.commit()` (or
|
|
175
|
+
`collectWorkflowBuildResults`) and produces actual implementations. Each entry
|
|
176
|
+
is the original function reference supplied to the builder, so any captured
|
|
177
|
+
state remains intact.
|
|
178
|
+
|
|
179
|
+
### `buildWorkflowBundleCode(source, filename?)`
|
|
180
|
+
|
|
181
|
+
Runs Temporal's `bundleWorkflowCode` against the generated workflow source(s)
|
|
182
|
+
and returns bundled JavaScript with an inline source map. `source` can be:
|
|
183
|
+
|
|
184
|
+
- A raw workflow source string (preserved for backward compatibility).
|
|
185
|
+
- A single `WorkflowBuildResult` or `WorkflowSourceArtifact`.
|
|
186
|
+
- An array of `WorkflowSourceArtifact` values (for multiple workflows).
|
|
187
|
+
|
|
188
|
+
`filename` controls the `file` attribute recorded in the map. When omitted the
|
|
189
|
+
helper generates deterministic filenames per workflow and normalizes the map so
|
|
190
|
+
Temporal tooling can attribute stack traces correctly.
|
|
191
|
+
|
|
192
|
+
## Development
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
bun install
|
|
196
|
+
bun run type-check
|
|
197
|
+
bun run lint
|
|
198
|
+
bun run build
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type FetchUserInput = {
|
|
2
|
+
userId: string;
|
|
3
|
+
};
|
|
4
|
+
export declare const fetchUserProfile: import("@primero.ai/temporal-graph-tools").ConfiguredActivityReference<FetchUserInput, FetchUserOutput, "fetchUserProfile">;
|
|
5
|
+
export declare const sendWelcomeEmail: import("@primero.ai/temporal-graph-tools").ConfiguredActivityReference<FetchUserOutput, {
|
|
6
|
+
sent: boolean;
|
|
7
|
+
recipientSlug: string;
|
|
8
|
+
}, "sendWelcomeEmail">;
|
|
9
|
+
export type UserProfile = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
};
|
|
14
|
+
export type FetchUserOutput = {
|
|
15
|
+
profile: UserProfile;
|
|
16
|
+
};
|
|
17
|
+
export declare const syncCrmRecord: import("@primero.ai/temporal-graph-tools").ConfiguredActivityReference<FetchUserOutput, {
|
|
18
|
+
synced: boolean;
|
|
19
|
+
recordSlug: string;
|
|
20
|
+
}, "syncCrmRecord">;
|
|
21
|
+
export type ParallelResult = {
|
|
22
|
+
sendWelcomeEmail: {
|
|
23
|
+
sent: boolean;
|
|
24
|
+
recipientSlug: string;
|
|
25
|
+
};
|
|
26
|
+
syncCrmRecord: {
|
|
27
|
+
synced: boolean;
|
|
28
|
+
recordSlug: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare const logCompletion: import("@primero.ai/temporal-graph-tools").ConfiguredActivityReference<ParallelResult, void, "logCompletion">;
|
|
32
|
+
export declare const startGreet: import("@primero.ai/temporal-graph-tools").ConfiguredActivityReference<unknown, void, "startGreet">;
|
|
33
|
+
export declare const endGreet: import("@primero.ai/temporal-graph-tools").ConfiguredActivityReference<unknown, void, "endGreet">;
|
|
34
|
+
//# sourceMappingURL=activities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activities.d.ts","sourceRoot":"","sources":["../../../examples/bundle/activities.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/C,eAAO,MAAM,gBAAgB,6HAiB5B,CAAA;AAED,eAAO,MAAM,gBAAgB;UAC2B,OAAO;mBAAiB,MAAM;sBAOrF,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AACpE,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAA;AAEtD,eAAO,MAAM,aAAa;YACgC,OAAO;gBAAc,MAAM;mBAOpF,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,gBAAgB,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D,aAAa,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,CAAA;AAED,eAAO,MAAM,aAAa,+GASzB,CAAA;AACD,eAAO,MAAM,UAAU,qGAA0E,CAAA;AACjG,eAAO,MAAM,QAAQ,mGAAuE,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import kebabCase from 'lodash/kebabCase.js';
|
|
2
|
+
import { createActivity, } from '@primero.ai/temporal-graph-tools';
|
|
3
|
+
export const fetchUserProfile = createActivity(async (input) => {
|
|
4
|
+
const profileName = `User ${input.userId.slice(0, 6)}`;
|
|
5
|
+
const slug = kebabCase(profileName);
|
|
6
|
+
console.log(`[fetchUserProfile] fetching ${input.userId} as ${slug}`);
|
|
7
|
+
return {
|
|
8
|
+
profile: {
|
|
9
|
+
id: input.userId,
|
|
10
|
+
name: profileName,
|
|
11
|
+
slug,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}, {
|
|
15
|
+
id: 'fetchUserProfile',
|
|
16
|
+
});
|
|
17
|
+
export const sendWelcomeEmail = createActivity(async ({ profile }) => {
|
|
18
|
+
console.log(`[sendWelcomeEmail] sent email to ${profile.name} (${profile.slug})`);
|
|
19
|
+
return { sent: true, recipientSlug: profile.slug };
|
|
20
|
+
}, {
|
|
21
|
+
id: 'sendWelcomeEmail',
|
|
22
|
+
});
|
|
23
|
+
export const syncCrmRecord = createActivity(async ({ profile }) => {
|
|
24
|
+
console.log(`[syncCrmRecord] synced record ${profile.id} (${profile.slug})`);
|
|
25
|
+
return { synced: true, recordSlug: profile.slug };
|
|
26
|
+
}, {
|
|
27
|
+
id: 'syncCrmRecord',
|
|
28
|
+
});
|
|
29
|
+
export const logCompletion = createActivity(async (result) => {
|
|
30
|
+
console.log(`[logCompletion] email sent=${result.sendWelcomeEmail.sent} (slug=${result.sendWelcomeEmail.recipientSlug}), crm synced=${result.syncCrmRecord.synced}`);
|
|
31
|
+
}, {
|
|
32
|
+
id: 'logCompletion',
|
|
33
|
+
});
|
|
34
|
+
export const startGreet = createActivity(async () => console.log('Hello '), { id: 'startGreet' });
|
|
35
|
+
export const endGreet = createActivity(async () => console.log('World'), { id: 'endGreet' });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../../../examples/bundle/workflows.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,iBAAiB,gEAOnB,CAAA;AAEX,eAAO,MAAM,iBAAiB,gEAGnB,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createWorkflowBuilder } from '@primero.ai/temporal-graph-tools';
|
|
2
|
+
import { endGreet, fetchUserProfile, logCompletion, sendWelcomeEmail, startGreet, syncCrmRecord, } from './activities';
|
|
3
|
+
export const builderOnboarding = createWorkflowBuilder({
|
|
4
|
+
workflowName: 'customerOnboardingWorkflow',
|
|
5
|
+
proxyOptions: { startToCloseTimeout: '2 minutes' },
|
|
6
|
+
})
|
|
7
|
+
.then(fetchUserProfile)
|
|
8
|
+
.parallel([sendWelcomeEmail, syncCrmRecord])
|
|
9
|
+
.then(logCompletion)
|
|
10
|
+
.commit();
|
|
11
|
+
export const builderHelloWorld = createWorkflowBuilder({ workflowName: 'greetWorkflow' })
|
|
12
|
+
.then(startGreet)
|
|
13
|
+
.then(endGreet)
|
|
14
|
+
.commit();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-workflows.d.ts","sourceRoot":"","sources":["../../examples/trigger-workflows.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Connection, WorkflowClient } from '@temporalio/client';
|
|
2
|
+
import { builderHelloWorld, builderOnboarding } from './bundle/workflows';
|
|
3
|
+
const DEFAULT_NAMESPACE = 'default';
|
|
4
|
+
const DEFAULT_TASK_QUEUE = 'default';
|
|
5
|
+
async function run() {
|
|
6
|
+
var _a, _b, _c, _d;
|
|
7
|
+
const connection = await Connection.connect({
|
|
8
|
+
address: (_a = process.env.TEMPORAL_ADDRESS) !== null && _a !== void 0 ? _a : 'localhost:7233',
|
|
9
|
+
tls: process.env.TEMPORAL_API_KEY ? {} : undefined,
|
|
10
|
+
apiKey: process.env.TEMPORAL_API_KEY,
|
|
11
|
+
});
|
|
12
|
+
const client = new WorkflowClient({
|
|
13
|
+
connection,
|
|
14
|
+
namespace: (_b = process.env.TEMPORAL_NAMESPACE) !== null && _b !== void 0 ? _b : DEFAULT_NAMESPACE,
|
|
15
|
+
});
|
|
16
|
+
const [onboardingHandle, greetHandle] = await Promise.all([
|
|
17
|
+
client.start(builderOnboarding.workflowName, {
|
|
18
|
+
taskQueue: (_c = process.env.TEMPORAL_TASK_QUEUE) !== null && _c !== void 0 ? _c : DEFAULT_TASK_QUEUE,
|
|
19
|
+
workflowId: `customer-onboarding-${Date.now()}`,
|
|
20
|
+
args: [{ userId: 'user-123' }],
|
|
21
|
+
}),
|
|
22
|
+
client.start(builderHelloWorld.workflowName, {
|
|
23
|
+
taskQueue: (_d = process.env.TEMPORAL_TASK_QUEUE) !== null && _d !== void 0 ? _d : DEFAULT_TASK_QUEUE,
|
|
24
|
+
workflowId: `greet-${Date.now()}`,
|
|
25
|
+
args: [{ userId: 'user-123' }],
|
|
26
|
+
}),
|
|
27
|
+
]);
|
|
28
|
+
console.log('Onboarding Workflow started:', {
|
|
29
|
+
workflowId: onboardingHandle.workflowId,
|
|
30
|
+
runId: onboardingHandle.firstExecutionRunId,
|
|
31
|
+
});
|
|
32
|
+
console.log('Greet Workflow started:', {
|
|
33
|
+
workflowId: greetHandle.workflowId,
|
|
34
|
+
runId: greetHandle.firstExecutionRunId,
|
|
35
|
+
});
|
|
36
|
+
const result = await Promise.all([onboardingHandle.result(), greetHandle.result()]);
|
|
37
|
+
console.log('Workflows completed with result:', result);
|
|
38
|
+
await connection.close();
|
|
39
|
+
}
|
|
40
|
+
run().catch((error) => {
|
|
41
|
+
console.error('Failed to run workflow example:', error);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../examples/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,uBAAuB,EAAU,MAAM,oBAAoB,CAAA;AAItF,wBAAgB,oBAAoB,IAAI,uBAAuB,CAM9D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NativeConnection, Worker } from '@temporalio/worker';
|
|
2
|
+
import { bundleWorkflows, loadActivitiesFromBundle } from '@primero.ai/temporal-graph-tools';
|
|
3
|
+
import { builderHelloWorld, builderOnboarding } from './bundle/workflows';
|
|
4
|
+
export function getConnectionOptions() {
|
|
5
|
+
return {
|
|
6
|
+
address: process.env.TEMPORAL_ADDRESS,
|
|
7
|
+
apiKey: process.env.TEMPORAL_API_KEY,
|
|
8
|
+
tls: {},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async function run() {
|
|
12
|
+
const { workflowBundle, activityBundle } = await bundleWorkflows([builderHelloWorld, builderOnboarding], {
|
|
13
|
+
activityBundle: {},
|
|
14
|
+
});
|
|
15
|
+
const activities = await loadActivitiesFromBundle(activityBundle);
|
|
16
|
+
const connection = await NativeConnection.connect(getConnectionOptions());
|
|
17
|
+
const worker = await Worker.create({
|
|
18
|
+
connection,
|
|
19
|
+
namespace: process.env.TEMPORAL_NAMESPACE,
|
|
20
|
+
taskQueue: process.env.TEMPORAL_TASK_QUEUE || 'default',
|
|
21
|
+
activities,
|
|
22
|
+
workflowBundle,
|
|
23
|
+
});
|
|
24
|
+
await worker.run();
|
|
25
|
+
}
|
|
26
|
+
run().catch((err) => {
|
|
27
|
+
console.error(err);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkflowBuildResult } from './types.js';
|
|
2
|
+
import type { ActivityImplementations } from './workflow-bundler.js';
|
|
3
|
+
export type BundleWorkflowsOptions = {
|
|
4
|
+
filename?: string;
|
|
5
|
+
activityBundle?: BundleActivitiesOptions;
|
|
6
|
+
};
|
|
7
|
+
export type BundleWorkflowsResult = {
|
|
8
|
+
activityBundle: BundledActivitiesArtifact;
|
|
9
|
+
workflowBundle: {
|
|
10
|
+
code: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type BundleActivitiesOptions = {
|
|
14
|
+
entrypoints?: readonly string[];
|
|
15
|
+
filename?: string;
|
|
16
|
+
externals?: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
export type BundledActivitiesArtifact = {
|
|
19
|
+
filename: string;
|
|
20
|
+
code: string;
|
|
21
|
+
map?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function loadActivitiesFromBundle(bundle: BundledActivitiesArtifact): Promise<ActivityImplementations>;
|
|
24
|
+
export declare function bundleWorkflows(plans: readonly WorkflowBuildResult[], options?: BundleWorkflowsOptions): Promise<BundleWorkflowsResult>;
|
|
25
|
+
//# sourceMappingURL=bundler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundler.d.ts","sourceRoot":"","sources":["../../src/bundler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAkB,mBAAmB,EAA0B,MAAM,YAAY,CAAA;AAC7F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAIpE,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,uBAAuB,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,cAAc,EAAE,yBAAyB,CAAA;IACzC,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAoCD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAkClC;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CAqChC"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { isAbsolute, resolve, join } from 'node:path';
|
|
2
|
+
import { mkdtemp, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { buildWorkflowBundleCode } from './workflow-bundler.js';
|
|
5
|
+
import { collectWorkflowBuildResults } from './workflow/collection.js';
|
|
6
|
+
let cachedEsbuild;
|
|
7
|
+
async function loadEsbuild() {
|
|
8
|
+
if (cachedEsbuild) {
|
|
9
|
+
return cachedEsbuild;
|
|
10
|
+
}
|
|
11
|
+
const createLoadError = (error) => {
|
|
12
|
+
if (error instanceof Error) {
|
|
13
|
+
error.message = `Failed to load esbuild. Install it as a dependency before calling bundleWorkflows(). Original error: ${error.message}`;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
return new Error(`Failed to load esbuild. Install it as a dependency before calling bundleWorkflows(). Original error: ${String(error)}`);
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
const esbuildModule = await import('esbuild');
|
|
20
|
+
cachedEsbuild = esbuildModule;
|
|
21
|
+
return esbuildModule;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw createLoadError(error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function loadActivitiesFromBundle(bundle) {
|
|
28
|
+
if (!bundle || typeof bundle.code !== 'string' || bundle.code.trim().length === 0) {
|
|
29
|
+
throw new Error('Activity bundle code must be a non-empty string.');
|
|
30
|
+
}
|
|
31
|
+
const source = ensureActivityInlineSourceMap(bundle.code, bundle.map);
|
|
32
|
+
const tempDir = await mkdtemp(join(process.cwd(), '.temporal-graph-tools-'));
|
|
33
|
+
const tempFile = join(tempDir, `activities-${Date.now()}-${Math.random().toString(16).slice(2)}.cjs`);
|
|
34
|
+
await writeFile(tempFile, source, 'utf8');
|
|
35
|
+
const require = createRequire(import.meta.url);
|
|
36
|
+
const moduleNamespace = require(tempFile);
|
|
37
|
+
const activitiesExport = moduleNamespace.activities;
|
|
38
|
+
if (isActivityImplementations(activitiesExport)) {
|
|
39
|
+
return activitiesExport;
|
|
40
|
+
}
|
|
41
|
+
const collected = {};
|
|
42
|
+
Object.entries(moduleNamespace).forEach(([key, value]) => {
|
|
43
|
+
if (typeof value === 'function') {
|
|
44
|
+
collected[key] = value;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (Object.keys(collected).length > 0) {
|
|
48
|
+
return collected;
|
|
49
|
+
}
|
|
50
|
+
throw new Error('Activity bundle did not expose an activities object or callable exports.');
|
|
51
|
+
}
|
|
52
|
+
export async function bundleWorkflows(plans, options) {
|
|
53
|
+
var _a;
|
|
54
|
+
if (plans.length === 0) {
|
|
55
|
+
throw new Error('bundleWorkflows requires at least one workflow plan.');
|
|
56
|
+
}
|
|
57
|
+
const seenWorkflowNames = new Set();
|
|
58
|
+
plans.forEach((plan, index) => {
|
|
59
|
+
const workflowName = typeof plan.workflowName === 'string' ? plan.workflowName.trim() : '';
|
|
60
|
+
if (workflowName.length === 0) {
|
|
61
|
+
throw new Error(`Workflow plan at index ${index} is missing a valid workflowName. Provide workflowName when building workflows.`);
|
|
62
|
+
}
|
|
63
|
+
if (seenWorkflowNames.has(workflowName)) {
|
|
64
|
+
throw new Error(`Duplicate workflow name detected in bundle: '${workflowName}' at index ${index}. Workflow names must be unique.`);
|
|
65
|
+
}
|
|
66
|
+
seenWorkflowNames.add(workflowName);
|
|
67
|
+
});
|
|
68
|
+
const collection = collectWorkflowBuildResults(plans);
|
|
69
|
+
const activityBundle = await bundleActivitiesWithEsbuild(collection.activities, options === null || options === void 0 ? void 0 : options.activityBundle);
|
|
70
|
+
const filename = (_a = options === null || options === void 0 ? void 0 : options.filename) !== null && _a !== void 0 ? _a : createBundleFilename(collection.workflows);
|
|
71
|
+
const code = await buildWorkflowBundleCode(collection.workflows, filename);
|
|
72
|
+
return {
|
|
73
|
+
activityBundle,
|
|
74
|
+
workflowBundle: { code },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createBundleFilename(workflows) {
|
|
78
|
+
const baseName = workflows
|
|
79
|
+
.map((workflow) => workflow.workflowName)
|
|
80
|
+
.filter((name) => typeof name === 'string' && name.trim().length > 0)
|
|
81
|
+
.join('-')
|
|
82
|
+
.trim();
|
|
83
|
+
const sanitizedBase = sanitizeFilenameBase(baseName.length > 0 ? baseName : 'workflow');
|
|
84
|
+
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
85
|
+
return `${sanitizedBase}-${randomSuffix}.workflow.js`;
|
|
86
|
+
}
|
|
87
|
+
function sanitizeFilenameBase(candidate) {
|
|
88
|
+
const sanitized = candidate.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
89
|
+
if (sanitized.length === 0 || /^_+$/.test(sanitized)) {
|
|
90
|
+
return 'workflow';
|
|
91
|
+
}
|
|
92
|
+
return sanitized;
|
|
93
|
+
}
|
|
94
|
+
async function bundleActivitiesWithEsbuild(bundles, options = {}) {
|
|
95
|
+
var _a, _b, _c, _d, _e;
|
|
96
|
+
const entrypoints = normalizeActivityEntrypoints((_a = options.entrypoints) !== null && _a !== void 0 ? _a : deriveActivityEntrypoints(bundles));
|
|
97
|
+
if (entrypoints.length === 0) {
|
|
98
|
+
throw new Error('bundleActivities requires at least one entrypoint.');
|
|
99
|
+
}
|
|
100
|
+
const filename = ((_b = options.filename) !== null && _b !== void 0 ? _b : 'activities.bundle.cjs').trim();
|
|
101
|
+
const externals = new Set(['@primero.ai/temporal-graph-tools', '@swc/*']);
|
|
102
|
+
((_c = options.externals) !== null && _c !== void 0 ? _c : []).forEach((name) => {
|
|
103
|
+
if (typeof name === 'string' && name.trim().length > 0) {
|
|
104
|
+
externals.add(name.trim());
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const entrySource = createActivitiesEntrySource(entrypoints);
|
|
108
|
+
const esbuild = await loadEsbuild();
|
|
109
|
+
const buildResult = await esbuild.build({
|
|
110
|
+
stdin: {
|
|
111
|
+
contents: entrySource,
|
|
112
|
+
resolveDir: process.cwd(),
|
|
113
|
+
sourcefile: filename,
|
|
114
|
+
loader: 'ts',
|
|
115
|
+
},
|
|
116
|
+
bundle: true,
|
|
117
|
+
platform: 'node',
|
|
118
|
+
format: 'cjs',
|
|
119
|
+
target: ['node18'],
|
|
120
|
+
absWorkingDir: process.cwd(),
|
|
121
|
+
outfile: filename,
|
|
122
|
+
write: false,
|
|
123
|
+
sourcemap: 'inline',
|
|
124
|
+
external: Array.from(externals),
|
|
125
|
+
});
|
|
126
|
+
const jsFile = (_d = buildResult.outputFiles) === null || _d === void 0 ? void 0 : _d.find((file) => file.path.endsWith('.js') || file.path.endsWith('.mjs') || file.path.endsWith('.cjs'));
|
|
127
|
+
const mapFile = (_e = buildResult.outputFiles) === null || _e === void 0 ? void 0 : _e.find((file) => file.path.endsWith('.js.map') ||
|
|
128
|
+
file.path.endsWith('.mjs.map') ||
|
|
129
|
+
file.path.endsWith('.cjs.map'));
|
|
130
|
+
if (!jsFile) {
|
|
131
|
+
throw new Error('Failed to generate activities bundle.');
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
filename,
|
|
135
|
+
code: jsFile.text,
|
|
136
|
+
...(mapFile ? { map: mapFile.text } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function normalizeActivityEntrypoints(entrypoints) {
|
|
140
|
+
return entrypoints
|
|
141
|
+
.map((entry) => entry.trim())
|
|
142
|
+
.filter((entry) => entry.length > 0)
|
|
143
|
+
.map((entry) => (isAbsolute(entry) ? entry : resolve(process.cwd(), entry)));
|
|
144
|
+
}
|
|
145
|
+
function deriveActivityEntrypoints(bundles) {
|
|
146
|
+
const paths = new Set();
|
|
147
|
+
Object.values(bundles).forEach((bundle) => {
|
|
148
|
+
if (bundle === null || bundle === void 0 ? void 0 : bundle.sourceFile) {
|
|
149
|
+
paths.add(bundle.sourceFile);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (paths.size === 0) {
|
|
153
|
+
throw new Error('Unable to derive activity sources automatically. Provide activityBundle.entrypoints.');
|
|
154
|
+
}
|
|
155
|
+
return Array.from(paths);
|
|
156
|
+
}
|
|
157
|
+
function createActivitiesEntrySource(entrypoints) {
|
|
158
|
+
const imports = entrypoints
|
|
159
|
+
.map((entry, index) => `import * as module${index} from ${JSON.stringify(entry)};`)
|
|
160
|
+
.join('\n');
|
|
161
|
+
const moduleRefs = entrypoints.map((_, index) => `module${index}`).join(', ');
|
|
162
|
+
return `
|
|
163
|
+
${imports}
|
|
164
|
+
|
|
165
|
+
const merged = {} as Record<string, unknown>;
|
|
166
|
+
for (const mod of [${moduleRefs}]) {
|
|
167
|
+
if (!mod || typeof mod !== 'object') {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const candidate = (mod as { activities?: unknown }).activities;
|
|
171
|
+
if (candidate && typeof candidate === 'object') {
|
|
172
|
+
Object.assign(merged, candidate as Record<string, unknown>);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
for (const [key, value] of Object.entries(mod)) {
|
|
176
|
+
if (key === 'default' || key === '__esModule') {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (value && typeof value === 'object' && 'activity' in (value as { activity?: unknown })) {
|
|
180
|
+
const activity = (value as { activity?: unknown }).activity;
|
|
181
|
+
if (typeof activity === 'function') {
|
|
182
|
+
merged[key] = activity;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === 'function') {
|
|
187
|
+
merged[key] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const activities = merged;
|
|
193
|
+
`.trimStart();
|
|
194
|
+
}
|
|
195
|
+
function ensureActivityInlineSourceMap(code, map) {
|
|
196
|
+
const marker = '//# sourceMappingURL=data:application/json;base64,';
|
|
197
|
+
if (!map || code.includes(marker)) {
|
|
198
|
+
return code;
|
|
199
|
+
}
|
|
200
|
+
const base64 = Buffer.from(map, 'utf8').toString('base64');
|
|
201
|
+
return `${code}\n${marker}${base64}`;
|
|
202
|
+
}
|
|
203
|
+
function isActivityImplementations(value) {
|
|
204
|
+
if (typeof value !== 'object' || value === null) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return Object.values(value).every((entry) => typeof entry === 'function');
|
|
208
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export { WorkflowBuilder, createWorkflowBuilder } from './workflow/builder.js';
|
|
3
|
+
export * from './workflow-bundler.js';
|
|
4
|
+
export { collectWorkflowBuildResults } from './workflow/collection.js';
|
|
5
|
+
export * from './bundler.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC9E,cAAc,uBAAuB,CAAA;AACrC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAA;AACtE,cAAc,cAAc,CAAA"}
|