@pikku/inspector 0.12.7 → 0.12.9

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 (51) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/add/add-ai-agent.js +24 -7
  3. package/dist/add/add-channel.js +2 -2
  4. package/dist/add/add-cli.js +13 -10
  5. package/dist/add/add-file-with-factory.js +22 -5
  6. package/dist/add/add-functions.js +4 -3
  7. package/dist/add/add-http-route.js +1 -0
  8. package/dist/add/add-mcp-prompt.js +4 -0
  9. package/dist/add/add-mcp-resource.js +4 -0
  10. package/dist/add/add-rpc-invocations.js +2 -2
  11. package/dist/add/add-workflow.d.ts +5 -0
  12. package/dist/add/add-workflow.js +20 -2
  13. package/dist/inspector.js +1 -0
  14. package/dist/types.d.ts +1 -0
  15. package/dist/utils/extract-function-name.d.ts +1 -0
  16. package/dist/utils/extract-function-name.js +27 -32
  17. package/dist/utils/extract-node-value.js +6 -1
  18. package/dist/utils/filter-inspector-state.js +211 -8
  19. package/dist/utils/load-addon-functions-meta.js +47 -0
  20. package/dist/utils/post-process.js +63 -0
  21. package/dist/utils/resolve-versions.js +30 -0
  22. package/dist/utils/schema-generator.js +124 -33
  23. package/dist/utils/serialize-inspector-state.d.ts +1 -0
  24. package/dist/utils/serialize-inspector-state.js +2 -0
  25. package/dist/visit.js +1 -1
  26. package/package.json +2 -2
  27. package/src/add/add-ai-agent.ts +25 -10
  28. package/src/add/add-channel.ts +2 -2
  29. package/src/add/add-cli.ts +17 -16
  30. package/src/add/add-file-with-factory.ts +26 -7
  31. package/src/add/add-functions.ts +4 -4
  32. package/src/add/add-http-route.ts +6 -1
  33. package/src/add/add-mcp-prompt.ts +5 -0
  34. package/src/add/add-mcp-resource.ts +5 -0
  35. package/src/add/add-queue-worker.ts +5 -1
  36. package/src/add/add-rpc-invocations.ts +2 -2
  37. package/src/add/add-workflow.ts +22 -2
  38. package/src/inspector.ts +1 -0
  39. package/src/types.ts +1 -0
  40. package/src/utils/extract-function-name.ts +36 -37
  41. package/src/utils/extract-node-value.test.ts +67 -0
  42. package/src/utils/extract-node-value.ts +5 -1
  43. package/src/utils/filter-inspector-state.ts +246 -11
  44. package/src/utils/load-addon-functions-meta.ts +59 -0
  45. package/src/utils/post-process.ts +74 -0
  46. package/src/utils/resolve-versions.test.ts +141 -0
  47. package/src/utils/resolve-versions.ts +37 -0
  48. package/src/utils/schema-generator.ts +191 -41
  49. package/src/utils/serialize-inspector-state.ts +3 -0
  50. package/src/visit.ts +2 -1
  51. package/tsconfig.tsbuildinfo +1 -1
@@ -139,6 +139,10 @@ export function filterInspectorState(state, filters, logger) {
139
139
  (!filters.httpMethods || filters.httpMethods.length === 0))) {
140
140
  return state;
141
141
  }
142
+ // Snapshot the original workflow graph meta before filtering prunes it
143
+ const originalGraphMeta = {
144
+ ...(state.workflows?.graphMeta ?? {}),
145
+ };
142
146
  // Create a shallow copy with new Maps/Sets to avoid mutating the original
