@pikku/inspector 0.12.0 → 0.12.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 (109) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/add/add-ai-agent.d.ts +1 -1
  3. package/dist/add/add-ai-agent.js +1 -1
  4. package/dist/add/add-channel.js +12 -0
  5. package/dist/add/add-cli.d.ts +1 -1
  6. package/dist/add/add-cli.js +6 -1
  7. package/dist/add/add-file-extends-core-type.d.ts +1 -1
  8. package/dist/add/add-file-with-config.d.ts +1 -1
  9. package/dist/add/add-file-with-factory.d.ts +1 -1
  10. package/dist/add/add-file-with-factory.js +2 -2
  11. package/dist/add/add-functions.d.ts +1 -1
  12. package/dist/add/add-functions.js +6 -7
  13. package/dist/add/add-http-route.d.ts +3 -2
  14. package/dist/add/add-http-route.js +5 -1
  15. package/dist/add/add-http-routes.d.ts +1 -1
  16. package/dist/add/add-http-routes.js +1 -0
  17. package/dist/add/add-keyed-wiring.d.ts +1 -1
  18. package/dist/add/add-mcp-prompt.d.ts +1 -1
  19. package/dist/add/add-mcp-resource.d.ts +1 -1
  20. package/dist/add/add-queue-worker.d.ts +1 -1
  21. package/dist/add/add-rpc-invocations.d.ts +3 -3
  22. package/dist/add/add-rpc-invocations.js +10 -10
  23. package/dist/add/add-schedule.d.ts +1 -1
  24. package/dist/add/add-secret.d.ts +1 -1
  25. package/dist/add/add-trigger.d.ts +1 -1
  26. package/dist/add/add-trigger.js +2 -2
  27. package/dist/add/add-wire-addon.d.ts +7 -0
  28. package/dist/add/add-wire-addon.js +70 -0
  29. package/dist/add/add-workflow.d.ts +1 -1
  30. package/dist/error-codes.d.ts +1 -0
  31. package/dist/error-codes.js +1 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/inspector.d.ts +1 -1
  34. package/dist/inspector.js +7 -4
  35. package/dist/types.d.ts +26 -20
  36. package/dist/utils/compute-required-schemas.js +1 -2
  37. package/dist/utils/contract-hashes.d.ts +20 -3
  38. package/dist/utils/contract-hashes.js +77 -10
  39. package/dist/utils/custom-types-generator.d.ts +1 -1
  40. package/dist/utils/detect-schema-vendor.d.ts +1 -1
  41. package/dist/utils/does-type-extend-core-type.d.ts +1 -1
  42. package/dist/utils/ensure-function-metadata.d.ts +1 -1
  43. package/dist/utils/extract-services.d.ts +1 -1
  44. package/dist/utils/filter-inspector-state.d.ts +1 -1
  45. package/dist/utils/filter-utils.d.ts +2 -2
  46. package/dist/utils/get-files-and-methods.d.ts +1 -1
  47. package/dist/utils/middleware.d.ts +2 -2
  48. package/dist/utils/permissions.d.ts +2 -2
  49. package/dist/utils/post-process.d.ts +4 -3
  50. package/dist/utils/post-process.js +24 -8
  51. package/dist/utils/resolve-addon-package.d.ts +16 -0
  52. package/dist/utils/{resolve-external-package.js → resolve-addon-package.js} +8 -8
  53. package/dist/utils/schema-generator.d.ts +2 -2
  54. package/dist/utils/serialize-inspector-state.d.ts +13 -3
  55. package/dist/utils/serialize-inspector-state.js +6 -2
  56. package/dist/utils/validate-auth-sessionless.d.ts +3 -0
  57. package/dist/utils/validate-auth-sessionless.js +14 -0
  58. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
  59. package/dist/visit.d.ts +1 -1
  60. package/dist/visit.js +2 -0
  61. package/package.json +2 -2
  62. package/src/add/add-ai-agent.ts +2 -2
  63. package/src/add/add-channel.ts +21 -0
  64. package/src/add/add-cli.ts +19 -4
  65. package/src/add/add-file-extends-core-type.ts +1 -1
  66. package/src/add/add-file-with-config.ts +1 -1
  67. package/src/add/add-file-with-factory.ts +3 -3
  68. package/src/add/add-functions.ts +21 -19
  69. package/src/add/add-http-route.ts +19 -2
  70. package/src/add/add-http-routes.ts +2 -1
  71. package/src/add/add-keyed-wiring.ts +1 -1
  72. package/src/add/add-mcp-prompt.ts +1 -1
  73. package/src/add/add-mcp-resource.ts +1 -1
  74. package/src/add/add-queue-worker.ts +1 -1
  75. package/src/add/add-rpc-invocations.ts +11 -11
  76. package/src/add/add-schedule.ts +1 -1
  77. package/src/add/add-secret.ts +1 -1
  78. package/src/add/add-trigger.ts +4 -4
  79. package/src/add/add-wire-addon.ts +80 -0
  80. package/src/add/add-workflow.ts +2 -2
  81. package/src/error-codes.ts +1 -0
  82. package/src/index.ts +1 -0
  83. package/src/inspector.ts +13 -5
  84. package/src/types.ts +33 -20
  85. package/src/utils/compute-required-schemas.ts +1 -2
  86. package/src/utils/contract-hashes.test.ts +30 -5
  87. package/src/utils/contract-hashes.ts +110 -14
  88. package/src/utils/custom-types-generator.ts +1 -1
  89. package/src/utils/detect-schema-vendor.ts +1 -1
  90. package/src/utils/does-type-extend-core-type.ts +1 -1
  91. package/src/utils/ensure-function-metadata.ts +1 -1
  92. package/src/utils/extract-services.ts +1 -1
  93. package/src/utils/filter-inspector-state.test.ts +3 -5
  94. package/src/utils/filter-inspector-state.ts +7 -2
  95. package/src/utils/filter-utils.test.ts +1 -1
  96. package/src/utils/filter-utils.ts +2 -2
  97. package/src/utils/get-files-and-methods.ts +1 -1
  98. package/src/utils/middleware.ts +2 -2
  99. package/src/utils/permissions.ts +2 -2
  100. package/src/utils/post-process.ts +34 -12
  101. package/src/utils/{resolve-external-package.ts → resolve-addon-package.ts} +17 -10
  102. package/src/utils/resolve-versions.test.ts +1 -1
  103. package/src/utils/schema-generator.ts +4 -4
  104. package/src/utils/serialize-inspector-state.ts +23 -5
  105. package/src/utils/validate-auth-sessionless.ts +29 -0
  106. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
  107. package/src/visit.ts +7 -1
  108. package/tsconfig.tsbuildinfo +1 -1
  109. package/dist/utils/resolve-external-package.d.ts +0 -12
