@pikku/inspector 0.12.103 → 0.12.107

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/dist/add/add-auth.d.ts +1 -9
  2. package/dist/add/add-auth.js +64 -17
  3. package/dist/add/add-cli.js +25 -11
  4. package/dist/add/add-functions.js +35 -39
  5. package/dist/add/add-keyed-wiring.js +22 -2
  6. package/dist/add/add-middleware.js +17 -16
  7. package/dist/add/add-permission.d.ts +0 -3
  8. package/dist/add/add-permission.js +213 -22
  9. package/dist/add/add-scope.d.ts +1 -7
  10. package/dist/add/add-scope.js +1 -21
  11. package/dist/add/add-virtual-users.d.ts +13 -0
  12. package/dist/add/{add-personas.js → add-virtual-users.js} +33 -33
  13. package/dist/add/add-workflow.js +14 -10
  14. package/dist/add/check-indirect-registrations.d.ts +9 -0
  15. package/dist/add/check-indirect-registrations.js +114 -0
  16. package/dist/error-codes.d.ts +5 -3
  17. package/dist/error-codes.js +5 -3
  18. package/dist/finalize.d.ts +10 -0
  19. package/dist/finalize.js +102 -0
  20. package/dist/index.d.ts +8 -6
  21. package/dist/index.js +6 -5
  22. package/dist/initial-state.d.ts +7 -0
  23. package/dist/initial-state.js +216 -0
  24. package/dist/inspector.d.ts +6 -4
  25. package/dist/inspector.js +28 -300
  26. package/dist/lite.d.ts +26 -0
  27. package/dist/lite.js +25 -0
  28. package/dist/source-file-cache.d.ts +11 -0
  29. package/dist/source-file-cache.js +34 -10
  30. package/dist/types-map.js +2 -1
  31. package/dist/types.d.ts +35 -3
  32. package/dist/utils/apply-declared-scopes.d.ts +8 -0
  33. package/dist/utils/apply-declared-scopes.js +21 -0
  34. package/dist/utils/canonical-type-string.d.ts +14 -0
  35. package/dist/utils/canonical-type-string.js +162 -0
  36. package/dist/utils/context-based-id.d.ts +7 -0
  37. package/dist/utils/context-based-id.js +9 -0
  38. package/dist/utils/custom-types-generator.d.ts +15 -1
  39. package/dist/utils/custom-types-generator.js +17 -5
  40. package/dist/utils/ensure-function-metadata.d.ts +0 -14
  41. package/dist/utils/ensure-function-metadata.js +4 -1
  42. package/dist/utils/extract-function-name.d.ts +1 -1
  43. package/dist/utils/extract-function-name.js +2 -4
  44. package/dist/utils/filter-inspector-state.js +31 -1
  45. package/dist/utils/get-property-value.d.ts +5 -0
  46. package/dist/utils/get-property-value.js +15 -1
  47. package/dist/utils/inspector-constants.d.ts +16 -0
  48. package/dist/utils/inspector-constants.js +16 -0
  49. package/dist/utils/load-addon-functions-meta.js +1 -0
  50. package/dist/utils/post-process.d.ts +35 -36
  51. package/dist/utils/post-process.js +218 -193
  52. package/dist/utils/resolve-callee-name.d.ts +27 -0
  53. package/dist/utils/resolve-callee-name.js +147 -0
  54. package/dist/utils/resolve-service-types.d.ts +8 -0
  55. package/dist/utils/resolve-service-types.js +21 -0
  56. package/dist/utils/schema-generator.js +29 -2
  57. package/dist/utils/serialize-inspector-state.d.ts +9 -3
  58. package/dist/utils/serialize-inspector-state.js +22 -7
  59. package/dist/utils/single-declaration.d.ts +3 -3
  60. package/dist/utils/single-declaration.js +3 -3
  61. package/dist/utils/validate-single-step-workflows.d.ts +3 -0
  62. package/dist/utils/validate-single-step-workflows.js +32 -0
  63. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +3 -3
  64. package/dist/utils/workflow/dsl/patterns.d.ts +4 -11
  65. package/dist/utils/workflow/dsl/patterns.js +5 -11
  66. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +6 -6
  67. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
  68. package/dist/visit.js +4 -2
  69. package/package.json +25 -9
  70. package/CHANGELOG.md +0 -4318
  71. package/OPTIMIZATION-PLAN.md +0 -201
  72. package/dist/add/add-personas.d.ts +0 -13
  73. package/run-tests.sh +0 -68
  74. package/src/utils/test-data/inspector-state.json +0 -1687
@@ -1,13 +1,5 @@
1
1
  import type { AddWiring } from '../types.js';
