@pikku/inspector 0.12.9 → 0.12.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
+ ## 0.12.11
2
+
3
+ ### Patch Changes
4
+
5
+ - 033d172: Log a critical inspector error when multiple functions resolve to the same `pikku` function name, instead of silently allowing routing map collisions. This may cause builds to fail if multiple functions previously resolved to the same `pikku` function name.
6
+ - Updated dependencies [b9ed73e]
7
+ - @pikku/core@0.12.19
8
+
1
9
  ## 0.12.0
2
10
 
11
+ ## 0.12.10
12
+
13
+ ### Patch Changes
14
+
15
+ - ba8d6ff: Support inline functions in pikkuWorkflowComplexFunc with full DSL extraction
16
+ - d3ace0e: Inspector now captures the `deploy: 'serverless' | 'server' | 'auto'` option
17
+ from `pikkuFunc` / `pikkuSessionlessFunc` calls, alongside the other runtime
18
+ metadata (`expose`, `remote`, `mcp`, `readonly`, `approvalRequired`).
19
+
20
+ Previously this field was defined on `FunctionRuntimeMeta` but never read
21
+ from the user's source, so `deploy: 'server'` was silently dropped. That
22
+ left downstream consumers — notably `@pikku/cli`'s deployment analyzer,
23
+ which routes server-targeted functions to a container unit — treating
24
+ every function as `serverless` regardless of its declared intent.
25
+
26
+ - Updated dependencies [311c0c4]
27
+ - @pikku/core@0.12.18
28
+
3
29
  ## 0.12.9
4
30
 
5
31
  ### Patch Changes
@@ -14,7 +40,6 @@
14
40
  ### Patch Changes
15
41
 
16
42
  - 624097e: Add deploy pipeline with provider-agnostic architecture
17
-
18
43
  - Add MetaService with explicit typed API, absorb WiringService reads
19
44
  - Add deployment service, traceId propagation, scoped logger
20
45
  - Rewrite analyzer: one function = one worker, gateways dispatch via RPC
@@ -61,7 +86,6 @@
61
86
 
62
87
  - 5866b66: Add critical error (PKU490) when Zod schemas and wiring calls (wireHTTPRoutes, addPermission, addHTTPMiddleware) coexist in the same file. The CLI uses tsImport to extract Zod schemas at runtime, which executes all top-level code — wiring side-effects crash in this context because pikku state metadata doesn't exist. Schemas and wirings must be in separate files.
63
88
  - e412b4d: Optimize CLI codegen performance: 12x faster `pikku all`
64
-
65
89
  - Reuse schemas across re-inspections (skip redundant `ts-json-schema-generator` runs)
66
90
  - Cache TS schemas to disk (`.pikku/schema-cache.json`) for cross-run reuse
67
91
  - Pass `oldProgram` to `ts.createProgram` for incremental TS compilation
@@ -190,14 +214,12 @@
190
214
  - 1967172: Update code generation to support channel middleware enhancements
191
215
 
192
216
  **Code Generation Updates:**
193
-
194
217
  - Update channel type serialization to include middleware support
195
218
  - Improve WebSocket wrapper generation for middleware handling
196
219
  - Update CLI channel client generation with better type support
197
220
  - Enhance services and schema generation for channel configurations
198
221
 
199
222
  **Inspector Updates:**
200
-
201
223
  - Improve channel metadata extraction for middleware
202
224
  - Better type analysis for channel lifecycle functions
203
225
  - Enhanced post-processing for channel configurations
@@ -205,19 +227,16 @@
205
227
  - 753481a: Add bootstrap command, performance optimizations, and CLI improvements
206
228
 
207
229
  **New Features:**
208
-
209
230
  - Add `pikku bootstrap` command for type-only generation (~13.5% faster than `pikku all`)
210
231
  - Add configurable `ignoreFiles` option to pikku.config.json with sensible defaults (_.gen.ts, _.test.ts, \*.spec.ts)
211
232
  - Export pikkuCLIRender helper from serialize-cli-types.ts with JSDoc documentation
212
233
 
213
234
  **Performance Improvements:**
