@blokjs/helper 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,426 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Blok Workflow v2",
4
+ "description": "Schema for Blok v2 JSON workflows. Steps inline their `inputs`; output auto-persists to ctx.state[id]. Use the `branch` step shape for if/else flow control. See https://blok.build/docs/workflow-v2 for full reference.",
5
+ "type": "object",
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "minLength": 3,
10
+ "description": "Workflow display name. Min 3 characters. Shown in Studio."
11
+ },
12
+ "version": {
13
+ "type": "string",
14
+ "minLength": 5,
15
+ "description": "Semantic version (x.x.x). Used for trace recording and audit."
16
+ },
17
+ "description": {
18
+ "type": "string",
19
+ "description": "What this workflow does. Optional but recommended — surfaces in Studio and CLI."
20
+ },
21
+ "trigger": {
22
+ "type": "object",
23
+ "additionalProperties": {},
24
+ "propertyNames": {
25
+ "enum": [
26
+ "http",
27
+ "grpc",
28
+ "manual",
29
+ "cron",
30
+ "queue",
31
+ "pubsub",
32
+ "worker",
33
+ "webhook",
34
+ "sse",
35
+ "websocket"
36
+ ]
37
+ },
38
+ "description": "Trigger configuration. Most workflows use { http: { method: 'GET' } }. See TRIGGER_SCHEMAS for per-kind shapes."
39
+ },
40
+ "steps": {
41
+ "type": "array",
42
+ "items": {
43
+ "anyOf": [
44
+ {
45
+ "type": "object",
46
+ "properties": {
47
+ "id": {
48
+ "type": "string",
49
+ "minLength": 1,
50
+ "description": "Stable identifier for the branch step. Visible in traces."
51
+ },
52
+ "branch": {
53
+ "type": "object",
54
+ "properties": {
55
+ "when": {
56
+ "type": "string",
57
+ "minLength": 1,
58
+ "description": "JavaScript expression. Truthy → run `then` branch; falsy → run `else` branch. $ proxy expressions compile to strings at the call site (e.g. $.req.query.kind === 'true')."
59
+ },
60
+ "then": {
61
+ "type": "array",
62
+ "items": {},
63
+ "description": "Steps to execute when `when` is truthy."
64
+ },
65
+ "else": {
66
+ "type": "array",
67
+ "items": {},
68
+ "description": "Steps to execute when `when` is falsy. Optional."
69
+ }
70
+ },
71
+ "required": [
72
+ "when",
73
+ "then"
74
+ ],
75
+ "additionalProperties": false,
76
+ "description": "Conditional sub-pipeline."
77
+ },
78
+ "active": {
79
+ "type": "boolean"
80
+ },
81
+ "stop": {
82
+ "type": "boolean"
83
+ }
84
+ },
85
+ "required": [
86
+ "id",
87
+ "branch"
88
+ ],
89
+ "additionalProperties": false
90
+ },
91
+ {
92
+ "type": "object",
93
+ "properties": {
94
+ "id": {
95
+ "type": "string",
96
+ "minLength": 1,
97
+ "description": "Stable identifier. The sub-workflow's output lands on $.state[id] after the child completes. Required."
98
+ },
99
+ "subworkflow": {
100
+ "type": "string",
101
+ "minLength": 1,
102
+ "description": "Name of the workflow to invoke. Looked up in the WorkflowRegistry at run time — must match the `name:` field of an HTTP-loaded or manually-registered workflow."
103
+ },
104
+ "inputs": {
105
+ "type": "object",
106
+ "additionalProperties": {},
107
+ "description": "Inputs passed to the child as `ctx.request.body`. The child reads them via `$.req.body.<key>` exactly as if HTTP-triggered. May contain $ proxy refs."
108
+ },
109
+ "wait": {
110
+ "type": "boolean",
111
+ "description": "If true (default), parent step blocks until child completes and the child's ctx.response becomes the parent step's output. If false, dispatch is fire-and-forget — the parent step returns immediately with `{runId, workflowName, scheduledAt}` and the child runs asynchronously via setImmediate. The child still appears in Studio's Sub-runs strip and the parentRunId/parentNodeRunId lineage is preserved. Combine with `idempotencyKey` for at-most-once dispatch (Trigger.dev / Stripe semantics: the runId is cached against the key regardless of child outcome; new key needed to retry on failure)."
112
+ },
113
+ "as": {
114
+ "type": "string",
115
+ "minLength": 1,
116
+ "description": "Alternative state key (defaults to id). Mutually exclusive with spread."
117
+ },
118
+ "spread": {
119
+ "type": "boolean",
120
+ "description": "Shallow-merge child's response keys into state. Mutually exclusive with as."
121
+ },
122
+ "ephemeral": {
123
+ "type": "boolean",
124
+ "description": "If true, child output is NOT stored in state. Only ctx.prev carries it."
125
+ },
126
+ "active": {
127
+ "type": "boolean",
128
+ "description": "If false, the step is skipped at runtime. Default true."
129
+ },
130
+ "stop": {
131
+ "type": "boolean",
132
+ "description": "If true, the workflow halts after this step completes."
133
+ },
134
+ "idempotencyKey": {
135
+ "type": "string",
136
+ "minLength": 1,
137
+ "description": "When set, the sub-workflow's parent step output is cached against the triple (parentWorkflow, step.id, key). Cache semantics depend on `wait`: with `wait: true` (default), cache HIT means the child workflow is NEVER invoked — including any side effects (use with care for sub-workflows that send emails, charge cards, etc.). With `wait: false`, cache HIT returns the SAME `{runId, workflowName, scheduledAt}` for the lifetime of the cache entry — at-most-once dispatch deduplication. To retry on child failure, use a new key."
138
+ },
139
+ "idempotencyKeyTTL": {
140
+ "type": "integer",
141
+ "minimum": 0,
142
+ "description": "Cache lifetime in milliseconds. Defaults to 24h. Pass 0 to immediately expire."
143
+ },
144
+ "retry": {
145
+ "type": "object",
146
+ "properties": {
147
+ "maxAttempts": {
148
+ "type": "integer",
149
+ "minimum": 1,
150
+ "maximum": 20,
151
+ "description": "Total attempts including the first run. 1 = no retry. Capped at 20."
152
+ },
153
+ "minTimeoutInMs": {
154
+ "type": "integer",
155
+ "minimum": 0,
156
+ "description": "Initial backoff delay in ms before the second attempt. Default 1000."
157
+ },
158
+ "maxTimeoutInMs": {
159
+ "type": "integer",
160
+ "minimum": 0,
161
+ "description": "Cap on the backoff delay between attempts. Default 30000."
162
+ },
163
+ "factor": {
164
+ "type": "number",
165
+ "minimum": 1,
166
+ "description": "Exponential backoff factor: delay = min(maxTimeout, minTimeout * factor^(attempt-1)). Default 2."
167
+ }
168
+ },
169
+ "required": [
170
+ "maxAttempts"
171
+ ],
172
+ "additionalProperties": false,
173
+ "description": "Retry the WHOLE sub-workflow on failure. Each retry creates a fresh child run record under the same parent."
174
+ },
175
+ "maxDuration": {
176
+ "anyOf": [
177
+ {
178
+ "type": "integer",
179
+ "minimum": 0
180
+ },
181
+ {
182
+ "type": "string",
183
+ "minLength": 1,
184
+ "pattern": "^\\d+(ms|s|m|h|d)$"
185
+ }
186
+ ],
187
+ "description": "OPTIONAL. Per-attempt execution timeout. Caps the synchronous wait for `wait: true` sub-workflows. No-op for `wait: false` (parent returns immediately; the child's max-duration is the child's concern). Number (ms) or duration string. On final-attempt timeout, the run auto-flips to `\"timedOut\"`."
188
+ }
189
+ },
190
+ "required": [
191
+ "id",
192
+ "subworkflow"
193
+ ],
194
+ "additionalProperties": false
195
+ },
196
+ {
197
+ "type": "object",
198
+ "properties": {
199
+ "id": {
200
+ "type": "string",
201
+ "minLength": 1,
202
+ "description": "Stable identifier."
203
+ },
204
+ "wait": {
205
+ "type": "object",
206
+ "properties": {
207
+ "for": {
208
+ "anyOf": [
209
+ {
210
+ "type": "integer",
211
+ "minimum": 0
212
+ },
213
+ {
214
+ "type": "string",
215
+ "minLength": 1,
216
+ "pattern": "^\\d+(ms|s|m|h|d)$"
217
+ }
218
+ ],
219
+ "description": "Wait this long. Mutually exclusive with `until`. Number (ms) or duration string (`500ms`, `30s`, `5m`, `2h`, `1d`)."
220
+ },
221
+ "until": {
222
+ "type": [
223
+ "number",
224
+ "string"
225
+ ],
226
+ "description": "Wait until this absolute time. Number is ms-since-epoch; string is an ISO date or a $-proxy expression. Mutually exclusive with `for`."
227
+ }
228
+ },
229
+ "additionalProperties": false
230
+ },
231
+ "as": {
232
+ "type": "string",
233
+ "minLength": 1,
234
+ "description": "Alternative state key (defaults to `id`)."
235
+ },
236
+ "ephemeral": {
237
+ "type": "boolean",
238
+ "description": "If true, no state entry is recorded."
239
+ },
240
+ "active": {
241
+ "type": "boolean"
242
+ },
243
+ "stop": {
244
+ "type": "boolean"
245
+ },
246
+ "idempotencyKey": {
247
+ "not": {}
248
+ },
249
+ "retry": {
250
+ "not": {}
251
+ },
252
+ "maxDuration": {
253
+ "not": {}
254
+ },
255
+ "concurrencyKey": {
256
+ "not": {}
257
+ },
258
+ "spread": {
259
+ "not": {}
260
+ }
261
+ },
262
+ "required": [
263
+ "id",
264
+ "wait"
265
+ ],
266
+ "additionalProperties": false
267
+ },
268
+ {
269
+ "type": "object",
270
+ "properties": {
271
+ "id": {
272
+ "type": "string",
273
+ "minLength": 1,
274
+ "description": "Stable identifier. Other steps reference this step's output as $.state[id]. Required."
275
+ },
276
+ "use": {
277
+ "type": "string",
278
+ "minLength": 1,
279
+ "description": "Node reference. Examples: '@blokjs/api-call', 'my-custom-node'. Type is inferred from this value when `type` is not set."
280
+ },
281
+ "type": {
282
+ "type": "string",
283
+ "enum": [
284
+ "local",
285
+ "module",
286
+ "runtime.python3",
287
+ "runtime.nodejs",
288
+ "runtime.bun",
289
+ "runtime.go",
290
+ "runtime.java",
291
+ "runtime.rust",
292
+ "runtime.php",
293
+ "runtime.csharp",
294
+ "runtime.ruby",
295
+ "runtime.docker",
296
+ "runtime.wasm"
297
+ ],
298
+ "description": "Node type (module/local/runtime.*). When omitted, inferred from `use`: runtime.* prefixes are explicit; @blokjs/* and most others default to 'module'."
299
+ },
300
+ "inputs": {
301
+ "type": "object",
302
+ "additionalProperties": {},
303
+ "description": "Inputs passed to the node. May contain $ proxy references (e.g. $.state.foo, $.req.body.id) or 'js/...' expressions for runtime evaluation."
304
+ },
305
+ "as": {
306
+ "type": "string",
307
+ "minLength": 1,
308
+ "description": "Alternative name for this step's output in state. Defaults to `id`. Useful when the id is implementation-detail-y and the output is referenced by a domain term."
309
+ },
310
+ "spread": {
311
+ "type": "boolean",
312
+ "description": "If true, the result.data object's top-level keys are shallow-merged into state. Use for multi-output nodes in data-pipeline workflows. Mutually exclusive with `as`."
313
+ },
314
+ "ephemeral": {
315
+ "type": "boolean",
316
+ "description": "If true, this step's output is NOT stored in state. Only ctx.prev carries it to the immediately next step. Use for side-effects (logging, audit, telemetry)."
317
+ },
318
+ "runtime": {
319
+ "type": "string",
320
+ "enum": [
321
+ "nodejs",
322
+ "bun",
323
+ "python3",
324
+ "go",
325
+ "java",
326
+ "rust",
327
+ "php",
328
+ "csharp",
329
+ "ruby",
330
+ "docker",
331
+ "wasm"
332
+ ],
333
+ "description": "Optional runtime hint. Most authors don't need this; the type already encodes it."
334
+ },
335
+ "active": {
336
+ "type": "boolean",
337
+ "description": "If false, the step is skipped at runtime. Default true."
338
+ },
339
+ "stop": {
340
+ "type": "boolean",
341
+ "description": "If true, the workflow halts after this step completes. Default false."
342
+ },
343
+ "stream_logs": {
344
+ "type": "boolean",
345
+ "description": "Per-step opt-in for live log streaming. Inherits from BLOK_STREAM_LOGS env when unset."
346
+ },
347
+ "idempotencyKey": {
348
+ "type": "string",
349
+ "minLength": 1,
350
+ "description": "When set, the step's result is cached against the triple (workflowName, step.id, idempotencyKey). On a subsequent run with the same triple, execution is skipped and the cached result populates state through the same persistence rules (ephemeral / spread / as). Accepts a literal string or a $ proxy expression that compiles to `js/ctx....` (e.g. $.req.body.requestId)."
351
+ },
352
+ "idempotencyKeyTTL": {
353
+ "type": "integer",
354
+ "minimum": 0,
355
+ "description": "Cache lifetime in milliseconds. Defaults to 24h (86_400_000) when omitted. Pass 0 to mark a cached result as immediately expired (effectively disables caching)."
356
+ },
357
+ "retry": {
358
+ "type": "object",
359
+ "properties": {
360
+ "maxAttempts": {
361
+ "type": "integer",
362
+ "minimum": 1,
363
+ "maximum": 20,
364
+ "description": "Total attempts including the first run. 1 = no retry. Capped at 20."
365
+ },
366
+ "minTimeoutInMs": {
367
+ "type": "integer",
368
+ "minimum": 0,
369
+ "description": "Initial backoff delay in ms before the second attempt. Default 1000."
370
+ },
371
+ "maxTimeoutInMs": {
372
+ "type": "integer",
373
+ "minimum": 0,
374
+ "description": "Cap on the backoff delay between attempts. Default 30000."
375
+ },
376
+ "factor": {
377
+ "type": "number",
378
+ "minimum": 1,
379
+ "description": "Exponential backoff factor: delay = min(maxTimeout, minTimeout * factor^(attempt-1)). Default 2."
380
+ }
381
+ },
382
+ "required": [
383
+ "maxAttempts"
384
+ ],
385
+ "additionalProperties": false,
386
+ "description": "Retry configuration with capped exponential backoff. When omitted, the step runs at most once (no retry) — matches pre-v0.3.x behavior."
387
+ },
388
+ "maxDuration": {
389
+ "anyOf": [
390
+ {
391
+ "type": "integer",
392
+ "minimum": 0
393
+ },
394
+ {
395
+ "type": "string",
396
+ "minLength": 1,
397
+ "pattern": "^\\d+(ms|s|m|h|d)$"
398
+ }
399
+ ],
400
+ "description": "OPTIONAL. Per-attempt execution timeout. Number (ms) or duration string ('30s', '5m', '500ms'). When the step's `step.process()` exceeds this duration, the attempt fails with a StepTimeoutError. Pairs with `retry` — each attempt gets its own timeout (total budget = maxDuration × maxAttempts). On final-attempt timeout, the run auto-flips to `\"timedOut\"` status (distinct from `\"failed\"` so SLA dashboards can separate timeouts from logic failures)."
401
+ },
402
+ "set_var": {
403
+ "type": "boolean",
404
+ "description": "@deprecated v2 default-stores every step's output. `set_var: true` is a no-op; `set_var: false` is normalized to `ephemeral: true`."
405
+ }
406
+ },
407
+ "required": [
408
+ "id",
409
+ "use"
410
+ ],
411
+ "additionalProperties": false
412
+ }
413
+ ]
414
+ },
415
+ "minItems": 1,
416
+ "description": "Pipeline of steps to execute in order. At least one step required."
417
+ }
418
+ },
419
+ "required": [
420
+ "name",
421
+ "version",
422
+ "trigger",
423
+ "steps"
424
+ ],
425
+ "additionalProperties": false
426
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blokjs/helper",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -8,14 +8,13 @@
8
8
  "type": "module",
9
9
  "main": "dist/index.js",
10
10
  "types": "dist/index.d.ts",
11
- "files": [
12
- "dist"
13
- ],
11
+ "files": ["dist"],
14
12
  "author": "Marco A. Castillo Della Sera",
15
13
  "license": "MIT",
16
14
  "scripts": {
17
- "build": "rm -rf dist && bun run tsc",
15
+ "build": "rm -rf dist && bun run tsc && bun run build:schema",
18
16
  "build:dev": "tsc --watch",
17
+ "build:schema": "bun run scripts/build-schema.ts",
19
18
  "test:dev": "vitest",
20
19
  "test": "vitest run",
21
20
  "typecheck": "tsc --noEmit --incremental"
@@ -23,7 +22,8 @@
23
22
  "devDependencies": {
24
23
  "@types/node": "^22.15.21",
25
24
  "typescript": "^5.8.3",
26
- "vitest": "^4.0.18"
25
+ "vitest": "^4.0.18",
26
+ "zod-to-json-schema": "^3.25.2"
27
27
  },
28
28
  "dependencies": {
29
29
  "zod": "^3.24.2"