143
147
  const filteredState = {
144
148
  ...state,
@@ -149,11 +153,21 @@ export function filterInspectorState(state, filters, logger) {
149
153
  usedMiddleware: new Set(),
150
154
  usedPermissions: new Set(),
151
155
  },
156
+ functions: {
157
+ ...state.functions,
158
+ meta: JSON.parse(JSON.stringify(state.functions.meta)), // Deep clone to avoid mutating original
159
+ files: new Map(state.functions.files),
160
+ },
152
161
  http: {
153
162
  ...state.http,
154
163
  meta: JSON.parse(JSON.stringify(state.http.meta)), // Deep clone metadata
155
164
  files: new Set(), // Will be repopulated with filtered files
156
165
  },
166
+ workflows: {
167
+ ...state.workflows,
168
+ graphMeta: JSON.parse(JSON.stringify(state.workflows?.graphMeta ?? {})),
169
+ meta: JSON.parse(JSON.stringify(state.workflows?.meta ?? {})),
170
+ },
157
171
  channels: {
158
172
  ...state.channels,
159
173
  meta: JSON.parse(JSON.stringify(state.channels.meta)),
@@ -186,6 +200,14 @@ export function filterInspectorState(state, filters, logger) {
186
200
  agentsMeta: JSON.parse(JSON.stringify(state.agents?.agentsMeta ?? {})),
187
201
  files: new Map(),
188
202
  },
203
+ rpc: {
204
+ ...state.rpc,
205
+ internalMeta: { ...state.rpc.internalMeta }, // Clone to avoid mutating original
206
+ internalFiles: new Map(state.rpc.internalFiles),
207
+ exposedMeta: { ...state.rpc.exposedMeta },
208
+ exposedFiles: new Map(state.rpc.exposedFiles),
209
+ invokedFunctions: new Set(state.rpc.invokedFunctions),
210
+ },
189
211
  cli: {
190
212
  ...state.cli,
191
213
  meta: JSON.parse(JSON.stringify(state.cli.meta)),
@@ -216,16 +238,33 @@ export function filterInspectorState(state, filters, logger) {
216
238
  // Track used functions/middleware/permissions
217
239
  if (routeMeta.pikkuFuncId) {
218
240
  filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncId);
241
+ // For workflow/agent routes, also add the base name
242
+ // so the workflow/agent definition survives pruning
243
+ const colonIdx = routeMeta.pikkuFuncId.indexOf(':');
244
+ if (colonIdx !== -1) {
245
+ filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncId.slice(colonIdx + 1));
246
+ }
219
247
  }
220
248
  extractWireNames(routeMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
221
249
  extractWireNames(routeMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
222
250
  }
223
251
  }
224
252
  }
225
- // Repopulate http.files if any routes remain
226
- const hasHttpRoutes = Object.values(filteredState.http.meta).some((routes) => Object.keys(routes).length > 0);
227
- if (hasHttpRoutes) {
228
- filteredState.http.files = new Set(state.http.files);
253
+ // Repopulate http.files with only files that have surviving routes
254
+ for (const method of Object.keys(filteredState.http.meta)) {
255
+ const routes = filteredState.http.meta[method];
256
+ for (const routeMeta of Object.values(routes)) {
257
+ if (routeMeta.sourceFile) {
258
+ filteredState.http.files.add(routeMeta.sourceFile);
259
+ }
260
+ }
261
+ }
262
+ // Fallback: if no sourceFile info available but routes exist, include all files
263
+ if (filteredState.http.files.size === 0) {
264
+ const hasHttpRoutes = Object.values(filteredState.http.meta).some((routes) => Object.keys(routes).length > 0);
265
+ if (hasHttpRoutes) {
266
+ filteredState.http.files = new Set(state.http.files);
267
+ }
229
268
  }
230
269
  // Filter channels
231
270
  for (const name of Object.keys(filteredState.channels.meta)) {
@@ -239,9 +278,30 @@ export function filterInspectorState(state, filters, logger) {
239
278
  delete filteredState.channels.meta[name];
240
279
  }
241
280
  else {
242
- if (channelMeta.pikkuFuncId) {
281
+ // Add all functions referenced by this channel
282
+ if ('pikkuFuncId' in channelMeta && channelMeta.pikkuFuncId) {
243
283
  filteredState.serviceAggregation.usedFunctions.add(channelMeta.pikkuFuncId);
244
284
  }
285
+ if (channelMeta.connect?.pikkuFuncId) {
286
+ filteredState.serviceAggregation.usedFunctions.add(channelMeta.connect.pikkuFuncId);
287
+ }
288
+ if (channelMeta.disconnect?.pikkuFuncId) {
289
+ filteredState.serviceAggregation.usedFunctions.add(channelMeta.disconnect.pikkuFuncId);
290
+ }
291
+ if (channelMeta.message?.pikkuFuncId) {
292
+ filteredState.serviceAggregation.usedFunctions.add(channelMeta.message.pikkuFuncId);
293
+ }
294
+ if (channelMeta.messageWirings) {
295
+ for (const groupKey of Object.keys(channelMeta.messageWirings)) {
296
+ const commands = channelMeta.messageWirings[groupKey];
297
+ for (const cmdKey of Object.keys(commands)) {
298
+ const wiring = commands[cmdKey];
299
+ if (wiring.pikkuFuncId) {
300
+ filteredState.serviceAggregation.usedFunctions.add(wiring.pikkuFuncId);
301
+ }
302
+ }
303
+ }
304
+ }
245
305
  extractWireNames(channelMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
246
306
  extractWireNames(channelMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
247
307
  }
@@ -307,6 +367,10 @@ export function filterInspectorState(state, filters, logger) {
307
367
  else {
308
368
  if (workerMeta.pikkuFuncId) {
309
369
  filteredState.serviceAggregation.usedFunctions.add(workerMeta.pikkuFuncId);
370
+ const colonIdx = workerMeta.pikkuFuncId.indexOf(':');
371
+ if (colonIdx !== -1) {
372
+ filteredState.serviceAggregation.usedFunctions.add(workerMeta.pikkuFuncId.slice(colonIdx + 1));
373
+ }
310
374
  }
311
375
  extractWireNames(workerMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
312
376
  }
@@ -444,11 +508,39 @@ export function filterInspectorState(state, filters, logger) {
444
508
  if (hasCliPrograms || hasCliRenderers) {
445
509
  filteredState.cli.files = new Set(state.cli.files);
446
510
  }
447
- // Post-filter version expansion: include all versions of matched functions
511
+ // Direct function filtering: functions that match the names/tags/directories
512
+ // filters should be included even if no wiring (HTTP, scheduler, etc.) references them.
513
+ // This ensures standalone RPC-callable functions survive filtering.
514
+ // Only run when function-level filters are active — httpRoutes/httpMethods work
515
+ // through the HTTP wiring pass which already adds the right functions.
516
+ const hasFunctionLevelFilters = (filters.names && filters.names.length > 0) ||
517
+ (filters.tags && filters.tags.length > 0) ||
518
+ (filters.directories && filters.directories.length > 0);
519
+ for (const funcId of Object.keys(filteredState.functions.meta)) {
520
+ if (!hasFunctionLevelFilters)
521
+ break;
522
+ const funcMeta = filteredState.functions.meta[funcId];
523
+ const funcFile = filteredState.functions.files.get(funcId);
524
+ const filePath = funcFile?.path;
525
+ const matches = matchesFilters(filters, {
526
+ type: 'rpc',
527
+ name: funcId,
528
+ tags: funcMeta.tags,
529
+ filePath,
530
+ }, logger);
531
+ if (matches) {
532
+ filteredState.serviceAggregation.usedFunctions.add(funcId);
533
+ }
534
+ }
535
+ // Post-filter version expansion: when an unversioned base name is matched,
536
+ // include all its versions. Specific version matches (e.g. analyzeData@v1)
537
+ // do NOT expand to include other versions.
448
538
  const includedBaseNames = new Set();
449
539
  for (const funcId of filteredState.serviceAggregation.usedFunctions) {
450
- const { baseName } = parseVersionedId(funcId);
451
- includedBaseNames.add(baseName);
540
+ const { baseName, version } = parseVersionedId(funcId);
541
+ if (version === null) {
542
+ includedBaseNames.add(baseName);
543
+ }
452
544
  }
453
545
  if (includedBaseNames.size > 0) {
454
546
  for (const funcId of Object.keys(state.functions.meta)) {
@@ -458,6 +550,117 @@ export function filterInspectorState(state, filters, logger) {
458
550
  }
459
551
  }
460
552
  }
553
+ // Prune functions.meta and functions.files to only include used functions
554
+ if (filteredState.serviceAggregation.usedFunctions.size > 0) {
555
+ for (const funcId of Object.keys(filteredState.functions.meta)) {
556
+ if (!filteredState.serviceAggregation.usedFunctions.has(funcId)) {
557
+ delete filteredState.functions.meta[funcId];
558
+ filteredState.functions.files.delete(funcId);
559
+ }
560
+ }
561
+ // Prune channels whose functions were filtered out
562
+ for (const name of Object.keys(filteredState.channels.meta)) {
563
+ const channelMeta = filteredState.channels.meta[name];
564
+ // Check if any of the channel's functions are in the used set
565
+ const channelFuncIds = [];
566
+ if (channelMeta.connect?.pikkuFuncId)
567
+ channelFuncIds.push(channelMeta.connect.pikkuFuncId);
568
+ if (channelMeta.disconnect?.pikkuFuncId)
569
+ channelFuncIds.push(channelMeta.disconnect.pikkuFuncId);
570
+ if (channelMeta.message?.pikkuFuncId)
571
+ channelFuncIds.push(channelMeta.message.pikkuFuncId);
572
+ if (channelMeta.messageWirings) {
573
+ for (const groupKey of Object.keys(channelMeta.messageWirings)) {
574
+ const commands = channelMeta.messageWirings[groupKey];
575
+ for (const cmdKey of Object.keys(commands)) {
576
+ const wiring = commands[cmdKey];
577
+ if (wiring.pikkuFuncId)
578
+ channelFuncIds.push(wiring.pikkuFuncId);
579
+ }
580
+ }
581
+ }
582
+ const hasUsedFunc = channelFuncIds.some((id) => filteredState.serviceAggregation.usedFunctions.has(id));
583
+ if (channelFuncIds.length > 0 && !hasUsedFunc) {
584
+ delete filteredState.channels.meta[name];
585
+ }
586
+ }
587
+ // Prune workflow graphs whose function was filtered out
588
+ const workflowKeys = new Set([
589
+ ...Object.keys(filteredState.workflows.graphMeta),
590
+ ...Object.keys(filteredState.workflows.meta),
591
+ ]);
592
+ for (const name of workflowKeys) {
593
+ const graphMeta = filteredState.workflows.graphMeta[name];
594
+ const workflowMeta = filteredState.workflows.meta[name];
595
+ // Check both graphMeta.pikkuFuncId and meta.pikkuFuncId
596
+ const pikkuFuncId = graphMeta?.pikkuFuncId ?? workflowMeta?.pikkuFuncId;
597
+ if (pikkuFuncId &&
598
+ !filteredState.serviceAggregation.usedFunctions.has(pikkuFuncId)) {
599
+ delete filteredState.workflows.graphMeta[name];
600
+ delete filteredState.workflows.meta[name];
601
+ }
602
+ else if (!pikkuFuncId) {
603
+ // No function ID found — prune it
604
+ delete filteredState.workflows.graphMeta[name];
605
+ delete filteredState.workflows.meta[name];
606
+ }
607
+ }
608
+ // Prune RPC meta to only include entries whose target function survived
609
+ const survivingFuncIds = new Set(Object.keys(filteredState.functions.meta));
610
+ for (const key of Object.keys(filteredState.rpc.internalMeta)) {
611
+ const targetFuncId = filteredState.rpc.internalMeta[key];
612
+ if (!survivingFuncIds.has(targetFuncId) && !survivingFuncIds.has(key)) {
613
+ delete filteredState.rpc.internalMeta[key];
614
+ filteredState.rpc.internalFiles.delete(key);
615
+ }
616
+ }
617
+ for (const key of Object.keys(filteredState.rpc.exposedMeta)) {
618
+ const targetFuncId = filteredState.rpc.exposedMeta[key];
619
+ if (!survivingFuncIds.has(targetFuncId) && !survivingFuncIds.has(key)) {
620
+ delete filteredState.rpc.exposedMeta[key];
621
+ filteredState.rpc.exposedFiles.delete(key);
622
+ }
623
+ }
624
+ // Prune invokedFunctions to match surviving functions
625
+ for (const funcId of filteredState.rpc.invokedFunctions) {
626
+ if (!survivingFuncIds.has(funcId)) {
627
+ filteredState.rpc.invokedFunctions.delete(funcId);
628
+ }
629
+ }
630
+ }
631
+ // Recompute requiredSchemas based on pruned functions.meta
632
+ if (filteredState.serviceAggregation.usedFunctions.size > 0) {
633
+ const prunedSchemas = new Set();
634
+ for (const funcMeta of Object.values(filteredState.functions.meta)) {
635
+ if (funcMeta.inputs?.[0])
636
+ prunedSchemas.add(funcMeta.inputs[0]);
637
+ if (funcMeta.outputs?.[0])
638
+ prunedSchemas.add(funcMeta.outputs[0]);
639
+ }
640
+ filteredState.requiredSchemas = prunedSchemas;
641
+ }
642
+ // If any surviving function is a non-inline workflow step, the unit needs
643
+ // workflowService + queueService even though the function doesn't use them.
644
+ // Check the ORIGINAL graph meta (before filtering pruned it).
645
+ const survivingFuncIds = new Set(Object.keys(filteredState.functions.meta));
646
+ // Use the snapshot taken before filtering
647
+ for (const graph of Object.values(originalGraphMeta)) {
648
+ if (!graph.nodes)
649
+ continue;
650
+ for (const node of Object.values(graph.nodes)) {
651
+ if (!('rpcName' in node) || !node.rpcName)
652
+ continue;
653
+ const rpcName = node.rpcName;
654
+ if (!survivingFuncIds.has(rpcName))
655
+ continue;
656
+ const isInline = node.options?.async !== true &&
657
+ graph.inline === true;
658
+ if (!isInline) {
659
+ filteredState.serviceAggregation.requiredServices.add('workflowService');
660
+ filteredState.serviceAggregation.requiredServices.add('queueService');
661
+ }
662
+ }
663
+ }
461
664
  // Recalculate requiredServices based on filtered functions/middleware/permissions
462
665
  // Need to cast to InspectorState temporarily for aggregateRequiredServices
463
666
  const stateForAggregation = filteredState;
@@ -34,6 +34,53 @@ export async function loadAddonFunctionsMeta(logger, state) {
34
34
  }
35
35
  }
36
36
  }
37
+ // Load addon secrets meta
38
+ try {
39
+ const secretsMetaPath = require.resolve(`${decl.package}/.pikku/secrets/pikku-secrets-meta.gen.json`);
40
+ const secretsRaw = await readFile(secretsMetaPath, 'utf-8');
41
+ const secretsMeta = JSON.parse(secretsRaw);
42
+ for (const [key, def] of Object.entries(secretsMeta)) {
43
+ const existing = state.secrets.definitions.find((d) => d.name === key);
44
+ if (!existing) {
45
+ state.secrets.definitions.push(def);
46
+ logger.debug(`Loaded addon secret '${key}' from ${decl.package}`);
47
+ }
48
+ }
49
+ }
50
+ catch {
51
+ // No secrets meta — that's fine
52
+ }
53
+ // Load addon variables meta
54
+ try {
55
+ const variablesMetaPath = require.resolve(`${decl.package}/.pikku/variables/pikku-variables-meta.gen.json`);
56
+ const variablesRaw = await readFile(variablesMetaPath, 'utf-8');
57
+ const variablesMeta = JSON.parse(variablesRaw);
58
+ for (const [key, def] of Object.entries(variablesMeta)) {
59
+ const existing = state.variables.definitions.find((d) => d.name === key);
60
+ if (!existing) {
61
+ state.variables.definitions.push(def);
62
+ logger.debug(`Loaded addon variable '${key}' from ${decl.package}`);
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ // No variables meta — that's fine
68
+ }
69
+ // Load addon required parent services from pikku-services.gen
70
+ try {
71
+ const servicesGenPath = require.resolve(`${decl.package}/.pikku/pikku-services.gen.js`);
72
+ const servicesModule = await import(servicesGenPath);
73
+ if (servicesModule.requiredParentServices &&
74
+ Array.isArray(servicesModule.requiredParentServices)) {
75
+ for (const service of servicesModule.requiredParentServices) {
76
+ state.addonRequiredParentServices.push(service);
77
+ }
78
+ logger.debug(`Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`);
79
+ }
80
+ }
81
+ catch {
82
+ // No services gen — addon may not have requiredParentServices
83
+ }
37
84
  }
38
85
  catch (error) {
39
86
  logger.warn(`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`);
@@ -160,6 +160,69 @@ export function aggregateRequiredServices(state) {
160
160
  }
161
161
  });
162
162
  }
163
+ // 6. Implicit platform services required by wiring types
164
+ // Workflows need workflowService + workflowRunService + schedulerService + queueService.
165
+ // Check workflow definitions, graph meta, AND helper functions (workflowStart:*, etc.)
166
+ // that wrap workflow operations but don't destructure the services.
167
+ const hasWorkflows = Object.keys(state.workflows.graphMeta).length > 0 ||
168
+ Object.keys(state.workflows.meta).length > 0 ||
169
+ Object.keys(state.functions.meta).some((id) => id.startsWith('workflowStart:') ||
170
+ id.startsWith('workflowStatus:') ||
171
+ id.startsWith('workflow:'));
172
+ if (hasWorkflows) {
173
+ requiredServices.add('workflowService');
174
+ requiredServices.add('workflowRunService');
175
+ requiredServices.add('schedulerService');
176
+ requiredServices.add('queueService');
177
+ }
178
+ // 6b. Inject synthetic queue workers for workflow graph steps.
179
+ // Each workflow gets an orchestrator queue and per-step queues.
180
+ // Without these, the PikkuWorkflowService constructor can't find
181
+ // per-workflow queue entries and falls back to shared queue names.
182
+ for (const [, graph] of Object.entries(state.workflows.graphMeta)) {
183
+ if (!graph.nodes || !graph.name)
184
+ continue;
185
+ const toKebab = (s) => s
186
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
187
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
188
+ .toLowerCase();
189
+ // Orchestrator queue
190
+ const orchQueueName = `wf-orchestrator-${toKebab(graph.name)}`;
191
+ if (!state.queueWorkers.meta[orchQueueName]) {
192
+ state.queueWorkers.meta[orchQueueName] = {
193
+ name: orchQueueName,
194
+ pikkuFuncId: `pikkuWorkflowOrchestrator:${graph.name}`,
195
+ };
196
+ }
197
+ // Per-step queues
198
+ for (const node of Object.values(graph.nodes)) {
199
+ if (!('rpcName' in node) || !node.rpcName)
200
+ continue;
201
+ const rpcName = node.rpcName;
202
+ const stepQueueName = `wf-step-${toKebab(rpcName)}`;
203
+ if (!state.queueWorkers.meta[stepQueueName]) {
204
+ state.queueWorkers.meta[stepQueueName] = {
205
+ name: stepQueueName,
206
+ pikkuFuncId: `pikkuWorkflowWorker:${rpcName}`,
207
+ };
208
+ }
209
+ }
210
+ }
211
+ // AI agents need aiStorage + aiRunState + agentRunService + aiAgentRunner
212
+ if (Object.keys(state.agents.agentsMeta).length > 0) {
213
+ requiredServices.add('aiStorage');
214
+ requiredServices.add('aiRunState');
215
+ requiredServices.add('agentRunService');
216
+ requiredServices.add('aiAgentRunner');
217
+ }
218
+ // Channels need eventHub for pub/sub
219
+ if (Object.keys(state.channels.meta).length > 0) {
220
+ requiredServices.add('eventHub');
221
+ }
222
+ // 7. Services that addons need from the parent project
223
+ for (const service of state.addonRequiredParentServices ?? []) {
224
+ requiredServices.add(service);
225
+ }
163
226
  }
164
227
  export function validateSecretOverrides(logger, state) {
165
228
  const { wireAddonDeclarations } = state.rpc;
@@ -66,13 +66,43 @@ export function resolveLatestVersions(state, logger) {
66
66
  if (state.rpc.exposedMeta[baseName] === oldId) {
67
67
  state.rpc.exposedMeta[baseName] = newId;
68
68
  }
69
+ updateWiringReferences(state, oldId, newId);
69
70
  }
70
71
  else {
71
72
  const latest = group.explicit.reduce((a, b) => a.version > b.version ? a : b);
72
73
  state.rpc.internalMeta[baseName] = latest.id;
73
74
  }
75
+ if (state.rpc.exposedMeta[baseName]) {
76
+ const latestId = state.rpc.internalMeta[baseName];
77
+ state.rpc.exposedMeta[baseName] = latestId;
78
+ for (const entry of group.explicit) {
79
+ state.rpc.exposedMeta[entry.id] = entry.id;
80
+ const fileEntry = state.rpc.internalFiles.get(entry.id);
81
+ if (fileEntry) {
82
+ state.rpc.exposedFiles.set(entry.id, fileEntry);
83
+ }
84
+ }
85
+ if (group.unversioned) {
86
+ state.rpc.exposedMeta[latestId] = latestId;
87
+ const fileEntry = state.rpc.internalFiles.get(latestId);
88
+ if (fileEntry) {
89
+ state.rpc.exposedFiles.set(latestId, fileEntry);
90
+ }
91
+ }
92
+ }
74
93
  for (const entry of group.explicit) {
75
94
  state.rpc.invokedFunctions.add(entry.id);
76
95
  }
77
96
  }
78
97
  }
98
+ function updateWiringReferences(state, oldId, newId) {
99
+ if (state.http) {
100
+ for (const methods of Object.values(state.http.meta)) {
101
+ for (const meta of Object.values(methods)) {
102
+ if (meta.pikkuFuncId === oldId) {
103
+ meta.pikkuFuncId = newId;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }