@pikku/inspector 0.12.0 → 0.12.2

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 (112) hide show
  1. package/CHANGELOG.md +34 -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 +14 -0
  5. package/dist/add/add-cli.d.ts +1 -1
  6. package/dist/add/add-cli.js +29 -8
  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-gateway.d.ts +2 -0
  14. package/dist/add/add-gateway.js +57 -0
  15. package/dist/add/add-http-route.d.ts +3 -2
  16. package/dist/add/add-http-route.js +5 -1
  17. package/dist/add/add-http-routes.d.ts +1 -1
  18. package/dist/add/add-http-routes.js +1 -0
  19. package/dist/add/add-keyed-wiring.d.ts +1 -1
  20. package/dist/add/add-mcp-prompt.d.ts +1 -1
  21. package/dist/add/add-mcp-resource.d.ts +1 -1
  22. package/dist/add/add-queue-worker.d.ts +1 -1
  23. package/dist/add/add-rpc-invocations.d.ts +3 -3
  24. package/dist/add/add-rpc-invocations.js +10 -10
  25. package/dist/add/add-schedule.d.ts +1 -1
  26. package/dist/add/add-secret.d.ts +1 -1
  27. package/dist/add/add-trigger.d.ts +1 -1
  28. package/dist/add/add-trigger.js +2 -2
  29. package/dist/add/add-wire-addon.d.ts +7 -0
  30. package/dist/add/add-wire-addon.js +70 -0
  31. package/dist/add/add-workflow.d.ts +1 -1
  32. package/dist/error-codes.d.ts +1 -0
  33. package/dist/error-codes.js +1 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/inspector.d.ts +1 -1
  36. package/dist/inspector.js +11 -4
  37. package/dist/types.d.ts +31 -20
  38. package/dist/utils/compute-required-schemas.js +1 -2
  39. package/dist/utils/contract-hashes.d.ts +20 -3
  40. package/dist/utils/contract-hashes.js +77 -10
  41. package/dist/utils/custom-types-generator.d.ts +1 -1
  42. package/dist/utils/detect-schema-vendor.d.ts +1 -1
  43. package/dist/utils/does-type-extend-core-type.d.ts +1 -1
  44. package/dist/utils/ensure-function-metadata.d.ts +1 -1
  45. package/dist/utils/extract-services.d.ts +1 -1
  46. package/dist/utils/filter-inspector-state.d.ts +1 -1
  47. package/dist/utils/filter-utils.d.ts +2 -2
  48. package/dist/utils/get-files-and-methods.d.ts +1 -1
  49. package/dist/utils/middleware.d.ts +2 -2
  50. package/dist/utils/permissions.d.ts +2 -2
  51. package/dist/utils/post-process.d.ts +4 -3
  52. package/dist/utils/post-process.js +24 -8
  53. package/dist/utils/resolve-addon-package.d.ts +16 -0
  54. package/dist/utils/{resolve-external-package.js → resolve-addon-package.js} +8 -8
  55. package/dist/utils/schema-generator.d.ts +2 -2
  56. package/dist/utils/serialize-inspector-state.d.ts +17 -3
  57. package/dist/utils/serialize-inspector-state.js +14 -2
  58. package/dist/utils/validate-auth-sessionless.d.ts +3 -0
  59. package/dist/utils/validate-auth-sessionless.js +14 -0
  60. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
  61. package/dist/visit.d.ts +1 -1
  62. package/dist/visit.js +4 -0
  63. package/package.json +3 -3
  64. package/src/add/add-ai-agent.ts +2 -2
  65. package/src/add/add-channel.ts +17 -0
  66. package/src/add/add-cli.ts +53 -15
  67. package/src/add/add-file-extends-core-type.ts +1 -1
  68. package/src/add/add-file-with-config.ts +1 -1
  69. package/src/add/add-file-with-factory.ts +3 -3
  70. package/src/add/add-functions.ts +21 -19
  71. package/src/add/add-gateway.ts +95 -0
  72. package/src/add/add-http-route.ts +19 -2
  73. package/src/add/add-http-routes.ts +2 -1
  74. package/src/add/add-keyed-wiring.ts +1 -1
  75. package/src/add/add-mcp-prompt.ts +1 -1
  76. package/src/add/add-mcp-resource.ts +1 -1
  77. package/src/add/add-queue-worker.ts +1 -1
  78. package/src/add/add-rpc-invocations.ts +11 -11
  79. package/src/add/add-schedule.ts +1 -1
  80. package/src/add/add-secret.ts +1 -1
  81. package/src/add/add-trigger.ts +4 -4
  82. package/src/add/add-wire-addon.ts +80 -0
  83. package/src/add/add-workflow.ts +2 -2
  84. package/src/error-codes.ts +1 -0
  85. package/src/index.ts +1 -0
  86. package/src/inspector.ts +17 -5
  87. package/src/types.ts +38 -20
  88. package/src/utils/compute-required-schemas.ts +1 -2
  89. package/src/utils/contract-hashes.test.ts +30 -5
  90. package/src/utils/contract-hashes.ts +110 -14
  91. package/src/utils/custom-types-generator.ts +1 -1
  92. package/src/utils/detect-schema-vendor.ts +1 -1
  93. package/src/utils/does-type-extend-core-type.ts +1 -1
  94. package/src/utils/ensure-function-metadata.ts +1 -1
  95. package/src/utils/extract-services.ts +1 -1
  96. package/src/utils/filter-inspector-state.test.ts +3 -5
  97. package/src/utils/filter-inspector-state.ts +7 -2
  98. package/src/utils/filter-utils.test.ts +1 -1
  99. package/src/utils/filter-utils.ts +2 -2
  100. package/src/utils/get-files-and-methods.ts +1 -1
  101. package/src/utils/middleware.ts +2 -2
  102. package/src/utils/permissions.ts +2 -2
  103. package/src/utils/post-process.ts +34 -12
  104. package/src/utils/{resolve-external-package.ts → resolve-addon-package.ts} +17 -10
  105. package/src/utils/resolve-versions.test.ts +1 -1
  106. package/src/utils/schema-generator.ts +4 -4
  107. package/src/utils/serialize-inspector-state.ts +35 -5
  108. package/src/utils/validate-auth-sessionless.ts +29 -0
  109. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
  110. package/src/visit.ts +9 -1
  111. package/tsconfig.tsbuildinfo +1 -1
  112. package/dist/utils/resolve-external-package.d.ts +0 -12
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export type { SerializableInspectorState } from './utils/serialize-inspector-sta
8
8
  export { filterInspectorState } from './utils/filter-inspector-state.js';
