@pikku/inspector 0.11.2 → 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 (211) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +81 -61
  6. package/dist/add/add-cli.d.ts +1 -1
  7. package/dist/add/add-cli.js +42 -19
  8. package/dist/add/add-file-extends-core-type.d.ts +1 -1
  9. package/dist/add/add-file-with-config.d.ts +1 -1
  10. package/dist/add/add-file-with-factory.d.ts +1 -1
  11. package/dist/add/add-file-with-factory.js +2 -0
  12. package/dist/add/add-functions.d.ts +1 -1
  13. package/dist/add/add-functions.js +256 -82
  14. package/dist/add/add-http-route.d.ts +20 -10
  15. package/dist/add/add-http-route.js +156 -66
  16. package/dist/add/add-http-routes.d.ts +5 -0
  17. package/dist/add/add-http-routes.js +160 -0
  18. package/dist/add/add-keyed-wiring.d.ts +12 -0
  19. package/dist/add/add-keyed-wiring.js +97 -0
  20. package/dist/add/add-mcp-prompt.d.ts +1 -1
  21. package/dist/add/add-mcp-prompt.js +14 -9
  22. package/dist/add/add-mcp-resource.d.ts +1 -1
  23. package/dist/add/add-mcp-resource.js +14 -9
  24. package/dist/add/add-middleware.d.ts +1 -4
  25. package/dist/add/add-middleware.js +364 -79
  26. package/dist/add/add-permission.d.ts +1 -1
  27. package/dist/add/add-permission.js +152 -40
  28. package/dist/add/add-queue-worker.d.ts +1 -1
  29. package/dist/add/add-queue-worker.js +18 -12
  30. package/dist/add/add-rpc-invocations.d.ts +3 -3
  31. package/dist/add/add-rpc-invocations.js +24 -10
  32. package/dist/add/add-schedule.d.ts +1 -1
  33. package/dist/add/add-schedule.js +11 -5
  34. package/dist/add/add-secret.d.ts +3 -0
  35. package/dist/add/add-secret.js +82 -0
  36. package/dist/add/add-trigger.d.ts +2 -0
  37. package/dist/add/add-trigger.js +87 -0
  38. package/dist/add/add-variable.d.ts +1 -0
  39. package/dist/add/add-variable.js +8 -0
  40. package/dist/add/add-wire-addon.d.ts +7 -0
  41. package/dist/add/add-wire-addon.js +70 -0
  42. package/dist/add/add-workflow-graph.d.ts +3 -2
  43. package/dist/add/add-workflow-graph.js +143 -406
  44. package/dist/add/add-workflow.d.ts +1 -1
  45. package/dist/add/add-workflow.js +6 -4
  46. package/dist/error-codes.d.ts +15 -1
  47. package/dist/error-codes.js +20 -1
  48. package/dist/index.d.ts +9 -8
  49. package/dist/index.js +5 -4
  50. package/dist/inspector.d.ts +2 -2
  51. package/dist/inspector.js +95 -15
  52. package/dist/schema-generator.d.ts +1 -0
  53. package/dist/schema-generator.js +1 -0
  54. package/dist/types-map.js +10 -1
  55. package/dist/types.d.ts +180 -50
  56. package/dist/utils/compute-required-schemas.d.ts +4 -0
  57. package/dist/utils/compute-required-schemas.js +40 -0
  58. package/dist/utils/contract-hashes.d.ts +52 -0
  59. package/dist/utils/contract-hashes.js +269 -0
  60. package/dist/utils/custom-types-generator.d.ts +9 -0
  61. package/dist/utils/custom-types-generator.js +71 -0
  62. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  63. package/dist/utils/detect-schema-vendor.js +76 -0
  64. package/dist/utils/does-type-extend-core-type.d.ts +1 -1
  65. package/dist/utils/ensure-function-metadata.d.ts +6 -3
  66. package/dist/utils/ensure-function-metadata.js +220 -6
  67. package/dist/utils/extract-function-name.d.ts +5 -16
  68. package/dist/utils/extract-function-name.js +86 -291
  69. package/dist/utils/extract-services.d.ts +2 -1
  70. package/dist/utils/extract-services.js +25 -1
  71. package/dist/utils/filter-inspector-state.d.ts +1 -1
  72. package/dist/utils/filter-inspector-state.js +107 -23
  73. package/dist/utils/filter-utils.d.ts +2 -2
  74. package/dist/utils/get-files-and-methods.d.ts +1 -1
  75. package/dist/utils/get-property-value.d.ts +6 -1
  76. package/dist/utils/get-property-value.js +28 -3
  77. package/dist/utils/hash.d.ts +2 -0
  78. package/dist/utils/hash.js +23 -0
  79. package/dist/utils/middleware.d.ts +9 -32
  80. package/dist/utils/middleware.js +80 -66
  81. package/dist/utils/permissions.d.ts +4 -4
  82. package/dist/utils/permissions.js +10 -10
  83. package/dist/utils/post-process.d.ts +11 -11
  84. package/dist/utils/post-process.js +247 -24
  85. package/dist/utils/resolve-addon-package.d.ts +16 -0
  86. package/dist/utils/resolve-addon-package.js +34 -0
  87. package/dist/utils/resolve-function-types.d.ts +6 -0
  88. package/dist/utils/resolve-function-types.js +29 -0
  89. package/dist/utils/resolve-identifier.d.ts +10 -0
  90. package/dist/utils/resolve-identifier.js +36 -0
  91. package/dist/utils/resolve-versions.d.ts +2 -0
  92. package/dist/utils/resolve-versions.js +78 -0
  93. package/dist/utils/schema-generator.d.ts +9 -0
  94. package/dist/utils/schema-generator.js +209 -0
  95. package/dist/utils/serialize-inspector-state.d.ts +70 -23
  96. package/dist/utils/serialize-inspector-state.js +98 -22
  97. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  98. package/dist/utils/serialize-mcp-json.js +99 -0
  99. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  100. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  101. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  102. package/dist/utils/serialize-openapi-json.js +151 -0
  103. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  104. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  105. package/dist/utils/validate-auth-sessionless.d.ts +3 -0
  106. package/dist/utils/validate-auth-sessionless.js +14 -0
  107. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  108. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
  109. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  110. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  111. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  112. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  113. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  114. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  115. package/dist/utils/workflow/graph/index.d.ts +2 -0
  116. package/dist/utils/workflow/graph/index.js +2 -0
  117. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  118. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  119. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  120. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  121. package/dist/visit.d.ts +1 -1
  122. package/dist/visit.js +13 -6
  123. package/package.json +14 -4
  124. package/src/add/add-ai-agent.ts +468 -0
  125. package/src/add/add-channel.ts +103 -79
  126. package/src/add/add-cli.ts +68 -24
  127. package/src/add/add-file-extends-core-type.ts +1 -1
  128. package/src/add/add-file-with-config.ts +1 -1
  129. package/src/add/add-file-with-factory.ts +3 -1
  130. package/src/add/add-functions.ts +349 -103
  131. package/src/add/add-http-route.ts +263 -89
  132. package/src/add/add-http-routes.ts +229 -0
  133. package/src/add/add-keyed-wiring.ts +151 -0
  134. package/src/add/add-mcp-prompt.ts +27 -16
  135. package/src/add/add-mcp-resource.ts +28 -16
  136. package/src/add/add-middleware.ts +482 -80
  137. package/src/add/add-permission.ts +199 -40
  138. package/src/add/add-queue-worker.ts +25 -20
  139. package/src/add/add-rpc-invocations.ts +28 -11
  140. package/src/add/add-schedule.ts +17 -12
  141. package/src/add/add-secret.ts +140 -0
  142. package/src/add/add-trigger.ts +154 -0
  143. package/src/add/add-variable.ts +9 -0
  144. package/src/add/add-wire-addon.ts +80 -0
  145. package/src/add/add-workflow-graph.ts +180 -522
  146. package/src/add/add-workflow.ts +7 -6
  147. package/src/error-codes.ts +25 -1
  148. package/src/index.ts +23 -13
  149. package/src/inspector.ts +139 -19
  150. package/src/schema-generator.ts +1 -0
  151. package/src/types-map.ts +12 -1
  152. package/src/types.ts +199 -69
  153. package/src/utils/compute-required-schemas.ts +48 -0
  154. package/src/utils/contract-hashes.test.ts +553 -0
  155. package/src/utils/contract-hashes.ts +386 -0
  156. package/src/utils/custom-types-generator.ts +88 -0
  157. package/src/utils/detect-schema-vendor.ts +90 -0
  158. package/src/utils/does-type-extend-core-type.ts +1 -1
  159. package/src/utils/ensure-function-metadata.ts +325 -8
  160. package/src/utils/extract-function-name.ts +101 -351
  161. package/src/utils/extract-services.ts +35 -2
  162. package/src/utils/filter-inspector-state.test.ts +37 -25
  163. package/src/utils/filter-inspector-state.ts +146 -32
  164. package/src/utils/filter-utils.test.ts +1 -1
  165. package/src/utils/filter-utils.ts +2 -2
  166. package/src/utils/get-files-and-methods.ts +1 -1
  167. package/src/utils/get-property-value.ts +42 -4
  168. package/src/utils/hash.ts +26 -0
  169. package/src/utils/middleware.test.ts +204 -0
  170. package/src/utils/middleware.ts +131 -69
  171. package/src/utils/permissions.test.ts +35 -12
  172. package/src/utils/permissions.ts +12 -12
  173. package/src/utils/post-process.ts +306 -44
  174. package/src/utils/resolve-addon-package.ts +49 -0
  175. package/src/utils/resolve-function-types.ts +42 -0
  176. package/src/utils/resolve-identifier.ts +46 -0
  177. package/src/utils/resolve-versions.test.ts +249 -0
  178. package/src/utils/resolve-versions.ts +105 -0
  179. package/src/utils/schema-generator.ts +329 -0
  180. package/src/utils/serialize-inspector-state.ts +184 -43
  181. package/src/utils/serialize-mcp-json.ts +145 -0
  182. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  183. package/src/utils/serialize-openapi-json.ts +277 -0
  184. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  185. package/src/utils/test-data/inspector-state.json +69 -66
  186. package/src/utils/validate-auth-sessionless.ts +29 -0
  187. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  188. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +26 -6
  189. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  190. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  191. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  192. package/src/utils/workflow/graph/index.ts +5 -0
  193. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  194. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  195. package/src/visit.ts +19 -7
  196. package/tsconfig.tsbuildinfo +1 -1
  197. package/dist/add/add-forge-credential.d.ts +0 -8
  198. package/dist/add/add-forge-credential.js +0 -77
  199. package/dist/add/add-forge-node.d.ts +0 -7
  200. package/dist/add/add-forge-node.js +0 -77
  201. package/dist/add/add-mcp-tool.d.ts +0 -2
  202. package/dist/add/add-mcp-tool.js +0 -81
  203. package/dist/utils/extract-service-metadata.d.ts +0 -19
  204. package/dist/utils/extract-service-metadata.js +0 -244
  205. package/dist/utils/write-service-metadata.d.ts +0 -13
  206. package/dist/utils/write-service-metadata.js +0 -37
  207. package/src/add/add-forge-credential.ts +0 -119
  208. package/src/add/add-forge-node.ts +0 -132
  209. package/src/add/add-mcp-tool.ts +0 -141
  210. package/src/utils/extract-service-metadata.ts +0 -353
  211. package/src/utils/write-service-metadata.ts +0 -51