@@ -1,18 +1,35 @@
1
- import { FunctionsMeta, JSONValue } from '@pikku/core';
2
- import { TypesMap } from '../types-map.js';
1
+ import type { FunctionsMeta, JSONValue } from '@pikku/core';
2
+ import type { TypesMap } from '../types-map.js';
3
3
  import { ErrorCode } from '../error-codes.js';
4
4
  export type ContractEntry = {
5
5
  functionKey: string;
6
6
  version: number;
7
7
  contractHash: string;
8
+ inputHash: string;
9
+ outputHash: string;
8
10
  };
9
11
  export type VersionValidateError = {
10
12
  code: ErrorCode;
11
13
  message: string;
14
+ functionKey?: string;
15
+ version?: number;
16
+ previousHash?: string;
17
+ currentHash?: string;
18
+ previousInputHash?: string;
19
+ currentInputHash?: string;
20
+ previousOutputHash?: string;
21
+ currentOutputHash?: string;
22
+ nextVersion?: number;
23
+ latestVersion?: number;
24
+ expectedNextVersion?: number;
25
+ };
26
+ export type VersionHashEntry = {
27
+ inputHash: string;
28
+ outputHash: string;
12
29
  };
13
30
  export type VersionManifestEntry = {
14
31
  latest: number;
15
- versions: Record<string, string>;
32
+ versions: Record<string, string | VersionHashEntry>;
16
33
  };
