ocel 0.0.0 → 0.0.1-alpha.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.
Files changed (74) hide show
  1. package/bin/run.js +39 -0
  2. package/dist/blob/bucket.d.ts +55 -0
  3. package/dist/blob/bucket.js +65 -0
  4. package/dist/blob/index.d.ts +8 -0
  5. package/dist/blob/index.js +13 -0
  6. package/dist/blob/integrations/client.d.ts +25 -0
  7. package/dist/blob/integrations/client.js +71 -0
  8. package/dist/blob/integrations/hono.d.ts +31 -0
  9. package/dist/blob/integrations/hono.js +11 -0
  10. package/dist/blob/mime-types.d.ts +1 -0
  11. package/dist/blob/mime-types.js +1 -0
  12. package/dist/blob/parse.d.ts +16 -0
  13. package/dist/blob/parse.js +69 -0
  14. package/dist/blob/s3-listener.d.ts +7 -0
  15. package/dist/blob/s3-listener.js +35 -0
  16. package/dist/blob/types.d.ts +128 -0
  17. package/dist/blob/types.js +1 -0
  18. package/dist/blob/upload-error.d.ts +35 -0
  19. package/dist/blob/upload-error.js +54 -0
  20. package/dist/blob/uploader.d.ts +67 -0
  21. package/dist/blob/uploader.js +323 -0
  22. package/dist/internal/db/index.d.ts +68 -0
  23. package/dist/internal/db/index.js +73 -0
  24. package/dist/lambda/index.d.ts +2 -0
  25. package/dist/lambda/index.js +4 -0
  26. package/dist/lambda/integrations/express.d.ts +3 -0
  27. package/dist/lambda/integrations/express.js +13 -0
  28. package/dist/lambda/integrations/hono.d.ts +3 -0
  29. package/dist/lambda/integrations/hono.js +13 -0
  30. package/dist/lambda/lambda.d.ts +43 -0
  31. package/dist/lambda/lambda.js +30 -0
  32. package/dist/postgres/index.d.ts +2 -0
  33. package/dist/postgres/index.js +4 -0
  34. package/dist/postgres/pg.d.ts +11 -0
  35. package/dist/postgres/pg.js +24 -0
  36. package/dist/utils/component.d.ts +3 -0
  37. package/dist/utils/component.js +1 -0
  38. package/dist/utils/constants.d.ts +2 -0
  39. package/dist/utils/constants.js +2 -0
  40. package/dist/utils/nanoid.d.ts +1 -0
  41. package/dist/utils/nanoid.js +6 -0
  42. package/dist/utils/rpc.d.ts +11 -0
  43. package/dist/utils/rpc.js +17 -0
  44. package/dist/utils/stack.d.ts +4 -0
  45. package/dist/utils/stack.js +23 -0
  46. package/dist/workflow/index.d.ts +3 -0
  47. package/dist/workflow/index.js +4 -0
  48. package/dist/workflow/workflow.d.ts +34 -0
  49. package/dist/workflow/workflow.js +146 -0
  50. package/package.json +93 -8
  51. package/src/blob/bucket.ts +92 -0
  52. package/src/blob/index.ts +34 -0
  53. package/src/blob/integrations/client.ts +127 -0
  54. package/src/blob/integrations/hono.ts +16 -0
  55. package/src/blob/mime-types.ts +68 -0
  56. package/src/blob/parse.ts +93 -0
  57. package/src/blob/s3-listener.ts +48 -0
  58. package/src/blob/types.ts +142 -0
  59. package/src/blob/upload-error.ts +67 -0
  60. package/src/blob/uploader.ts +426 -0
  61. package/src/internal/db/index.ts +78 -0
  62. package/src/lambda/index.ts +8 -0
  63. package/src/lambda/integrations/express.ts +25 -0
  64. package/src/lambda/integrations/hono.ts +25 -0
  65. package/src/lambda/lambda.ts +94 -0
  66. package/src/postgres/index.ts +5 -0
  67. package/src/postgres/pg.ts +33 -0
  68. package/src/utils/component.ts +3 -0
  69. package/src/utils/constants.ts +2 -0
  70. package/src/utils/nanoid.ts +9 -0
  71. package/src/utils/rpc.ts +28 -0
  72. package/src/utils/stack.ts +26 -0
  73. package/src/workflow/index.ts +9 -0
  74. package/src/workflow/workflow.ts +235 -0