214
-
215
235
  - Add aggressive TypeScript compiler options (skipDefaultLibCheck, types: []) - ~37% faster TypeScript setup
216
236
  - Add detailed performance timing to inspector phases (--logLevel=debug)
217
237
  - Optimize file inspection with ignore patterns - ~10-20% faster overall
218
238
 
219
239
  **Enhancements:**
220
-
221
240
  - Fix --logLevel flag to properly apply log level to logger
222
241
  - Update middleware logging to use structured log format
223
242
  - Improve CLI renderers to consistently use destructured logger service
@@ -318,7 +337,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
318
337
  ### Patch Changes
319
338
 
320
339
  - 44e3ff4: feat: enhance CLI filtering with type and directory filters
321
-
322
340
  - Add --types filter to filter by PikkuEventTypes (http, channel, queue, scheduler, rpc, mcp)
323
341
  - Add --directories filter to filter by file paths/directories
324
342
  - All filters (tags, types, directories) now work together with AND logic
@@ -329,7 +347,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
329
347
  - 7c592b8: feat: support for required services and improved service configuration
330
348
 
331
349
  This release includes several enhancements to service management and configuration:
332
-
333
350
  - Added support for required services configuration
334
351
  - Improved service discovery and registration
335
352
  - Added typed RPC clients for service communication
@@ -3,7 +3,7 @@ import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
3
3
  import { extractFunctionName, funcIdToTypeName, } from '../utils/extract-function-name.js';
4
4
  import { extractFunctionNode } from '../utils/extract-function-node.js';
5
5
  import { extractUsedWires } from '../utils/extract-services.js';
6
- import { formatVersionedId } from '@pikku/core';
6
+ import { formatVersionedId, parseVersionedId } from '@pikku/core';
7
7
  import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
8
8
  import { resolveMiddleware } from '../utils/middleware.js';
9
9
  import { resolvePermissions } from '../utils/permissions.js';
@@ -209,6 +209,19 @@ function unwrapPromise(checker, type) {
209
209
  }
210
210
  return type;
211
211
  }