17
34
  export type VersionManifest = {
18
35
  manifestVersion: 1;
@@ -1,6 +1,9 @@
1
1
  import { parseVersionedId } from '@pikku/core';
2
2
  import { ErrorCode } from '../error-codes.js';
3
3
  import { canonicalJSON, hashString } from './hash.js';
4
+ function isHashEntry(v) {
5
+ return typeof v === 'object';
6
+ }
4
7
  export function createEmptyManifest() {
5
8
  return {
6
9
  manifestVersion: 1,
@@ -32,6 +35,12 @@ export function serializeManifest(manifest) {
32
35
  export function computeContractHash(data) {
33
36
  return hashString(canonicalJSON(data), 16);
34
37
  }
38
+ function computeInputHash(functionKey, inputSchema) {
39
+ return hashString(canonicalJSON({ functionKey, inputSchema }), 8);
40
+ }
41
+ function computeOutputHash(functionKey, outputSchema) {
42
+ return hashString(canonicalJSON({ functionKey, outputSchema }), 8);
43
+ }
35
44
  function resolveSchema(typeNames, allSchemas, typesMap) {
36
45
  if (!typeNames) {
37
46
  return null;
@@ -75,7 +84,15 @@ export function buildCurrentContracts(functionsMeta, allSchemas, typesMap) {
75
84
  inputSchema,
76
85
  outputSchema,
77
86
  });
78
- result.set(funcId, { functionKey, version, contractHash });
87
+ const inputHash = computeInputHash(functionKey, inputSchema);
88
+ const outputHash = computeOutputHash(functionKey, outputSchema);
89
+ result.set(funcId, {
90
+ functionKey,
91
+ version,
92
+ contractHash,
93
+ inputHash,
94
+ outputHash,
95
+ });
79
96
  }
80
97
  return result;
81
98
  }
@@ -85,6 +102,8 @@ export function computeContractHashes(allSchemas, typesMap, functionsMeta) {
85
102
  const meta = functionsMeta[funcId];
86
103
  if (meta) {
87
104
  meta.contractHash = entry.contractHash;
105
+ meta.inputHash = entry.inputHash;
106
+ meta.outputHash = entry.outputHash;
88
107
  }
89
108
  }
90
109
  return contracts;
@@ -98,6 +117,13 @@ function groupByFunctionKey(contracts) {
98
117
  }
99
118
  return grouped;
100
119
  }
120
+ function entryChanged(existing, current) {
121
+ if (isHashEntry(existing)) {
122
+ return (existing.inputHash !== current.inputHash ||
123
+ existing.outputHash !== current.outputHash);
124
+ }
125
+ return existing !== current.contractHash;
126
+ }
101
127
  export function validateContracts(manifest, currentContracts) {
102
128
  const errors = [];
103
129
  const grouped = groupByFunctionKey(currentContracts);
@@ -107,14 +133,31 @@ export function validateContracts(manifest, currentContracts) {
107
133
  if (!manifestEntry) {
108
134
  continue;
109
135
  }
110
- for (const { version, contractHash } of entries) {
111
- const existingHash = manifestEntry.versions[String(version)];
112
- if (existingHash !== undefined) {
113
- if (existingHash !== contractHash) {
136
+ for (const current of entries) {
137
+ const { version, contractHash, inputHash, outputHash } = current;
138
+ const existingEntry = manifestEntry.versions[String(version)];
139
+ if (existingEntry !== undefined) {
140
+ if (entryChanged(existingEntry, current)) {
114
141
  reportedKeys.add(`${functionKey}@${version}`);
142
+ const prevInputHash = isHashEntry(existingEntry)
143
+ ? existingEntry.inputHash
144
+ : existingEntry;
145
+ const prevOutputHash = isHashEntry(existingEntry)
146
+ ? existingEntry.outputHash
147
+ : existingEntry;
115
148
  errors.push({
116
149
  code: ErrorCode.FUNCTION_VERSION_MODIFIED,
117
- message: `Contract for ${functionKey}@v${version} has changed (recorded: ${existingHash}, current: ${contractHash}). Existing versions are immutable.`,
150
+ message: `Contract for ${functionKey}@v${version} has changed (recorded: ${isHashEntry(existingEntry) ? `${existingEntry.inputHash}/${existingEntry.outputHash}` : existingEntry}, current: ${contractHash}). Existing versions are immutable.`,
151
+ functionKey,
152
+ version,
153
+ previousHash: isHashEntry(existingEntry)
154
+ ? existingEntry.inputHash
155
+ : existingEntry,
156
+ currentHash: contractHash,
157
+ previousInputHash: prevInputHash,
158
+ currentInputHash: inputHash,
159
+ previousOutputHash: prevOutputHash,
160
+ currentOutputHash: outputHash,
118
161
  });
119
162
  }
120
163
  }
@@ -123,30 +166,49 @@ export function validateContracts(manifest, currentContracts) {
123
166
  errors.push({
124
167
  code: ErrorCode.VERSION_REGRESSION_OR_CONFLICT,
125
168
  message: `Version ${version} for ${functionKey} is <= latest (${manifestEntry.latest}) but not recorded. Possible merge conflict.`,
169
+ functionKey,
170
+ version,
171
+ latestVersion: manifestEntry.latest,
126
172
  });
127
173
  }
128
174
  else if (version > manifestEntry.latest + 1) {
129
175
  errors.push({
130
176
  code: ErrorCode.VERSION_GAP_NOT_ALLOWED,
131
177
  message: `Version ${version} for ${functionKey} skips versions. Latest is ${manifestEntry.latest}, next must be ${manifestEntry.latest + 1}.`,
178
+ functionKey,
179
+ version,
180
+ latestVersion: manifestEntry.latest,
181
+ expectedNextVersion: manifestEntry.latest + 1,
132
182
  });
133
183
  }
134
184
  }
135
185
  }
136
186
  }
137
187
  for (const [functionKey, manifestEntry] of Object.entries(manifest.contracts)) {
138
- const latestHash = manifestEntry.versions[String(manifestEntry.latest)];
188
+ const latestEntry = manifestEntry.versions[String(manifestEntry.latest)];
139
189
  const currentEntries = grouped.get(functionKey);
140
190
  if (!currentEntries) {
141
191
  continue;
142
192
  }
143
193
  const currentLatest = currentEntries.find((e) => e.version === manifestEntry.latest);
144
194
  if (currentLatest &&
145
- currentLatest.contractHash !== latestHash &&
195
+ entryChanged(latestEntry, currentLatest) &&
146
196
  !reportedKeys.has(`${functionKey}@${manifestEntry.latest}`)) {
197
+ const prevInputHash = isHashEntry(latestEntry)
198
+ ? latestEntry.inputHash
199
+ : undefined;
200
+ const prevOutputHash = isHashEntry(latestEntry)
201
+ ? latestEntry.outputHash
202
+ : undefined;
147
203
  errors.push({
148
204
  code: ErrorCode.CONTRACT_CHANGED_REQUIRES_BUMP,
149
205
  message: `Contract for ${functionKey} changed. Set \`version: ${manifestEntry.latest + 1}\` on the function or run 'pikku versions update'.`,
206
+ functionKey,
207
+ previousInputHash: prevInputHash,
208
+ currentInputHash: currentLatest.inputHash,
209
+ previousOutputHash: prevOutputHash,
210
+ currentOutputHash: currentLatest.outputHash,
211
+ nextVersion: manifestEntry.latest + 1,
150
212
  });
151
213
  }
152
214
  }
@@ -160,6 +222,9 @@ export function validateContracts(manifest, currentContracts) {
160
222
  errors.push({
161
223
  code: ErrorCode.MANIFEST_INTEGRITY_ERROR,
162
224
  message: `Manifest integrity error for ${functionKey}: latest field (${manifestEntry.latest}) inconsistent with version keys (max: ${maxVersion}).`,
225
+ functionKey,
226
+ latestVersion: manifestEntry.latest,
227
+ expectedNextVersion: maxVersion,
163
228
  });
164
229
  }
165
230
  }
@@ -176,8 +241,8 @@ export function updateManifest(existing, currentContracts) {
176
241
  manifest.contracts[functionKey] = { latest: 0, versions: {} };
177
242
  }
178
243
  const entry = manifest.contracts[functionKey];
179
- for (const { version, contractHash } of entries) {
180
- entry.versions[String(version)] = contractHash;
244
+ for (const { version, inputHash, outputHash } of entries) {
245
+ entry.versions[String(version)] = { inputHash, outputHash };
181
246
  entry.latest = Math.max(entry.latest, version);
182
247
  }
183
248
  }
@@ -196,6 +261,8 @@ export function extractContractsFromMeta(functionsMeta) {
196
261
  functionKey,
197
262
  version,
198
263
  contractHash: meta.contractHash,
264
+ inputHash: meta.inputHash ?? '',
265
+ outputHash: meta.outputHash ?? '',
199
266
  });
200
267
  }
201
268
  return result;
@@ -1,4 +1,4 @@
1
- import { TypesMap } from '../types-map.js';
1
+ import type { TypesMap } from '../types-map.js';
2
2
  /**
3
3
  * NOTE: Code generation normally belongs in @pikku/cli, not the inspector.
4
4
  * This is here because the schema generator needs the custom types content
@@ -1,4 +1,4 @@
1
- import * as ts from 'typescript';
1
+ import type * as ts from 'typescript';
2
2
  import type { SchemaVendor, InspectorLogger } from '../types.js';
3
3
  /**
4
4
  * Detect the schema vendor by tracing the type back to its library origin.
@@ -1,2 +1,2 @@
1
- import * as ts from 'typescript';
1
+ import type * as ts from 'typescript';
2
2
  export declare const doesTypeExtendsCore: (type: ts.Type, checker: ts.TypeChecker, visitedTypes: Set<ts.Type>, coreType: string) => boolean;
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { InspectorState } from '../types.js';
2
+ import type { InspectorState } from '../types.js';
3
3
  /**
4
4
  * Ensures that function metadata exists for a given pikkuFuncId.
5
5
  * Creates stub metadata if it doesn't exist (useful for inline functions).
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core';
2
+ import type { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core';
3
3
  /**
4
4
  * Extract services from a function's first parameter destructuring pattern
5
5
  */
@@ -1,4 +1,4 @@
1
- import { InspectorState, InspectorFilters, InspectorLogger } from '../types.js';
1
+ import type { InspectorState, InspectorFilters, InspectorLogger } from '../types.js';
2
2
  /**
3
3
  * Filters inspector state based on provided filters
4
4
  * This is applied post-inspection to support the inspect-once, filter-many pattern
@@ -1,5 +1,5 @@
1
- import { InspectorFilters, InspectorLogger } from '../types.js';
2
- import { PikkuWiringTypes } from '@pikku/core';
1
+ import type { InspectorFilters, InspectorLogger } from '../types.js';
2
+ import type { PikkuWiringTypes } from '@pikku/core';
3
3
  /**
4
4
  * Match a value against a pattern with wildcard support
5
5
  * Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
@@ -1,4 +1,4 @@
1
- import { PathToNameAndType, InspectorState, InspectorOptions } from '../types.js';
1
+ import type { PathToNameAndType, InspectorState, InspectorOptions } from '../types.js';
2
2
  interface Meta {
3
3
  file: string;
4
4
  variable: string;
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
- import { MiddlewareMetadata } from '@pikku/core';
3
- import { InspectorState } from '../types.js';
2
+ import type { MiddlewareMetadata } from '@pikku/core';
3
+ import type { InspectorState } from '../types.js';
4
4
  export interface MiddlewareRef {
5
5
  definitionId: string;
6
6
  isFactoryCall: boolean;
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
- import { PermissionMetadata } from '@pikku/core';
3
- import { InspectorState } from '../types.js';
2
+ import type { PermissionMetadata } from '@pikku/core';
3
+ import type { InspectorState } from '../types.js';
4
4
  /**
5
5
  * Extract permission pikkuFuncIds from an expression (array or object literal)
6
6
  * Resolves each identifier to its pikkuFuncId using extractFunctionName
@@ -1,5 +1,5 @@
1
- import { InspectorState, InspectorLogger, InspectorOptions, InspectorModelConfig, ExternalPackageConfig } from '../types.js';
2
- import { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
1
+ import type { InspectorState, InspectorLogger, InspectorOptions, InspectorModelConfig } from '../types.js';
2
+ import type { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
3
3
  /**
4
4
  * Helper to extract wire-level middleware/permission names from metadata.
5
5
  * Only extracts type:'wire' variants (individual middleware/permissions).
@@ -14,7 +14,8 @@ export declare function extractWireNames(list?: MiddlewareMetadata[] | Permissio
14
14
  * in the add-* methods during AST traversal for efficiency.
15
15
  */
16
16
  export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
17
- export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, externalPackages?: Record<string, ExternalPackageConfig>): void;
17
+ export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
18
+ export declare function validateVariableOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
18
19
  export declare function computeResolvedIOTypes(state: InspectorState): void;
19
20
  export declare function computeMiddlewareGroupsMeta(state: InspectorState): void;
20
21
  export declare function computePermissionsGroupsMeta(state: InspectorState): void;
@@ -161,17 +161,34 @@ export function aggregateRequiredServices(state) {
161
161
  });