9
9
  export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
10
10
  export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
11
- export type { ContractEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
11
+ export type { ContractEntry, VersionHashEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
12
12
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
13
13
  export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
14
14
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
@@ -1,4 +1,4 @@
1
- import { InspectorState, InspectorLogger, InspectorOptions } from './types.js';
1
+ import type { InspectorState, InspectorLogger, InspectorOptions } from './types.js';
2
2
  /**
3
3
  * Creates an initial/empty inspector state with all required properties initialized
4
4
  * @param rootDir - The root directory for the project
package/dist/inspector.js CHANGED
@@ -4,9 +4,9 @@ import { visitSetup, visitRoutes } from './visit.js';
4
4
  import { TypesMap } from './types-map.js';
5
5
  import { getFilesAndMethods } from './utils/get-files-and-methods.js';
6
6
  import { findCommonAncestor } from './utils/find-root-dir.js';
7
- import { aggregateRequiredServices, validateSecretOverrides, validateAgentModels, validateAgentOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, } from './utils/post-process.js';
7
+ import { aggregateRequiredServices, validateAgentModels, validateAgentOverrides, validateSecretOverrides, validateVariableOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, } from './utils/post-process.js';
8
8
  import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
9
- import { pikkuState } from '@pikku/core';
9
+ import { pikkuState } from '@pikku/core/internal';
10
10
  import { resolveLatestVersions } from './utils/resolve-versions.js';
11
11
  import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
12
12
  import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
@@ -57,6 +57,10 @@ export function getInitialInspectorState(rootDir) {
57
57
  files: new Set(),
58
58
  meta: {},
59
59
  },
60
+ gateways: {
61
+ meta: {},
62
+ files: new Set(),
63
+ },
60
64
  triggers: {
61
65
  meta: {},
62
66
  sourceMeta: {},
@@ -83,7 +87,9 @@ export function getInitialInspectorState(rootDir) {
83
87
  exposedMeta: {},
84
88
  exposedFiles: new Map(),
85
89
  invokedFunctions: new Set(),
86
- usedExternalPackages: new Set(),
90
+ usedAddons: new Set(),
91
+ wireAddonDeclarations: new Map(),
92
+ wireAddonFiles: new Set(),
87
93
  },
88
94
  mcpEndpoints: {
89
95
  resourcesMeta: {},
@@ -238,9 +244,10 @@ export const inspect = async (logger, routeFiles, options = {}) => {
238
244
  if (options.openAPI) {
239
245
  state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
240
246
  }
241
- validateSecretOverrides(logger, state, options.externalPackages);
242
247
  validateAgentModels(logger, state, options.modelConfig);
243
248
  validateAgentOverrides(logger, state, options.modelConfig);
249
+ validateSecretOverrides(logger, state);
250
+ validateVariableOverrides(logger, state);
244
251
  }
245
252
  return state;
246
253
  };
package/dist/types.d.ts CHANGED
@@ -1,20 +1,21 @@
1
- import * as ts from 'typescript';
2
- import { ChannelsMeta } from '@pikku/core/channel';
3
- import { HTTPWiringsMeta } from '@pikku/core/http';
4
- import { ScheduledTasksMeta } from '@pikku/core/scheduler';
5
- import { TriggerMeta, TriggerSourceMeta } from '@pikku/core/trigger';
6
- import { QueueWorkersMeta } from '@pikku/core/queue';
7
- import { WorkflowsMeta } from '@pikku/core/workflow';
8
- import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
9
- import { AIAgentMeta } from '@pikku/core/ai-agent';
10
- import { CLIMeta } from '@pikku/core/cli';
11
- import { NodesMeta } from '@pikku/core/node';
12
- import { SecretDefinitions } from '@pikku/core/secret';
13
- import { VariableDefinitions } from '@pikku/core/variable';
14
- import { TypesMap } from './types-map.js';
15
- import { FunctionsMeta, FunctionServicesMeta, FunctionWiresMeta, JSONValue } from '@pikku/core';
1
+ import type * as ts from 'typescript';
2
+ import type { ChannelsMeta } from '@pikku/core/channel';
3
+ import type { GatewaysMeta } from '@pikku/core/gateway';
4
+ import type { HTTPWiringsMeta } from '@pikku/core/http';
5
+ import type { ScheduledTasksMeta } from '@pikku/core/scheduler';
6
+ import type { TriggerMeta, TriggerSourceMeta } from '@pikku/core/trigger';
7
+ import type { QueueWorkersMeta } from '@pikku/core/queue';
8
+ import type { WorkflowsMeta } from '@pikku/core/workflow';
9
+ import type { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
10
+ import type { AIAgentMeta } from '@pikku/core/ai-agent';
11
+ import type { CLIMeta } from '@pikku/core/cli';
12
+ import type { NodesMeta } from '@pikku/core/node';
13
+ import type { SecretDefinitions } from '@pikku/core/secret';
14
+ import type { VariableDefinitions } from '@pikku/core/variable';
15
+ import type { TypesMap } from './types-map.js';
16
+ import type { FunctionsMeta, FunctionServicesMeta, FunctionWiresMeta, JSONValue } from '@pikku/core';
16
17
  import type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
17
- import { ErrorCode } from './error-codes.js';
18
+ import type { ErrorCode } from './error-codes.js';
18
19
  import type { VersionManifest, VersionValidateError } from './utils/contract-hashes.js';
19
20
  import type { SerializedWorkflowGraphs } from './utils/workflow/graph/workflow-graph.types.js';
20
21
  export type PathToNameAndType = Map<string, {
@@ -138,7 +139,7 @@ export type InspectorFilters = {
138
139
  httpRoutes?: string[];
139
140
  httpMethods?: string[];
140
141
  };
141
- export type ExternalPackageConfig = {
142
+ export type AddonConfig = {
142
143
  package: string;
143
144
  rpcEndpoint?: string;
144
145
  secretOverrides?: Record<string, string>;
@@ -164,14 +165,13 @@ export type InspectorModelConfig = {
164
165
  export type InspectorOptions = Partial<{
165
166
  setupOnly: boolean;
166
167
  rootDir: string;
167
- isExternalPackage: boolean;
168
+ isAddon: boolean;
168
169
  types: Partial<{
169
170
  configFileType: string;
170
171
  userSessionType: string;
171
172
  singletonServicesFactoryType: string;
172
173
  wireServicesFactoryType: string;
173
174
  }>;
174
- externalPackages: Record<string, ExternalPackageConfig>;
175
175
  schemaConfig: {
176
176
  tsconfig: string;
177
177
  schemasFromTypes?: string[];
@@ -263,6 +263,10 @@ export interface InspectorState {
263
263
  http: InspectorHTTPState;
264
264
  functions: InspectorFunctionState;
265
265
  channels: InspectorChannelState;
266
+ gateways: {
267
+ meta: GatewaysMeta;
268
+ files: Set<string>;
269
+ };
266
270
  triggers: {
267
271
  meta: TriggerMeta;
268
272
  sourceMeta: TriggerSourceMeta;
@@ -301,7 +305,14 @@ export interface InspectorState {
301
305
  exportedName: string;
302
306
  }>;
303
307
  invokedFunctions: Set<string>;
304
- usedExternalPackages: Set<string>;
308
+ usedAddons: Set<string>;
309
+ wireAddonDeclarations: Map<string, {
310
+ package: string;
311
+ rpcEndpoint?: string;
312
+ secretOverrides?: Record<string, string>;
313
+ variableOverrides?: Record<string, string>;
314
+ }>;
315
+ wireAddonFiles: Set<string>;
305
316
  };
306
317
  mcpEndpoints: {
307
318
  resourcesMeta: MCPResourceMeta;
@@ -12,7 +12,7 @@ const PRIMITIVE_TYPES = new Set([
12
12
  export function computeRequiredSchemas(functionsMeta, typesMap, additionalTypes, schemaLookup) {
13
13
  return new Set([
14
14
  ...Object.values(functionsMeta)
15
- .map(({ inputs, outputs }) => {
15
+ .flatMap(({ inputs, outputs }) => {
16
16
  const types = [];
17
17
  if (inputs?.[0]) {
18
18
  try {
@@ -32,7 +32,6 @@ export function computeRequiredSchemas(functionsMeta, typesMap, additionalTypes,
32
32
  }
33
33
  return types;
34
34
  })
35
- .flat()
36
35
  .filter((s) => !!s && !PRIMITIVE_TYPES.has(s)),
37
36
  ...typesMap.customTypes.keys(),
38
37
  ...(additionalTypes || []),
@@ -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
@@ -125,6 +125,10 @@ export interface SerializableInspectorState {
125
125
  files: string[];
126
126
  meta: InspectorState['channels']['meta'];
127
127
  };
128
+ gateways: {
129
+ meta: InspectorState['gateways']['meta'];
130
+ files: string[];
131
+ };
128
132
  triggers: {
129
133
  meta: InspectorState['triggers']['meta'];
130
134
  sourceMeta: InspectorState['triggers']['sourceMeta'];
@@ -163,7 +167,17 @@ export interface SerializableInspectorState {
163
167
  exportedName: string;
164
168
  }]>;
165
169
  invokedFunctions: string[];
166
- usedExternalPackages: string[];
170
+ usedAddons: string[];
171
+ wireAddonDeclarations: Array<[
172
+ string,
173
+ {
174
+ package: string;
175
+ rpcEndpoint?: string;
176
+ secretOverrides?: Record<string, string>;
177
+ variableOverrides?: Record<string, string>;
178
+ }
179
+ ]>;
180
+ wireAddonFiles: string[];
167
181
  };
168
182
  mcpEndpoints: {
169
183
  resourcesMeta: InspectorState['mcpEndpoints']['resourcesMeta'];