2
- /**
3
- * The pikku function id of the single shared auth handler the CLI generates
4
- * (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
5
- * exported top-level const collapses the catch-all `/api/auth/**` route onto one
6
- * worker, and the export name becomes the function id. Shared with the CLI
7
- * codegen and the post-process service stamp so all three agree on the same id
8
- * without the inspector having to import `@pikku/better-auth`.
9
- */
10
- export declare const AUTH_HANDLER_FUNC_ID = "authHandler";
2
+ export { AUTH_HANDLER_FUNC_ID } from '../utils/inspector-constants.js';
11
3
  /**
12
4
  * Detects `pikkuBetterAuth((services) => betterAuth({...}))` calls.
13
5
  *
@@ -1,15 +1,10 @@
1
1
  import * as ts from 'typescript';
2
2
  import { ErrorCode } from '../error-codes.js';
3
3
  import { extractServicesFromFunction } from '../utils/extract-services.js';
4
- /**
5
- * The pikku function id of the single shared auth handler the CLI generates
6
- * (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
7
- * exported top-level const collapses the catch-all `/api/auth/**` route onto one
8
- * worker, and the export name becomes the function id. Shared with the CLI
9
- * codegen and the post-process service stamp so all three agree on the same id
10
- * without the inspector having to import `@pikku/better-auth`.
11
- */
12
- export const AUTH_HANDLER_FUNC_ID = 'authHandler';
4
+ import { resolveCalleeName } from '../utils/resolve-callee-name.js';
5
+ const isBetterAuth = (name) => name === 'betterAuth';
6
+ const isPikkuBetterAuth = (name) => name === 'pikkuBetterAuth';
7
+ export { AUTH_HANDLER_FUNC_ID } from '../utils/inspector-constants.js';
13
8
  /** The default better-auth base path when `basePath` is not configured. */
14
9
  const DEFAULT_BASE_PATH = '/api/auth';
15
10
  /**
@@ -17,14 +12,12 @@ const DEFAULT_BASE_PATH = '/api/auth';
17
12
  * factory body. Supports both `(s) => betterAuth({...})` and
18
13
  * `(s) => { ...; return betterAuth({...}) }`.
19
14
  */