162
162
  }
163
163
  }
164
- export function validateSecretOverrides(logger, state, externalPackages) {
165
- if (!externalPackages)
164
+ export function validateSecretOverrides(logger, state) {
165
+ const { wireAddonDeclarations } = state.rpc;
166
+ if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
166
167
  return;
167
168
  const secretNames = new Set(state.secrets.definitions.map((d) => d.name));
168
- for (const [namespace, pkgConfig] of Object.entries(externalPackages)) {
169
- if (!pkgConfig.secretOverrides)
169
+ for (const [namespace, addonDecl] of wireAddonDeclarations.entries()) {
170
+ if (!addonDecl.secretOverrides)
170
171
  continue;
171
- for (const secretKey of Object.keys(pkgConfig.secretOverrides)) {
172
+ for (const secretKey of Object.keys(addonDecl.secretOverrides)) {
172
173
  if (!secretNames.has(secretKey)) {
173
174
  const availableSecrets = Array.from(secretNames);
174
- logger.critical(ErrorCode.INVALID_VALUE, `Secret override '${secretKey}' in external package '${namespace}' (${pkgConfig.package}) does not exist. Available secrets: ${availableSecrets.join(', ') || 'none'}`);
175
+ logger.critical(ErrorCode.INVALID_VALUE, `Secret override '${secretKey}' in addon '${namespace}' (${addonDecl.package}) does not exist. Available secrets: ${availableSecrets.join(', ') || 'none'}`);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ export function validateVariableOverrides(logger, state) {
181
+ const { wireAddonDeclarations } = state.rpc;
182
+ if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
183
+ return;
184
+ const variableNames = new Set(state.variables.definitions.map((d) => d.name));
185
+ for (const [namespace, addonDecl] of wireAddonDeclarations.entries()) {
186
+ if (!addonDecl.variableOverrides)
187
+ continue;
188
+ for (const variableKey of Object.keys(addonDecl.variableOverrides)) {
189
+ if (!variableNames.has(variableKey)) {
190
+ const availableVariables = Array.from(variableNames);
191
+ logger.critical(ErrorCode.INVALID_VALUE, `Variable override '${variableKey}' in addon '${namespace}' (${addonDecl.package}) does not exist. Available variables: ${availableVariables.join(', ') || 'none'}`);
175
192
  }
176
193
  }
177
194
  }
@@ -277,7 +294,7 @@ export function computeRequiredSchemas(state, options) {
277
294
  const schemasFromTypes = options.schemaConfig?.schemasFromTypes;
278
295
  state.requiredSchemas = new Set([
279
296
  ...Object.values(functions.meta)
280
- .map(({ inputs, outputs }) => {
297
+ .flatMap(({ inputs, outputs }) => {
281
298
  const types = [];
282
299
  if (inputs?.[0]) {
283
300
  try {
@@ -297,7 +314,6 @@ export function computeRequiredSchemas(state, options) {
297
314
  }
298
315
  return types;
299
316
  })
300
- .flat()
301
317
  .filter((s) => !!s && !PRIMITIVE_TYPES.has(s)),
302
318
  ...functions.typesMap.customTypes.keys(),
303
319
  ...(schemasFromTypes || []),
@@ -0,0 +1,16 @@
1
+ import * as ts from 'typescript';
2
+ /**
3
+ * Resolve the addon package name from an imported identifier.
4
+ * Checks if the identifier's import module specifier matches any
5
+ * configured addon package.
6
+ *
7
+ * This is a general utility — any wire handler that processes a `func`
8
+ * property can use it to detect when the function comes from an
9
+ * addon package.
10
+ */
11
+ export declare const resolveAddonName: (identifier: ts.Identifier, checker: ts.TypeChecker, wireAddonDeclarations?: Map<string, {
12
+ package: string;
13
+ rpcEndpoint?: string;
14
+ secretOverrides?: Record<string, string>;
15
+ variableOverrides?: Record<string, string>;
16
+ }>) => string | null;
@@ -1,15 +1,15 @@
1
1
  import * as ts from 'typescript';
2
2
  /**
3
- * Resolve the external package name from an imported identifier.
3
+ * Resolve the addon package name from an imported identifier.
4
4
  * Checks if the identifier's import module specifier matches any
5
- * configured external package.
5
+ * configured addon package.
6
6
  *
7
7
  * This is a general utility — any wire handler that processes a `func`
8
8
  * property can use it to detect when the function comes from an
9
- * external package.
9
+ * addon package.
10
10
  */
11
- export const resolveExternalPackageName = (identifier, checker, externalPackages) => {
12
- if (!externalPackages || Object.keys(externalPackages).length === 0) {
11
+ export const resolveAddonName = (identifier, checker, wireAddonDeclarations) => {
12
+ if (!wireAddonDeclarations || wireAddonDeclarations.size === 0) {
13
13
  return null;
14
14
  }
15
15
  const sym = checker.getSymbolAtLocation(identifier);
@@ -25,9 +25,9 @@ export const resolveExternalPackageName = (identifier, checker, externalPackages
25
25
  if (!ts.isStringLiteral(importDecl.moduleSpecifier))
26
26
  return null;
27
27
  const moduleSpecifier = importDecl.moduleSpecifier.text;
28
- for (const config of Object.values(externalPackages)) {
29
- if (config.package === moduleSpecifier) {
30
- return config.package;
28
+ for (const addonDecl of wireAddonDeclarations.values()) {
29
+ if (addonDecl.package === moduleSpecifier) {
30
+ return addonDecl.package;
31
31
  }
32
32
  }
33
33
  return null;
@@ -1,5 +1,5 @@
1
- import { JSONValue } from '@pikku/core';
2
- import { InspectorLogger, InspectorState } from '../types.js';
1
+ import type { JSONValue } from '@pikku/core';
2
+ import type { InspectorLogger, InspectorState } from '../types.js';
3
3
  export declare function generateAllSchemas(logger: InspectorLogger, config: {
4
4
  tsconfig: string;
5
5
  schemasFromTypes?: string[];
@@ -1,5 +1,5 @@
1
- import { JSONValue } from '@pikku/core';
2
- import { InspectorDiagnostic, InspectorState } from '../types.js';
1
+ import type { JSONValue } from '@pikku/core';
2
+ import type { InspectorDiagnostic, InspectorState } from '../types.js';
3
3
  /**
4
4
  * Serializable version of InspectorState that can be saved to JSON
5
5
  * Omits typesLookup (contains non-serializable ts.Type objects) and converts Maps/Sets to arrays
@@ -163,7 +163,17 @@ export interface SerializableInspectorState {
163
163
  exportedName: string;
164
164
  }]>;
165
165
  invokedFunctions: string[];
166
- usedExternalPackages: string[];
166
+ usedAddons: string[];
167
+ wireAddonDeclarations: Array<[
168
+ string,
169
+ {
170
+ package: string;
171
+ rpcEndpoint?: string;
172
+ secretOverrides?: Record<string, string>;
173
+ variableOverrides?: Record<string, string>;
174
+ }
175
+ ]>;
176
+ wireAddonFiles: string[];
167
177
  };
168
178
  mcpEndpoints: {
169
179
  resourcesMeta: InspectorState['mcpEndpoints']['resourcesMeta'];
@@ -70,7 +70,9 @@ export function serializeInspectorState(state) {
70
70
  exposedMeta: state.rpc.exposedMeta,
71
71
  exposedFiles: Array.from(state.rpc.exposedFiles.entries()),
72
72
  invokedFunctions: Array.from(state.rpc.invokedFunctions),
73
- usedExternalPackages: Array.from(state.rpc.usedExternalPackages),
73
+ usedAddons: Array.from(state.rpc.usedAddons),
74
+ wireAddonDeclarations: Array.from(state.rpc.wireAddonDeclarations.entries()),
75
+ wireAddonFiles: Array.from(state.rpc.wireAddonFiles),
74
76
  },
75
77
  mcpEndpoints: {
76
78
  resourcesMeta: state.mcpEndpoints.resourcesMeta,
@@ -205,7 +207,9 @@ export function deserializeInspectorState(data) {
205
207
  exposedMeta: data.rpc.exposedMeta,
206
208
  exposedFiles: new Map(data.rpc.exposedFiles),
207
209
  invokedFunctions: new Set(data.rpc.invokedFunctions),
208
- usedExternalPackages: new Set(data.rpc.usedExternalPackages || []),
210
+ usedAddons: new Set(data.rpc.usedAddons || []),
211
+ wireAddonDeclarations: new Map(data.rpc.wireAddonDeclarations || []),
212
+ wireAddonFiles: new Set(data.rpc.wireAddonFiles || []),
209
213
  },
210
214
  mcpEndpoints: {
211
215
  resourcesMeta: data.mcpEndpoints.resourcesMeta,
@@ -0,0 +1,3 @@
1
+ import * as ts from 'typescript';
2
+ import type { InspectorLogger, InspectorState } from '../types.js';
3
+ export declare function validateAuthSessionless(logger: InspectorLogger, obj: ts.ObjectLiteralExpression, state: InspectorState, funcName: string, wireDescription: string, inheritedAuth?: boolean): boolean;
@@ -0,0 +1,14 @@
1
+ import { getPropertyValue } from './get-property-value.js';
2
+ import { ErrorCode } from '../error-codes.js';
3
+ export function validateAuthSessionless(logger, obj, state, funcName, wireDescription, inheritedAuth) {
4
+ const fnMeta = state.functions.meta[funcName];
5
+ if (!fnMeta)
6
+ return true;
7
+ const routeAuth = getPropertyValue(obj, 'auth');
8
+ const resolvedAuth = routeAuth === true || routeAuth === false ? routeAuth : inheritedAuth;
9
+ if (resolvedAuth === false && fnMeta.sessionless === false) {
10
+ logger.critical(ErrorCode.AUTH_DISABLED_REQUIRES_SESSIONLESS, `${wireDescription} has auth disabled but function '${funcName}' uses pikkuFunc (requires session). Use pikkuSessionlessFunc instead.`);
11
+ return false;
12
+ }
13
+ return true;
14
+ }
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { WorkflowStepMeta, WorkflowContext } from '@pikku/core/workflow';
2
+ import type { WorkflowStepMeta, WorkflowContext } from '@pikku/core/workflow';
3
3
  /**
4
4
  * Result of simple workflow extraction
5
5
  */
package/dist/visit.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import * as ts from 'typescript';
2
- import { InspectorState, InspectorLogger, InspectorOptions } from './types.js';
2
+ import type { InspectorState, InspectorLogger, InspectorOptions } from './types.js';
3
3
  export declare const visitSetup: (logger: InspectorLogger, checker: ts.TypeChecker, node: ts.Node, state: InspectorState, options: InspectorOptions) => void;
4
4
  export declare const visitRoutes: (logger: InspectorLogger, checker: ts.TypeChecker, node: ts.Node, state: InspectorState, options: InspectorOptions) => void;
package/dist/visit.js CHANGED
@@ -12,6 +12,7 @@ import { addMCPPrompt } from './add/add-mcp-prompt.js';
12
12
  import { addFunctions } from './add/add-functions.js';
13
13
  import { addChannel } from './add/add-channel.js';
14
14
  import { addRPCInvocations } from './add/add-rpc-invocations.js';
15
+ import { addWireAddon } from './add/add-wire-addon.js';
15
16
  import { addMiddleware } from './add/add-middleware.js';
16
17
  import { addPermission } from './add/add-permission.js';
17
18
  import { addCLI, addCLIRenderers } from './add/add-cli.js';
@@ -28,6 +29,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
28
29
  addFileWithFactory(node, checker, state.wireServicesFactories, 'CreateWireServices', state);
29
30
  addFileWithFactory(node, checker, state.configFactories, 'CreateConfig');
30
31
  addRPCInvocations(node, state, logger);
32
+ addWireAddon(node, state, logger);
31
33
  addMiddleware(logger, node, checker, state, options);
32
34
  addPermission(logger, node, checker, state, options);
33
35
  addWorkflow(logger, node, checker, state, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "BUSL-1.1",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
37
- "@pikku/core": "^0.12.0",
37
+ "@pikku/core": "^0.12.1",
38
38
  "path-to-regexp": "^8.3.0",
39
39
  "ts-json-schema-generator": "^2.5.0",
40
40
  "tsx": "^4.21.0",
@@ -4,7 +4,7 @@ import {
4
4
  getCommonWireMetaData,
5
5
  } from '../utils/get-property-value.js'
6
6
  import { extractWireNames } from '../utils/post-process.js'
7
- import { AddWiring, InspectorLogger, SchemaRef } from '../types.js'
7
+ import type { AddWiring, InspectorLogger, SchemaRef } from '../types.js'
8
8
  import {
9
9
  extractFunctionName,
10
10
  funcIdToTypeName,
@@ -63,7 +63,7 @@ function resolveToolReferences(
63
63
  }
64
64
  }
65
65
 
66
- if (calleeName === 'external') {
66
+ if (calleeName === 'addon') {
67
67
  const [firstArg] = element.arguments
68
68
  if (firstArg && ts.isStringLiteral(firstArg)) {
69
69
  resolved.push(firstArg.text)
@@ -18,6 +18,7 @@ import {
18
18
  } from '../utils/middleware.js'
19
19
  import { extractWireNames } from '../utils/post-process.js'
20
20
  import { resolveIdentifier } from '../utils/resolve-identifier.js'
21
+ import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js'
21
22
 
22
23
  /**
23
24
  * Safely get the "initializer" expression of a property-like AST node:
@@ -611,6 +612,26 @@ export const addChannel: AddWiring = (
611
612
  state.serviceAggregation.usedMiddleware.add(name)
612
613
  )
613
614
 
615
+ // --- validate auth/sessionless ---
616
+ const handlersToValidate = [
617
+ connectFuncId,
618
+ disconnectFuncId,
619
+ message?.pikkuFuncId,
620
+ ].filter(Boolean) as string[]
621
+ for (const funcId of handlersToValidate) {
622
+ if (
623
+ !validateAuthSessionless(
624
+ logger,
625
+ obj,
626
+ state,
627
+ funcId,
628
+ `Channel '${name}'`
629
+ )
630
+ ) {
631
+ return
632
+ }
633
+ }
634
+
614
635
  state.channels.files.add(node.getSourceFile().fileName)
615
636
  state.channels.meta[name] = {
616
637
  name,