@@ -0,0 +1,9 @@
1
+ import { customAlphabet } from "nanoid";
2
+
3
+ export function getNanoid(length = 7) {
4
+ const alphabet =
5
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
6
+ const nanoid = customAlphabet(alphabet, length);
7
+
8
+ return nanoid();
9
+ }
@@ -0,0 +1,28 @@
1
+ declare global {
2
+ var __ocelRegister: Promise<unknown>[];
3
+ }
4
+
5
+ export const rpc = {
6
+ register: async (args: {
7
+ id: string;
8
+ type: "lambda" | "bucket" | "workflow" | "postgres";
9
+ source: string;
10
+ config?: Record<string, unknown>;
11
+ }) => {
12
+ const SERVER_URL = process.env.OCEL_SERVER;
13
+ if (!SERVER_URL) {
14
+ throw new Error("OCEL_SERVER environment variable is not set");
15
+ }
16
+
17
+ const p = fetch(`${SERVER_URL}/register`, {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ },
22
+ body: JSON.stringify(args),
23
+ });
24
+
25
+ globalThis.__ocelRegister ??= [];
26
+ globalThis.__ocelRegister.push(p);
27
+ },
28
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Returns absolute path of the caller file.
3
+ */
4
+ export function getCallerFile(depth = 1): string | null {
5
+ const original = Error.prepareStackTrace;
6
+
7
+ try {
8
+ Error.prepareStackTrace = (_, stack) => stack;
9
+ const err = new Error();
10
+
11
+ const frames = err.stack as unknown as NodeJS.CallSite[];
12
+
13
+ // index 0 = getCallerFile
14
+ // index 1 = the function that called getCallerFile
15
+ return (
16
+ frames[2 + depth]
17
+ ?.getFileName()
18
+ ?.replace("file://", "")
19
+ ?.split("?")?.[0] ?? null
20
+ );
21
+ } catch {
22
+ return null;
23
+ } finally {
24
+ Error.prepareStackTrace = original;
25
+ }
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { ZodType } from "zod";
2
+ import { Workflow, type WorkflowConfig } from "./workflow";
3
+
4
+ export function workflow<TInput extends ZodType = never>(
5
+ id: string,
6
+ config: WorkflowConfig<TInput>
7
+ ) {
8
+ return new Workflow<TInput>(id, config);
9
+ }
@@ -0,0 +1,235 @@
1
+ import type { z, ZodType } from "zod";
2
+ import type { Component } from "../utils/component";
3
+ import {
4
+ withDurableExecution,
5
+ type DurableContext,
6
+ } from "@aws/durable-execution-sdk-js";
7
+ import { getCallerFile } from "../utils/stack";
8
+ import { rpc } from "../utils/rpc";
9
+ import { SpanStatusCode, trace } from "@opentelemetry/api";
10
+
11
+ export interface WorkflowConfig<TInput extends ZodType = never> {
12
+ input?: TInput;
13
+ queue?: {
14
+ concurrencyLimit?: number;
15
+ };
16
+ cron?: string;
17
+ run: [TInput] extends [never]
18
+ ? (args: { ctx: DurableContext }) => Promise<void>
19
+ : (args: { input: z.infer<TInput>; ctx: DurableContext }) => Promise<void>;
20
+ link?: Component[];
21
+ retry?:
22
+ | {
23
+ maxAttempts: number;
24
+ backoffRate?: number;
25
+ intervalSeconds?: number;
26
+ maxTimeoutSeconds?: number;
27
+ }
28
+ | false;
29
+ }
30
+
31
+ export class Workflow<TInput extends ZodType = never> implements Component {
32
+ constructor(private id: string, config: WorkflowConfig<TInput>) {
33
+ const { run, input, link, ...rest } = config;
34
+ const links = link?.map((component) => component.__id()) || [];
35
+ const tracer = trace.getTracer("ocel/workflow");
36
+
37
+ const handler = (originalEvent: any, originalFnContext: any) => {
38
+ return withDurableExecution(async (event, durableCtx) => {
39
+ const ctx = durableCtx as any;
40
+ const isReplay = ctx.durableExecutionMode === "ReplayMode";
41
+ const ops = originalEvent.InitialExecutionState?.Operations || [];
42
+
43
+ const stepCompleted = (name: string) => {
44
+ return (
45
+ isReplay &&
46
+ ops.some((op: any) => op.Name === name && op.Status === "SUCCEEDED")
47
+ );
48
+ };
49
+
50
+ return tracer.startActiveSpan(
51
+ isReplay ? `workflow-${id}-replay` : `workflow-${id}`,
52
+ {
53
+ attributes: { workflow_id: id },
54
+ },
55
+ async (rootSpan) => {
56
+ // override context to add tracing
57
+ const instrumentedCtx = new Proxy(durableCtx, {
58
+ get(target, prop, receiver) {
59
+ if (prop === "step") {
60
+ return async (
61
+ name: string,
62
+ fn: () => Promise<any>,
63
+ opts: any
64
+ ) => {
65
+ if (stepCompleted(name)) {
66
+ return target.step(name, fn, opts);
67
+ }
68
+
69
+ return tracer.startActiveSpan(
70
+ `step: ${name}`,
71
+ async (stepSpan) => {
72
+ try {
73
+ console.info(
74
+ `stepSpan isRecording=${stepSpan.isRecording()} spanContext=${JSON.stringify(
75
+ stepSpan.spanContext()
76
+ )}`
77
+ );
78
+
79
+ const result = await target.step(name, fn, opts);
80
+ stepSpan.setStatus({ code: SpanStatusCode.OK });
81
+ return result;
82
+ } catch (err) {
83
+ // Only record legitimate errors, not Durable Suspend/Sleep interruptions
84
+ // (You can filter specifically if you know the Suspend error type)
85
+ stepSpan.recordException(err as Error);
86
+ stepSpan.setStatus({ code: SpanStatusCode.ERROR });
87
+ throw err;
88
+ } finally {
89
+ stepSpan.end();
90
+ }
91
+ }
92
+ );
93
+ };
94
+ }
95
+
96
+ if (prop === "wait") {
97
+ return async (name: string, opts: any) => {
98
+ if (stepCompleted(name)) {
99
+ return target.wait(name, opts);
100
+ }
101
+
102
+ return tracer.startActiveSpan(
103
+ `wait: ${name}`,
104
+ async (waitSpan) => {
105
+ console.info(
106
+ `waitSpan isRecording=${waitSpan.isRecording()} spanContext=${JSON.stringify(
107
+ waitSpan.spanContext()
108
+ )}`
109
+ );
110
+
111
+ waitSpan.setAttribute("duration_seconds", opts.seconds);
112
+ waitSpan.setAttribute("workflow.status", "suspending");
113
+
114
+ console.log(
115
+ "[OTEL-FIX] Closing spans before suspend..."
116
+ );
117
+
118
+ waitSpan.setStatus({ code: SpanStatusCode.OK });
119
+ waitSpan.end();
120
+
121
+ rootSpan.setAttribute("workflow.suspend_trigger", name);
122
+ rootSpan.setStatus({ code: SpanStatusCode.OK });
123
+ rootSpan.end();
124
+
125
+ const provider = trace.getTracerProvider();
126
+ // @ts-ignore
127
+ if (typeof provider.forceFlush === "function") {
128
+ // @ts-ignore
129
+ await provider.forceFlush();
130
+ console.log("[OTEL-FIX] Flush complete.");
131
+ }
132
+
133
+ await target.wait(name, opts);
134
+ }
135
+ );
136
+ };
137
+ }
138
+
139
+ // Default: Return the original property (e.g., ctx.runId, ctx.now)
140
+ return Reflect.get(target, prop, receiver);
141
+ },
142
+ });
143
+
144
+ console.log("eventss", {
145
+ originalEvent,
146
+ originalFnContext,
147
+ });
148
+
149
+ const executionArn = originalEvent.DurableExecutionArn;
150
+ const parts = executionArn.split("/");
151
+ const executionId = `${parts[parts.length - 2]}/${
152
+ parts[parts.length - 1]
153
+ }`;
154
+
155
+ console.log("execution details", {
156
+ event,
157
+ originalCtx: durableCtx,
158
+ executionArn,
159
+ executionId,
160
+ });
161
+
162
+ rootSpan.setAttribute("execution_id", executionId);
163
+
164
+ try {
165
+ console.info(
166
+ `startRootSpan isRecording=${rootSpan.isRecording()} spanContext=${JSON.stringify(
167
+ rootSpan.spanContext()
168
+ )}`
169
+ );
170
+
171
+ if (input) {
172
+ const parsed = input.parse(event);
173
+ await run({ input: parsed, ctx: instrumentedCtx });
174
+ } else {
175
+ await run({ ctx: instrumentedCtx, input: event });
176
+ }
177
+
178
+ rootSpan.setStatus({ code: 1 });
179
+ } catch (error) {
180
+ console.log("Error in workflow:", error);
181
+ throw error;
182
+ } finally {
183
+ console.log("Ending span for workflow:", id);
184
+ console.log("Span details:", rootSpan);
185
+
186
+ if (rootSpan.isRecording()) {
187
+ console.log("Ending root span (normal exit)");
188
+ rootSpan.end();
189
+ }
190
+ }
191
+ }
192
+ );
193
+ })(originalEvent, originalFnContext);
194
+ };
195
+
196
+ // @ts-expect-error - accessed by cli during build
197
+ this.__handler = handler;
198
+ // @ts-expect-error - accessed by cli during build
199
+ this[Symbol.for("ocel:lambda:id")] = id;
200
+
201
+ if (process.env.OCEL_PHASE === "discovery") {
202
+ const source = getCallerFile();
203
+
204
+ if (!source) {
205
+ throw new Error(
206
+ "Could not determine caller file for workflow registration"
207
+ );
208
+ }
209
+
210
+ rpc.register({
211
+ id: this.id,
212
+ type: "workflow",
213
+ source,
214
+ config: { ...rest, links },
215
+ });
216
+ }
217
+ }
218
+
219
+ __id() {
220
+ return this.id;
221
+ }
222
+
223
+ trigger(
224
+ payload: [TInput] extends [never] ? {} : z.infer<TInput>,
225
+ opts?: {
226
+ idempotencyKey?: string;
227
+ }
228
+ ) {}
229
+
230
+ batchTrigger() {}
231
+
232
+ triggerAndWait() {}
233
+
234
+ batchTriggerAndWait() {}
235
+ }