@astami/temporal-functions 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/README.md +215 -0
- package/dist/client/index.d.mts +34 -0
- package/dist/client/index.d.ts +34 -0
- package/dist/client/index.js +168 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +162 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.d.mts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +111 -0
- package/dist/index.mjs.map +1 -0
- package/dist/testing/index.d.mts +74 -0
- package/dist/testing/index.d.ts +74 -0
- package/dist/testing/index.js +129 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/index.mjs +121 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/types-C-cNq1Id.d.mts +292 -0
- package/dist/types-C-cNq1Id.d.ts +292 -0
- package/dist/worker/index.d.mts +37 -0
- package/dist/worker/index.d.ts +37 -0
- package/dist/worker/index.js +239 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/index.mjs +233 -0
- package/dist/worker/index.mjs.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { NativeConnection, Worker } from '@temporalio/worker';
|
|
2
|
+
|
|
3
|
+
// src/worker/index.ts
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
function isFunction(def) {
|
|
7
|
+
return def?.__type === "function";
|
|
8
|
+
}
|
|
9
|
+
function isWorkflow(def) {
|
|
10
|
+
return def?.__type === "workflow";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/worker/index.ts
|
|
14
|
+
var TemporalFunctionsWorker = class {
|
|
15
|
+
config;
|
|
16
|
+
functions = /* @__PURE__ */ new Map();
|
|
17
|
+
workflows = /* @__PURE__ */ new Map();
|
|
18
|
+
worker = null;
|
|
19
|
+
shutdownRequested = false;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = {
|
|
22
|
+
...config,
|
|
23
|
+
temporal: {
|
|
24
|
+
namespace: "default",
|
|
25
|
+
...config.temporal
|
|
26
|
+
},
|
|
27
|
+
maxConcurrentActivities: config.maxConcurrentActivities ?? 100,
|
|
28
|
+
maxConcurrentWorkflows: config.maxConcurrentWorkflows ?? 50
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Register a function or workflow
|
|
33
|
+
*/
|
|
34
|
+
register(def) {
|
|
35
|
+
if (isFunction(def)) {
|
|
36
|
+
this.functions.set(def.name, def);
|
|
37
|
+
} else if (isWorkflow(def)) {
|
|
38
|
+
this.workflows.set(def.name, def);
|
|
39
|
+
} else {
|
|
40
|
+
throw new Error("Invalid definition: must be a function or workflow created with tfn.fn() or tfn.workflow()");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Register all exported functions and workflows from a module
|
|
45
|
+
*/
|
|
46
|
+
registerModule(module) {
|
|
47
|
+
for (const [, value] of Object.entries(module)) {
|
|
48
|
+
if (isFunction(value) || isWorkflow(value)) {
|
|
49
|
+
this.register(value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build activities object from registered functions
|
|
55
|
+
*/
|
|
56
|
+
buildActivities() {
|
|
57
|
+
const activities = {};
|
|
58
|
+
for (const [name, def] of this.functions) {
|
|
59
|
+
activities[name] = async (input) => {
|
|
60
|
+
return def.handler(input);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return activities;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate workflow wrapper code for Temporal
|
|
67
|
+
*
|
|
68
|
+
* This creates the workflow module that Temporal requires,
|
|
69
|
+
* wrapping our user-defined workflow handlers with the proper
|
|
70
|
+
* Temporal workflow context.
|
|
71
|
+
*/
|
|
72
|
+
generateWorkflowBundle() {
|
|
73
|
+
const workflowNames = Array.from(this.workflows.keys());
|
|
74
|
+
const code = `
|
|
75
|
+
import { proxyActivities, sleep, workflowInfo, setHandler, defineSignal, defineQuery } from '@temporalio/workflow';
|
|
76
|
+
|
|
77
|
+
// Proxy all activities
|
|
78
|
+
const activities = proxyActivities({
|
|
79
|
+
startToCloseTimeout: '1 minute',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Create workflow context
|
|
83
|
+
function createContext() {
|
|
84
|
+
const signalHandlers = new Map();
|
|
85
|
+
const queryHandlers = new Map();
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
run: async (fn, input, options = {}) => {
|
|
89
|
+
const activity = activities[fn.name];
|
|
90
|
+
if (!activity) {
|
|
91
|
+
throw new Error(\`Function \${fn.name} not registered\`);
|
|
92
|
+
}
|
|
93
|
+
return activity(input);
|
|
94
|
+
},
|
|
95
|
+
sleep: async (duration) => {
|
|
96
|
+
if (typeof duration === 'string') {
|
|
97
|
+
const ms = parseDuration(duration);
|
|
98
|
+
return sleep(ms);
|
|
99
|
+
}
|
|
100
|
+
return sleep(duration);
|
|
101
|
+
},
|
|
102
|
+
now: () => new Date(),
|
|
103
|
+
startChild: async (workflow, input, options = {}) => {
|
|
104
|
+
throw new Error('startChild not yet implemented');
|
|
105
|
+
},
|
|
106
|
+
continueAsNew: async (input) => {
|
|
107
|
+
throw new Error('continueAsNew not yet implemented');
|
|
108
|
+
},
|
|
109
|
+
onSignal: (signalName, handler) => {
|
|
110
|
+
signalHandlers.set(signalName, handler);
|
|
111
|
+
const signal = defineSignal(signalName);
|
|
112
|
+
setHandler(signal, handler);
|
|
113
|
+
},
|
|
114
|
+
onQuery: (queryName, handler) => {
|
|
115
|
+
queryHandlers.set(queryName, handler);
|
|
116
|
+
const query = defineQuery(queryName);
|
|
117
|
+
setHandler(query, handler);
|
|
118
|
+
},
|
|
119
|
+
get info() {
|
|
120
|
+
const info = workflowInfo();
|
|
121
|
+
return {
|
|
122
|
+
workflowId: info.workflowId,
|
|
123
|
+
runId: info.runId,
|
|
124
|
+
taskQueue: info.taskQueue,
|
|
125
|
+
workflowType: info.workflowType,
|
|
126
|
+
namespace: info.namespace,
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Parse duration string to milliseconds
|
|
133
|
+
function parseDuration(duration) {
|
|
134
|
+
const match = duration.match(/^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/);
|
|
135
|
+
if (!match) {
|
|
136
|
+
throw new Error(\`Invalid duration: \${duration}\`);
|
|
137
|
+
}
|
|
138
|
+
const value = parseFloat(match[1]);
|
|
139
|
+
const unit = match[2];
|
|
140
|
+
switch (unit) {
|
|
141
|
+
case 'ms': return value;
|
|
142
|
+
case 's': return value * 1000;
|
|
143
|
+
case 'm': return value * 60 * 1000;
|
|
144
|
+
case 'h': return value * 60 * 60 * 1000;
|
|
145
|
+
case 'd': return value * 24 * 60 * 60 * 1000;
|
|
146
|
+
default: throw new Error(\`Unknown duration unit: \${unit}\`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Export workflow functions
|
|
151
|
+
${workflowNames.map((name) => `
|
|
152
|
+
export async function ${name}(input) {
|
|
153
|
+
const ctx = createContext();
|
|
154
|
+
const handler = __workflowHandlers__.get('${name}');
|
|
155
|
+
if (!handler) {
|
|
156
|
+
throw new Error('Workflow handler not found: ${name}');
|
|
157
|
+
}
|
|
158
|
+
return handler(ctx, input);
|
|
159
|
+
}
|
|
160
|
+
`).join("\n")}
|
|
161
|
+
`;
|
|
162
|
+
return code;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Start the worker
|
|
166
|
+
*/
|
|
167
|
+
async start() {
|
|
168
|
+
if (this.functions.size === 0 && this.workflows.size === 0) {
|
|
169
|
+
throw new Error("No functions or workflows registered. Call register() before start().");
|
|
170
|
+
}
|
|
171
|
+
console.log(`Starting Temporal Functions worker...`);
|
|
172
|
+
console.log(` Temporal: ${this.config.temporal.address}`);
|
|
173
|
+
console.log(` Namespace: ${this.config.temporal.namespace}`);
|
|
174
|
+
console.log(` Task Queue: ${this.config.taskQueue}`);
|
|
175
|
+
console.log(` Functions: ${this.functions.size}`);
|
|
176
|
+
console.log(` Workflows: ${this.workflows.size}`);
|
|
177
|
+
const connection = await NativeConnection.connect({
|
|
178
|
+
address: this.config.temporal.address,
|
|
179
|
+
tls: this.config.temporal.tls ? {
|
|
180
|
+
clientCertPair: this.config.temporal.tls.clientCertPath ? {
|
|
181
|
+
crt: await import('fs').then(
|
|
182
|
+
(fs) => fs.promises.readFile(this.config.temporal.tls.clientCertPath)
|
|
183
|
+
),
|
|
184
|
+
key: await import('fs').then(
|
|
185
|
+
(fs) => fs.promises.readFile(this.config.temporal.tls.clientKeyPath)
|
|
186
|
+
)
|
|
187
|
+
} : void 0
|
|
188
|
+
} : void 0
|
|
189
|
+
});
|
|
190
|
+
const activities = this.buildActivities();
|
|
191
|
+
this.worker = await Worker.create({
|
|
192
|
+
connection,
|
|
193
|
+
namespace: this.config.temporal.namespace,
|
|
194
|
+
taskQueue: this.config.taskQueue,
|
|
195
|
+
activities,
|
|
196
|
+
// workflowsPath will need to be provided by the user
|
|
197
|
+
// or we need to implement dynamic bundling
|
|
198
|
+
maxConcurrentActivityTaskExecutions: this.config.maxConcurrentActivities,
|
|
199
|
+
maxConcurrentWorkflowTaskExecutions: this.config.maxConcurrentWorkflows
|
|
200
|
+
});
|
|
201
|
+
const shutdown = async () => {
|
|
202
|
+
if (this.shutdownRequested) return;
|
|
203
|
+
this.shutdownRequested = true;
|
|
204
|
+
console.log("\nShutting down worker...");
|
|
205
|
+
await this.shutdown();
|
|
206
|
+
process.exit(0);
|
|
207
|
+
};
|
|
208
|
+
process.on("SIGINT", shutdown);
|
|
209
|
+
process.on("SIGTERM", shutdown);
|
|
210
|
+
console.log("\nWorker started. Press Ctrl+C to stop.\n");
|
|
211
|
+
await this.worker.run();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Gracefully shutdown the worker
|
|
215
|
+
*/
|
|
216
|
+
async shutdown() {
|
|
217
|
+
if (this.worker) {
|
|
218
|
+
this.worker.shutdown();
|
|
219
|
+
this.worker = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function createWorker(config) {
|
|
224
|
+
return new TemporalFunctionsWorker(config);
|
|
225
|
+
}
|
|
226
|
+
var tfn = {
|
|
227
|
+
worker: createWorker
|
|
228
|
+
};
|
|
229
|
+
var worker_default = tfn;
|
|
230
|
+
|
|
231
|
+
export { createWorker, worker_default as default, tfn };
|
|
232
|
+
//# sourceMappingURL=index.mjs.map
|
|
233
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/worker/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Temporal Functions - Main Entry Point\n *\n * This module provides the core API for defining functions and workflows.\n * Import from 'temporal-functions' for function/workflow definitions.\n * Import from 'temporal-functions/client' for triggering workflows.\n * Import from 'temporal-functions/worker' for running workers.\n */\n\nimport type {\n FunctionDef,\n FunctionOptions,\n WorkflowDef,\n WorkflowOptions,\n WorkflowHandler,\n WorkflowContext,\n Registry,\n HttpTriggerOptions,\n CronTriggerOptions,\n} from './types.js';\n\n// =============================================================================\n// Global Registry\n// =============================================================================\n\n/**\n * Global registry for all defined functions and workflows\n */\nexport const registry: Registry = {\n functions: new Map(),\n workflows: new Map(),\n};\n\n// =============================================================================\n// Function Definition\n// =============================================================================\n\n/**\n * Define a function (maps to a Temporal Activity)\n *\n * @example\n * ```typescript\n * export const sendEmail = tfn.fn(\n * 'sendEmail',\n * async (params: EmailParams) => {\n * return await emailService.send(params);\n * },\n * { timeout: '30s', retries: 3 }\n * );\n * ```\n */\nfunction fn<TInput, TOutput>(\n name: string,\n handler: (input: TInput) => Promise<TOutput>,\n options: FunctionOptions = {}\n): FunctionDef<TInput, TOutput> {\n const def: FunctionDef<TInput, TOutput> = {\n name,\n handler,\n options: {\n startToCloseTimeout: '1m',\n ...options,\n },\n __type: 'function',\n };\n\n registry.functions.set(name, def as FunctionDef);\n return def;\n}\n\n// =============================================================================\n// Workflow Definition\n// =============================================================================\n\n/**\n * Define a workflow\n *\n * @example\n * ```typescript\n * export const processOrder = tfn.workflow(\n * 'processOrder',\n * async (ctx, order: Order) => {\n * const validated = await ctx.run(validateOrder, order);\n * const paid = await ctx.run(chargePayment, validated);\n * return { orderId: paid.id, status: 'complete' };\n * }\n * );\n * ```\n */\nfunction workflow<TInput, TOutput>(\n name: string,\n handler: WorkflowHandler<TInput, TOutput>,\n options: WorkflowOptions = {}\n): WorkflowDef<TInput, TOutput> {\n const def: WorkflowDef<TInput, TOutput> = {\n name,\n handler,\n options: {\n taskQueue: 'default',\n ...options,\n },\n __type: 'workflow',\n };\n\n registry.workflows.set(name, def as WorkflowDef);\n return def;\n}\n\n// =============================================================================\n// Trigger Definitions (Declarative)\n// =============================================================================\n\ninterface HttpTriggerDef {\n type: 'http';\n method: string;\n path: string;\n workflow: WorkflowDef;\n options: HttpTriggerOptions;\n}\n\ninterface CronTriggerDef {\n type: 'cron';\n schedule: string;\n workflow: WorkflowDef;\n options: CronTriggerOptions;\n}\n\ninterface SignalTriggerDef {\n type: 'signal';\n signalName: string;\n handler: (payload: unknown) => void | Promise<void>;\n}\n\ntype TriggerDef = HttpTriggerDef | CronTriggerDef | SignalTriggerDef;\n\n/**\n * Registry for trigger definitions\n */\nexport const triggers: TriggerDef[] = [];\n\n/**\n * Define an HTTP trigger for a workflow\n *\n * @example\n * ```typescript\n * tfn.http('POST', '/api/orders', processOrder);\n * ```\n */\nfunction http<TInput, TOutput>(\n method: string,\n path: string,\n workflow: WorkflowDef<TInput, TOutput>,\n options: HttpTriggerOptions = {}\n): void {\n triggers.push({\n type: 'http',\n method: method.toUpperCase(),\n path,\n workflow: workflow as WorkflowDef,\n options,\n });\n}\n\n/**\n * Define a cron/scheduled trigger for a workflow\n *\n * @example\n * ```typescript\n * tfn.cron('0 9 * * *', dailyReport); // Every day at 9am\n * ```\n */\nfunction cron<TInput, TOutput>(\n schedule: string,\n workflow: WorkflowDef<TInput, TOutput>,\n options: CronTriggerOptions = {}\n): void {\n triggers.push({\n type: 'cron',\n schedule,\n workflow: workflow as WorkflowDef,\n options,\n });\n}\n\n/**\n * Define an interval trigger (convenience wrapper around cron)\n *\n * @example\n * ```typescript\n * tfn.interval('5m', healthCheck); // Every 5 minutes\n * ```\n */\nfunction interval<TInput, TOutput>(\n duration: string,\n workflow: WorkflowDef<TInput, TOutput>,\n options: CronTriggerOptions = {}\n): void {\n // Convert duration to cron expression\n const cronSchedule = durationToCron(duration);\n cron(cronSchedule, workflow, options);\n}\n\n/**\n * Define a signal trigger\n *\n * @example\n * ```typescript\n * tfn.signal('order.cancel', handleCancellation);\n * ```\n */\nfunction signal(\n signalName: string,\n handler: (payload: unknown) => void | Promise<void>\n): void {\n triggers.push({\n type: 'signal',\n signalName,\n handler,\n });\n}\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\n/**\n * Convert a duration string to a cron expression\n */\nfunction durationToCron(duration: string): string {\n const match = duration.match(/^(\\d+)(s|m|h|d)$/);\n if (!match) {\n throw new Error(`Invalid duration format: ${duration}. Use format like '5m', '1h', '30s'`);\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2];\n\n switch (unit) {\n case 's':\n if (value < 60) {\n return `*/${value} * * * * *`; // Every N seconds (non-standard)\n }\n throw new Error('Seconds interval must be less than 60');\n case 'm':\n return `*/${value} * * * *`; // Every N minutes\n case 'h':\n return `0 */${value} * * *`; // Every N hours\n case 'd':\n return `0 0 */${value} * *`; // Every N days\n default:\n throw new Error(`Unknown duration unit: ${unit}`);\n }\n}\n\n/**\n * Check if a definition is a function\n */\nexport function isFunction(def: unknown): def is FunctionDef {\n return (def as FunctionDef)?.__type === 'function';\n}\n\n/**\n * Check if a definition is a workflow\n */\nexport function isWorkflow(def: unknown): def is WorkflowDef {\n return (def as WorkflowDef)?.__type === 'workflow';\n}\n\n// =============================================================================\n// Main API Export\n// =============================================================================\n\n/**\n * The main Temporal Functions API\n */\nexport const tfn = {\n /** Define a function (activity) */\n fn,\n /** Define a workflow */\n workflow,\n /** Define an HTTP trigger */\n http,\n /** Define a cron trigger */\n cron,\n /** Define an interval trigger */\n interval,\n /** Define a signal trigger */\n signal,\n};\n\n// Re-export types\nexport type {\n FunctionDef,\n FunctionOptions,\n WorkflowDef,\n WorkflowOptions,\n WorkflowContext,\n WorkflowHandler,\n WorkflowInfo,\n RetryPolicy,\n HttpTriggerOptions,\n CronTriggerOptions,\n TemporalConfig,\n ClientConfig,\n WorkerConfig,\n StartWorkflowOptions,\n WorkflowHandle,\n TFNClient,\n TFNWorker,\n} from './types.js';\n\nexport default tfn;\n","/**\n * Temporal Functions - Worker Package\n *\n * Full worker implementation for executing functions and workflows.\n * Import from 'temporal-functions/worker'.\n */\n\nimport { Worker, NativeConnection } from '@temporalio/worker';\nimport type {\n WorkerConfig,\n FunctionDef,\n WorkflowDef,\n TFNWorker,\n} from '../types.js';\nimport { isFunction, isWorkflow } from '../index.js';\n\n// =============================================================================\n// Worker Implementation\n// =============================================================================\n\nclass TemporalFunctionsWorker implements TFNWorker {\n private config: WorkerConfig;\n private functions: Map<string, FunctionDef> = new Map();\n private workflows: Map<string, WorkflowDef> = new Map();\n private worker: Worker | null = null;\n private shutdownRequested = false;\n\n constructor(config: WorkerConfig) {\n this.config = {\n ...config,\n temporal: {\n namespace: 'default',\n ...config.temporal,\n },\n maxConcurrentActivities: config.maxConcurrentActivities ?? 100,\n maxConcurrentWorkflows: config.maxConcurrentWorkflows ?? 50,\n };\n }\n\n /**\n * Register a function or workflow\n */\n register(def: FunctionDef | WorkflowDef): void {\n if (isFunction(def)) {\n this.functions.set(def.name, def);\n } else if (isWorkflow(def)) {\n this.workflows.set(def.name, def);\n } else {\n throw new Error('Invalid definition: must be a function or workflow created with tfn.fn() or tfn.workflow()');\n }\n }\n\n /**\n * Register all exported functions and workflows from a module\n */\n registerModule(module: Record<string, unknown>): void {\n for (const [, value] of Object.entries(module)) {\n if (isFunction(value) || isWorkflow(value)) {\n this.register(value);\n }\n }\n }\n\n /**\n * Build activities object from registered functions\n */\n private buildActivities(): Record<string, (...args: unknown[]) => Promise<unknown>> {\n const activities: Record<string, (...args: unknown[]) => Promise<unknown>> = {};\n\n for (const [name, def] of this.functions) {\n activities[name] = async (input: unknown) => {\n return def.handler(input);\n };\n }\n\n return activities;\n }\n\n /**\n * Generate workflow wrapper code for Temporal\n *\n * This creates the workflow module that Temporal requires,\n * wrapping our user-defined workflow handlers with the proper\n * Temporal workflow context.\n */\n private generateWorkflowBundle(): string {\n const workflowNames = Array.from(this.workflows.keys());\n\n // Generate the workflow module code\n const code = `\n import { proxyActivities, sleep, workflowInfo, setHandler, defineSignal, defineQuery } from '@temporalio/workflow';\n\n // Proxy all activities\n const activities = proxyActivities({\n startToCloseTimeout: '1 minute',\n });\n\n // Create workflow context\n function createContext() {\n const signalHandlers = new Map();\n const queryHandlers = new Map();\n\n return {\n run: async (fn, input, options = {}) => {\n const activity = activities[fn.name];\n if (!activity) {\n throw new Error(\\`Function \\${fn.name} not registered\\`);\n }\n return activity(input);\n },\n sleep: async (duration) => {\n if (typeof duration === 'string') {\n const ms = parseDuration(duration);\n return sleep(ms);\n }\n return sleep(duration);\n },\n now: () => new Date(),\n startChild: async (workflow, input, options = {}) => {\n throw new Error('startChild not yet implemented');\n },\n continueAsNew: async (input) => {\n throw new Error('continueAsNew not yet implemented');\n },\n onSignal: (signalName, handler) => {\n signalHandlers.set(signalName, handler);\n const signal = defineSignal(signalName);\n setHandler(signal, handler);\n },\n onQuery: (queryName, handler) => {\n queryHandlers.set(queryName, handler);\n const query = defineQuery(queryName);\n setHandler(query, handler);\n },\n get info() {\n const info = workflowInfo();\n return {\n workflowId: info.workflowId,\n runId: info.runId,\n taskQueue: info.taskQueue,\n workflowType: info.workflowType,\n namespace: info.namespace,\n };\n },\n };\n }\n\n // Parse duration string to milliseconds\n function parseDuration(duration) {\n const match = duration.match(/^(\\\\d+(?:\\\\.\\\\d+)?)(ms|s|m|h|d)$/);\n if (!match) {\n throw new Error(\\`Invalid duration: \\${duration}\\`);\n }\n const value = parseFloat(match[1]);\n const unit = match[2];\n switch (unit) {\n case 'ms': return value;\n case 's': return value * 1000;\n case 'm': return value * 60 * 1000;\n case 'h': return value * 60 * 60 * 1000;\n case 'd': return value * 24 * 60 * 60 * 1000;\n default: throw new Error(\\`Unknown duration unit: \\${unit}\\`);\n }\n }\n\n // Export workflow functions\n ${workflowNames.map((name) => `\n export async function ${name}(input) {\n const ctx = createContext();\n const handler = __workflowHandlers__.get('${name}');\n if (!handler) {\n throw new Error('Workflow handler not found: ${name}');\n }\n return handler(ctx, input);\n }\n `).join('\\n')}\n `;\n\n return code;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.functions.size === 0 && this.workflows.size === 0) {\n throw new Error('No functions or workflows registered. Call register() before start().');\n }\n\n console.log(`Starting Temporal Functions worker...`);\n console.log(` Temporal: ${this.config.temporal.address}`);\n console.log(` Namespace: ${this.config.temporal.namespace}`);\n console.log(` Task Queue: ${this.config.taskQueue}`);\n console.log(` Functions: ${this.functions.size}`);\n console.log(` Workflows: ${this.workflows.size}`);\n\n // Connect to Temporal\n const connection = await NativeConnection.connect({\n address: this.config.temporal.address,\n tls: this.config.temporal.tls\n ? {\n clientCertPair: this.config.temporal.tls.clientCertPath\n ? {\n crt: await import('fs').then((fs) =>\n fs.promises.readFile(this.config.temporal.tls!.clientCertPath!)\n ),\n key: await import('fs').then((fs) =>\n fs.promises.readFile(this.config.temporal.tls!.clientKeyPath!)\n ),\n }\n : undefined,\n }\n : undefined,\n });\n\n // Build activities\n const activities = this.buildActivities();\n\n // For now, we'll use a simplified approach where workflows\n // are bundled separately. In a full implementation, we'd\n // generate the workflow bundle dynamically.\n //\n // TODO: Implement dynamic workflow bundling\n // const workflowBundle = this.generateWorkflowBundle();\n\n // Create worker\n this.worker = await Worker.create({\n connection,\n namespace: this.config.temporal.namespace,\n taskQueue: this.config.taskQueue,\n activities,\n // workflowsPath will need to be provided by the user\n // or we need to implement dynamic bundling\n maxConcurrentActivityTaskExecutions: this.config.maxConcurrentActivities,\n maxConcurrentWorkflowTaskExecutions: this.config.maxConcurrentWorkflows,\n });\n\n // Handle graceful shutdown\n const shutdown = async () => {\n if (this.shutdownRequested) return;\n this.shutdownRequested = true;\n console.log('\\nShutting down worker...');\n await this.shutdown();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n console.log('\\nWorker started. Press Ctrl+C to stop.\\n');\n\n // Run the worker (blocks until shutdown)\n await this.worker.run();\n }\n\n /**\n * Gracefully shutdown the worker\n */\n async shutdown(): Promise<void> {\n if (this.worker) {\n this.worker.shutdown();\n this.worker = null;\n }\n }\n}\n\n// =============================================================================\n// Factory Function\n// =============================================================================\n\n/**\n * Create a Temporal Functions worker\n *\n * @example\n * ```typescript\n * import { tfn } from 'temporal-functions/worker';\n * import { validateOrder, processOrder } from './functions';\n *\n * const worker = tfn.worker({\n * temporal: {\n * address: 'localhost:7233',\n * namespace: 'default',\n * },\n * taskQueue: 'my-queue',\n * });\n *\n * worker.register(validateOrder);\n * worker.register(processOrder);\n *\n * await worker.start();\n * ```\n */\nfunction createWorker(config: WorkerConfig): TFNWorker {\n return new TemporalFunctionsWorker(config);\n}\n\n// =============================================================================\n// Export\n// =============================================================================\n\nexport const tfn = {\n worker: createWorker,\n};\n\nexport { createWorker };\nexport type { WorkerConfig, TFNWorker };\nexport default tfn;\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astami/temporal-functions",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight TypeScript framework providing lambda-like DX on top of Temporal's durable execution engine",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"types": "./dist/client/index.d.ts",
|
|
16
|
+
"import": "./dist/client/index.mjs",
|
|
17
|
+
"require": "./dist/client/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./worker": {
|
|
20
|
+
"types": "./dist/worker/index.d.ts",
|
|
21
|
+
"import": "./dist/worker/index.mjs",
|
|
22
|
+
"require": "./dist/worker/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./testing": {
|
|
25
|
+
"types": "./dist/testing/index.d.ts",
|
|
26
|
+
"import": "./dist/testing/index.mjs",
|
|
27
|
+
"require": "./dist/testing/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"dev": "tsup --watch",
|
|
36
|
+
"test": "vitest",
|
|
37
|
+
"test:run": "vitest run",
|
|
38
|
+
"lint": "eslint src/",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"temporal",
|
|
45
|
+
"workflow",
|
|
46
|
+
"durable",
|
|
47
|
+
"functions",
|
|
48
|
+
"serverless",
|
|
49
|
+
"lambda",
|
|
50
|
+
"typescript"
|
|
51
|
+
],
|
|
52
|
+
"author": "",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/ninformations/temporal-functions.git"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"@temporalio/client": "^1.9.0",
|
|
63
|
+
"@temporalio/worker": "^1.9.0",
|
|
64
|
+
"@temporalio/workflow": "^1.9.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@temporalio/worker": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@temporalio/workflow": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@temporalio/client": "^1.11.7",
|
|
76
|
+
"@temporalio/worker": "^1.11.7",
|
|
77
|
+
"@temporalio/workflow": "^1.11.7",
|
|
78
|
+
"@temporalio/activity": "^1.11.7",
|
|
79
|
+
"@types/node": "^20.11.0",
|
|
80
|
+
"tsup": "^8.0.1",
|
|
81
|
+
"typescript": "^5.3.3",
|
|
82
|
+
"vitest": "^2.0.0",
|
|
83
|
+
"eslint": "^8.56.0",
|
|
84
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
85
|
+
"@typescript-eslint/parser": "^6.19.0"
|
|
86
|
+
}
|
|
87
|
+
}
|