@@ -0,0 +1,386 @@
1
+ import type { FunctionsMeta, JSONValue } from '@pikku/core'
2
+ import { parseVersionedId } from '@pikku/core'
3
+ import type { TypesMap } from '../types-map.js'
4
+ import { ErrorCode } from '../error-codes.js'
5
+ import { canonicalJSON, hashString } from './hash.js'
6
+
7
+ export type ContractEntry = {
8
+ functionKey: string
9
+ version: number
10
+ contractHash: string
11
+ inputHash: string
12
+ outputHash: string
13
+ }
14
+
15
+ export type VersionValidateError = {
16
+ code: ErrorCode
17
+ message: string
18
+ functionKey?: string
19
+ version?: number
20
+ previousHash?: string
21
+ currentHash?: string
22
+ previousInputHash?: string
23
+ currentInputHash?: string
24
+ previousOutputHash?: string
25
+ currentOutputHash?: string
26
+ nextVersion?: number
27
+ latestVersion?: number
28
+ expectedNextVersion?: number
29
+ }
30
+
31
+ export type VersionHashEntry = {
32
+ inputHash: string
33
+ outputHash: string
34
+ }
35
+
36
+ export type VersionManifestEntry = {
37
+ latest: number
38
+ versions: Record<string, string | VersionHashEntry>
39
+ }
40
+
41
+ export type VersionManifest = {
42
+ manifestVersion: 1
43
+ contracts: Record<string, VersionManifestEntry>
44
+ }
45
+
46
+ function isHashEntry(v: string | VersionHashEntry): v is VersionHashEntry {
47
+ return typeof v === 'object'
48
+ }
49
+
50
+ export function createEmptyManifest(): VersionManifest {
51
+ return {
52
+ manifestVersion: 1,
53
+ contracts: {},
54
+ }
55
+ }
56
+
57
+ export function serializeManifest(manifest: VersionManifest): string {
58
+ const sortedContracts: Record<string, VersionManifestEntry> = {}
59
+
60
+ for (const key of Object.keys(manifest.contracts).sort()) {
61
+ const entry = manifest.contracts[key]
62
+ const sortedVersions: Record<string, string | VersionHashEntry> = {}
63
+ const numericKeys = Object.keys(entry.versions)
64
+ .map(Number)
65
+ .sort((a, b) => a - b)
66
+ for (const vk of numericKeys) {
67
+ sortedVersions[String(vk)] = entry.versions[String(vk)]
68
+ }
69
+ sortedContracts[key] = {
70
+ latest: entry.latest,
71
+ versions: sortedVersions,
72
+ }
73
+ }
74
+
75
+ const sorted: VersionManifest = {
76
+ manifestVersion: manifest.manifestVersion,
77
+ contracts: sortedContracts,
78
+ }
79
+
80
+ return JSON.stringify(sorted, null, 2) + '\n'
81
+ }
82
+
83
+ export function computeContractHash(data: {
84
+ functionKey: string
85
+ inputSchema: unknown
86
+ outputSchema: unknown
87
+ }): string {
88
+ return hashString(canonicalJSON(data), 16)
89
+ }
90
+
91
+ function computeInputHash(functionKey: string, inputSchema: unknown): string {
92
+ return hashString(canonicalJSON({ functionKey, inputSchema }), 8)
93
+ }
94
+
95
+ function computeOutputHash(functionKey: string, outputSchema: unknown): string {
96
+ return hashString(canonicalJSON({ functionKey, outputSchema }), 8)
97
+ }
98
+
99
+ function resolveSchema(
100
+ typeNames: string[] | null | undefined,
101
+ allSchemas: Record<string, JSONValue>,
102
+ typesMap: TypesMap
103
+ ): unknown {
104
+ if (!typeNames) {
105
+ return null
106
+ }
107
+ const filtered = typeNames.filter((n) => n !== 'void')
108
+ if (filtered.length === 0) {
109
+ return null
110
+ }
111
+ const parts: unknown[] = []
112
+ for (const name of filtered) {
113
+ let key: string
114
+ try {
115
+ key = typesMap.getUniqueName(name)
116
+ } catch {
117
+ key = name
118
+ }
119
+ const schema = allSchemas[key]
120
+ if (schema) {
121
+ parts.push(schema)
122
+ }
123
+ }
124
+ if (parts.length === 0) {
125
+ return null
126
+ }
127
+ return parts.length === 1 ? parts[0] : parts
128
+ }
129
+
130
+ export function buildCurrentContracts(
131
+ functionsMeta: FunctionsMeta,
132
+ allSchemas: Record<string, JSONValue>,
133
+ typesMap: TypesMap
134
+ ): Map<string, ContractEntry> {
135
+ const result = new Map<string, ContractEntry>()
136
+
137
+ for (const [funcId, meta] of Object.entries(functionsMeta)) {
138
+ if (meta.remote === true) {
139
+ continue
140
+ }
141
+
142
+ const parsed = parseVersionedId(funcId)
143
+ const functionKey = parsed.baseName
144
+ const version = parsed.version ?? meta.version ?? 1
145
+
146
+ const inputSchema = resolveSchema(meta.inputs, allSchemas, typesMap)
147
+ const outputSchema = resolveSchema(meta.outputs, allSchemas, typesMap)
148
+
149
+ const contractHash = computeContractHash({
150
+ functionKey,
151
+ inputSchema,
152
+ outputSchema,
153
+ })
154
+ const inputHash = computeInputHash(functionKey, inputSchema)
155
+ const outputHash = computeOutputHash(functionKey, outputSchema)
156
+
157
+ result.set(funcId, {
158
+ functionKey,
159
+ version,
160
+ contractHash,
161
+ inputHash,
162
+ outputHash,
163
+ })
164
+ }
165
+
166
+ return result
167
+ }
168
+
169
+ export function computeContractHashes(
170
+ allSchemas: Record<string, JSONValue>,
171
+ typesMap: TypesMap,
172
+ functionsMeta: FunctionsMeta
173
+ ): Map<string, ContractEntry> {
174
+ const contracts = buildCurrentContracts(functionsMeta, allSchemas, typesMap)
175
+
176
+ for (const [funcId, entry] of contracts) {
177
+ const meta = functionsMeta[funcId]
178
+ if (meta) {
179
+ meta.contractHash = entry.contractHash
180
+ meta.inputHash = entry.inputHash
181
+ meta.outputHash = entry.outputHash
182
+ }
183
+ }
184
+
185
+ return contracts
186
+ }
187
+
188
+ function groupByFunctionKey(
189
+ contracts: Map<string, ContractEntry>
190
+ ): Map<string, ContractEntry[]> {
191
+ const grouped = new Map<string, ContractEntry[]>()
192
+ for (const entry of contracts.values()) {
193
+ const existing = grouped.get(entry.functionKey) ?? []
194
+ existing.push(entry)
195
+ grouped.set(entry.functionKey, existing)
196
+ }
197
+ return grouped
198
+ }
199
+
200
+ function entryChanged(
201
+ existing: string | VersionHashEntry,
202
+ current: ContractEntry
203
+ ): boolean {
204
+ if (isHashEntry(existing)) {
205
+ return (
206
+ existing.inputHash !== current.inputHash ||
207
+ existing.outputHash !== current.outputHash
208
+ )
209
+ }
210
+ return existing !== current.contractHash
211
+ }
212
+
213
+ export function validateContracts(
214
+ manifest: VersionManifest,
215
+ currentContracts: Map<string, ContractEntry>
216
+ ): { valid: boolean; errors: VersionValidateError[] } {
217
+ const errors: VersionValidateError[] = []
218
+ const grouped = groupByFunctionKey(currentContracts)
219
+ const reportedKeys = new Set<string>()
220
+
221
+ for (const [functionKey, entries] of grouped) {
222
+ const manifestEntry = manifest.contracts[functionKey]
223
+ if (!manifestEntry) {
224
+ continue
225
+ }
226
+
227
+ for (const current of entries) {
228
+ const { version, contractHash, inputHash, outputHash } = current
229
+ const existingEntry = manifestEntry.versions[String(version)]
230
+
231
+ if (existingEntry !== undefined) {
232
+ if (entryChanged(existingEntry, current)) {
233
+ reportedKeys.add(`${functionKey}@${version}`)
234
+ const prevInputHash = isHashEntry(existingEntry)
235
+ ? existingEntry.inputHash
236
+ : existingEntry
237
+ const prevOutputHash = isHashEntry(existingEntry)
238
+ ? existingEntry.outputHash
239
+ : existingEntry
240
+ errors.push({
241
+ code: ErrorCode.FUNCTION_VERSION_MODIFIED,
242
+ message: `Contract for ${functionKey}@v${version} has changed (recorded: ${isHashEntry(existingEntry) ? `${existingEntry.inputHash}/${existingEntry.outputHash}` : existingEntry}, current: ${contractHash}). Existing versions are immutable.`,
243
+ functionKey,
244
+ version,
245
+ previousHash: isHashEntry(existingEntry)
246
+ ? existingEntry.inputHash
247
+ : existingEntry,
248
+ currentHash: contractHash,
249
+ previousInputHash: prevInputHash,
250
+ currentInputHash: inputHash,
251
+ previousOutputHash: prevOutputHash,
252
+ currentOutputHash: outputHash,
253
+ })
254
+ }
255
+ } else {
256
+ if (version <= manifestEntry.latest) {
257
+ errors.push({
258
+ code: ErrorCode.VERSION_REGRESSION_OR_CONFLICT,
259
+ message: `Version ${version} for ${functionKey} is <= latest (${manifestEntry.latest}) but not recorded. Possible merge conflict.`,
260
+ functionKey,
261
+ version,
262
+ latestVersion: manifestEntry.latest,
263
+ })
264
+ } else if (version > manifestEntry.latest + 1) {
265
+ errors.push({
266
+ code: ErrorCode.VERSION_GAP_NOT_ALLOWED,
267
+ message: `Version ${version} for ${functionKey} skips versions. Latest is ${manifestEntry.latest}, next must be ${manifestEntry.latest + 1}.`,
268
+ functionKey,
269
+ version,
270
+ latestVersion: manifestEntry.latest,
271
+ expectedNextVersion: manifestEntry.latest + 1,
272
+ })
273
+ }
274
+ }
275
+ }
276
+ }
277
+
278
+ for (const [functionKey, manifestEntry] of Object.entries(
279
+ manifest.contracts
280
+ )) {
281
+ const latestEntry = manifestEntry.versions[String(manifestEntry.latest)]
282
+ const currentEntries = grouped.get(functionKey)
283
+ if (!currentEntries) {
284
+ continue
285
+ }
286
+
287
+ const currentLatest = currentEntries.find(
288
+ (e) => e.version === manifestEntry.latest
289
+ )
290
+ if (
291
+ currentLatest &&
292
+ entryChanged(latestEntry, currentLatest) &&
293
+ !reportedKeys.has(`${functionKey}@${manifestEntry.latest}`)
294
+ ) {
295
+ const prevInputHash = isHashEntry(latestEntry)
296
+ ? latestEntry.inputHash
297
+ : undefined
298
+ const prevOutputHash = isHashEntry(latestEntry)
299
+ ? latestEntry.outputHash
300
+ : undefined
301
+ errors.push({
302
+ code: ErrorCode.CONTRACT_CHANGED_REQUIRES_BUMP,
303
+ message: `Contract for ${functionKey} changed. Set \`version: ${manifestEntry.latest + 1}\` on the function or run 'pikku versions update'.`,
304
+ functionKey,
305
+ previousInputHash: prevInputHash,
306
+ currentInputHash: currentLatest.inputHash,
307
+ previousOutputHash: prevOutputHash,
308
+ currentOutputHash: currentLatest.outputHash,
309
+ nextVersion: manifestEntry.latest + 1,
310
+ })
311
+ }
312
+ }
313
+
314
+ for (const [functionKey, manifestEntry] of Object.entries(
315
+ manifest.contracts
316
+ )) {
317
+ const numericKeys = Object.keys(manifestEntry.versions).map(Number)
318
+ if (numericKeys.length === 0) {
319
+ continue
320
+ }
321
+ const maxVersion = Math.max(...numericKeys)
322
+ if (manifestEntry.latest !== maxVersion) {
323
+ errors.push({
324
+ code: ErrorCode.MANIFEST_INTEGRITY_ERROR,
325
+ message: `Manifest integrity error for ${functionKey}: latest field (${manifestEntry.latest}) inconsistent with version keys (max: ${maxVersion}).`,
326
+ functionKey,
327
+ latestVersion: manifestEntry.latest,
328
+ expectedNextVersion: maxVersion,
329
+ })
330
+ }
331
+ }
332
+
333
+ return { valid: errors.length === 0, errors }
334
+ }
335
+
336
+ export function updateManifest(
337
+ existing: VersionManifest,
338
+ currentContracts: Map<string, ContractEntry>
339
+ ): VersionManifest {
340
+ const manifest: VersionManifest = {
341
+ manifestVersion: existing.manifestVersion,
342
+ contracts: JSON.parse(JSON.stringify(existing.contracts)),
343
+ }
344
+
345
+ const grouped = groupByFunctionKey(currentContracts)
346
+
347
+ for (const [functionKey, entries] of grouped) {
348
+ if (!manifest.contracts[functionKey]) {
349
+ manifest.contracts[functionKey] = { latest: 0, versions: {} }
350
+ }
351
+
352
+ const entry = manifest.contracts[functionKey]
353
+ for (const { version, inputHash, outputHash } of entries) {
354
+ entry.versions[String(version)] = { inputHash, outputHash }
355
+ entry.latest = Math.max(entry.latest, version)
356
+ }
357
+ }
358
+
359
+ return manifest
360
+ }
361
+
362
+ export function extractContractsFromMeta(
363
+ functionsMeta: FunctionsMeta
364
+ ): Map<string, ContractEntry> {
365
+ const result = new Map<string, ContractEntry>()
366
+
367
+ for (const [funcId, meta] of Object.entries(functionsMeta)) {
368
+ if (meta.remote === true || !meta.contractHash) {
369
+ continue
370
+ }
371
+
372
+ const parsed = parseVersionedId(funcId)
373
+ const functionKey = parsed.baseName
374
+ const version = parsed.version ?? meta.version ?? 1
375
+
376
+ result.set(funcId, {
377
+ functionKey,
378
+ version,
379
+ contractHash: meta.contractHash,
380
+ inputHash: meta.inputHash ?? '',
381
+ outputHash: meta.outputHash ?? '',
382
+ })
383
+ }
384
+
385
+ return result
386
+ }
@@ -0,0 +1,88 @@
1
+ import type { TypesMap } from '../types-map.js'
2
+
3
+ /**
4
+ * NOTE: Code generation normally belongs in @pikku/cli, not the inspector.
5
+ * This is here because the schema generator needs the custom types content
6
+ * as a virtual TypeScript source file (in-memory, no disk write) so that
7
+ * ts-json-schema-generator can discover inline/custom types from typesMap.
8
+ */
9
+
10
+ export function sanitizeTypeName(name: string): string {
11
+ return name.replace(/[^a-zA-Z0-9_$]/g, '_')
12
+ }
13
+
14
+ export function generateCustomTypes(
15
+ typesMap: TypesMap,
16
+ requiredTypes: Set<string>
17
+ ) {
18
+ const typeDeclarations = Array.from(typesMap.customTypes.entries())
19
+ .filter(([_name, { type }]) => {
20
+ const hasUndefinedGeneric =
21
+ /\b(Name|In|Out|Key)\b/.test(type) && /\[.*\]/.test(type)
22
+ return !hasUndefinedGeneric
23
+ })
24
+ .map(([originalName, { type, references }]) => {
25
+ const name = sanitizeTypeName(originalName)
26
+ references.forEach((refName) => {
27
+ if (refName !== '__object' && !refName.startsWith('__object_')) {
28
+ requiredTypes.add(refName)
29
+ }
30
+ })
31
+
32
+ const typeString = type
33
+ const typeNameRegex = /\b[A-Z][a-zA-Z0-9]*\b/g
34
+ const potentialTypes = typeString.match(typeNameRegex) || []
35
+
36
+ potentialTypes.forEach((typeName) => {
37
+ if (
38
+ typeString.includes(`"${typeName}"`) ||
39
+ [
40
+ 'Pick',
41
+ 'Omit',
42
+ 'Partial',
43
+ 'Required',
44
+ 'Record',
45
+ 'Readonly',
46
+ ].includes(typeName)
47
+ ) {
48
+ return
49
+ }
50
+
51
+ try {
52
+ const typeMeta = typesMap.getTypeMeta(typeName)
53
+ if (typeMeta.path) {
54
+ requiredTypes.add(typeMeta.originalName)
55
+ }
56
+ } catch {
57
+ // Type not found in map (ambient/builtin type)
58
+ }
59
+ })
60
+
61
+ if (name === type) return null
62
+ return `export type ${name} = ${type}`
63
+ })
64
+
65
+ const importsByPath = new Map<string, Set<string>>()
66
+ for (const typeName of requiredTypes) {
67
+ try {
68
+ const typeMeta = typesMap.getTypeMeta(typeName)
69
+ if (typeMeta.path) {
70
+ if (!importsByPath.has(typeMeta.path)) {
71
+ importsByPath.set(typeMeta.path, new Set())
72
+ }
73
+ importsByPath.get(typeMeta.path)!.add(typeMeta.originalName)
74
+ }
75
+ } catch {
76
+ // Type not found in map
77
+ }
78
+ }
79
+
80
+ const importLines = Array.from(importsByPath.entries())
81
+ .map(
82
+ ([path, types]) =>
83
+ `import type { ${Array.from(types).join(', ')} } from '${path}'`
84
+ )
85
+ .join('\n')
86
+
87
+ return `${importLines}\n\n${typeDeclarations.filter(Boolean).join('\n')}`
88
+ }
@@ -0,0 +1,90 @@
1
+ import type * as ts from 'typescript'
2
+ import type { SchemaVendor, InspectorLogger } from '../types.js'
3
+ import { ErrorCode } from '../error-codes.js'
4
+
5
+ /**
6
+ * Detect the schema vendor by tracing the type back to its library origin.
7
+ * This handles locally-defined schemas like `export const MySchema = z.object({...})`
8
+ * by checking where the type itself originates from.
9
+ *
10
+ * Supports multiple schema libraries in the same project (e.g., during migration
11
+ * from one library to another).
12
+ */
13
+ export const detectSchemaVendor = (
14
+ identifier: ts.Identifier,
15
+ checker: ts.TypeChecker
16
+ ): SchemaVendor => {
17
+ const type = checker.getTypeAtLocation(identifier)
18
+ if (!type) return 'unknown'
19
+
20
+ // Check the type's symbol declarations to find the library origin
21
+ const checkTypeOrigin = (t: ts.Type): SchemaVendor | null => {
22
+ const symbol = t.getSymbol() || t.aliasSymbol
23
+ if (symbol) {
24
+ const decls = symbol.getDeclarations()
25
+ if (decls) {
26
+ for (const decl of decls) {
27
+ const fileName = decl.getSourceFile().fileName
28
+ if (fileName.includes('node_modules/zod')) return 'zod'
29
+ if (fileName.includes('node_modules/valibot')) return 'valibot'
30
+ if (fileName.includes('node_modules/arktype')) return 'arktype'
31
+ if (fileName.includes('node_modules/@effect/schema')) return 'effect'
32
+ }
33
+ }
34
+ }
35
+
36
+ // Check base types for class/interface hierarchies
37
+ const baseTypes = t.getBaseTypes?.()
38
+ if (baseTypes) {
39
+ for (const baseType of baseTypes) {
40
+ const result = checkTypeOrigin(baseType)
41
+ if (result) return result
42
+ }
43
+ }
44
+
45
+ return null
46
+ }
47
+
48
+ const vendor = checkTypeOrigin(type)
49
+ if (vendor) return vendor
50
+
51
+ // Fallback: check type arguments (for generic types like z.ZodObject<...>)
52
+ if ((type as ts.TypeReference).typeArguments) {
53
+ for (const arg of (type as ts.TypeReference).typeArguments || []) {
54
+ const result = checkTypeOrigin(arg)
55
+ if (result) return result
56
+ }
57
+ }
58
+
59
+ return 'unknown'
60
+ }
61
+
62
+ /**
63
+ * Detect schema vendor and log a fatal error if unknown.
64
+ * Returns the vendor if successful, or undefined if unknown (after logging error).
65
+ *
66
+ * @param identifier - The TypeScript identifier for the schema variable
67
+ * @param checker - TypeScript type checker
68
+ * @param logger - Inspector logger for error reporting
69
+ * @param context - Description of what the schema is for (e.g., "Credential 'myCredential'")
70
+ * @param sourceFile - Source file path for error message
71
+ */
72
+ export const detectSchemaVendorOrError = (
73
+ identifier: ts.Identifier,
74
+ checker: ts.TypeChecker,
75
+ logger: InspectorLogger,
76
+ context: string,
77
+ sourceFile: string
78
+ ): Exclude<SchemaVendor, 'unknown'> | undefined => {
79
+ const vendor = detectSchemaVendor(identifier, checker)
80
+ if (vendor === 'unknown') {
81
+ logger.critical(
82
+ ErrorCode.INLINE_SCHEMA,
83
+ `${context} schema vendor could not be determined from '${sourceFile}'. ` +
84
+ `Supported vendors: zod, valibot, arktype, @effect/schema. ` +
85
+ `Ensure your schema is imported from a supported validation library.`
86
+ )
87
+ return undefined
88
+ }
89
+ return vendor
90
+ }
@@ -1,4 +1,4 @@
1
- import * as ts from 'typescript'
1
+ import type * as ts from 'typescript'
2
2
 
3
3
  export const doesTypeExtendsCore = (
4
4
  type: ts.Type,