212
+ const resolveExistingFunctionSource = (state, pikkuFuncId) => {
213
+ return (state.functions.meta[pikkuFuncId]?.sourceFile ||
214
+ state.rpc.internalFiles.get(pikkuFuncId)?.path ||
215
+ null);
216
+ };
217
+ const areCompatibleFunctionIds = (existingId, incomingId) => {
218
+ if (existingId === incomingId) {
219
+ return true;
220
+ }
221
+ const existingParsed = parseVersionedId(existingId);
222
+ const incomingParsed = parseVersionedId(incomingId);
223
+ return existingParsed.baseName === incomingParsed.baseName;
224
+ };
212
225
  /**
213
226
  * Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
214
227
  * then push into state.functions.meta.
@@ -245,6 +258,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
245
258
  let remote;
246
259
  let mcp;
247
260
  let readonly_;
261
+ let deploy;
248
262
  let approvalRequired;
249
263
  let approvalDescription;
250
264
  let version;
@@ -312,6 +326,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
312
326
  remote = getPropertyValue(firstArg, 'remote');
313
327
  mcp = getPropertyValue(firstArg, 'mcp');
314
328
  readonly_ = getPropertyValue(firstArg, 'readonly');
329
+ deploy = getPropertyValue(firstArg, 'deploy');
315
330
  approvalRequired = getPropertyValue(firstArg, 'approvalRequired');
316
331
  // Extract approvalDescription identifier reference
317
332
  for (const prop of firstArg.properties) {
@@ -398,6 +413,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
398
413
  pikkuFuncId = formatVersionedId(baseName, version);
399
414
  }
400
415
  const isMCPToolFunc = expression.text === 'pikkuMCPToolFunc';
416
+ const isListFunc = expression.text === 'pikkuListFunc';
401
417
  const mcpEnabled = mcp || isMCPToolFunc;
402
418
  // Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
403
419
  const handler = resolvedFunc &&
@@ -459,7 +475,22 @@ export const addFunctions = (logger, node, checker, state, options) => {
459
475
  state.schemaLookup.set(schemaName, inputSchemaRef);
460
476
  state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
461
477
  }
462
- else if (genericTypes.length >= 1 && genericTypes[0]) {
478
+ else if (isListFunc && genericTypes.length >= 1 && genericTypes[0]) {
479
+ const inputAliasName = `${capitalizedName}Input`;
480
+ const filterType = genericTypes[0];
481
+ const filterTypeText = checker.typeToString(filterType, undefined, ts.TypeFormatFlags.NoTruncation);
482
+ const refs = resolveTypeImports(filterType, state.functions.typesMap, true, checker);
483
+ state.functions.typesMap.addCustomType(inputAliasName, `{ cursor?: string; limit?: number; sort?: Array<{ column: string; direction: 'asc' | 'desc' }>; filter?: unknown; search?: string; } & ${filterTypeText}`, [...new Set(refs)]);
484
+ inputNames = [inputAliasName];
485
+ const secondParam = handler.parameters[1];
486
+ if (secondParam) {
487
+ inputTypes = [checker.getTypeAtLocation(secondParam)];
488
+ }
489
+ else {
490
+ inputTypes = [filterType];
491
+ }
492
+ }
493
+ else if (!isListFunc && genericTypes.length >= 1 && genericTypes[0]) {
463
494
  // Fall back to extracting from generic type arguments
464
495
  const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
465
496
  inputNames = result.names;
@@ -483,7 +514,15 @@ export const addFunctions = (logger, node, checker, state, options) => {
483
514
  state.schemaLookup.set(schemaName, outputSchemaRef);
484
515
  state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
485
516
  }
486
- else if (genericTypes.length >= 2) {
517
+ else if (isListFunc && genericTypes.length >= 2 && genericTypes[1]) {
518
+ const outputAliasName = `${capitalizedName}Output`;
519
+ const rowType = genericTypes[1];
520
+ const rowTypeText = checker.typeToString(rowType, undefined, ts.TypeFormatFlags.NoTruncation);
521
+ const refs = resolveTypeImports(rowType, state.functions.typesMap, true, checker);
522
+ state.functions.typesMap.addCustomType(outputAliasName, `{ rows: Array<${rowTypeText}>; nextCursor: string | null; totalCount?: number; }`, [...new Set(refs)]);
523
+ outputNames = [outputAliasName];
524
+ }
525
+ else if (!isListFunc && genericTypes.length >= 2) {
487
526
  outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', name, genericTypes[1]).names;
488
527
  }
489
528
  else {
@@ -537,6 +576,36 @@ export const addFunctions = (logger, node, checker, state, options) => {
537
576
  if (inputTypes.length > 0) {
538
577
  state.typesLookup.set(pikkuFuncId, inputTypes);
539
578
  }
579
+ const sourceFile = node.getSourceFile().fileName;
580
+ const existingFunction = state.functions.meta[pikkuFuncId];
581
+ if (existingFunction &&
582
+ existingFunction.sourceFile &&
583
+ existingFunction.sourceFile !== sourceFile) {
584
+ logger.critical(ErrorCode.DUPLICATE_FUNCTION_NAME, `Function name '${name}' is not unique. ` +
585
+ `'${pikkuFuncId}' is already defined in '${existingFunction.sourceFile}' and cannot be redefined in '${sourceFile}'.`);
586
+ return;
587
+ }
588
+ if (exportedName || explicitName) {
589
+ const existingInternal = state.rpc.internalMeta[name];
590
+ if (existingInternal &&
591
+ !areCompatibleFunctionIds(existingInternal, pikkuFuncId)) {
592
+ const existingSource = resolveExistingFunctionSource(state, existingInternal) || 'unknown file';
593
+ logger.critical(ErrorCode.DUPLICATE_FUNCTION_NAME, `Function name '${name}' is not unique. ` +
594
+ `It already points to '${existingInternal}' in '${existingSource}', but '${pikkuFuncId}' in '${sourceFile}' tried to use the same name.`);
595
+ return;
596
+ }
597
+ if (expose) {
598
+ const existingExposed = state.rpc.exposedMeta[name];
599
+ if (existingExposed &&
600
+ !areCompatibleFunctionIds(existingExposed, pikkuFuncId)) {
601
+ const existingSource = resolveExistingFunctionSource(state, existingExposed) ||
602
+ 'unknown file';
603
+ logger.critical(ErrorCode.DUPLICATE_FUNCTION_NAME, `Exposed function name '${name}' is not unique. ` +
604
+ `It already points to '${existingExposed}' in '${existingSource}', but '${pikkuFuncId}' in '${sourceFile}' tried to use the same name.`);
605
+ return;
606
+ }
607
+ }
608
+ }
540
609
  // --- resolve middleware ---
541
610
  let middleware = objectNode
542
611
  ? resolveMiddleware(state, objectNode, tags, checker)
@@ -583,6 +652,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
583
652
  remote: remote || undefined,
584
653
  mcp: mcpEnabled || undefined,
585
654
  readonly: readonly_ || undefined,
655
+ deploy: deploy || undefined,
586
656
  approvalRequired: approvalRequired || undefined,
587
657
  approvalDescription: approvalDescription || undefined,
588
658
  version,
@@ -594,7 +664,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
594
664
  middleware,
595
665
  permissions,
596
666
  isDirectFunction,
597
- sourceFile: node.getSourceFile().fileName,
667
+ sourceFile,
598
668
  exportedName: exportedName || undefined,
599
669
  };
600
670
  // Populate node metadata if node config is present
@@ -7,6 +7,7 @@ import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
7
7
  import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js';
8
8
  import { extractWireNames } from '../utils/post-process.js';
9
9
  import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
10
+ import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
10
11
  import { ErrorCode } from '../error-codes.js';
11
12
  import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
12
13
  import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
@@ -113,12 +114,30 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
113
114
  if (funcName.startsWith('__temp_')) {
114
115
  funcName = makeContextBasedId('http', method, fullRoute);
115
116
  }
117
+ let refAddonTarget = null;
118
+ if (ts.isCallExpression(funcInitializer) &&
119
+ ts.isIdentifier(funcInitializer.expression) &&
120
+ funcInitializer.expression.text === 'ref') {
121
+ const [firstArg] = funcInitializer.arguments;
122
+ if (firstArg &&
123
+ ts.isStringLiteral(firstArg) &&
124
+ firstArg.text.includes(':')) {
125
+ refAddonTarget = firstArg.text;
126
+ }
127
+ }
116
128
  const packageName = ts.isIdentifier(funcInitializer)
117
129
  ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
118
130
  : null;
131
+ if (refAddonTarget) {
132
+ const targetMeta = resolveFunctionMeta(state, refAddonTarget);
133
+ if (!targetMeta) {
134
+ logger.warn(`Skipping route '${fullRoute}': addon function metadata for '${refAddonTarget}' is not available yet.`);
135
+ return;
136
+ }
137
+ }
119
138
  ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
120
139
  // Lookup existing function metadata
121
- const fnMeta = state.functions.meta[funcName];
140
+ const fnMeta = resolveFunctionMeta(state, funcName);
122
141
  if (!fnMeta) {
123
142
  logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${funcName}'.`);
124
143
  return;
@@ -5,7 +5,7 @@ import type { WorkflowStepMeta } from '@pikku/core/workflow';
5
5
  */