20
- const findBetterAuthCall = (node) => {
15
+ const findBetterAuthCall = (node, checker) => {
21
16
  let found;
22
17
  const visit = (n) => {
23
18
  if (found)
24
19
  return;
25
- if (ts.isCallExpression(n) &&
26
- ts.isIdentifier(n.expression) &&
27
- n.expression.text === 'betterAuth') {
20
+ if (ts.isCallExpression(n) && resolveCalleeName(n, checker, isBetterAuth)) {
28
21
  found = n;
29
22
  return;
30
23
  }
@@ -42,6 +35,30 @@ const readStringProp = (obj, name) => {
42
35
  return prop.initializer.text;
43
36
  return undefined;
44
37
  };
38
+ /**
39
+ * Whether a property is written at all, literal or not — so a value the
40
+ * inspector cannot read can be told apart from one that was never set.
41
+ */
42
+ const hasProp = (obj, name) => obj.properties.some((p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
43
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
44
+ p.name.text === name);
45
+ /**
46
+ * True when a spread comes after the last assignment of `name` in `obj`, so the
47
+ * value written there may not be the one that applies. A spread before it is
48
+ * harmless: the property wins.
49
+ */
50
+ const isOverriddenBySpread = (obj, name) => {
51
+ let assigned = -1;
52
+ obj.properties.forEach((p, index) => {
53
+ if ((ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
54
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
55
+ p.name.text === name) {
56
+ assigned = index;
57
+ }
58
+ });
59
+ return (assigned >= 0 &&
60
+ obj.properties.slice(assigned + 1).some(ts.isSpreadAssignment));
61
+ };
45
62
  /** Find an object-literal-valued property off an object literal, if present. */
46
63
  const readObjectProp = (obj, name) => {
47
64
  const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
@@ -159,11 +176,10 @@ and set password.`;
159
176
  * `socialProviders` keys, whether email/password is enabled, and the services
160
177
  * the factory touches. Exactly one `pikkuBetterAuth` is allowed per codebase.
161
178
  */
162
- export const addAuth = (logger, node, _checker, state) => {
179
+ export const addAuth = (logger, node, checker, state) => {
163
180
  if (!ts.isCallExpression(node))
164
181
  return;
165
- const expression = node.expression;
166
- if (!ts.isIdentifier(expression) || expression.text !== 'pikkuBetterAuth')
182
+ if (!resolveCalleeName(node, checker, isPikkuBetterAuth))
167
183
  return;
168
184
  const sourceFile = node.getSourceFile().fileName;
169
185
  // Walk up to the `export const <name> = pikkuBetterAuth(...)` binding.
@@ -206,7 +222,21 @@ export const addAuth = (logger, node, _checker, state) => {
206
222
  let basePath = DEFAULT_BASE_PATH;
207
223
  let hasCredentials = false;
208
224
  let cookieCache = false;
209
- const betterAuthCall = findBetterAuthCall(factory);
225
+ let cookiePrefix;
226
+ let cookieCacheStrategy;
227
+ let cookieCacheVersion;
228
+ const unreadCookieOptions = [];
229
+ const readCookieOption = (block, name, path, enclosingOverridden) => {
230
+ if (!hasProp(block, name))
231
+ return undefined;
232
+ const value = enclosingOverridden || isOverriddenBySpread(block, name)
233
+ ? undefined
234
+ : readStringProp(block, name);
235
+ if (value === undefined)
236
+ unreadCookieOptions.push(path);
237
+ return value;
238
+ };
239
+ const betterAuthCall = findBetterAuthCall(factory, checker);
210
240
  const config = betterAuthCall?.arguments[0];
211
241
  if (config && ts.isObjectLiteralExpression(config)) {
212
242
  basePath = readStringProp(config, 'basePath') ?? DEFAULT_BASE_PATH;
@@ -221,8 +251,22 @@ export const addAuth = (logger, node, _checker, state) => {
221
251
  cookieCache =
222
252
  !enabledProp ||
223
253
  enabledProp.initializer.kind !== ts.SyntaxKind.FalseKeyword;
254
+ const sessionOverridden = isOverriddenBySpread(config, 'session') ||
255
+ isOverriddenBySpread(session, 'cookieCache');
256
+ cookieCacheStrategy = readCookieOption(cookieCacheBlock, 'strategy', 'session.cookieCache.strategy', sessionOverridden);
257
+ cookieCacheVersion = readCookieOption(cookieCacheBlock, 'version', 'session.cookieCache.version', sessionOverridden);
224
258
  }
225
259
  }
260
+ // The stateless middleware never builds the auth instance, so it has to be
261
+ // told how the cookie was written: read the parts that change its name and
262
+ // its verification, and say so when one is not a literal.
263
+ const advanced = readObjectProp(config, 'advanced');
264
+ if (advanced) {
265
+ cookiePrefix = readCookieOption(advanced, 'cookiePrefix', 'advanced.cookiePrefix', isOverriddenBySpread(config, 'advanced'));
266
+ }
267
+ if (cookieCache && unreadCookieOptions.length > 0) {
268
+ logger.warn(`pikkuBetterAuth in ${sourceFile}: ${unreadCookieOptions.join(', ')} is not a string literal, or a later spread can replace it, so the generated betterAuthStatelessSession cannot read the session cookie the way it is written. Write it as a literal, or register betterAuthStatelessSession({ cookiePrefix, cookieCache: { strategy, version } }) yourself with addGlobalMiddleware — yours runs first and the generated one then does nothing.`);
269
+ }
226
270
  const emailAndPassword = readObjectProp(config, 'emailAndPassword');
227
271
  if (emailAndPassword) {
228
272
  // `emailAndPassword: { enabled: true }` — treat a present block without an
@@ -272,6 +316,9 @@ export const addAuth = (logger, node, _checker, state) => {
272
316
  basePath,
273
317
  hasCredentials,
274
318
  cookieCache,
319
+ ...(cookiePrefix !== undefined && { cookiePrefix }),
320
+ ...(cookieCacheStrategy !== undefined && { cookieCacheStrategy }),
321
+ ...(cookieCacheVersion !== undefined && { cookieCacheVersion }),
275
322
  plugins: [...state.auth.plugins],
276
323
  services,
277
324
  };
@@ -325,15 +325,23 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
325
325
  }
326
326
  break;
327
327
  case 'subcommands': {
328
- let subcommandsNode = prop.initializer;
329
- if (ts.isIdentifier(prop.initializer)) {
330
- subcommandsNode = resolveIdentifier(prop.initializer, typeChecker, [
331
- 'defineCLICommands',
332
- ]);
333
- }
334
- if (subcommandsNode && ts.isObjectLiteralExpression(subcommandsNode)) {
335
- meta.subcommands = {};
336
- for (const subProp of subcommandsNode.properties) {
328
+ const subcommands = {};
329
+ const collect = (node) => {
330
+ const refContract = resolveRefContract(node, 'refCLI', inspectorState.exportedContracts.addonCli);
331
+ if (refContract) {
332
+ Object.assign(subcommands, refContract.contract);
333
+ return;
334
+ }
335
+ const target = ts.isIdentifier(node)
336
+ ? resolveIdentifier(node, typeChecker, ['defineCLICommands'])
337
+ : node;
338
+ if (!target || !ts.isObjectLiteralExpression(target))
339
+ return;
340
+ for (const subProp of target.properties) {
341
+ if (ts.isSpreadAssignment(subProp)) {
342
+ collect(subProp.expression);
343
+ continue;
344
+ }
337
345
  if (!ts.isPropertyAssignment(subProp))
338
346
  continue;
339
347
  const subName = getPropertyName(subProp);
@@ -341,10 +349,12 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
341
349
  continue;
342
350
  const subCommand = processCommand(logger, inspectorState, options, subName, subProp.initializer, sourceFile, typeChecker, programName, fullPath, programTags);
343
351
  if (subCommand) {
344
- meta.subcommands[subName] = subCommand;
352
+ subcommands[subName] = subCommand;
345
353
  }
346
354
  }
347
- }
355
+ };
356
+ collect(prop.initializer);
357
+ meta.subcommands = subcommands;
348
358
  break;
349
359
  }
350
360
  case 'isDefault':
@@ -489,6 +499,7 @@ function extractEnumFromPropertyType(type, propertyName, typeChecker) {
489
499
  const enumValues = [];
490
500
  // Check if it's a union type (e.g., 'debug' | 'info' | 'warn')
491
501
  if (propertyType.isUnion()) {
502
+ let fromEnum = false;
492
503
  for (const unionType of propertyType.types) {
493
504
  // Check if it's a string literal type
494
505
  if (unionType.flags & ts.TypeFlags.StringLiteral) {
@@ -497,6 +508,7 @@ function extractEnumFromPropertyType(type, propertyName, typeChecker) {
497
508
  }
498
509
  // Check if it's an enum member (could be string or number enum)
499
510
  else if (unionType.flags & ts.TypeFlags.EnumLiteral) {
511
+ fromEnum = true;
500
512
  const enumLiteralType = unionType;
501
513
  // For string enums, use the value directly
502
514
  if (typeof enumLiteralType.value === 'string') {
@@ -511,6 +523,8 @@ function extractEnumFromPropertyType(type, propertyName, typeChecker) {
511
523
  }
512
524
  }
513
525
  }
526
+ if (!fromEnum)
527
+ enumValues.sort();
514
528
  }
515
529
  // Check if it's an enum type directly
516
530
  else if (propertyType.flags & ts.TypeFlags.Enum) {
@@ -21,10 +21,15 @@ const SCENARIO_SURFACE_KEYS = [
21
21
  * copied into an addon package by a consumer, a called identifier cannot.
22
22
  */
23
23
  const SCENARIO_STEP_KINDS = new Map([
24
- ['pikkuScenarioStep', 'persona'],
24
+ ['pikkuScenarioStep', 'virtualUser'],
25
25
  ['pikkuPlatformScenarioStep', 'platform'],
26
26
  ['pikkuAddonScenarioStep', 'addon'],
27
27
  ]);
28
+ const PIKKU_FUNC_PATTERN = /pikku.*func/i;
29
+ const isFunctionHelper = (name) => PIKKU_FUNC_PATTERN.test(name) ||
30
+ name === 'pikkuScenario' ||
31
+ name === 'pikkuWebhookReceive' ||
32
+ SCENARIO_STEP_KINDS.has(name);
28
33
  import { getPropertyValue, getArrayPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
29
34
  import { canonicalJSON, hashString } from '../utils/hash.js';
30
35
  import { resolveMiddleware } from '../utils/middleware.js';
@@ -34,6 +39,7 @@ import { ErrorCode } from '../error-codes.js';
34
39
  import { findPiiPaths } from '../utils/check-pii-output.js';
35
40
  import { findClassifiedSinks } from '../utils/check-classified-sinks.js';
36
41
  import { isScenarioInstrumentationFunction } from './scenario-instrumentation.js';
42
+ import { resolveCalleeName } from '../utils/resolve-callee-name.js';
37
43
  const isValidVariableName = (name) => {
38
44
  const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
39
45
  return regex.test(name);
@@ -351,37 +357,27 @@ export const addFunctions = (logger, node, checker, state, options) => {
351
357
  if (!ts.isCallExpression(node))
352
358
  return;
353
359
  const { expression, arguments: args, typeArguments } = node;
354
- // only handle calls like pikkuFunc(...)
355
- if (!ts.isIdentifier(expression)) {
360
+ // Match helpers whose name contains both "pikku" and "func" (case
361
+ // insensitive), plus pikkuScenario and the three scenario step wrappers
362
+ // (workflow wrappers without "func" in their names). The callee is matched
363
+ // by what it resolves to, so an aliased `pikkuFunc` is still a pikkuFunc —
364
+ // and still requires a session.
365
+ const callee = resolveCalleeName(node, checker, isFunctionHelper);
366
+ if (!callee)
356
367
  return;
357
- }
358
- // Match identifiers that contain both "pikku" and "func" (case insensitive),
359
- // plus pikkuScenario and the three scenario step wrappers (workflow wrappers
360
- // without "func" in their names)
361
- const pikkuFuncPattern = /pikku.*func/i;
362
- if (!pikkuFuncPattern.test(expression.text) &&
363
- expression.text !== 'pikkuScenario' &&
364
- expression.text !== 'pikkuWebhookReceive' &&
365
- !SCENARIO_STEP_KINDS.has(expression.text)) {
366
- return;
367
- }
368
368
  // A scenario step is bundled like any invoked function, but is deliberately
369
369
  // never RPC-registered: a step may drive a browser, so exposing it over the
370
370
  // network would put a test harness on the public API surface.
371
- const scenarioStepKind = SCENARIO_STEP_KINDS.get(expression.text);
371
+ const scenarioStepKind = SCENARIO_STEP_KINDS.get(callee);
372
372
  const isScenarioStep = scenarioStepKind !== undefined;
373
373
  // A scenario's own body. Marked so the codegen partitions and the deploy
374
374
  // analyzer can tell it apart from an application workflow without having to
375
375
  // reach into the workflow graph.
376
- const isScenario = expression.text === 'pikkuScenario';
376
+ const isScenario = callee === 'pikkuScenario';
377
377
  // A webhook source's `receive`. Only its source's route runs it, with the
378
378
  // raw request, so like a scenario step it is bundled but never RPC-registered.
379
- const isWebhookReceive = expression.text === 'pikkuWebhookReceive';
379
+ const isWebhookReceive = callee === 'pikkuWebhookReceive';
380
380
  const notRpc = isScenarioStep || isWebhookReceive;
381
- // only handle calls like pikkuFunc(...)
382
- if (!ts.isIdentifier(expression) || !expression.text.startsWith('pikku')) {
383
- return;
384
- }
385
381
  if (args.length === 0)
386
382
  return;
387
383
  let { pikkuFuncId, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
@@ -424,7 +420,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
424
420
  let workflowRetries;
425
421
  let workflowTimeout;
426
422
  let scenarioStepSurfaces;
427
- let scenarioStepRequiresActor;
423
+ let scenarioStepRequiresVirtualUser;
428
424
  let scenarioStepAddon;
429
425
  let scenarioStepTemplate;
430
426
  let scopes;
@@ -540,7 +536,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
540
536
  // take a single `func`, and a binding key here is a coded error rather
541
537
  // than a convention nobody reads.
542
538
  if (declaredSurfaces.length > 0) {
543
- logger.error(`• ${expression.text}('${name}') declares ${declaredSurfaces.join('/')} — a ${scenarioStepKind} step has one implementation, declared as \`func\`. Surfaces are how a person drives the system, and nobody drives this one.`);
539
+ logger.error(`• ${callee}('${name}') declares ${declaredSurfaces.join('/')} — a ${scenarioStepKind} step has one implementation, declared as \`func\`. Surfaces are how a person drives the system, and nobody drives this one.`);
544
540
  return;
545
541
  }
546
542
  // The phase rule depends on it: an assertion runs every witness it has and
@@ -560,9 +556,9 @@ export const addFunctions = (logger, node, checker, state, options) => {
560
556
  ? declaredSurfaces
561
557
  : undefined;
562
558
  // A browser binding cannot be provisioned without a window to open, so it
563
- // requires an actor whether or not the step said so.
564
- scenarioStepRequiresActor =
565
- getPropertyValue(firstArg, 'actor') === true ||
559
+ // requires a virtual user whether or not the step said so.
560
+ scenarioStepRequiresVirtualUser =
561
+ getPropertyValue(firstArg, 'virtualUser') === true ||
566
562
  declaredSurfaces.includes('browser')
567
563
  ? true
568
564
  : undefined;
@@ -665,7 +661,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
665
661
  // A remote channel capability is a contract with no body: the connected
666
662
  // client answers it. Everything past this point reads the handler, so it gets
667
663
  // its own path rather than a nullable handler threaded through all of it.
668
- if (expression.text === 'pikkuRemoteChannelFunc') {
664
+ if (callee === 'pikkuRemoteChannelFunc') {
669
665
  addRemoteChannelFunc({
670
666
  state,
671
667
  logger,
@@ -679,8 +675,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
679
675
  });
680
676
  return;
681
677
  }
682
- const isMCPToolFunc = expression.text === 'pikkuMCPToolFunc';
683
- const isListFunc = expression.text === 'pikkuListFunc';
678
+ const isMCPToolFunc = callee === 'pikkuMCPToolFunc';
679
+ const isListFunc = callee === 'pikkuListFunc';
684
680
  const mcpEnabled = mcp || isMCPToolFunc;
685
681
  // Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
686
682
  const handler = resolvedFunc &&
@@ -777,7 +773,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
777
773
  // Every other wrapper reads generic[0] as INPUT, so without this guard the
778
774
  // connect handler's output generic is mis-recorded as inputSchemaName and the
779
775
  // empty WS handshake fails input validation at connect (1008/403).
780
- const isChannelConnectionFunc = /ChannelConnection/i.test(expression.text);
776
+ const isChannelConnectionFunc = /ChannelConnection/i.test(callee);
781
777
  const capitalizedName = funcIdToTypeName(name);
782
778
  // --- Input Extraction ---
783
779
  let inputNames = [];
@@ -857,7 +853,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
857
853
  pikkuMCPToolFunc: 'MCPToolResponse',
858
854
  pikkuMCPPromptFunc: 'MCPPromptResponse',
859
855
  };
860
- const mcpOutputType = mcpOutputTypes[expression.text];
856
+ const mcpOutputType = mcpOutputTypes[callee];
861
857
  if (mcpOutputType && outputNames[0] !== mcpOutputType) {
862
858
  let resolved = false;
863
859
  const rawSymbol = checker.getSymbolAtLocation(expression);
@@ -925,7 +921,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
925
921
  }
926
922
  }
927
923
  }
928
- const sessionless = expression.text !== 'pikkuFunc';
924
+ const sessionless = callee !== 'pikkuFunc';
929
925
  // ── Classification brand check ─────────────────────────────────────────────
930
926
  // Walk the function body's ACTUAL inferred return type looking for classification
931
927
  // brands (__classification__ property on Private<T>, Pii<T>, Secret<T>).
@@ -1017,7 +1013,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
1017
1013
  }
1018
1014
  }
1019
1015
  const implementationHash = computeImplementationHash({
1020
- wrapper: expression.text,
1016
+ wrapper: ts.isIdentifier(expression) ? callee : callee,
1021
1017
  handler,
1022
1018
  objectNode,
1023
1019
  isDirectFunction,
@@ -1051,7 +1047,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
1051
1047
  state.functions.meta[pikkuFuncId] = {
1052
1048
  pikkuFuncId,
1053
1049
  functionType: 'user',
1054
- funcWrapper: expression.text,
1050
+ funcWrapper: callee,
1055
1051
  sessionless,
1056
1052
  name,
1057
1053
  services,
@@ -1083,10 +1079,10 @@ export const addFunctions = (logger, node, checker, state, options) => {
1083
1079
  workflowTimeout: workflowTimeout ?? undefined,
1084
1080
  compensate: compensateInitializer ? true : undefined,
1085
1081
  scenarioStepSurfaces,
1086
- scenarioStepRequiresActor,
1087
- // `persona` is the default reading of an unmarked step, so it is left off
1082
+ scenarioStepRequiresVirtualUser,
1083
+ // `virtualUser` is the default reading of an unmarked step, so it is left off
1088
1084
  // rather than written into every step meta a project already has.
1089
- scenarioStepKind: scenarioStepKind && scenarioStepKind !== 'persona'
1085
+ scenarioStepKind: scenarioStepKind && scenarioStepKind !== 'virtualUser'
1090
1086
  ? scenarioStepKind
1091
1087
  : undefined,
1092
1088
  scenarioStepAddon,
@@ -1162,7 +1158,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
1162
1158
  }
1163
1159
  // Workflow functions don't get registered as RPC functions,
1164
1160
  // they are their own type handled by add-workflow
1165
- if (expression.text.includes('Workflow')) {
1161
+ if (callee.includes('Workflow')) {
1166
1162
  return;
1167
1163
  }
1168
1164
  // Trigger and channel connect/disconnect functions are not callable via RPC
@@ -1171,7 +1167,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
1171
1167
  /ChannelConnection/i,
1172
1168
  /ChannelDisconnection/i,
1173
1169
  ];
1174
- if (nonRPCPatterns.some((pattern) => pattern.test(expression.text))) {
1170
+ if (nonRPCPatterns.some((pattern) => pattern.test(callee))) {
1175
1171
  return;
1176
1172
  }
1177
1173
  // Store function file location for wiring generation
@@ -1,8 +1,9 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getArrayPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getArrayPropertyValue, getObjectPropertyLiteral, assertStringLiteralProperty, } from '../utils/get-property-value.js';
3
3
  import { ErrorCode } from '../error-codes.js';
4
4
  import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
5
5
  import { parseDurationString } from '@pikku/core/utils';
6
+ import { SECRET_GENERATE_MIN_SIZE } from '@pikku/core/secret';
6
7
  export const createAddKeyedWiring = (config) => {
7
8
  return (logger, node, checker, state, _options) => {
8
9
  if (!ts.isCallExpression(node)) {
@@ -79,7 +80,7 @@ export const createAddKeyedWiring = (config) => {
79
80
  logger.critical(ErrorCode.MISSING_NAME, `${config.label} '${nameValue}' is missing the required 'schema' property or schema is not a variable reference.`);
80
81
  return;
81
82
  }
82
- if (rotationPeriodValue) {
83
+ if (rotationPeriodValue && rotationPeriodValue !== 'never') {
83
84
  try {
84
85
  parseDurationString(rotationPeriodValue);
85
86
  }
@@ -88,6 +89,24 @@ export const createAddKeyedWiring = (config) => {
88
89
  return;
89
90
  }
90
91
  }
92
+ let generateValue;
93
+ const generateObj = config.idField === 'secretId'
94
+ ? getObjectPropertyLiteral(obj, 'generate')
95
+ : null;
96
+ if (generateObj) {
97
+ const sizeValue = getPropertyValue(generateObj, 'size');
98
+ if (typeof sizeValue !== 'number' ||
99
+ !Number.isInteger(sizeValue) ||
100
+ sizeValue < SECRET_GENERATE_MIN_SIZE) {
101
+ logger.critical(ErrorCode.INVALID_VALUE, `${config.label} '${nameValue}' needs a 'generate' size of at least ${SECRET_GENERATE_MIN_SIZE} bytes.`);
102
+ return;
103
+ }
104
+ if (!rotationPeriodValue) {
105
+ logger.critical(ErrorCode.INVALID_VALUE, `${config.label} '${nameValue}' is generated, so it needs a 'rotationPeriod' such as '30day', or 'never' to generate it once.`);
106
+ return;
107
+ }
108
+ generateValue = { size: sizeValue };
109
+ }
91
110
  const sourceFile = node.getSourceFile().fileName;
92
111
  const wiringState = config.getState(state);
93
112
  wiringState.files.add(sourceFile);
@@ -105,6 +124,7 @@ export const createAddKeyedWiring = (config) => {
105
124
  displayName: displayNameValue,
106
125
  description: descriptionValue || undefined,
107
126
  rotationPeriod: rotationPeriodValue || undefined,
127
+ generate: generateValue,
108
128
  docsUrl: docsUrlValue || undefined,
109
129
  allowedHosts: allowedHostsValue || undefined,
110
130
  [config.idField]: idValue,
@@ -4,6 +4,7 @@ import { extractServicesFromFunction, extractUsedWires, } from '../utils/extract
4
4
  import { extractMiddlewareRefs } from '../utils/middleware.js';
5
5
  import { getPropertyValue } from '../utils/get-property-value.js';
6
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
+ import { MIDDLEWARE_HELPERS, resolveCalleeName, } from '../utils/resolve-callee-name.js';
7
8
  /**
8
9
  * Record a middleware group, keeping any registration already stored under the
9
10
  * same pattern or tag.
@@ -113,11 +114,11 @@ function renameTempDefinitions(state, definitionIds, groupType, groupKey, storeK
113
114
  export const addMiddleware = (logger, node, checker, state) => {
114
115
  if (!ts.isCallExpression(node))
115
116
  return;
116
- const { expression, arguments: args } = node;
117
- if (!ts.isIdentifier(expression)) {
117
+ const { arguments: args } = node;
118
+ const callee = resolveCalleeName(node, checker, MIDDLEWARE_HELPERS);
119
+ if (!callee)
118
120
  return;
119
- }
120
- if (expression.text === 'pikkuMiddleware') {
121
+ if (callee === 'pikkuMiddleware') {
121
122
  const arg = args[0];
122
123
  if (!arg)
123
124
  return;
@@ -174,7 +175,7 @@ export const addMiddleware = (logger, node, checker, state) => {
174
175
  logger.debug(`• Found middleware with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
175
176
  return;
176
177
  }
177
- if (expression.text === 'pikkuMiddlewareFactory') {
178
+ if (callee === 'pikkuMiddlewareFactory') {
178
179
  const factoryNode = args[0];
179
180
  if (!factoryNode)
180
181
  return;
@@ -192,8 +193,8 @@ export const addMiddleware = (logger, node, checker, state) => {
192
193
  let description;
193
194
  const findPikkuMiddlewareCall = (node) => {
194
195
  if (ts.isCallExpression(node)) {
195
- const expr = node.expression;
196
- if (ts.isIdentifier(expr) && expr.text === 'pikkuMiddleware') {
196
+ if (resolveCalleeName(node, checker, MIDDLEWARE_HELPERS) ===
197
+ 'pikkuMiddleware') {
197
198
  return node;
198
199
  }
199
200
  }
@@ -260,7 +261,7 @@ export const addMiddleware = (logger, node, checker, state) => {
260
261
  logger.debug(`• Found middleware factory with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
261
262
  return;
262
263
  }
263
- if (expression.text === 'addTagMiddleware') {
264
+ if (callee === 'addTagMiddleware') {
264
265
  const tagArg = args[0];
265
266
  const middlewareArrayArg = args[1];
266
267
  if (!tagArg || !middlewareArrayArg)
@@ -335,7 +336,7 @@ export const addMiddleware = (logger, node, checker, state) => {
335
336
  logger.debug(`• Found tag middleware group: ${tag} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
336
337
  return;
337
338
  }
338
- if (expression.text === 'addGlobalMiddleware') {
339
+ if (callee === 'addGlobalMiddleware') {
339
340
  const middlewareArrayArg = args[0];
340
341
  if (!middlewareArrayArg ||
341
342
  !ts.isArrayLiteralExpression(middlewareArrayArg)) {
@@ -362,7 +363,7 @@ export const addMiddleware = (logger, node, checker, state) => {
362
363
  logger.debug(`• Found global middleware group with ${refs.length} entries`);
363
364
  return;
364
365
  }
365
- if (expression.text === 'addHTTPMiddleware') {
366
+ if (callee === 'addHTTPMiddleware') {
366
367
  const patternArg = args[0];
367
368
  const middlewareArrayArg = args[1];
368
369
  if (!patternArg || !middlewareArrayArg)
@@ -438,7 +439,7 @@ export const addMiddleware = (logger, node, checker, state) => {
438
439
  logger.debug(`• Found HTTP route middleware group: ${pattern} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
439
440
  return;
440
441
  }
441
- if (expression.text === 'pikkuChannelMiddleware') {
442
+ if (callee === 'pikkuChannelMiddleware') {
442
443
  const arg = args[0];
443
444
  if (!arg)
444
445
  return;
@@ -476,7 +477,7 @@ export const addMiddleware = (logger, node, checker, state) => {
476
477
  logger.debug(`• Found channel middleware with services: ${services.services.join(', ')}`);
477
478
  return;
478
479
  }
479
- if (expression.text === 'pikkuChannelMiddlewareFactory') {
480
+ if (callee === 'pikkuChannelMiddlewareFactory') {
480
481
  const factoryNode = args[0];
481
482
  if (!factoryNode)
482
483
  return;
@@ -488,8 +489,8 @@ export const addMiddleware = (logger, node, checker, state) => {
488
489
  let services = { optimized: false, services: [] };
489
490
  const findPikkuChannelMiddlewareCall = (n) => {
490
491
  if (ts.isCallExpression(n)) {
491
- const expr = n.expression;
492
- if (ts.isIdentifier(expr) && expr.text === 'pikkuChannelMiddleware') {
492
+ if (resolveCalleeName(n, checker, MIDDLEWARE_HELPERS) ===
493
+ 'pikkuChannelMiddleware') {
493
494
  return n;
494
495
  }
495
496
  }
@@ -539,7 +540,7 @@ export const addMiddleware = (logger, node, checker, state) => {
539
540
  logger.debug(`• Found channel middleware factory with services: ${services.services.join(', ')}`);
540
541
  return;
541
542
  }
542
- if (expression.text === 'pikkuAgentMiddleware') {
543
+ if (callee === 'pikkuAgentMiddleware') {
543
544
  const arg = args[0];
544
545
  if (!arg)
545
546
  return;
@@ -587,7 +588,7 @@ export const addMiddleware = (logger, node, checker, state) => {
587
588
  logger.debug(`• Found AI middleware with services: ${services.services.join(', ')}`);
588
589
  return;
589
590
  }
590
- if (expression.text === 'addChannelMiddleware') {
591
+ if (callee === 'addChannelMiddleware') {
591
592
  const tagArg = args[0];
592
593
  const middlewareArrayArg = args[1];
593
594
  if (!tagArg || !middlewareArrayArg)
@@ -1,5 +1,2 @@
1
1
  import type { AddWiring } from '../types.js';
2
- /**
3
- * Inspect pikkuPermission, pikkuAuth, and pikkuPermissionFactory definitions.
4
- */
5
2
  export declare const addPermission: AddWiring;