@astami/temporal-functions 0.2.2 → 0.3.1
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/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/observability/index.d.mts +66 -0
- package/dist/observability/index.d.ts +66 -0
- package/dist/observability/index.js +93 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/index.mjs +76 -0
- package/dist/observability/index.mjs.map +1 -0
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/{types-Curq8YlS.d.mts → types-DY4y7IE5.d.mts} +31 -1
- package/dist/{types-Curq8YlS.d.ts → types-DY4y7IE5.d.ts} +31 -1
- package/dist/worker/index.d.mts +20 -5
- package/dist/worker/index.d.ts +20 -5
- package/dist/worker/index.js +39 -10
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/index.mjs +39 -10
- package/dist/worker/index.mjs.map +1 -1
- package/package.json +17 -2
package/dist/client/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { g as ClientConfig, j as TFNClient } from '../types-
|
|
2
|
-
export { S as StartWorkflowOptions, i as WorkflowHandle } from '../types-
|
|
1
|
+
import { g as ClientConfig, j as TFNClient } from '../types-DY4y7IE5.mjs';
|
|
2
|
+
export { S as StartWorkflowOptions, i as WorkflowHandle } from '../types-DY4y7IE5.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Temporal Functions - Client Package
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { g as ClientConfig, j as TFNClient } from '../types-
|
|
2
|
-
export { S as StartWorkflowOptions, i as WorkflowHandle } from '../types-
|
|
1
|
+
import { g as ClientConfig, j as TFNClient } from '../types-DY4y7IE5.js';
|
|
2
|
+
export { S as StartWorkflowOptions, i as WorkflowHandle } from '../types-DY4y7IE5.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Temporal Functions - Client Package
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FunctionOptions, a as FunctionDef, W as WorkflowHandler, b as WorkflowOptions, c as WorkflowDef, H as HttpTriggerOptions, C as CronTriggerOptions, R as Registry } from './types-
|
|
2
|
-
export { g as ClientConfig, f as RetryPolicy, S as StartWorkflowOptions, j as TFNClient, k as TFNWorker, T as TemporalConfig, h as WorkerConfig, d as WorkflowContext, i as WorkflowHandle, e as WorkflowInfo } from './types-
|
|
1
|
+
import { F as FunctionOptions, a as FunctionDef, W as WorkflowHandler, b as WorkflowOptions, c as WorkflowDef, H as HttpTriggerOptions, C as CronTriggerOptions, R as Registry } from './types-DY4y7IE5.mjs';
|
|
2
|
+
export { g as ClientConfig, f as RetryPolicy, S as StartWorkflowOptions, j as TFNClient, k as TFNWorker, T as TemporalConfig, h as WorkerConfig, d as WorkflowContext, i as WorkflowHandle, e as WorkflowInfo } from './types-DY4y7IE5.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Temporal Functions - Main Entry Point
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FunctionOptions, a as FunctionDef, W as WorkflowHandler, b as WorkflowOptions, c as WorkflowDef, H as HttpTriggerOptions, C as CronTriggerOptions, R as Registry } from './types-
|
|
2
|
-
export { g as ClientConfig, f as RetryPolicy, S as StartWorkflowOptions, j as TFNClient, k as TFNWorker, T as TemporalConfig, h as WorkerConfig, d as WorkflowContext, i as WorkflowHandle, e as WorkflowInfo } from './types-
|
|
1
|
+
import { F as FunctionOptions, a as FunctionDef, W as WorkflowHandler, b as WorkflowOptions, c as WorkflowDef, H as HttpTriggerOptions, C as CronTriggerOptions, R as Registry } from './types-DY4y7IE5.js';
|
|
2
|
+
export { g as ClientConfig, f as RetryPolicy, S as StartWorkflowOptions, j as TFNClient, k as TFNWorker, T as TemporalConfig, h as WorkerConfig, d as WorkflowContext, i as WorkflowHandle, e as WorkflowInfo } from './types-DY4y7IE5.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Temporal Functions - Main Entry Point
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { l as WorkerInterceptors } from '../types-DY4y7IE5.mjs';
|
|
2
|
+
export { OpenTelemetryActivityInboundInterceptor } from '@temporalio/interceptors-opentelemetry';
|
|
3
|
+
export { OpenTelemetryInboundInterceptor, OpenTelemetryInternalsInterceptor, OpenTelemetryOutboundInterceptor } from '@temporalio/interceptors-opentelemetry/lib/workflow';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Temporal Functions - Observability Package
|
|
7
|
+
*
|
|
8
|
+
* OpenTelemetry integration for Temporal workers with trace context propagation.
|
|
9
|
+
* Import from '@astami/temporal-functions/observability'.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface WorkerInterceptorsConfig {
|
|
13
|
+
/** Service/processor name for logging */
|
|
14
|
+
serviceName: string;
|
|
15
|
+
/** Optional custom logger (defaults to console) */
|
|
16
|
+
logger?: {
|
|
17
|
+
info: (obj: Record<string, unknown>, msg: string) => void;
|
|
18
|
+
error: (obj: Record<string, unknown>, msg: string) => void;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates worker interceptors configuration with OpenTelemetry trace propagation
|
|
23
|
+
*
|
|
24
|
+
* This returns a WorkerInterceptors object that can be passed directly to
|
|
25
|
+
* the worker configuration. It includes:
|
|
26
|
+
* - Activity inbound interceptors for tracing and logging
|
|
27
|
+
*
|
|
28
|
+
* **IMPORTANT**: For end-to-end trace propagation from client -> workflow -> activities,
|
|
29
|
+
* your workflow module must also export an `interceptors` factory function:
|
|
30
|
+
*
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // In your workflows/index.ts
|
|
33
|
+
* import type { WorkflowInterceptorsFactory } from '@temporalio/workflow';
|
|
34
|
+
* import {
|
|
35
|
+
* OpenTelemetryInboundInterceptor,
|
|
36
|
+
* OpenTelemetryOutboundInterceptor,
|
|
37
|
+
* OpenTelemetryInternalsInterceptor,
|
|
38
|
+
* } from '@temporalio/interceptors-opentelemetry/lib/workflow';
|
|
39
|
+
*
|
|
40
|
+
* export const interceptors: WorkflowInterceptorsFactory = () => ({
|
|
41
|
+
* inbound: [new OpenTelemetryInboundInterceptor()],
|
|
42
|
+
* outbound: [new OpenTelemetryOutboundInterceptor()],
|
|
43
|
+
* internals: [new OpenTelemetryInternalsInterceptor()],
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { createWorker } from '@astami/temporal-functions/worker';
|
|
50
|
+
* import { createWorkerInterceptors } from '@astami/temporal-functions/observability';
|
|
51
|
+
* import * as activities from './activities';
|
|
52
|
+
*
|
|
53
|
+
* const worker = createWorker({
|
|
54
|
+
* temporal: { address: 'localhost:7233', namespace: 'loop' },
|
|
55
|
+
* taskQueue: 'stripe-payments',
|
|
56
|
+
* workflowsPath: './dist/workflows/index.js',
|
|
57
|
+
* activities,
|
|
58
|
+
* interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* await worker.start();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function createWorkerInterceptors(config: WorkerInterceptorsConfig): WorkerInterceptors;
|
|
65
|
+
|
|
66
|
+
export { type WorkerInterceptorsConfig, createWorkerInterceptors };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { l as WorkerInterceptors } from '../types-DY4y7IE5.js';
|
|
2
|
+
export { OpenTelemetryActivityInboundInterceptor } from '@temporalio/interceptors-opentelemetry';
|
|
3
|
+
export { OpenTelemetryInboundInterceptor, OpenTelemetryInternalsInterceptor, OpenTelemetryOutboundInterceptor } from '@temporalio/interceptors-opentelemetry/lib/workflow';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Temporal Functions - Observability Package
|
|
7
|
+
*
|
|
8
|
+
* OpenTelemetry integration for Temporal workers with trace context propagation.
|
|
9
|
+
* Import from '@astami/temporal-functions/observability'.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface WorkerInterceptorsConfig {
|
|
13
|
+
/** Service/processor name for logging */
|
|
14
|
+
serviceName: string;
|
|
15
|
+
/** Optional custom logger (defaults to console) */
|
|
16
|
+
logger?: {
|
|
17
|
+
info: (obj: Record<string, unknown>, msg: string) => void;
|
|
18
|
+
error: (obj: Record<string, unknown>, msg: string) => void;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates worker interceptors configuration with OpenTelemetry trace propagation
|
|
23
|
+
*
|
|
24
|
+
* This returns a WorkerInterceptors object that can be passed directly to
|
|
25
|
+
* the worker configuration. It includes:
|
|
26
|
+
* - Activity inbound interceptors for tracing and logging
|
|
27
|
+
*
|
|
28
|
+
* **IMPORTANT**: For end-to-end trace propagation from client -> workflow -> activities,
|
|
29
|
+
* your workflow module must also export an `interceptors` factory function:
|
|
30
|
+
*
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // In your workflows/index.ts
|
|
33
|
+
* import type { WorkflowInterceptorsFactory } from '@temporalio/workflow';
|
|
34
|
+
* import {
|
|
35
|
+
* OpenTelemetryInboundInterceptor,
|
|
36
|
+
* OpenTelemetryOutboundInterceptor,
|
|
37
|
+
* OpenTelemetryInternalsInterceptor,
|
|
38
|
+
* } from '@temporalio/interceptors-opentelemetry/lib/workflow';
|
|
39
|
+
*
|
|
40
|
+
* export const interceptors: WorkflowInterceptorsFactory = () => ({
|
|
41
|
+
* inbound: [new OpenTelemetryInboundInterceptor()],
|
|
42
|
+
* outbound: [new OpenTelemetryOutboundInterceptor()],
|
|
43
|
+
* internals: [new OpenTelemetryInternalsInterceptor()],
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { createWorker } from '@astami/temporal-functions/worker';
|
|
50
|
+
* import { createWorkerInterceptors } from '@astami/temporal-functions/observability';
|
|
51
|
+
* import * as activities from './activities';
|
|
52
|
+
*
|
|
53
|
+
* const worker = createWorker({
|
|
54
|
+
* temporal: { address: 'localhost:7233', namespace: 'loop' },
|
|
55
|
+
* taskQueue: 'stripe-payments',
|
|
56
|
+
* workflowsPath: './dist/workflows/index.js',
|
|
57
|
+
* activities,
|
|
58
|
+
* interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* await worker.start();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function createWorkerInterceptors(config: WorkerInterceptorsConfig): WorkerInterceptors;
|
|
65
|
+
|
|
66
|
+
export { type WorkerInterceptorsConfig, createWorkerInterceptors };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var api = require('@opentelemetry/api');
|
|
4
|
+
var interceptorsOpentelemetry = require('@temporalio/interceptors-opentelemetry');
|
|
5
|
+
var workflow = require('@temporalio/interceptors-opentelemetry/lib/workflow');
|
|
6
|
+
|
|
7
|
+
// src/observability/index.ts
|
|
8
|
+
var TracingActivityInterceptor = class {
|
|
9
|
+
otelInterceptor;
|
|
10
|
+
config;
|
|
11
|
+
ctx;
|
|
12
|
+
constructor(ctx, config) {
|
|
13
|
+
this.ctx = ctx;
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.otelInterceptor = new interceptorsOpentelemetry.OpenTelemetryActivityInboundInterceptor(ctx);
|
|
16
|
+
}
|
|
17
|
+
async execute(input, next) {
|
|
18
|
+
const activityType = this.ctx.info.activityType;
|
|
19
|
+
const workflowId = this.ctx.info.workflowExecution.workflowId;
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
const config = this.config;
|
|
22
|
+
const logger = config.logger ?? console;
|
|
23
|
+
const loggingNext = async (nextInput) => {
|
|
24
|
+
const currentSpan = api.trace.getActiveSpan();
|
|
25
|
+
const spanContext = currentSpan?.spanContext();
|
|
26
|
+
logger.info(
|
|
27
|
+
{
|
|
28
|
+
activity: activityType,
|
|
29
|
+
workflowId,
|
|
30
|
+
processor: config.serviceName,
|
|
31
|
+
trace_id: spanContext?.traceId,
|
|
32
|
+
span_id: spanContext?.spanId
|
|
33
|
+
},
|
|
34
|
+
"Activity started"
|
|
35
|
+
);
|
|
36
|
+
try {
|
|
37
|
+
const result = await next(nextInput);
|
|
38
|
+
logger.info(
|
|
39
|
+
{
|
|
40
|
+
activity: activityType,
|
|
41
|
+
workflowId,
|
|
42
|
+
duration: Date.now() - startTime,
|
|
43
|
+
trace_id: spanContext?.traceId,
|
|
44
|
+
span_id: spanContext?.spanId
|
|
45
|
+
},
|
|
46
|
+
"Activity completed"
|
|
47
|
+
);
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error(
|
|
51
|
+
{
|
|
52
|
+
activity: activityType,
|
|
53
|
+
workflowId,
|
|
54
|
+
error,
|
|
55
|
+
duration: Date.now() - startTime,
|
|
56
|
+
trace_id: spanContext?.traceId,
|
|
57
|
+
span_id: spanContext?.spanId
|
|
58
|
+
},
|
|
59
|
+
"Activity failed"
|
|
60
|
+
);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return this.otelInterceptor.execute(input, loggingNext);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function createWorkerInterceptors(config) {
|
|
68
|
+
return {
|
|
69
|
+
activityInbound: [
|
|
70
|
+
(ctx) => new TracingActivityInterceptor(ctx, config)
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Object.defineProperty(exports, "OpenTelemetryActivityInboundInterceptor", {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
get: function () { return interceptorsOpentelemetry.OpenTelemetryActivityInboundInterceptor; }
|
|
78
|
+
});
|
|
79
|
+
Object.defineProperty(exports, "OpenTelemetryInboundInterceptor", {
|
|
80
|
+
enumerable: true,
|
|
81
|
+
get: function () { return workflow.OpenTelemetryInboundInterceptor; }
|
|
82
|
+
});
|
|
83
|
+
Object.defineProperty(exports, "OpenTelemetryInternalsInterceptor", {
|
|
84
|
+
enumerable: true,
|
|
85
|
+
get: function () { return workflow.OpenTelemetryInternalsInterceptor; }
|
|
86
|
+
});
|
|
87
|
+
Object.defineProperty(exports, "OpenTelemetryOutboundInterceptor", {
|
|
88
|
+
enumerable: true,
|
|
89
|
+
get: function () { return workflow.OpenTelemetryOutboundInterceptor; }
|
|
90
|
+
});
|
|
91
|
+
exports.createWorkerInterceptors = createWorkerInterceptors;
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/observability/index.ts"],"names":["OpenTelemetryActivityInboundInterceptor","trace"],"mappings":";;;;;;;AA2CA,IAAM,6BAAN,MAA4E;AAAA,EACzD,eAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAA;AAAA,EAEjB,WAAA,CAAY,KAAsB,MAAA,EAAkC;AAClE,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAEd,IAAA,IAAA,CAAK,eAAA,GAAkB,IAAIA,iEAAA,CAAwC,GAAG,CAAA;AAAA,EACxE;AAAA,EAEA,MAAM,OAAA,CACJ,KAAA,EACA,IAAA,EACkB;AAClB,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,YAAA;AACnC,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,iBAAA,CAAkB,UAAA;AACnD,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AACpB,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,IAAU,OAAA;AAGhC,IAAA,MAAM,WAAA,GAAgE,OACpE,SAAA,KACG;AAEH,MAAA,MAAM,WAAA,GAAcC,UAAM,aAAA,EAAc;AACxC,MAAA,MAAM,WAAA,GAAc,aAAa,WAAA,EAAY;AAE7C,MAAA,MAAA,CAAO,IAAA;AAAA,QACL;AAAA,UACE,QAAA,EAAU,YAAA;AAAA,UACV,UAAA;AAAA,UACA,WAAW,MAAA,CAAO,WAAA;AAAA,UAClB,UAAU,WAAA,EAAa,OAAA;AAAA,UACvB,SAAS,WAAA,EAAa;AAAA,SACxB;AAAA,QACA;AAAA,OACF;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,SAAS,CAAA;AAEnC,QAAA,MAAA,CAAO,IAAA;AAAA,UACL;AAAA,YACE,QAAA,EAAU,YAAA;AAAA,YACV,UAAA;AAAA,YACA,QAAA,EAAU,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,YACvB,UAAU,WAAA,EAAa,OAAA;AAAA,YACvB,SAAS,WAAA,EAAa;AAAA,WACxB;AAAA,UACA;AAAA,SACF;AAEA,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,MAAA,CAAO,KAAA;AAAA,UACL;AAAA,YACE,QAAA,EAAU,YAAA;AAAA,YACV,UAAA;AAAA,YACA,KAAA;AAAA,YACA,QAAA,EAAU,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,YACvB,UAAU,WAAA,EAAa,OAAA;AAAA,YACvB,SAAS,WAAA,EAAa;AAAA,WACxB;AAAA,UACA;AAAA,SACF;AAEA,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF,CAAA;AAIA,IAAA,OAAO,IAAA,CAAK,eAAA,CAAgB,OAAA,CAAQ,KAAA,EAAO,WAAW,CAAA;AAAA,EACxD;AACF,CAAA;AAiDO,SAAS,yBAAyB,MAAA,EAAsD;AAC7F,EAAA,OAAO;AAAA,IACL,eAAA,EAAiB;AAAA,MACf,CAAC,GAAA,KAAyB,IAAI,0BAAA,CAA2B,KAAK,MAAM;AAAA;AACtE,GACF;AACF","file":"index.js","sourcesContent":["/**\n * Temporal Functions - Observability Package\n *\n * OpenTelemetry integration for Temporal workers with trace context propagation.\n * Import from '@astami/temporal-functions/observability'.\n */\n\nimport { trace } from '@opentelemetry/api';\nimport {\n OpenTelemetryActivityInboundInterceptor,\n} from '@temporalio/interceptors-opentelemetry';\nimport type { Context as ActivityContext } from '@temporalio/activity';\nimport type {\n ActivityInboundCallsInterceptor,\n ActivityExecuteInput,\n Next,\n} from '@temporalio/worker';\nimport type { WorkerInterceptors } from '../types.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface WorkerInterceptorsConfig {\n /** Service/processor name for logging */\n serviceName: string;\n /** Optional custom logger (defaults to console) */\n logger?: {\n info: (obj: Record<string, unknown>, msg: string) => void;\n error: (obj: Record<string, unknown>, msg: string) => void;\n };\n}\n\n// =============================================================================\n// Tracing Activity Interceptor\n// =============================================================================\n\n/**\n * Creates an activity interceptor that combines OpenTelemetry tracing with structured logging\n *\n * Uses Temporal's OpenTelemetryActivityInboundInterceptor for trace propagation,\n * and wraps it with additional structured logging that includes trace IDs.\n */\nclass TracingActivityInterceptor implements ActivityInboundCallsInterceptor {\n private readonly otelInterceptor: OpenTelemetryActivityInboundInterceptor;\n private readonly config: WorkerInterceptorsConfig;\n private readonly ctx: ActivityContext;\n\n constructor(ctx: ActivityContext, config: WorkerInterceptorsConfig) {\n this.ctx = ctx;\n this.config = config;\n // Use Temporal's built-in OTel interceptor for proper trace context propagation\n this.otelInterceptor = new OpenTelemetryActivityInboundInterceptor(ctx);\n }\n\n async execute(\n input: ActivityExecuteInput,\n next: Next<ActivityInboundCallsInterceptor, 'execute'>\n ): Promise<unknown> {\n const activityType = this.ctx.info.activityType;\n const workflowId = this.ctx.info.workflowExecution.workflowId;\n const startTime = Date.now();\n const config = this.config;\n const logger = config.logger ?? console;\n\n // Wrap the next function to add logging INSIDE the OTel span context\n const loggingNext: Next<ActivityInboundCallsInterceptor, 'execute'> = async (\n nextInput: ActivityExecuteInput\n ) => {\n // Now we're inside the OTel interceptor's span context\n const currentSpan = trace.getActiveSpan();\n const spanContext = currentSpan?.spanContext();\n\n logger.info(\n {\n activity: activityType,\n workflowId,\n processor: config.serviceName,\n trace_id: spanContext?.traceId,\n span_id: spanContext?.spanId,\n },\n 'Activity started'\n );\n\n try {\n const result = await next(nextInput);\n\n logger.info(\n {\n activity: activityType,\n workflowId,\n duration: Date.now() - startTime,\n trace_id: spanContext?.traceId,\n span_id: spanContext?.spanId,\n },\n 'Activity completed'\n );\n\n return result;\n } catch (error) {\n logger.error(\n {\n activity: activityType,\n workflowId,\n error,\n duration: Date.now() - startTime,\n trace_id: spanContext?.traceId,\n span_id: spanContext?.spanId,\n },\n 'Activity failed'\n );\n\n throw error;\n }\n };\n\n // Delegate to Temporal's OTel interceptor which sets up the span context,\n // then our loggingNext will execute with the proper trace context\n return this.otelInterceptor.execute(input, loggingNext);\n }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\n/**\n * Creates worker interceptors configuration with OpenTelemetry trace propagation\n *\n * This returns a WorkerInterceptors object that can be passed directly to\n * the worker configuration. It includes:\n * - Activity inbound interceptors for tracing and logging\n *\n * **IMPORTANT**: For end-to-end trace propagation from client -> workflow -> activities,\n * your workflow module must also export an `interceptors` factory function:\n *\n * ```typescript\n * // In your workflows/index.ts\n * import type { WorkflowInterceptorsFactory } from '@temporalio/workflow';\n * import {\n * OpenTelemetryInboundInterceptor,\n * OpenTelemetryOutboundInterceptor,\n * OpenTelemetryInternalsInterceptor,\n * } from '@temporalio/interceptors-opentelemetry/lib/workflow';\n *\n * export const interceptors: WorkflowInterceptorsFactory = () => ({\n * inbound: [new OpenTelemetryInboundInterceptor()],\n * outbound: [new OpenTelemetryOutboundInterceptor()],\n * internals: [new OpenTelemetryInternalsInterceptor()],\n * });\n * ```\n *\n * @example\n * ```typescript\n * import { createWorker } from '@astami/temporal-functions/worker';\n * import { createWorkerInterceptors } from '@astami/temporal-functions/observability';\n * import * as activities from './activities';\n *\n * const worker = createWorker({\n * temporal: { address: 'localhost:7233', namespace: 'loop' },\n * taskQueue: 'stripe-payments',\n * workflowsPath: './dist/workflows/index.js',\n * activities,\n * interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),\n * });\n *\n * await worker.start();\n * ```\n */\nexport function createWorkerInterceptors(config: WorkerInterceptorsConfig): WorkerInterceptors {\n return {\n activityInbound: [\n (ctx: ActivityContext) => new TracingActivityInterceptor(ctx, config),\n ],\n };\n}\n\n// =============================================================================\n// Re-exports from Temporal OTel package\n// =============================================================================\n\nexport {\n OpenTelemetryActivityInboundInterceptor,\n} from '@temporalio/interceptors-opentelemetry';\n\n// Re-export workflow interceptors for convenience\nexport {\n OpenTelemetryInboundInterceptor,\n OpenTelemetryOutboundInterceptor,\n OpenTelemetryInternalsInterceptor,\n} from '@temporalio/interceptors-opentelemetry/lib/workflow';\n"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { trace } from '@opentelemetry/api';
|
|
2
|
+
import { OpenTelemetryActivityInboundInterceptor } from '@temporalio/interceptors-opentelemetry';
|
|
3
|
+
export { OpenTelemetryActivityInboundInterceptor } from '@temporalio/interceptors-opentelemetry';
|
|
4
|
+
export { OpenTelemetryInboundInterceptor, OpenTelemetryInternalsInterceptor, OpenTelemetryOutboundInterceptor } from '@temporalio/interceptors-opentelemetry/lib/workflow';
|
|
5
|
+
|
|
6
|
+
// src/observability/index.ts
|
|
7
|
+
var TracingActivityInterceptor = class {
|
|
8
|
+
otelInterceptor;
|
|
9
|
+
config;
|
|
10
|
+
ctx;
|
|
11
|
+
constructor(ctx, config) {
|
|
12
|
+
this.ctx = ctx;
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.otelInterceptor = new OpenTelemetryActivityInboundInterceptor(ctx);
|
|
15
|
+
}
|
|
16
|
+
async execute(input, next) {
|
|
17
|
+
const activityType = this.ctx.info.activityType;
|
|
18
|
+
const workflowId = this.ctx.info.workflowExecution.workflowId;
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
const config = this.config;
|
|
21
|
+
const logger = config.logger ?? console;
|
|
22
|
+
const loggingNext = async (nextInput) => {
|
|
23
|
+
const currentSpan = trace.getActiveSpan();
|
|
24
|
+
const spanContext = currentSpan?.spanContext();
|
|
25
|
+
logger.info(
|
|
26
|
+
{
|
|
27
|
+
activity: activityType,
|
|
28
|
+
workflowId,
|
|
29
|
+
processor: config.serviceName,
|
|
30
|
+
trace_id: spanContext?.traceId,
|
|
31
|
+
span_id: spanContext?.spanId
|
|
32
|
+
},
|
|
33
|
+
"Activity started"
|
|
34
|
+
);
|
|
35
|
+
try {
|
|
36
|
+
const result = await next(nextInput);
|
|
37
|
+
logger.info(
|
|
38
|
+
{
|
|
39
|
+
activity: activityType,
|
|
40
|
+
workflowId,
|
|
41
|
+
duration: Date.now() - startTime,
|
|
42
|
+
trace_id: spanContext?.traceId,
|
|
43
|
+
span_id: spanContext?.spanId
|
|
44
|
+
},
|
|
45
|
+
"Activity completed"
|
|
46
|
+
);
|
|
47
|
+
return result;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logger.error(
|
|
50
|
+
{
|
|
51
|
+
activity: activityType,
|
|
52
|
+
workflowId,
|
|
53
|
+
error,
|
|
54
|
+
duration: Date.now() - startTime,
|
|
55
|
+
trace_id: spanContext?.traceId,
|
|
56
|
+
span_id: spanContext?.spanId
|
|
57
|
+
},
|
|
58
|
+
"Activity failed"
|
|
59
|
+
);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return this.otelInterceptor.execute(input, loggingNext);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function createWorkerInterceptors(config) {
|
|
67
|
+
return {
|
|
68
|
+
activityInbound: [
|
|
69
|
+
(ctx) => new TracingActivityInterceptor(ctx, config)
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { createWorkerInterceptors };
|
|
75
|
+
//# sourceMappingURL=index.mjs.map
|
|
76
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/observability/index.ts"],"names":[],"mappings":";;;;;;AA2CA,IAAM,6BAAN,MAA4E;AAAA,EACzD,eAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAA;AAAA,EAEjB,WAAA,CAAY,KAAsB,MAAA,EAAkC;AAClE,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAEd,IAAA,IAAA,CAAK,eAAA,GAAkB,IAAI,uCAAA,CAAwC,GAAG,CAAA;AAAA,EACxE;AAAA,EAEA,MAAM,OAAA,CACJ,KAAA,EACA,IAAA,EACkB;AAClB,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,YAAA;AACnC,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,iBAAA,CAAkB,UAAA;AACnD,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AACpB,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,IAAU,OAAA;AAGhC,IAAA,MAAM,WAAA,GAAgE,OACpE,SAAA,KACG;AAEH,MAAA,MAAM,WAAA,GAAc,MAAM,aAAA,EAAc;AACxC,MAAA,MAAM,WAAA,GAAc,aAAa,WAAA,EAAY;AAE7C,MAAA,MAAA,CAAO,IAAA;AAAA,QACL;AAAA,UACE,QAAA,EAAU,YAAA;AAAA,UACV,UAAA;AAAA,UACA,WAAW,MAAA,CAAO,WAAA;AAAA,UAClB,UAAU,WAAA,EAAa,OAAA;AAAA,UACvB,SAAS,WAAA,EAAa;AAAA,SACxB;AAAA,QACA;AAAA,OACF;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,SAAS,CAAA;AAEnC,QAAA,MAAA,CAAO,IAAA;AAAA,UACL;AAAA,YACE,QAAA,EAAU,YAAA;AAAA,YACV,UAAA;AAAA,YACA,QAAA,EAAU,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,YACvB,UAAU,WAAA,EAAa,OAAA;AAAA,YACvB,SAAS,WAAA,EAAa;AAAA,WACxB;AAAA,UACA;AAAA,SACF;AAEA,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,MAAA,CAAO,KAAA;AAAA,UACL;AAAA,YACE,QAAA,EAAU,YAAA;AAAA,YACV,UAAA;AAAA,YACA,KAAA;AAAA,YACA,QAAA,EAAU,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,YACvB,UAAU,WAAA,EAAa,OAAA;AAAA,YACvB,SAAS,WAAA,EAAa;AAAA,WACxB;AAAA,UACA;AAAA,SACF;AAEA,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF,CAAA;AAIA,IAAA,OAAO,IAAA,CAAK,eAAA,CAAgB,OAAA,CAAQ,KAAA,EAAO,WAAW,CAAA;AAAA,EACxD;AACF,CAAA;AAiDO,SAAS,yBAAyB,MAAA,EAAsD;AAC7F,EAAA,OAAO;AAAA,IACL,eAAA,EAAiB;AAAA,MACf,CAAC,GAAA,KAAyB,IAAI,0BAAA,CAA2B,KAAK,MAAM;AAAA;AACtE,GACF;AACF","file":"index.mjs","sourcesContent":["/**\n * Temporal Functions - Observability Package\n *\n * OpenTelemetry integration for Temporal workers with trace context propagation.\n * Import from '@astami/temporal-functions/observability'.\n */\n\nimport { trace } from '@opentelemetry/api';\nimport {\n OpenTelemetryActivityInboundInterceptor,\n} from '@temporalio/interceptors-opentelemetry';\nimport type { Context as ActivityContext } from '@temporalio/activity';\nimport type {\n ActivityInboundCallsInterceptor,\n ActivityExecuteInput,\n Next,\n} from '@temporalio/worker';\nimport type { WorkerInterceptors } from '../types.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface WorkerInterceptorsConfig {\n /** Service/processor name for logging */\n serviceName: string;\n /** Optional custom logger (defaults to console) */\n logger?: {\n info: (obj: Record<string, unknown>, msg: string) => void;\n error: (obj: Record<string, unknown>, msg: string) => void;\n };\n}\n\n// =============================================================================\n// Tracing Activity Interceptor\n// =============================================================================\n\n/**\n * Creates an activity interceptor that combines OpenTelemetry tracing with structured logging\n *\n * Uses Temporal's OpenTelemetryActivityInboundInterceptor for trace propagation,\n * and wraps it with additional structured logging that includes trace IDs.\n */\nclass TracingActivityInterceptor implements ActivityInboundCallsInterceptor {\n private readonly otelInterceptor: OpenTelemetryActivityInboundInterceptor;\n private readonly config: WorkerInterceptorsConfig;\n private readonly ctx: ActivityContext;\n\n constructor(ctx: ActivityContext, config: WorkerInterceptorsConfig) {\n this.ctx = ctx;\n this.config = config;\n // Use Temporal's built-in OTel interceptor for proper trace context propagation\n this.otelInterceptor = new OpenTelemetryActivityInboundInterceptor(ctx);\n }\n\n async execute(\n input: ActivityExecuteInput,\n next: Next<ActivityInboundCallsInterceptor, 'execute'>\n ): Promise<unknown> {\n const activityType = this.ctx.info.activityType;\n const workflowId = this.ctx.info.workflowExecution.workflowId;\n const startTime = Date.now();\n const config = this.config;\n const logger = config.logger ?? console;\n\n // Wrap the next function to add logging INSIDE the OTel span context\n const loggingNext: Next<ActivityInboundCallsInterceptor, 'execute'> = async (\n nextInput: ActivityExecuteInput\n ) => {\n // Now we're inside the OTel interceptor's span context\n const currentSpan = trace.getActiveSpan();\n const spanContext = currentSpan?.spanContext();\n\n logger.info(\n {\n activity: activityType,\n workflowId,\n processor: config.serviceName,\n trace_id: spanContext?.traceId,\n span_id: spanContext?.spanId,\n },\n 'Activity started'\n );\n\n try {\n const result = await next(nextInput);\n\n logger.info(\n {\n activity: activityType,\n workflowId,\n duration: Date.now() - startTime,\n trace_id: spanContext?.traceId,\n span_id: spanContext?.spanId,\n },\n 'Activity completed'\n );\n\n return result;\n } catch (error) {\n logger.error(\n {\n activity: activityType,\n workflowId,\n error,\n duration: Date.now() - startTime,\n trace_id: spanContext?.traceId,\n span_id: spanContext?.spanId,\n },\n 'Activity failed'\n );\n\n throw error;\n }\n };\n\n // Delegate to Temporal's OTel interceptor which sets up the span context,\n // then our loggingNext will execute with the proper trace context\n return this.otelInterceptor.execute(input, loggingNext);\n }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\n/**\n * Creates worker interceptors configuration with OpenTelemetry trace propagation\n *\n * This returns a WorkerInterceptors object that can be passed directly to\n * the worker configuration. It includes:\n * - Activity inbound interceptors for tracing and logging\n *\n * **IMPORTANT**: For end-to-end trace propagation from client -> workflow -> activities,\n * your workflow module must also export an `interceptors` factory function:\n *\n * ```typescript\n * // In your workflows/index.ts\n * import type { WorkflowInterceptorsFactory } from '@temporalio/workflow';\n * import {\n * OpenTelemetryInboundInterceptor,\n * OpenTelemetryOutboundInterceptor,\n * OpenTelemetryInternalsInterceptor,\n * } from '@temporalio/interceptors-opentelemetry/lib/workflow';\n *\n * export const interceptors: WorkflowInterceptorsFactory = () => ({\n * inbound: [new OpenTelemetryInboundInterceptor()],\n * outbound: [new OpenTelemetryOutboundInterceptor()],\n * internals: [new OpenTelemetryInternalsInterceptor()],\n * });\n * ```\n *\n * @example\n * ```typescript\n * import { createWorker } from '@astami/temporal-functions/worker';\n * import { createWorkerInterceptors } from '@astami/temporal-functions/observability';\n * import * as activities from './activities';\n *\n * const worker = createWorker({\n * temporal: { address: 'localhost:7233', namespace: 'loop' },\n * taskQueue: 'stripe-payments',\n * workflowsPath: './dist/workflows/index.js',\n * activities,\n * interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),\n * });\n *\n * await worker.start();\n * ```\n */\nexport function createWorkerInterceptors(config: WorkerInterceptorsConfig): WorkerInterceptors {\n return {\n activityInbound: [\n (ctx: ActivityContext) => new TracingActivityInterceptor(ctx, config),\n ],\n };\n}\n\n// =============================================================================\n// Re-exports from Temporal OTel package\n// =============================================================================\n\nexport {\n OpenTelemetryActivityInboundInterceptor,\n} from '@temporalio/interceptors-opentelemetry';\n\n// Re-export workflow interceptors for convenience\nexport {\n OpenTelemetryInboundInterceptor,\n OpenTelemetryOutboundInterceptor,\n OpenTelemetryInternalsInterceptor,\n} from '@temporalio/interceptors-opentelemetry/lib/workflow';\n"]}
|
package/dist/testing/index.d.mts
CHANGED
package/dist/testing/index.d.ts
CHANGED
|
@@ -194,6 +194,30 @@ interface ClientInterceptors {
|
|
|
194
194
|
/** Workflow client interceptors (e.g., for OpenTelemetry trace propagation) */
|
|
195
195
|
workflow?: any[];
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Worker interceptors configuration
|
|
199
|
+
*
|
|
200
|
+
* Supports both activity interceptors and workflow module interceptors
|
|
201
|
+
* for OpenTelemetry trace propagation and custom instrumentation.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* import { createWorkerInterceptors } from '@astami/temporal-functions/observability';
|
|
206
|
+
*
|
|
207
|
+
* const worker = tfn.worker({
|
|
208
|
+
* temporal: { address: 'localhost:7233' },
|
|
209
|
+
* taskQueue: 'payments',
|
|
210
|
+
* workflowsPath: './dist/workflows.js',
|
|
211
|
+
* interceptors: createWorkerInterceptors({ serviceName: 'stripe-processor' }),
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
interface WorkerInterceptors {
|
|
216
|
+
/** Activity inbound interceptors (e.g., for logging, tracing) */
|
|
217
|
+
activityInbound?: ((ctx: any) => any)[];
|
|
218
|
+
/** Workflow interceptor module paths for bundling (e.g., '@temporalio/interceptors-opentelemetry/lib/workflow') */
|
|
219
|
+
workflowModules?: string[];
|
|
220
|
+
}
|
|
197
221
|
/**
|
|
198
222
|
* Client configuration options
|
|
199
223
|
*/
|
|
@@ -274,6 +298,12 @@ interface WorkerConfig {
|
|
|
274
298
|
temporal: TemporalConfig;
|
|
275
299
|
/** Task queue to poll */
|
|
276
300
|
taskQueue: string;
|
|
301
|
+
/** Path to compiled workflow module (for external workflow files) */
|
|
302
|
+
workflowsPath?: string;
|
|
303
|
+
/** Activities object to register */
|
|
304
|
+
activities?: Record<string, (...args: any[]) => Promise<any>>;
|
|
305
|
+
/** Worker interceptors for observability and custom instrumentation */
|
|
306
|
+
interceptors?: WorkerInterceptors;
|
|
277
307
|
/** Maximum concurrent activity executions */
|
|
278
308
|
maxConcurrentActivities?: number;
|
|
279
309
|
/** Maximum concurrent workflow executions */
|
|
@@ -330,4 +360,4 @@ interface Registry {
|
|
|
330
360
|
workflows: Map<string, WorkflowDef>;
|
|
331
361
|
}
|
|
332
362
|
|
|
333
|
-
export type { CronTriggerOptions as C, FunctionOptions as F, HttpTriggerOptions as H, Registry as R, StartWorkflowOptions as S, TemporalConfig as T, WorkflowHandler as W, FunctionDef as a, WorkflowOptions as b, WorkflowDef as c, WorkflowContext as d, WorkflowInfo as e, RetryPolicy as f, ClientConfig as g, WorkerConfig as h, WorkflowHandle as i, TFNClient as j, TFNWorker as k };
|
|
363
|
+
export type { CronTriggerOptions as C, FunctionOptions as F, HttpTriggerOptions as H, Registry as R, StartWorkflowOptions as S, TemporalConfig as T, WorkflowHandler as W, FunctionDef as a, WorkflowOptions as b, WorkflowDef as c, WorkflowContext as d, WorkflowInfo as e, RetryPolicy as f, ClientConfig as g, WorkerConfig as h, WorkflowHandle as i, TFNClient as j, TFNWorker as k, WorkerInterceptors as l };
|
|
@@ -194,6 +194,30 @@ interface ClientInterceptors {
|
|
|
194
194
|
/** Workflow client interceptors (e.g., for OpenTelemetry trace propagation) */
|
|
195
195
|
workflow?: any[];
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Worker interceptors configuration
|
|
199
|
+
*
|
|
200
|
+
* Supports both activity interceptors and workflow module interceptors
|
|
201
|
+
* for OpenTelemetry trace propagation and custom instrumentation.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* import { createWorkerInterceptors } from '@astami/temporal-functions/observability';
|
|
206
|
+
*
|
|
207
|
+
* const worker = tfn.worker({
|
|
208
|
+
* temporal: { address: 'localhost:7233' },
|
|
209
|
+
* taskQueue: 'payments',
|
|
210
|
+
* workflowsPath: './dist/workflows.js',
|
|
211
|
+
* interceptors: createWorkerInterceptors({ serviceName: 'stripe-processor' }),
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
interface WorkerInterceptors {
|
|
216
|
+
/** Activity inbound interceptors (e.g., for logging, tracing) */
|
|
217
|
+
activityInbound?: ((ctx: any) => any)[];
|
|
218
|
+
/** Workflow interceptor module paths for bundling (e.g., '@temporalio/interceptors-opentelemetry/lib/workflow') */
|
|
219
|
+
workflowModules?: string[];
|
|
220
|
+
}
|
|
197
221
|
/**
|
|
198
222
|
* Client configuration options
|
|
199
223
|
*/
|
|
@@ -274,6 +298,12 @@ interface WorkerConfig {
|
|
|
274
298
|
temporal: TemporalConfig;
|
|
275
299
|
/** Task queue to poll */
|
|
276
300
|
taskQueue: string;
|
|
301
|
+
/** Path to compiled workflow module (for external workflow files) */
|
|
302
|
+
workflowsPath?: string;
|
|
303
|
+
/** Activities object to register */
|
|
304
|
+
activities?: Record<string, (...args: any[]) => Promise<any>>;
|
|
305
|
+
/** Worker interceptors for observability and custom instrumentation */
|
|
306
|
+
interceptors?: WorkerInterceptors;
|
|
277
307
|
/** Maximum concurrent activity executions */
|
|
278
308
|
maxConcurrentActivities?: number;
|
|
279
309
|
/** Maximum concurrent workflow executions */
|
|
@@ -330,4 +360,4 @@ interface Registry {
|
|
|
330
360
|
workflows: Map<string, WorkflowDef>;
|
|
331
361
|
}
|
|
332
362
|
|
|
333
|
-
export type { CronTriggerOptions as C, FunctionOptions as F, HttpTriggerOptions as H, Registry as R, StartWorkflowOptions as S, TemporalConfig as T, WorkflowHandler as W, FunctionDef as a, WorkflowOptions as b, WorkflowDef as c, WorkflowContext as d, WorkflowInfo as e, RetryPolicy as f, ClientConfig as g, WorkerConfig as h, WorkflowHandle as i, TFNClient as j, TFNWorker as k };
|
|
363
|
+
export type { CronTriggerOptions as C, FunctionOptions as F, HttpTriggerOptions as H, Registry as R, StartWorkflowOptions as S, TemporalConfig as T, WorkflowHandler as W, FunctionDef as a, WorkflowOptions as b, WorkflowDef as c, WorkflowContext as d, WorkflowInfo as e, RetryPolicy as f, ClientConfig as g, WorkerConfig as h, WorkflowHandle as i, TFNClient as j, TFNWorker as k, WorkerInterceptors as l };
|
package/dist/worker/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as WorkerConfig, k as TFNWorker } from '../types-
|
|
1
|
+
import { h as WorkerConfig, k as TFNWorker } from '../types-DY4y7IE5.mjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Temporal Functions - Worker Package
|
|
@@ -12,19 +12,34 @@ import { h as WorkerConfig, k as TFNWorker } from '../types-Curq8YlS.mjs';
|
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```typescript
|
|
15
|
+
* // Option 1: Using registered functions (tfn pattern)
|
|
15
16
|
* import { tfn } from 'temporal-functions/worker';
|
|
16
17
|
* import { validateOrder, processOrder } from './functions';
|
|
17
18
|
*
|
|
18
19
|
* const worker = tfn.worker({
|
|
19
|
-
* temporal: {
|
|
20
|
-
* address: 'localhost:7233',
|
|
21
|
-
* namespace: 'default',
|
|
22
|
-
* },
|
|
20
|
+
* temporal: { address: 'localhost:7233', namespace: 'default' },
|
|
23
21
|
* taskQueue: 'my-queue',
|
|
24
22
|
* });
|
|
25
23
|
*
|
|
26
24
|
* worker.register(validateOrder);
|
|
27
25
|
* worker.register(processOrder);
|
|
26
|
+
* await worker.start();
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Option 2: Using external workflowsPath and activities (processor pattern)
|
|
32
|
+
* import { createWorker } from '@astami/temporal-functions/worker';
|
|
33
|
+
* import { createWorkerInterceptors } from '@astami/temporal-functions/observability';
|
|
34
|
+
* import * as activities from './activities';
|
|
35
|
+
*
|
|
36
|
+
* const worker = createWorker({
|
|
37
|
+
* temporal: { address: 'localhost:7233', namespace: 'loop' },
|
|
38
|
+
* taskQueue: 'stripe-payments',
|
|
39
|
+
* workflowsPath: './dist/workflows/index.js',
|
|
40
|
+
* activities,
|
|
41
|
+
* interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),
|
|
42
|
+
* });
|
|
28
43
|
*
|
|
29
44
|
* await worker.start();
|
|
30
45
|
* ```
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as WorkerConfig, k as TFNWorker } from '../types-
|
|
1
|
+
import { h as WorkerConfig, k as TFNWorker } from '../types-DY4y7IE5.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Temporal Functions - Worker Package
|
|
@@ -12,19 +12,34 @@ import { h as WorkerConfig, k as TFNWorker } from '../types-Curq8YlS.js';
|
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```typescript
|
|
15
|
+
* // Option 1: Using registered functions (tfn pattern)
|
|
15
16
|
* import { tfn } from 'temporal-functions/worker';
|
|
16
17
|
* import { validateOrder, processOrder } from './functions';
|
|
17
18
|
*
|
|
18
19
|
* const worker = tfn.worker({
|
|
19
|
-
* temporal: {
|
|
20
|
-
* address: 'localhost:7233',
|
|
21
|
-
* namespace: 'default',
|
|
22
|
-
* },
|
|
20
|
+
* temporal: { address: 'localhost:7233', namespace: 'default' },
|
|
23
21
|
* taskQueue: 'my-queue',
|
|
24
22
|
* });
|
|
25
23
|
*
|
|
26
24
|
* worker.register(validateOrder);
|
|
27
25
|
* worker.register(processOrder);
|
|
26
|
+
* await worker.start();
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Option 2: Using external workflowsPath and activities (processor pattern)
|
|
32
|
+
* import { createWorker } from '@astami/temporal-functions/worker';
|
|
33
|
+
* import { createWorkerInterceptors } from '@astami/temporal-functions/observability';
|
|
34
|
+
* import * as activities from './activities';
|
|
35
|
+
*
|
|
36
|
+
* const worker = createWorker({
|
|
37
|
+
* temporal: { address: 'localhost:7233', namespace: 'loop' },
|
|
38
|
+
* taskQueue: 'stripe-payments',
|
|
39
|
+
* workflowsPath: './dist/workflows/index.js',
|
|
40
|
+
* activities,
|
|
41
|
+
* interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),
|
|
42
|
+
* });
|
|
28
43
|
*
|
|
29
44
|
* await worker.start();
|
|
30
45
|
* ```
|
package/dist/worker/index.js
CHANGED
|
@@ -198,15 +198,22 @@ var TemporalFunctionsWorker = class {
|
|
|
198
198
|
* Start the worker
|
|
199
199
|
*/
|
|
200
200
|
async start() {
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
const hasRegisteredFunctions = this.functions.size > 0 || this.workflows.size > 0;
|
|
202
|
+
const hasExternalConfig = this.config.workflowsPath || this.config.activities;
|
|
203
|
+
if (!hasRegisteredFunctions && !hasExternalConfig) {
|
|
204
|
+
throw new Error("No functions, workflows, or external config provided. Either call register() or provide workflowsPath/activities.");
|
|
203
205
|
}
|
|
204
206
|
console.log(`Starting Temporal Functions worker...`);
|
|
205
207
|
console.log(` Temporal: ${this.config.temporal.address}`);
|
|
206
208
|
console.log(` Namespace: ${this.config.temporal.namespace}`);
|
|
207
209
|
console.log(` Task Queue: ${this.config.taskQueue}`);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
if (this.config.workflowsPath) {
|
|
211
|
+
console.log(` Workflows Path: ${this.config.workflowsPath}`);
|
|
212
|
+
}
|
|
213
|
+
if (hasRegisteredFunctions) {
|
|
214
|
+
console.log(` Registered Functions: ${this.functions.size}`);
|
|
215
|
+
console.log(` Registered Workflows: ${this.workflows.size}`);
|
|
216
|
+
}
|
|
210
217
|
const connection = await worker.NativeConnection.connect({
|
|
211
218
|
address: this.config.temporal.address,
|
|
212
219
|
tls: this.config.temporal.tls ? {
|
|
@@ -220,17 +227,39 @@ var TemporalFunctionsWorker = class {
|
|
|
220
227
|
} : void 0
|
|
221
228
|
} : void 0
|
|
222
229
|
});
|
|
223
|
-
const
|
|
224
|
-
|
|
230
|
+
const registeredActivities = this.buildActivities();
|
|
231
|
+
const activities = {
|
|
232
|
+
...registeredActivities,
|
|
233
|
+
...this.config.activities
|
|
234
|
+
};
|
|
235
|
+
const workerOptions = {
|
|
225
236
|
connection,
|
|
226
237
|
namespace: this.config.temporal.namespace,
|
|
227
238
|
taskQueue: this.config.taskQueue,
|
|
228
|
-
activities,
|
|
229
|
-
// workflowsPath will need to be provided by the user
|
|
230
|
-
// or we need to implement dynamic bundling
|
|
239
|
+
activities: Object.keys(activities).length > 0 ? activities : void 0,
|
|
231
240
|
maxConcurrentActivityTaskExecutions: this.config.maxConcurrentActivities,
|
|
232
241
|
maxConcurrentWorkflowTaskExecutions: this.config.maxConcurrentWorkflows
|
|
233
|
-
}
|
|
242
|
+
};
|
|
243
|
+
if (this.config.workflowsPath) {
|
|
244
|
+
workerOptions.workflowsPath = this.config.workflowsPath;
|
|
245
|
+
}
|
|
246
|
+
if (this.config.interceptors || this.config.workflowsPath) {
|
|
247
|
+
workerOptions.interceptors = {};
|
|
248
|
+
if (this.config.interceptors?.activityInbound) {
|
|
249
|
+
workerOptions.interceptors.activityInbound = this.config.interceptors.activityInbound;
|
|
250
|
+
}
|
|
251
|
+
const workflowModules = [];
|
|
252
|
+
if (this.config.interceptors?.workflowModules) {
|
|
253
|
+
workflowModules.push(...this.config.interceptors.workflowModules);
|
|
254
|
+
}
|
|
255
|
+
if (this.config.workflowsPath) {
|
|
256
|
+
workflowModules.push(this.config.workflowsPath);
|
|
257
|
+
}
|
|
258
|
+
if (workflowModules.length > 0) {
|
|
259
|
+
workerOptions.interceptors.workflowModules = workflowModules;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.worker = await worker.Worker.create(workerOptions);
|
|
234
263
|
const shutdown = async () => {
|
|
235
264
|
if (this.shutdownRequested) return;
|
|
236
265
|
this.shutdownRequested = true;
|
package/dist/worker/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/worker/index.ts"],"names":["NativeConnection","Worker"],"mappings":"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","file":"index.js","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 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, condition, CancellationScope } 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 const signalPayloads = new Map();\n const signalReceived = 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 waitForSignal: async (signalName, options = {}) => {\n // Register signal handler if not already registered\n if (!signalReceived.has(signalName)) {\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n const signal = defineSignal(signalName);\n setHandler(signal, (payload) => {\n signalPayloads.set(signalName, payload);\n signalReceived.set(signalName, true);\n });\n }\n\n // Wait for signal with optional timeout\n const timeoutMs = options.timeout ? parseDuration(options.timeout) : undefined;\n const received = await condition(() => signalReceived.get(signalName), timeoutMs);\n\n if (!received) {\n throw new Error(\\`Timeout waiting for signal: \\${signalName}\\`);\n }\n\n // Reset for potential reuse\n const payload = signalPayloads.get(signalName);\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n\n return payload;\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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/worker/index.ts"],"names":["NativeConnection","Worker"],"mappings":";;;;;;;;;AAgQO,SAAS,WAAW,GAAA,EAAkC;AAC3D,EAAA,OAAQ,KAAqB,MAAA,KAAW,UAAA;AAC1C;AAKO,SAAS,WAAW,GAAA,EAAkC;AAC3D,EAAA,OAAQ,KAAqB,MAAA,KAAW,UAAA;AAC1C;;;ACrPA,IAAM,0BAAN,MAAmD;AAAA,EACzC,MAAA;AAAA,EACA,SAAA,uBAA0C,GAAA,EAAI;AAAA,EAC9C,SAAA,uBAA0C,GAAA,EAAI;AAAA,EAC9C,MAAA,GAAwB,IAAA;AAAA,EACxB,iBAAA,GAAoB,KAAA;AAAA,EAE5B,YAAY,MAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,GAAG,MAAA;AAAA,MACH,QAAA,EAAU;AAAA,QACR,SAAA,EAAW,SAAA;AAAA,QACX,GAAG,MAAA,CAAO;AAAA,OACZ;AAAA,MACA,uBAAA,EAAyB,OAAO,uBAAA,IAA2B,GAAA;AAAA,MAC3D,sBAAA,EAAwB,OAAO,sBAAA,IAA0B;AAAA,KAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,GAAA,EAAsC;AAC7C,IAAA,IAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AACnB,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAAA,IAClC,CAAA,MAAA,IAAW,UAAA,CAAW,GAAG,CAAA,EAAG;AAC1B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAAA,IAClC,CAAA,MAAO;AACL,MAAA,MAAM,IAAI,MAAM,4FAA4F,CAAA;AAAA,IAC9G;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAA,EAAuC;AACpD,IAAA,KAAA,MAAW,GAAG,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC9C,MAAA,IAAI,UAAA,CAAW,KAAK,CAAA,IAAK,UAAA,CAAW,KAAK,CAAA,EAAG;AAC1C,QAAA,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAA,GAA4E;AAClF,IAAA,MAAM,aAAuE,EAAC;AAE9E,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,GAAG,CAAA,IAAK,KAAK,SAAA,EAAW;AACxC,MAAA,UAAA,CAAW,IAAI,CAAA,GAAI,OAAO,KAAA,KAAmB;AAC3C,QAAA,OAAO,GAAA,CAAI,QAAQ,KAAK,CAAA;AAAA,MAC1B,CAAA;AAAA,IACF;AAEA,IAAA,OAAO,UAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,sBAAA,GAAiC;AACvC,IAAA,MAAM,gBAAgB,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AAGtD,IAAA,MAAM,IAAA,GAAO;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,MAAA,EA0GT,aAAA,CAAc,GAAA,CAAI,CAAC,IAAA,KAAS;AAAA,4BAAA,EACN,IAAI,CAAA;AAAA;AAAA,kDAAA,EAEkB,IAAI,CAAA;AAAA;AAAA,uDAAA,EAEC,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAItD,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC;AAAA,IAAA,CAAA;AAGf,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAE3B,IAAA,MAAM,yBAAyB,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA,IAAK,IAAA,CAAK,UAAU,IAAA,GAAO,CAAA;AAChF,IAAA,MAAM,iBAAA,GAAoB,IAAA,CAAK,MAAA,CAAO,aAAA,IAAiB,KAAK,MAAA,CAAO,UAAA;AAEnE,IAAA,IAAI,CAAC,sBAAA,IAA0B,CAAC,iBAAA,EAAmB;AACjD,MAAA,MAAM,IAAI,MAAM,mHAAmH,CAAA;AAAA,IACrI;AAEA,IAAA,OAAA,CAAQ,IAAI,CAAA,qCAAA,CAAuC,CAAA;AACnD,IAAA,OAAA,CAAQ,IAAI,CAAA,YAAA,EAAe,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,CAAE,CAAA;AACzD,IAAA,OAAA,CAAQ,IAAI,CAAA,aAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,CAAE,CAAA;AAC5D,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,cAAA,EAAiB,IAAA,CAAK,MAAA,CAAO,SAAS,CAAA,CAAE,CAAA;AACpD,IAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kBAAA,EAAqB,IAAA,CAAK,MAAA,CAAO,aAAa,CAAA,CAAE,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI,sBAAA,EAAwB;AAC1B,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wBAAA,EAA2B,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,CAAE,CAAA;AAC5D,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wBAAA,EAA2B,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,CAAE,CAAA;AAAA,IAC9D;AAGA,IAAA,MAAM,UAAA,GAAa,MAAMA,uBAAA,CAAiB,OAAA,CAAQ;AAAA,MAChD,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA;AAAA,MAC9B,GAAA,EAAK,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAA,GACtB;AAAA,QACE,cAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,IAAI,cAAA,GACrC;AAAA,UACE,GAAA,EAAK,MAAM,OAAO,IAAI,CAAA,CAAE,IAAA;AAAA,YAAK,CAAC,OAC5B,EAAA,CAAG,QAAA,CAAS,SAAS,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAA,CAAK,cAAe;AAAA,WAChE;AAAA,UACA,GAAA,EAAK,MAAM,OAAO,IAAI,CAAA,CAAE,IAAA;AAAA,YAAK,CAAC,OAC5B,EAAA,CAAG,QAAA,CAAS,SAAS,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAA,CAAK,aAAc;AAAA;AAC/D,SACF,GACA;AAAA,OACN,GACA;AAAA,KACL,CAAA;AAGD,IAAA,MAAM,oBAAA,GAAuB,KAAK,eAAA,EAAgB;AAClD,IAAA,MAAM,UAAA,GAAa;AAAA,MACjB,GAAG,oBAAA;AAAA,MACH,GAAG,KAAK,MAAA,CAAO;AAAA,KACjB;AAIA,IAAA,MAAM,aAAA,GAAqB;AAAA,MACzB,UAAA;AAAA,MACA,SAAA,EAAW,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,SAAA;AAAA,MAChC,SAAA,EAAW,KAAK,MAAA,CAAO,SAAA;AAAA,MACvB,YAAY,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CAAE,MAAA,GAAS,IAAI,UAAA,GAAa,MAAA;AAAA,MAC9D,mCAAA,EAAqC,KAAK,MAAA,CAAO,uBAAA;AAAA,MACjD,mCAAA,EAAqC,KAAK,MAAA,CAAO;AAAA,KACnD;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,MAAA,aAAA,CAAc,aAAA,GAAgB,KAAK,MAAA,CAAO,aAAA;AAAA,IAC5C;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,IAAgB,IAAA,CAAK,OAAO,aAAA,EAAe;AACzD,MAAA,aAAA,CAAc,eAAe,EAAC;AAE9B,MAAA,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,EAAc,eAAA,EAAiB;AAC7C,QAAA,aAAA,CAAc,YAAA,CAAa,eAAA,GAAkB,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,eAAA;AAAA,MACxE;AAMA,MAAA,MAAM,kBAA4B,EAAC;AAEnC,MAAA,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,EAAc,eAAA,EAAiB;AAC7C,QAAA,eAAA,CAAgB,IAAA,CAAK,GAAG,IAAA,CAAK,MAAA,CAAO,aAAa,eAAe,CAAA;AAAA,MAClE;AAKA,MAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,QAAA,eAAA,CAAgB,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,aAAa,CAAA;AAAA,MAChD;AAEA,MAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC9B,QAAA,aAAA,CAAc,aAAa,eAAA,GAAkB,eAAA;AAAA,MAC/C;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,MAAA,GAAS,MAAMC,aAAA,CAAO,MAAA,CAAO,aAAa,CAAA;AAG/C,IAAA,MAAM,WAAW,YAAY;AAC3B,MAAA,IAAI,KAAK,iBAAA,EAAmB;AAC5B,MAAA,IAAA,CAAK,iBAAA,GAAoB,IAAA;AACzB,MAAA,OAAA,CAAQ,IAAI,2BAA2B,CAAA;AACvC,MAAA,MAAM,KAAK,QAAA,EAAS;AACpB,MAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,IAChB,CAAA;AAEA,IAAA,OAAA,CAAQ,EAAA,CAAG,UAAU,QAAQ,CAAA;AAC7B,IAAA,OAAA,CAAQ,EAAA,CAAG,WAAW,QAAQ,CAAA;AAE9B,IAAA,OAAA,CAAQ,IAAI,2CAA2C,CAAA;AAGvD,IAAA,MAAM,IAAA,CAAK,OAAO,GAAA,EAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAA,GAA0B;AAC9B,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,OAAO,QAAA,EAAS;AACrB,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAAA,IAChB;AAAA,EACF;AACF,CAAA;AA2CA,SAAS,aAAa,MAAA,EAAiC;AACrD,EAAA,OAAO,IAAI,wBAAwB,MAAM,CAAA;AAC3C;AAMO,IAAM,GAAA,GAAM;AAAA,EACjB,MAAA,EAAQ;AACV;AAIA,IAAO,cAAA,GAAQ","file":"index.js","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 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, condition, CancellationScope } 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 const signalPayloads = new Map();\n const signalReceived = 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 waitForSignal: async (signalName, options = {}) => {\n // Register signal handler if not already registered\n if (!signalReceived.has(signalName)) {\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n const signal = defineSignal(signalName);\n setHandler(signal, (payload) => {\n signalPayloads.set(signalName, payload);\n signalReceived.set(signalName, true);\n });\n }\n\n // Wait for signal with optional timeout\n const timeoutMs = options.timeout ? parseDuration(options.timeout) : undefined;\n const received = await condition(() => signalReceived.get(signalName), timeoutMs);\n\n if (!received) {\n throw new Error(\\`Timeout waiting for signal: \\${signalName}\\`);\n }\n\n // Reset for potential reuse\n const payload = signalPayloads.get(signalName);\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n\n return payload;\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 // Allow starting with external workflowsPath and activities\n const hasRegisteredFunctions = this.functions.size > 0 || this.workflows.size > 0;\n const hasExternalConfig = this.config.workflowsPath || this.config.activities;\n\n if (!hasRegisteredFunctions && !hasExternalConfig) {\n throw new Error('No functions, workflows, or external config provided. Either call register() or provide workflowsPath/activities.');\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 if (this.config.workflowsPath) {\n console.log(` Workflows Path: ${this.config.workflowsPath}`);\n }\n if (hasRegisteredFunctions) {\n console.log(` Registered Functions: ${this.functions.size}`);\n console.log(` Registered Workflows: ${this.workflows.size}`);\n }\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 - merge registered functions with external activities\n const registeredActivities = this.buildActivities();\n const activities = {\n ...registeredActivities,\n ...this.config.activities,\n };\n\n // Build worker options\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workerOptions: any = {\n connection,\n namespace: this.config.temporal.namespace,\n taskQueue: this.config.taskQueue,\n activities: Object.keys(activities).length > 0 ? activities : undefined,\n maxConcurrentActivityTaskExecutions: this.config.maxConcurrentActivities,\n maxConcurrentWorkflowTaskExecutions: this.config.maxConcurrentWorkflows,\n };\n\n // Add workflowsPath if provided (for external workflow files)\n if (this.config.workflowsPath) {\n workerOptions.workflowsPath = this.config.workflowsPath;\n }\n\n // Add interceptors if provided\n if (this.config.interceptors || this.config.workflowsPath) {\n workerOptions.interceptors = {};\n\n if (this.config.interceptors?.activityInbound) {\n workerOptions.interceptors.activityInbound = this.config.interceptors.activityInbound;\n }\n\n // Build workflow modules list:\n // 1. Include any explicitly configured workflowModules\n // 2. IMPORTANT: Also include the workflowsPath so its `interceptors` export is picked up\n // This enables OpenTelemetry trace propagation from the workflow module\n const workflowModules: string[] = [];\n\n if (this.config.interceptors?.workflowModules) {\n workflowModules.push(...this.config.interceptors.workflowModules);\n }\n\n // Auto-include the workflows module as an interceptor module\n // This allows the workflow module to export an `interceptors` factory function\n // that will be automatically invoked by the Temporal runtime\n if (this.config.workflowsPath) {\n workflowModules.push(this.config.workflowsPath);\n }\n\n if (workflowModules.length > 0) {\n workerOptions.interceptors.workflowModules = workflowModules;\n }\n }\n\n // Create worker\n this.worker = await Worker.create(workerOptions);\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 * // Option 1: Using registered functions (tfn pattern)\n * import { tfn } from 'temporal-functions/worker';\n * import { validateOrder, processOrder } from './functions';\n *\n * const worker = tfn.worker({\n * temporal: { address: 'localhost:7233', namespace: 'default' },\n * taskQueue: 'my-queue',\n * });\n *\n * worker.register(validateOrder);\n * worker.register(processOrder);\n * await worker.start();\n * ```\n *\n * @example\n * ```typescript\n * // Option 2: Using external workflowsPath and activities (processor pattern)\n * import { createWorker } from '@astami/temporal-functions/worker';\n * import { createWorkerInterceptors } from '@astami/temporal-functions/observability';\n * import * as activities from './activities';\n *\n * const worker = createWorker({\n * temporal: { address: 'localhost:7233', namespace: 'loop' },\n * taskQueue: 'stripe-payments',\n * workflowsPath: './dist/workflows/index.js',\n * activities,\n * interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),\n * });\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/dist/worker/index.mjs
CHANGED
|
@@ -194,15 +194,22 @@ var TemporalFunctionsWorker = class {
|
|
|
194
194
|
* Start the worker
|
|
195
195
|
*/
|
|
196
196
|
async start() {
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
const hasRegisteredFunctions = this.functions.size > 0 || this.workflows.size > 0;
|
|
198
|
+
const hasExternalConfig = this.config.workflowsPath || this.config.activities;
|
|
199
|
+
if (!hasRegisteredFunctions && !hasExternalConfig) {
|
|
200
|
+
throw new Error("No functions, workflows, or external config provided. Either call register() or provide workflowsPath/activities.");
|
|
199
201
|
}
|
|
200
202
|
console.log(`Starting Temporal Functions worker...`);
|
|
201
203
|
console.log(` Temporal: ${this.config.temporal.address}`);
|
|
202
204
|
console.log(` Namespace: ${this.config.temporal.namespace}`);
|
|
203
205
|
console.log(` Task Queue: ${this.config.taskQueue}`);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
if (this.config.workflowsPath) {
|
|
207
|
+
console.log(` Workflows Path: ${this.config.workflowsPath}`);
|
|
208
|
+
}
|
|
209
|
+
if (hasRegisteredFunctions) {
|
|
210
|
+
console.log(` Registered Functions: ${this.functions.size}`);
|
|
211
|
+
console.log(` Registered Workflows: ${this.workflows.size}`);
|
|
212
|
+
}
|
|
206
213
|
const connection = await NativeConnection.connect({
|
|
207
214
|
address: this.config.temporal.address,
|
|
208
215
|
tls: this.config.temporal.tls ? {
|
|
@@ -216,17 +223,39 @@ var TemporalFunctionsWorker = class {
|
|
|
216
223
|
} : void 0
|
|
217
224
|
} : void 0
|
|
218
225
|
});
|
|
219
|
-
const
|
|
220
|
-
|
|
226
|
+
const registeredActivities = this.buildActivities();
|
|
227
|
+
const activities = {
|
|
228
|
+
...registeredActivities,
|
|
229
|
+
...this.config.activities
|
|
230
|
+
};
|
|
231
|
+
const workerOptions = {
|
|
221
232
|
connection,
|
|
222
233
|
namespace: this.config.temporal.namespace,
|
|
223
234
|
taskQueue: this.config.taskQueue,
|
|
224
|
-
activities,
|
|
225
|
-
// workflowsPath will need to be provided by the user
|
|
226
|
-
// or we need to implement dynamic bundling
|
|
235
|
+
activities: Object.keys(activities).length > 0 ? activities : void 0,
|
|
227
236
|
maxConcurrentActivityTaskExecutions: this.config.maxConcurrentActivities,
|
|
228
237
|
maxConcurrentWorkflowTaskExecutions: this.config.maxConcurrentWorkflows
|
|
229
|
-
}
|
|
238
|
+
};
|
|
239
|
+
if (this.config.workflowsPath) {
|
|
240
|
+
workerOptions.workflowsPath = this.config.workflowsPath;
|
|
241
|
+
}
|
|
242
|
+
if (this.config.interceptors || this.config.workflowsPath) {
|
|
243
|
+
workerOptions.interceptors = {};
|
|
244
|
+
if (this.config.interceptors?.activityInbound) {
|
|
245
|
+
workerOptions.interceptors.activityInbound = this.config.interceptors.activityInbound;
|
|
246
|
+
}
|
|
247
|
+
const workflowModules = [];
|
|
248
|
+
if (this.config.interceptors?.workflowModules) {
|
|
249
|
+
workflowModules.push(...this.config.interceptors.workflowModules);
|
|
250
|
+
}
|
|
251
|
+
if (this.config.workflowsPath) {
|
|
252
|
+
workflowModules.push(this.config.workflowsPath);
|
|
253
|
+
}
|
|
254
|
+
if (workflowModules.length > 0) {
|
|
255
|
+
workerOptions.interceptors.workflowModules = workflowModules;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
this.worker = await Worker.create(workerOptions);
|
|
230
259
|
const shutdown = async () => {
|
|
231
260
|
if (this.shutdownRequested) return;
|
|
232
261
|
this.shutdownRequested = true;
|
|
@@ -1 +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 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, condition, CancellationScope } 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 const signalPayloads = new Map();\n const signalReceived = 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 waitForSignal: async (signalName, options = {}) => {\n // Register signal handler if not already registered\n if (!signalReceived.has(signalName)) {\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n const signal = defineSignal(signalName);\n setHandler(signal, (payload) => {\n signalPayloads.set(signalName, payload);\n signalReceived.set(signalName, true);\n });\n }\n\n // Wait for signal with optional timeout\n const timeoutMs = options.timeout ? parseDuration(options.timeout) : undefined;\n const received = await condition(() => signalReceived.get(signalName), timeoutMs);\n\n if (!received) {\n throw new Error(\\`Timeout waiting for signal: \\${signalName}\\`);\n }\n\n // Reset for potential reuse\n const payload = signalPayloads.get(signalName);\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n\n return payload;\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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/worker/index.ts"],"names":[],"mappings":";;;;;AAgQO,SAAS,WAAW,GAAA,EAAkC;AAC3D,EAAA,OAAQ,KAAqB,MAAA,KAAW,UAAA;AAC1C;AAKO,SAAS,WAAW,GAAA,EAAkC;AAC3D,EAAA,OAAQ,KAAqB,MAAA,KAAW,UAAA;AAC1C;;;ACrPA,IAAM,0BAAN,MAAmD;AAAA,EACzC,MAAA;AAAA,EACA,SAAA,uBAA0C,GAAA,EAAI;AAAA,EAC9C,SAAA,uBAA0C,GAAA,EAAI;AAAA,EAC9C,MAAA,GAAwB,IAAA;AAAA,EACxB,iBAAA,GAAoB,KAAA;AAAA,EAE5B,YAAY,MAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,GAAG,MAAA;AAAA,MACH,QAAA,EAAU;AAAA,QACR,SAAA,EAAW,SAAA;AAAA,QACX,GAAG,MAAA,CAAO;AAAA,OACZ;AAAA,MACA,uBAAA,EAAyB,OAAO,uBAAA,IAA2B,GAAA;AAAA,MAC3D,sBAAA,EAAwB,OAAO,sBAAA,IAA0B;AAAA,KAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,GAAA,EAAsC;AAC7C,IAAA,IAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AACnB,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAAA,IAClC,CAAA,MAAA,IAAW,UAAA,CAAW,GAAG,CAAA,EAAG;AAC1B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAAA,IAClC,CAAA,MAAO;AACL,MAAA,MAAM,IAAI,MAAM,4FAA4F,CAAA;AAAA,IAC9G;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAA,EAAuC;AACpD,IAAA,KAAA,MAAW,GAAG,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC9C,MAAA,IAAI,UAAA,CAAW,KAAK,CAAA,IAAK,UAAA,CAAW,KAAK,CAAA,EAAG;AAC1C,QAAA,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAA,GAA4E;AAClF,IAAA,MAAM,aAAuE,EAAC;AAE9E,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,GAAG,CAAA,IAAK,KAAK,SAAA,EAAW;AACxC,MAAA,UAAA,CAAW,IAAI,CAAA,GAAI,OAAO,KAAA,KAAmB;AAC3C,QAAA,OAAO,GAAA,CAAI,QAAQ,KAAK,CAAA;AAAA,MAC1B,CAAA;AAAA,IACF;AAEA,IAAA,OAAO,UAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,sBAAA,GAAiC;AACvC,IAAA,MAAM,gBAAgB,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AAGtD,IAAA,MAAM,IAAA,GAAO;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,MAAA,EA0GT,aAAA,CAAc,GAAA,CAAI,CAAC,IAAA,KAAS;AAAA,4BAAA,EACN,IAAI,CAAA;AAAA;AAAA,kDAAA,EAEkB,IAAI,CAAA;AAAA;AAAA,uDAAA,EAEC,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAItD,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC;AAAA,IAAA,CAAA;AAGf,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAE3B,IAAA,MAAM,yBAAyB,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA,IAAK,IAAA,CAAK,UAAU,IAAA,GAAO,CAAA;AAChF,IAAA,MAAM,iBAAA,GAAoB,IAAA,CAAK,MAAA,CAAO,aAAA,IAAiB,KAAK,MAAA,CAAO,UAAA;AAEnE,IAAA,IAAI,CAAC,sBAAA,IAA0B,CAAC,iBAAA,EAAmB;AACjD,MAAA,MAAM,IAAI,MAAM,mHAAmH,CAAA;AAAA,IACrI;AAEA,IAAA,OAAA,CAAQ,IAAI,CAAA,qCAAA,CAAuC,CAAA;AACnD,IAAA,OAAA,CAAQ,IAAI,CAAA,YAAA,EAAe,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,CAAE,CAAA;AACzD,IAAA,OAAA,CAAQ,IAAI,CAAA,aAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,CAAE,CAAA;AAC5D,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,cAAA,EAAiB,IAAA,CAAK,MAAA,CAAO,SAAS,CAAA,CAAE,CAAA;AACpD,IAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kBAAA,EAAqB,IAAA,CAAK,MAAA,CAAO,aAAa,CAAA,CAAE,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI,sBAAA,EAAwB;AAC1B,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wBAAA,EAA2B,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,CAAE,CAAA;AAC5D,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wBAAA,EAA2B,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,CAAE,CAAA;AAAA,IAC9D;AAGA,IAAA,MAAM,UAAA,GAAa,MAAM,gBAAA,CAAiB,OAAA,CAAQ;AAAA,MAChD,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA;AAAA,MAC9B,GAAA,EAAK,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAA,GACtB;AAAA,QACE,cAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,IAAI,cAAA,GACrC;AAAA,UACE,GAAA,EAAK,MAAM,OAAO,IAAI,CAAA,CAAE,IAAA;AAAA,YAAK,CAAC,OAC5B,EAAA,CAAG,QAAA,CAAS,SAAS,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAA,CAAK,cAAe;AAAA,WAChE;AAAA,UACA,GAAA,EAAK,MAAM,OAAO,IAAI,CAAA,CAAE,IAAA;AAAA,YAAK,CAAC,OAC5B,EAAA,CAAG,QAAA,CAAS,SAAS,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAA,CAAK,aAAc;AAAA;AAC/D,SACF,GACA;AAAA,OACN,GACA;AAAA,KACL,CAAA;AAGD,IAAA,MAAM,oBAAA,GAAuB,KAAK,eAAA,EAAgB;AAClD,IAAA,MAAM,UAAA,GAAa;AAAA,MACjB,GAAG,oBAAA;AAAA,MACH,GAAG,KAAK,MAAA,CAAO;AAAA,KACjB;AAIA,IAAA,MAAM,aAAA,GAAqB;AAAA,MACzB,UAAA;AAAA,MACA,SAAA,EAAW,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,SAAA;AAAA,MAChC,SAAA,EAAW,KAAK,MAAA,CAAO,SAAA;AAAA,MACvB,YAAY,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CAAE,MAAA,GAAS,IAAI,UAAA,GAAa,MAAA;AAAA,MAC9D,mCAAA,EAAqC,KAAK,MAAA,CAAO,uBAAA;AAAA,MACjD,mCAAA,EAAqC,KAAK,MAAA,CAAO;AAAA,KACnD;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,MAAA,aAAA,CAAc,aAAA,GAAgB,KAAK,MAAA,CAAO,aAAA;AAAA,IAC5C;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,IAAgB,IAAA,CAAK,OAAO,aAAA,EAAe;AACzD,MAAA,aAAA,CAAc,eAAe,EAAC;AAE9B,MAAA,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,EAAc,eAAA,EAAiB;AAC7C,QAAA,aAAA,CAAc,YAAA,CAAa,eAAA,GAAkB,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,eAAA;AAAA,MACxE;AAMA,MAAA,MAAM,kBAA4B,EAAC;AAEnC,MAAA,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,EAAc,eAAA,EAAiB;AAC7C,QAAA,eAAA,CAAgB,IAAA,CAAK,GAAG,IAAA,CAAK,MAAA,CAAO,aAAa,eAAe,CAAA;AAAA,MAClE;AAKA,MAAA,IAAI,IAAA,CAAK,OAAO,aAAA,EAAe;AAC7B,QAAA,eAAA,CAAgB,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,aAAa,CAAA;AAAA,MAChD;AAEA,MAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC9B,QAAA,aAAA,CAAc,aAAa,eAAA,GAAkB,eAAA;AAAA,MAC/C;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,MAAA,GAAS,MAAM,MAAA,CAAO,MAAA,CAAO,aAAa,CAAA;AAG/C,IAAA,MAAM,WAAW,YAAY;AAC3B,MAAA,IAAI,KAAK,iBAAA,EAAmB;AAC5B,MAAA,IAAA,CAAK,iBAAA,GAAoB,IAAA;AACzB,MAAA,OAAA,CAAQ,IAAI,2BAA2B,CAAA;AACvC,MAAA,MAAM,KAAK,QAAA,EAAS;AACpB,MAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,IAChB,CAAA;AAEA,IAAA,OAAA,CAAQ,EAAA,CAAG,UAAU,QAAQ,CAAA;AAC7B,IAAA,OAAA,CAAQ,EAAA,CAAG,WAAW,QAAQ,CAAA;AAE9B,IAAA,OAAA,CAAQ,IAAI,2CAA2C,CAAA;AAGvD,IAAA,MAAM,IAAA,CAAK,OAAO,GAAA,EAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAA,GAA0B;AAC9B,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,OAAO,QAAA,EAAS;AACrB,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAAA,IAChB;AAAA,EACF;AACF,CAAA;AA2CA,SAAS,aAAa,MAAA,EAAiC;AACrD,EAAA,OAAO,IAAI,wBAAwB,MAAM,CAAA;AAC3C;AAMO,IAAM,GAAA,GAAM;AAAA,EACjB,MAAA,EAAQ;AACV;AAIA,IAAO,cAAA,GAAQ","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 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, condition, CancellationScope } 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 const signalPayloads = new Map();\n const signalReceived = 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 waitForSignal: async (signalName, options = {}) => {\n // Register signal handler if not already registered\n if (!signalReceived.has(signalName)) {\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n const signal = defineSignal(signalName);\n setHandler(signal, (payload) => {\n signalPayloads.set(signalName, payload);\n signalReceived.set(signalName, true);\n });\n }\n\n // Wait for signal with optional timeout\n const timeoutMs = options.timeout ? parseDuration(options.timeout) : undefined;\n const received = await condition(() => signalReceived.get(signalName), timeoutMs);\n\n if (!received) {\n throw new Error(\\`Timeout waiting for signal: \\${signalName}\\`);\n }\n\n // Reset for potential reuse\n const payload = signalPayloads.get(signalName);\n signalReceived.set(signalName, false);\n signalPayloads.set(signalName, undefined);\n\n return payload;\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 // Allow starting with external workflowsPath and activities\n const hasRegisteredFunctions = this.functions.size > 0 || this.workflows.size > 0;\n const hasExternalConfig = this.config.workflowsPath || this.config.activities;\n\n if (!hasRegisteredFunctions && !hasExternalConfig) {\n throw new Error('No functions, workflows, or external config provided. Either call register() or provide workflowsPath/activities.');\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 if (this.config.workflowsPath) {\n console.log(` Workflows Path: ${this.config.workflowsPath}`);\n }\n if (hasRegisteredFunctions) {\n console.log(` Registered Functions: ${this.functions.size}`);\n console.log(` Registered Workflows: ${this.workflows.size}`);\n }\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 - merge registered functions with external activities\n const registeredActivities = this.buildActivities();\n const activities = {\n ...registeredActivities,\n ...this.config.activities,\n };\n\n // Build worker options\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workerOptions: any = {\n connection,\n namespace: this.config.temporal.namespace,\n taskQueue: this.config.taskQueue,\n activities: Object.keys(activities).length > 0 ? activities : undefined,\n maxConcurrentActivityTaskExecutions: this.config.maxConcurrentActivities,\n maxConcurrentWorkflowTaskExecutions: this.config.maxConcurrentWorkflows,\n };\n\n // Add workflowsPath if provided (for external workflow files)\n if (this.config.workflowsPath) {\n workerOptions.workflowsPath = this.config.workflowsPath;\n }\n\n // Add interceptors if provided\n if (this.config.interceptors || this.config.workflowsPath) {\n workerOptions.interceptors = {};\n\n if (this.config.interceptors?.activityInbound) {\n workerOptions.interceptors.activityInbound = this.config.interceptors.activityInbound;\n }\n\n // Build workflow modules list:\n // 1. Include any explicitly configured workflowModules\n // 2. IMPORTANT: Also include the workflowsPath so its `interceptors` export is picked up\n // This enables OpenTelemetry trace propagation from the workflow module\n const workflowModules: string[] = [];\n\n if (this.config.interceptors?.workflowModules) {\n workflowModules.push(...this.config.interceptors.workflowModules);\n }\n\n // Auto-include the workflows module as an interceptor module\n // This allows the workflow module to export an `interceptors` factory function\n // that will be automatically invoked by the Temporal runtime\n if (this.config.workflowsPath) {\n workflowModules.push(this.config.workflowsPath);\n }\n\n if (workflowModules.length > 0) {\n workerOptions.interceptors.workflowModules = workflowModules;\n }\n }\n\n // Create worker\n this.worker = await Worker.create(workerOptions);\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 * // Option 1: Using registered functions (tfn pattern)\n * import { tfn } from 'temporal-functions/worker';\n * import { validateOrder, processOrder } from './functions';\n *\n * const worker = tfn.worker({\n * temporal: { address: 'localhost:7233', namespace: 'default' },\n * taskQueue: 'my-queue',\n * });\n *\n * worker.register(validateOrder);\n * worker.register(processOrder);\n * await worker.start();\n * ```\n *\n * @example\n * ```typescript\n * // Option 2: Using external workflowsPath and activities (processor pattern)\n * import { createWorker } from '@astami/temporal-functions/worker';\n * import { createWorkerInterceptors } from '@astami/temporal-functions/observability';\n * import * as activities from './activities';\n *\n * const worker = createWorker({\n * temporal: { address: 'localhost:7233', namespace: 'loop' },\n * taskQueue: 'stripe-payments',\n * workflowsPath: './dist/workflows/index.js',\n * activities,\n * interceptors: createWorkerInterceptors({ serviceName: 'stripe' }),\n * });\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astami/temporal-functions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A lightweight TypeScript framework providing lambda-like DX on top of Temporal's durable execution engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"types": "./dist/testing/index.d.ts",
|
|
26
26
|
"import": "./dist/testing/index.mjs",
|
|
27
27
|
"require": "./dist/testing/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./observability": {
|
|
30
|
+
"types": "./dist/observability/index.d.ts",
|
|
31
|
+
"import": "./dist/observability/index.mjs",
|
|
32
|
+
"require": "./dist/observability/index.js"
|
|
28
33
|
}
|
|
29
34
|
},
|
|
30
35
|
"files": [
|
|
@@ -64,7 +69,9 @@
|
|
|
64
69
|
"peerDependencies": {
|
|
65
70
|
"@temporalio/client": "^1.9.0",
|
|
66
71
|
"@temporalio/worker": "^1.9.0",
|
|
67
|
-
"@temporalio/workflow": "^1.9.0"
|
|
72
|
+
"@temporalio/workflow": "^1.9.0",
|
|
73
|
+
"@temporalio/interceptors-opentelemetry": "^1.9.0",
|
|
74
|
+
"@opentelemetry/api": "^1.0.0"
|
|
68
75
|
},
|
|
69
76
|
"peerDependenciesMeta": {
|
|
70
77
|
"@temporalio/worker": {
|
|
@@ -72,6 +79,12 @@
|
|
|
72
79
|
},
|
|
73
80
|
"@temporalio/workflow": {
|
|
74
81
|
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"@temporalio/interceptors-opentelemetry": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"@opentelemetry/api": {
|
|
87
|
+
"optional": true
|
|
75
88
|
}
|
|
76
89
|
},
|
|
77
90
|
"devDependencies": {
|
|
@@ -79,6 +92,8 @@
|
|
|
79
92
|
"@temporalio/worker": "^1.11.7",
|
|
80
93
|
"@temporalio/workflow": "^1.11.7",
|
|
81
94
|
"@temporalio/activity": "^1.11.7",
|
|
95
|
+
"@temporalio/interceptors-opentelemetry": "^1.11.7",
|
|
96
|
+
"@opentelemetry/api": "^1.9.0",
|
|
82
97
|
"@types/node": "^20.11.0",
|
|
83
98
|
"tsup": "^8.0.1",
|
|
84
99
|
"typescript": "^5.3.3",
|