6
6
  export declare function collectInvokedRPCs(steps: WorkflowStepMeta[], rpcs: Set<string>): void;
7
7
  /**
8
- * Inspector for pikkuWorkflow() and pikkuSimpleWorkflow() calls
8
+ * Inspector for pikkuWorkflowFunc() and pikkuWorkflowComplexFunc() calls
9
9
  * Detects workflow registration and extracts metadata
10
10
  */
11
11
  export declare const addWorkflow: AddWiring;
@@ -142,7 +142,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
142
142
  });
143
143
  }
144
144
  /**
145
- * Inspector for pikkuWorkflow() and pikkuSimpleWorkflow() calls
145
+ * Inspector for pikkuWorkflowFunc() and pikkuWorkflowComplexFunc() calls
146
146
  * Detects workflow registration and extracts metadata
147
147
  */
148
148
  export const addWorkflow = (logger, node, checker, state) => {
@@ -160,7 +160,7 @@ export const addWorkflow = (logger, node, checker, state) => {
160
160
  wrapperType = 'dsl';
161
161
  }
162
162
  else if (expression.text === 'pikkuWorkflowComplexFunc') {
163
- wrapperType = 'regular';
163
+ wrapperType = 'complex';
164
164
  }
165
165
  else {
166
166
  return;
@@ -220,7 +220,9 @@ export const addWorkflow = (logger, node, checker, state) => {
220
220
  let dsl = undefined;
221
221
  // Try DSL workflow extraction first
222
222
  // Pass the whole CallExpression node so findWorkflowFunction can find the arrow function
223
- const result = extractDSLWorkflow(node, checker);
223
+ const result = extractDSLWorkflow(node, checker, {
224
+ allowInline: wrapperType === 'complex',
225
+ });
224
226
  if (result.status === 'ok' && result.steps) {
225
227
  // Extraction succeeded
226
228
  steps = result.steps;
@@ -252,7 +254,7 @@ export const addWorkflow = (logger, node, checker, state) => {
252
254
  // For pikkuWorkflowFunc, this is a critical error
253
255
  // But still track RPC invocations for function registration
254
256
  getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
255
- logger.critical(ErrorCode.INVALID_DSL_WORKFLOW, `Workflow '${workflowName}' uses pikkuWorkflowFunc but does not conform to DSL workflow rules:\n${result.reason || 'Unknown error'}`);
257
+ logger.critical(ErrorCode.INVALID_DSL_WORKFLOW, `Workflow '${workflowName}' does not conform to DSL workflow rules:\n${result.reason || 'Unknown error'}`);
256
258
  return;
257
259
  }
258
260
  else {
@@ -261,12 +263,10 @@ export const addWorkflow = (logger, node, checker, state) => {
261
263
  dsl = false;
262
264
  }
263
265
  }
264
- /**
265
- * For non-dsl workflows or pikkuWorkflowComplexFunc, run basic extraction
266
- * to ensure all RPC invocations are tracked for function registration.
267
- * This catches RPCs in Promise.all callbacks and other patterns DSL can't extract.
268
- */
269
- if (!dsl || wrapperType === 'regular') {
266
+ // For pikkuWorkflowComplexFunc, also run basic extraction so RPCs in
267
+ // patterns the DSL extractor doesn't handle (array+push, nested Promise.all
268
+ // with identifier args, etc.) are still registered as invoked functions.
269
+ if (wrapperType === 'complex') {
270
270
  getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
271
271
  }
272
272
  state.workflows.meta[workflowName] = {
@@ -41,6 +41,7 @@ export declare enum ErrorCode {
41
41
  PERMISSION_EMPTY_ARRAY = "PKU937",
42
42
  PERMISSION_PATTERN_INVALID = "PKU975",
43
43
  DUPLICATE_FUNCTION_VERSION = "PKU850",
44
+ DUPLICATE_FUNCTION_NAME = "PKU851",
44
45
  MANIFEST_MISSING = "PKU860",
45
46
  FUNCTION_VERSION_MODIFIED = "PKU861",
46
47
  CONTRACT_CHANGED_REQUIRES_BUMP = "PKU862",
@@ -48,6 +48,7 @@ export var ErrorCode;
48
48
  ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
49
49
  // Versioning errors
50
50
  ErrorCode["DUPLICATE_FUNCTION_VERSION"] = "PKU850";
51
+ ErrorCode["DUPLICATE_FUNCTION_NAME"] = "PKU851";
51
52
  // Contract versioning errors
52
53
  ErrorCode["MANIFEST_MISSING"] = "PKU860";
53
54
  ErrorCode["FUNCTION_VERSION_MODIFIED"] = "PKU861";
@@ -20,6 +20,10 @@ export function extractStringLiteral(node, checker) {
20
20
  }
21
21
  return result;
22
22
  }
23
+ // Unwrap type assertions: `expr as Type` or `<Type>expr`
24
+ if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
25
+ return extractStringLiteral(node.expression, checker);
26
+ }
23
27
  // Try to evaluate constant identifiers
24
28
  if (ts.isIdentifier(node)) {
25
29
  const symbol = checker.getSymbolAtLocation(node);
@@ -258,10 +258,10 @@ async function generateZodSchemas(logger, schemaLookup, typesMap) {
258
258
  const uniqueSourceFiles = [
259
259
  ...new Set([...schemaLookup.values()].map((ref) => ref.sourceFile)),
260
260
  ];
261
- console.log(`[TIMING] Zod schemas: ${schemaLookup.size} schemas from ${uniqueSourceFiles.length} files`);
261
+ logger.info(`[TIMING] Zod schemas: ${schemaLookup.size} schemas from ${uniqueSourceFiles.length} files`);
262
262
  const importStart = performance.now();
263
263
  const importedModules = await batchImportWithRegister(logger, uniqueSourceFiles);
264
- console.log(`[TIMING] Batch import: ${(performance.now() - importStart).toFixed(0)}ms`);
264
+ logger.info(`[TIMING] Batch import: ${(performance.now() - importStart).toFixed(0)}ms`);
265
265
  const processStart = performance.now();
266
266
  // Track schemas that need per-file tsImport fallback
267
267
  const fallbackSchemas = [];
@@ -302,7 +302,7 @@ async function generateZodSchemas(logger, schemaLookup, typesMap) {
302
302
  }
303
303
  }
304
304
  }
305
- console.log(`[TIMING] Process schemas: ${(performance.now() - processStart).toFixed(0)}ms (${Object.keys(schemas).length} generated)`);
305
+ logger.info(`[TIMING] Process schemas: ${(performance.now() - processStart).toFixed(0)}ms (${Object.keys(schemas).length} generated)`);
306
306
  return schemas;
307
307
  }
308
308
  export async function generateAllSchemas(logger, config, state) {
@@ -1,3 +1,3 @@
1
- import * as ts from 'typescript';
1
+ import type * as ts from 'typescript';
2
2
  import type { InspectorLogger, InspectorState } from '../types.js';
3
3
  export declare function validateAuthSessionless(logger: InspectorLogger, obj: ts.ObjectLiteralExpression, state: InspectorState, funcName: string, wireDescription: string, inheritedAuth?: boolean): boolean;
@@ -1,7 +1,7 @@
1
1
  import * as ts from 'typescript';
2
2
  import type { WorkflowStepMeta, WorkflowContext } from '@pikku/core/workflow';
3
3
  /**
4
- * Result of simple workflow extraction
4
+ * Result of DSL workflow extraction
5
5
  */
6
6
  export interface ExtractionResult {
7
7
  status: 'ok' | 'error';
@@ -9,9 +9,10 @@ export interface ExtractionResult {
9
9
  /** Workflow context (top-level variables) */
10
10
  context?: WorkflowContext;
11
11
  reason?: string;
12
- simple?: boolean;
13
12
  }
14
13
  /**
15
- * Extract simple workflow metadata from a function declaration
14
+ * Extract DSL workflow metadata from a function declaration
16
15
  */
17
- export declare function extractDSLWorkflow(funcNode: ts.Node, checker: ts.TypeChecker): ExtractionResult;
16
+ export declare function extractDSLWorkflow(funcNode: ts.Node, checker: ts.TypeChecker, options?: {
17
+ allowInline?: boolean;
18
+ }): ExtractionResult;
@@ -1,26 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { isWorkflowDoCall, isWorkflowSleepCall, isThrowCancelException, extractCancelReason, isParallelFanout, isParallelGroup, isSequentialFanout, isArrayFilter, isArraySome, isArrayEvery, extractForOfVariable, isArrayType, getSourceText, } from './patterns.js';
2
+ import { isWorkflowDoCall, isWorkflowSleepCall, isThrowCancelException, extractCancelReason, isParallelFanout, isParallelGroup, isSequentialFanout, isArrayFilter, isArraySome, isArrayEvery, extractForOfVariable, isArrayType, getSourceText, extractSourcePath, } from './patterns.js';
3
3
  import { validateNoDisallowedPatterns, validateAwaitedCalls, formatValidationErrors, } from './validation.js';
4
4
  import { extractStringLiteral, extractNumberLiteral, } from '../../extract-node-value.js';
5
5
  /**
6
- * Extract full source path from an expression (e.g., data.memberEmails)
6
+ * Extract DSL workflow metadata from a function declaration
7
7
  */
8
- function extractSourcePath(expr) {
9
- if (ts.isIdentifier(expr)) {
10
- return expr.text;
11
- }
12
- if (ts.isPropertyAccessExpression(expr)) {
13
- const base = extractSourcePath(expr.expression);
14
- if (base) {
15
- return `${base}.${expr.name.text}`;
16
- }
17
- }
18
- return null;
19
- }
20
- /**
21
- * Extract simple workflow metadata from a function declaration
22
- */
23
- export function extractDSLWorkflow(funcNode, checker) {
8
+ export function extractDSLWorkflow(funcNode, checker, options) {
24
9
  try {
25
10
  // Find the async arrow function
26
11
  const arrowFunc = findWorkflowFunction(funcNode);
@@ -28,7 +13,6 @@ export function extractDSLWorkflow(funcNode, checker) {
28
13
  return {
29
14
  status: 'error',
30
15
  reason: 'Could not find async arrow function in workflow definition',
31
- simple: false,
32
16
  };
33
17
  }
34
18
  // Extract input parameter name (second parameter)
@@ -37,7 +21,6 @@ export function extractDSLWorkflow(funcNode, checker) {
37
21
  return {
38
22
  status: 'error',
39
23
  reason: 'Could not determine input parameter name',
40
- simple: false,
41
24
  };
42
25
  }
43
26
  // Initialize extraction context
@@ -53,12 +36,13 @@ export function extractDSLWorkflow(funcNode, checker) {
53
36
  depth: 0,
54
37
  };
55
38
  // Validate no disallowed patterns
56
- const patternErrors = validateNoDisallowedPatterns(arrowFunc.body);
39
+ const patternErrors = validateNoDisallowedPatterns(arrowFunc.body, {
40
+ allowInline: options?.allowInline,
41
+ });
57
42
  if (patternErrors.length > 0) {
58
43
  return {
59
44
  status: 'error',
60
45
  reason: formatValidationErrors(patternErrors),
61
- simple: false,
62
46
  };
63
47
  }
64
48
  // Validate all workflow calls are awaited
@@ -67,7 +51,6 @@ export function extractDSLWorkflow(funcNode, checker) {
67
51
  return {
68
52
  status: 'error',
69
53
  reason: formatValidationErrors(awaitErrors),
70
- simple: false,
71
54
  };
72
55
  }
73
56
  // Extract steps from function body
@@ -77,7 +60,6 @@ export function extractDSLWorkflow(funcNode, checker) {
77
60
  return {
78
61
  status: 'error',
79
62
  reason: formatValidationErrors(context.errors),
80
- simple: false,
81
63
  };
82
64
  }
83
65
  // Build workflow context from extracted context variables
@@ -92,14 +74,12 @@ export function extractDSLWorkflow(funcNode, checker) {
92
74
  status: 'ok',
93
75
  steps,
94
76
  context: Object.keys(workflowContext).length > 0 ? workflowContext : undefined,
95
- simple: true,
96
77
  };
97
78
  }
98
79
  catch (error) {
99
80
  return {
100
81
  status: 'error',
101
82
  reason: error instanceof Error ? error.message : String(error),
102
- simple: false,
103
83
  };
104
84
  }
105
85
  }
@@ -248,7 +228,9 @@ function extractVariableDeclaration(statement, context) {
248
228
  if (ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)) {
249
229
  const call = init.expression;
250
230
  if (isWorkflowDoCall(call, context.checker)) {
251
- const step = extractRpcStep(call, context, varName);
231
+ const step = isInlineDoCall(call)
232
+ ? extractInlineStep(call, context)
233
+ : extractRpcStep(call, context, varName);
252
234
  if (step) {
253
235
  // Track output variable
254
236
  const type = context.checker.getTypeAtLocation(decl);
@@ -335,7 +317,9 @@ function extractExpressionStatement(statement, context) {
335
317
  if (ts.isAwaitExpression(expr) && ts.isCallExpression(expr.expression)) {
336
318
  const call = expr.expression;
337
319
  if (isWorkflowDoCall(call, context.checker)) {
338
- const step = extractRpcStep(call, context, outputVar);
320
+ const step = isInlineDoCall(call)
321
+ ? extractInlineStep(call, context)
322
+ : extractRpcStep(call, context, outputVar);
339
323
  // Track output variable if this is an assignment
340
324
  if (outputVar && step) {
341
325
  const type = context.checker.getTypeAtLocation(expr);
@@ -394,6 +378,41 @@ function extractRpcStep(call, context, outputVar) {
394
378
  return null;
395
379
  }
396
380
  }
381
+ /**
382
+ * Extract inline step from workflow.do() call with a function argument
383
+ */
384
+ function extractInlineStep(call, context) {
385
+ const args = call.arguments;
386
+ if (args.length < 2)
387
+ return null;
388
+ try {
389
+ const stepName = extractStringLiteral(args[0], context.checker);
390
+ const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined;
391
+ const options = optionsArg && ts.isObjectLiteralExpression(optionsArg)
392
+ ? extractStepOptions(optionsArg, context)
393
+ : undefined;
394
+ return {
395
+ type: 'inline',
396
+ stepName,
397
+ options,
398
+ };
399
+ }
400
+ catch (error) {
401
+ context.errors.push({
402
+ message: `Failed to extract inline step: ${error instanceof Error ? error.message : String(error)}`,
403
+ node: call,
404
+ });
405
+ return null;
406
+ }
407
+ }
408
+ /**
409
+ * Check if the second argument of a workflow.do() call is a function
410
+ */
411
+ function isInlineDoCall(call) {
412
+ const secondArg = call.arguments[1];
413
+ return (!!secondArg &&
414
+ (ts.isArrowFunction(secondArg) || ts.isFunctionExpression(secondArg)));
415
+ }
397
416
  /**
398
417
  * Extract step options from options object
399
418
  */
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  /**
3
- * Pattern detection helpers for simple workflow extraction
3
+ * Pattern detection helpers for DSL workflow extraction
4
4
  */
5
5
  /**
6
6
  * Check if a call expression is workflow.do()
@@ -43,6 +43,10 @@ export declare function isParallelGroup(node: ts.CallExpression): boolean;
43
43
  * Check if a for statement is a valid sequential fanout (for..of)
44
44
  */
45
45
  export declare function isSequentialFanout(node: ts.ForOfStatement): boolean;
46
+ /**
47
+ * Extract full source path from an expression (e.g., data.memberEmails)
48
+ */
49
+ export declare function extractSourcePath(expr: ts.Expression): string | null;
46
50
  /**
47
51
  * Extract the variable name from a for..of statement
48
52
  */
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  /**
3
- * Pattern detection helpers for simple workflow extraction
3
+ * Pattern detection helpers for DSL workflow extraction
4
4
  */
5
5
  /**
6
6
  * Check if a call expression is workflow.do()
@@ -162,7 +162,7 @@ export function isSequentialFanout(node) {
162
162
  /**
163
163
  * Extract full source path from an expression (e.g., data.memberEmails)
164
164
  */
165
- function extractSourcePath(expr) {
165
+ export function extractSourcePath(expr) {
166
166
  if (ts.isIdentifier(expr)) {
167
167
  return expr.text;
168
168
  }
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  /**
3
- * Validation rules for simple workflows
3
+ * Validation rules for DSL workflows
4
4
  */
5
5
  export interface ValidationError {
6
6
  message: string;
@@ -19,7 +19,9 @@ export interface ValidationError {
19
19
  * - ThrowStatement (for WorkflowCancelledException)
20
20
  * - Block (containers)
21
21
  */
22
- export declare function validateNoDisallowedPatterns(node: ts.Node): ValidationError[];
22
+ export declare function validateNoDisallowedPatterns(node: ts.Node, options?: {
23
+ allowInline?: boolean;
24
+ }): ValidationError[];
23
25
  /**
24
26
  * Validate that all workflow.do calls are awaited
25
27
  */