@pikku/inspector 0.11.2 → 0.12.0

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 (182) hide show
  1. package/CHANGELOG.md +11 -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 +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +250 -75
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +152 -66
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.js +14 -0
  23. package/dist/add/add-schedule.js +11 -5
  24. package/dist/add/add-secret.d.ts +3 -0
  25. package/dist/add/add-secret.js +82 -0
  26. package/dist/add/add-trigger.d.ts +2 -0
  27. package/dist/add/add-trigger.js +87 -0
  28. package/dist/add/add-variable.d.ts +1 -0
  29. package/dist/add/add-variable.js +8 -0
  30. package/dist/add/add-workflow-graph.d.ts +3 -2
  31. package/dist/add/add-workflow-graph.js +143 -406
  32. package/dist/add/add-workflow.js +6 -4
  33. package/dist/error-codes.d.ts +14 -1
  34. package/dist/error-codes.js +19 -1
  35. package/dist/index.d.ts +9 -8
  36. package/dist/index.js +5 -4
  37. package/dist/inspector.d.ts +1 -1
  38. package/dist/inspector.js +91 -14
  39. package/dist/schema-generator.d.ts +1 -0
  40. package/dist/schema-generator.js +1 -0
  41. package/dist/types-map.js +10 -1
  42. package/dist/types.d.ts +163 -39
  43. package/dist/utils/compute-required-schemas.d.ts +4 -0
  44. package/dist/utils/compute-required-schemas.js +41 -0
  45. package/dist/utils/contract-hashes.d.ts +35 -0
  46. package/dist/utils/contract-hashes.js +202 -0
  47. package/dist/utils/custom-types-generator.d.ts +9 -0
  48. package/dist/utils/custom-types-generator.js +71 -0
  49. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  50. package/dist/utils/detect-schema-vendor.js +76 -0
  51. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  52. package/dist/utils/ensure-function-metadata.js +220 -6
  53. package/dist/utils/extract-function-name.d.ts +5 -16
  54. package/dist/utils/extract-function-name.js +86 -291
  55. package/dist/utils/extract-services.d.ts +2 -1
  56. package/dist/utils/extract-services.js +25 -1
  57. package/dist/utils/filter-inspector-state.js +107 -23
  58. package/dist/utils/get-property-value.d.ts +6 -1
  59. package/dist/utils/get-property-value.js +28 -3
  60. package/dist/utils/hash.d.ts +2 -0
  61. package/dist/utils/hash.js +23 -0
  62. package/dist/utils/middleware.d.ts +7 -30
  63. package/dist/utils/middleware.js +80 -66
  64. package/dist/utils/permissions.d.ts +2 -2
  65. package/dist/utils/permissions.js +10 -10
  66. package/dist/utils/post-process.d.ts +9 -10
  67. package/dist/utils/post-process.js +231 -24
  68. package/dist/utils/resolve-external-package.d.ts +12 -0
  69. package/dist/utils/resolve-external-package.js +34 -0
  70. package/dist/utils/resolve-function-types.d.ts +6 -0
  71. package/dist/utils/resolve-function-types.js +29 -0
  72. package/dist/utils/resolve-identifier.d.ts +10 -0
  73. package/dist/utils/resolve-identifier.js +36 -0
  74. package/dist/utils/resolve-versions.d.ts +2 -0
  75. package/dist/utils/resolve-versions.js +78 -0
  76. package/dist/utils/schema-generator.d.ts +9 -0
  77. package/dist/utils/schema-generator.js +209 -0
  78. package/dist/utils/serialize-inspector-state.d.ts +59 -22
  79. package/dist/utils/serialize-inspector-state.js +92 -20
  80. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  81. package/dist/utils/serialize-mcp-json.js +99 -0
  82. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  83. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  84. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  85. package/dist/utils/serialize-openapi-json.js +151 -0
  86. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  87. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  88. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  89. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  90. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  91. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  92. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  93. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  94. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  95. package/dist/utils/workflow/graph/index.d.ts +2 -0
  96. package/dist/utils/workflow/graph/index.js +2 -0
  97. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  98. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  99. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  100. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  101. package/dist/visit.js +11 -6
  102. package/package.json +14 -4
  103. package/src/add/add-ai-agent.ts +468 -0
  104. package/src/add/add-channel.ts +82 -79
  105. package/src/add/add-cli.ts +49 -20
  106. package/src/add/add-file-with-factory.ts +2 -0
  107. package/src/add/add-functions.ts +330 -86
  108. package/src/add/add-http-route.ts +245 -88
  109. package/src/add/add-http-routes.ts +228 -0
  110. package/src/add/add-keyed-wiring.ts +151 -0
  111. package/src/add/add-mcp-prompt.ts +26 -15
  112. package/src/add/add-mcp-resource.ts +27 -15
  113. package/src/add/add-middleware.ts +482 -80
  114. package/src/add/add-permission.ts +199 -40
  115. package/src/add/add-queue-worker.ts +24 -19
  116. package/src/add/add-rpc-invocations.ts +17 -0
  117. package/src/add/add-schedule.ts +16 -11
  118. package/src/add/add-secret.ts +140 -0
  119. package/src/add/add-trigger.ts +154 -0
  120. package/src/add/add-variable.ts +9 -0
  121. package/src/add/add-workflow-graph.ts +180 -522
  122. package/src/add/add-workflow.ts +5 -4
  123. package/src/error-codes.ts +24 -1
  124. package/src/index.ts +22 -13
  125. package/src/inspector.ts +129 -17
  126. package/src/schema-generator.ts +1 -0
  127. package/src/types-map.ts +12 -1
  128. package/src/types.ts +175 -58
  129. package/src/utils/compute-required-schemas.ts +49 -0
  130. package/src/utils/contract-hashes.test.ts +528 -0
  131. package/src/utils/contract-hashes.ts +290 -0
  132. package/src/utils/custom-types-generator.ts +88 -0
  133. package/src/utils/detect-schema-vendor.ts +90 -0
  134. package/src/utils/ensure-function-metadata.ts +324 -7
  135. package/src/utils/extract-function-name.ts +101 -351
  136. package/src/utils/extract-services.ts +35 -2
  137. package/src/utils/filter-inspector-state.test.ts +34 -20
  138. package/src/utils/filter-inspector-state.ts +140 -31
  139. package/src/utils/get-property-value.ts +42 -4
  140. package/src/utils/hash.ts +26 -0
  141. package/src/utils/middleware.test.ts +204 -0
  142. package/src/utils/middleware.ts +129 -67
  143. package/src/utils/permissions.test.ts +35 -12
  144. package/src/utils/permissions.ts +10 -10
  145. package/src/utils/post-process.ts +283 -43
  146. package/src/utils/resolve-external-package.ts +42 -0
  147. package/src/utils/resolve-function-types.ts +42 -0
  148. package/src/utils/resolve-identifier.ts +46 -0
  149. package/src/utils/resolve-versions.test.ts +249 -0
  150. package/src/utils/resolve-versions.ts +105 -0
  151. package/src/utils/schema-generator.ts +329 -0
  152. package/src/utils/serialize-inspector-state.ts +163 -40
  153. package/src/utils/serialize-mcp-json.ts +145 -0
  154. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  155. package/src/utils/serialize-openapi-json.ts +277 -0
  156. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  157. package/src/utils/test-data/inspector-state.json +69 -66
  158. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  159. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
  160. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  161. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  162. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  163. package/src/utils/workflow/graph/index.ts +5 -0
  164. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  165. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  166. package/src/visit.ts +12 -6
  167. package/tsconfig.tsbuildinfo +1 -1
  168. package/dist/add/add-forge-credential.d.ts +0 -8
  169. package/dist/add/add-forge-credential.js +0 -77
  170. package/dist/add/add-forge-node.d.ts +0 -7
  171. package/dist/add/add-forge-node.js +0 -77
  172. package/dist/add/add-mcp-tool.d.ts +0 -2
  173. package/dist/add/add-mcp-tool.js +0 -81
  174. package/dist/utils/extract-service-metadata.d.ts +0 -19
  175. package/dist/utils/extract-service-metadata.js +0 -244
  176. package/dist/utils/write-service-metadata.d.ts +0 -13
  177. package/dist/utils/write-service-metadata.js +0 -37
  178. package/src/add/add-forge-credential.ts +0 -119
  179. package/src/add/add-forge-node.ts +0 -132
  180. package/src/add/add-mcp-tool.ts +0 -141
  181. package/src/utils/extract-service-metadata.ts +0 -353
  182. package/src/utils/write-service-metadata.ts +0 -51
@@ -0,0 +1,528 @@
1
+ import { strict as assert } from 'assert'
2
+ import { describe, test } from 'node:test'
3
+ import { canonicalJSON } from './hash.js'
4
+ import {
5
+ computeContractHash,
6
+ createEmptyManifest,
7
+ serializeManifest,
8
+ validateContracts,
9
+ updateManifest,
10
+ extractContractsFromMeta,
11
+ } from './contract-hashes.js'
12
+ import type { ContractEntry, VersionManifest } from './contract-hashes.js'
13
+ import type { FunctionsMeta } from '@pikku/core'
14
+ import { ErrorCode } from '../error-codes.js'
15
+
16
+ function makeContracts(
17
+ entries: Record<
18
+ string,
19
+ { functionKey: string; version: number; contractHash: string }
20
+ >
21
+ ): Map<string, ContractEntry> {
22
+ const map = new Map<string, ContractEntry>()
23
+ for (const [id, entry] of Object.entries(entries)) {
24
+ map.set(id, entry)
25
+ }
26
+ return map
27
+ }
28
+
29
+ describe('canonicalJSON', () => {
30
+ test('sorts object keys alphabetically', () => {
31
+ assert.strictEqual(canonicalJSON({ b: 2, a: 1 }), '{"a":1,"b":2}')
32
+ })
33
+
34
+ test('sorts nested objects recursively', () => {
35
+ const input = { z: { b: 2, a: 1 }, a: { d: 4, c: 3 } }
36
+ const expected = '{"a":{"c":3,"d":4},"z":{"a":1,"b":2}}'
37
+ assert.strictEqual(canonicalJSON(input), expected)
38
+ })
39
+
40
+ test('preserves array order', () => {
41
+ assert.strictEqual(canonicalJSON([3, 1, 2]), '[3,1,2]')
42
+ assert.strictEqual(
43
+ canonicalJSON([
44
+ { b: 1, a: 2 },
45
+ { d: 3, c: 4 },
46
+ ]),
47
+ '[{"a":2,"b":1},{"c":4,"d":3}]'
48
+ )
49
+ })
50
+
51
+ test('handles null/undefined/primitives', () => {
52
+ assert.strictEqual(canonicalJSON(null), 'null')
53
+ assert.strictEqual(canonicalJSON(undefined), undefined)
54
+ assert.strictEqual(canonicalJSON(42), '42')
55
+ assert.strictEqual(canonicalJSON('hello'), '"hello"')
56
+ assert.strictEqual(canonicalJSON(true), 'true')
57
+ })
58
+
59
+ test('deterministic regardless of insertion order', () => {
60
+ const a: Record<string, number> = {}
61
+ a['x'] = 1
62
+ a['a'] = 2
63
+ a['m'] = 3
64
+
65
+ const b: Record<string, number> = {}
66
+ b['m'] = 3
67
+ b['a'] = 2
68
+ b['x'] = 1
69
+
70
+ assert.strictEqual(canonicalJSON(a), canonicalJSON(b))
71
+ })
72
+ })
73
+
74
+ describe('computeContractHash', () => {
75
+ test('returns 16-char hex string', () => {
76
+ const hash = computeContractHash({
77
+ functionKey: 'createUser',
78
+ inputSchema: { type: 'object' },
79
+ outputSchema: { type: 'object' },
80
+ })
81
+ assert.match(hash, /^[0-9a-f]{16}$/)
82
+ })
83
+
84
+ test('stable across calls', () => {
85
+ const data = {
86
+ functionKey: 'getUser',
87
+ inputSchema: { id: 'string' },
88
+ outputSchema: { name: 'string' },
89
+ }
90
+ assert.strictEqual(computeContractHash(data), computeContractHash(data))
91
+ })
92
+
93
+ test('different functionKey produces different hash', () => {
94
+ const schema = { type: 'object' }
95
+ const hash1 = computeContractHash({
96
+ functionKey: 'createUser',
97
+ inputSchema: schema,
98
+ outputSchema: schema,
99
+ })
100
+ const hash2 = computeContractHash({
101
+ functionKey: 'deleteUser',
102
+ inputSchema: schema,
103
+ outputSchema: schema,
104
+ })
105
+ assert.notStrictEqual(hash1, hash2)
106
+ })
107
+
108
+ test('different schema produces different hash', () => {
109
+ const hash1 = computeContractHash({
110
+ functionKey: 'createUser',
111
+ inputSchema: { type: 'string' },
112
+ outputSchema: null,
113
+ })
114
+ const hash2 = computeContractHash({
115
+ functionKey: 'createUser',
116
+ inputSchema: { type: 'number' },
117
+ outputSchema: null,
118
+ })
119
+ assert.notStrictEqual(hash1, hash2)
120
+ })
121
+ })
122
+
123
+ describe('createEmptyManifest', () => {
124
+ test('returns empty manifest with version 1', () => {
125
+ assert.deepStrictEqual(createEmptyManifest(), {
126
+ manifestVersion: 1,
127
+ contracts: {},
128
+ })
129
+ })
130
+ })
131
+
132
+ describe('serializeManifest', () => {
133
+ test('sorts contract keys alphabetically', () => {
134
+ const manifest: VersionManifest = {
135
+ manifestVersion: 1,
136
+ contracts: {
137
+ zeta: { latest: 1, versions: { '1': 'abc' } },
138
+ alpha: { latest: 1, versions: { '1': 'def' } },
139
+ },
140
+ }
141
+ const result = serializeManifest(manifest)
142
+ const keys = Object.keys(JSON.parse(result).contracts)
143
+ assert.deepStrictEqual(keys, ['alpha', 'zeta'])
144
+ })
145
+
146
+ test('sorts version keys numerically', () => {
147
+ const manifest: VersionManifest = {
148
+ manifestVersion: 1,
149
+ contracts: {
150
+ func: { latest: 10, versions: { '10': 'c', '2': 'b', '1': 'a' } },
151
+ },
152
+ }
153
+ const result = serializeManifest(manifest)
154
+ const versionKeys = Object.keys(JSON.parse(result).contracts.func.versions)
155
+ assert.deepStrictEqual(versionKeys, ['1', '2', '10'])
156
+ })
157
+
158
+ test('ends with trailing newline', () => {
159
+ const manifest: VersionManifest = {
160
+ manifestVersion: 1,
161
+ contracts: {},
162
+ }
163
+ const result = serializeManifest(manifest)
164
+ assert.ok(result.endsWith('\n'))
165
+ })
166
+
167
+ test('idempotent', () => {
168
+ const manifest: VersionManifest = {
169
+ manifestVersion: 1,
170
+ contracts: {
171
+ beta: { latest: 2, versions: { '2': 'xyz', '1': 'abc' } },
172
+ alpha: { latest: 1, versions: { '1': 'def' } },
173
+ },
174
+ }
175
+ const first = serializeManifest(manifest)
176
+ const second = serializeManifest(JSON.parse(first))
177
+ assert.strictEqual(first, second)
178
+ })
179
+ })
180
+
181
+ describe('validateContracts', () => {
182
+ describe('PKU861 — FUNCTION_VERSION_MODIFIED', () => {
183
+ test('hash changed for existing version produces error', () => {
184
+ const manifest: VersionManifest = {
185
+ manifestVersion: 1,
186
+ contracts: {
187
+ createUser: { latest: 1, versions: { '1': 'oldhash1234567890' } },
188
+ },
189
+ }
190
+ const contracts = makeContracts({
191
+ createUser: {
192
+ functionKey: 'createUser',
193
+ version: 1,
194
+ contractHash: 'newhash1234567890',
195
+ },
196
+ })
197
+ const result = validateContracts(manifest, contracts)
198
+ assert.strictEqual(result.valid, false)
199
+ assert.strictEqual(result.errors.length, 1)
200
+ assert.strictEqual(
201
+ result.errors[0].code,
202
+ ErrorCode.FUNCTION_VERSION_MODIFIED
203
+ )
204
+ })
205
+
206
+ test('hash unchanged for existing version produces no error', () => {
207
+ const manifest: VersionManifest = {
208
+ manifestVersion: 1,
209
+ contracts: {
210
+ createUser: { latest: 1, versions: { '1': 'samehash123456789' } },
211
+ },
212
+ }
213
+ const contracts = makeContracts({
214
+ createUser: {
215
+ functionKey: 'createUser',
216
+ version: 1,
217
+ contractHash: 'samehash123456789',
218
+ },
219
+ })
220
+ const result = validateContracts(manifest, contracts)
221
+ assert.strictEqual(result.valid, true)
222
+ assert.strictEqual(result.errors.length, 0)
223
+ })
224
+ })
225
+
226
+ describe('PKU862 — CONTRACT_CHANGED_REQUIRES_BUMP', () => {
227
+ test('latest hash changed without recorded version produces bump error', () => {
228
+ const manifest: VersionManifest = {
229
+ manifestVersion: 1,
230
+ contracts: {
231
+ getUser: { latest: 1, versions: {} },
232
+ },
233
+ }
234
+ const contracts = makeContracts({
235
+ getUser: {
236
+ functionKey: 'getUser',
237
+ version: 1,
238
+ contractHash: 'newhash1234567890',
239
+ },
240
+ })
241
+ const result = validateContracts(manifest, contracts)
242
+ const bump = result.errors.find(
243
+ (e) => e.code === ErrorCode.CONTRACT_CHANGED_REQUIRES_BUMP
244
+ )
245
+ assert.ok(bump)
246
+ })
247
+
248
+ test('function removed from code but still in manifest produces no error', () => {
249
+ const manifest: VersionManifest = {
250
+ manifestVersion: 1,
251
+ contracts: {
252
+ deletedFunc: { latest: 1, versions: { '1': 'abc1234567890abc' } },
253
+ },
254
+ }
255
+ const contracts = makeContracts({})
256
+ const result = validateContracts(manifest, contracts)
257
+ assert.strictEqual(result.valid, true)
258
+ assert.strictEqual(result.errors.length, 0)
259
+ })
260
+ })
261
+
262
+ describe('PKU863 — VERSION_REGRESSION_OR_CONFLICT', () => {
263
+ test('new version <= latest but not recorded produces error', () => {
264
+ const manifest: VersionManifest = {
265
+ manifestVersion: 1,
266
+ contracts: {
267
+ createUser: {
268
+ latest: 3,
269
+ versions: { '1': 'aaa', '2': 'bbb', '3': 'ccc' },
270
+ },
271
+ },
272
+ }
273
+ const contracts = makeContracts({
274
+ 'createUser@v2': {
275
+ functionKey: 'createUser',
276
+ version: 2,
277
+ contractHash: 'bbb',
278
+ },
279
+ 'createUser@v3': {
280
+ functionKey: 'createUser',
281
+ version: 3,
282
+ contractHash: 'ccc',
283
+ },
284
+ 'createUser@v1': {
285
+ functionKey: 'createUser',
286
+ version: 1,
287
+ contractHash: 'different_hash!',
288
+ },
289
+ })
290
+ const result = validateContracts(manifest, contracts)
291
+ const regression = result.errors.find(
292
+ (e) => e.code === ErrorCode.VERSION_REGRESSION_OR_CONFLICT
293
+ )
294
+ assert.strictEqual(regression, undefined)
295
+
296
+ const manifest2: VersionManifest = {
297
+ manifestVersion: 1,
298
+ contracts: {
299
+ myFunc: { latest: 3, versions: { '1': 'aaa', '3': 'ccc' } },
300
+ },
301
+ }
302
+ const contracts2 = makeContracts({
303
+ 'myFunc@v2': { functionKey: 'myFunc', version: 2, contractHash: 'xxx' },
304
+ 'myFunc@v3': { functionKey: 'myFunc', version: 3, contractHash: 'ccc' },
305
+ })
306
+ const result2 = validateContracts(manifest2, contracts2)
307
+ const regression2 = result2.errors.find(
308
+ (e) => e.code === ErrorCode.VERSION_REGRESSION_OR_CONFLICT
309
+ )
310
+ assert.ok(regression2)
311
+ })
312
+ })
313
+
314
+ describe('PKU864 — VERSION_GAP_NOT_ALLOWED', () => {
315
+ test('new version > latest + 1 produces error', () => {
316
+ const manifest: VersionManifest = {
317
+ manifestVersion: 1,
318
+ contracts: {
319
+ createUser: { latest: 1, versions: { '1': 'aaa' } },
320
+ },
321
+ }
322
+ const contracts = makeContracts({
323
+ 'createUser@v3': {
324
+ functionKey: 'createUser',
325
+ version: 3,
326
+ contractHash: 'xxx',
327
+ },
328
+ })
329
+ const result = validateContracts(manifest, contracts)
330
+ const gap = result.errors.find(
331
+ (e) => e.code === ErrorCode.VERSION_GAP_NOT_ALLOWED
332
+ )
333
+ assert.ok(gap)
334
+ })
335
+ })
336
+
337
+ describe('PKU865 — MANIFEST_INTEGRITY_ERROR', () => {
338
+ test('latest field not matching max version key produces error', () => {
339
+ const manifest: VersionManifest = {
340
+ manifestVersion: 1,
341
+ contracts: {
342
+ createUser: { latest: 1, versions: { '1': 'aaa', '2': 'bbb' } },
343
+ },
344
+ }
345
+ const contracts = makeContracts({})
346
+ const result = validateContracts(manifest, contracts)
347
+ const integrity = result.errors.find(
348
+ (e) => e.code === ErrorCode.MANIFEST_INTEGRITY_ERROR
349
+ )
350
+ assert.ok(integrity)
351
+ })
352
+ })
353
+
354
+ describe('happy paths', () => {
355
+ test('new function not in manifest is valid', () => {
356
+ const manifest: VersionManifest = {
357
+ manifestVersion: 1,
358
+ contracts: {},
359
+ }
360
+ const contracts = makeContracts({
361
+ createUser: {
362
+ functionKey: 'createUser',
363
+ version: 1,
364
+ contractHash: 'abc123',
365
+ },
366
+ })
367
+ const result = validateContracts(manifest, contracts)
368
+ assert.strictEqual(result.valid, true)
369
+ })
370
+
371
+ test('correct version bump is valid', () => {
372
+ const manifest: VersionManifest = {
373
+ manifestVersion: 1,
374
+ contracts: {
375
+ createUser: { latest: 1, versions: { '1': 'aaa' } },
376
+ },
377
+ }
378
+ const contracts = makeContracts({
379
+ 'createUser@v1': {
380
+ functionKey: 'createUser',
381
+ version: 1,
382
+ contractHash: 'aaa',
383
+ },
384
+ 'createUser@v2': {
385
+ functionKey: 'createUser',
386
+ version: 2,
387
+ contractHash: 'bbb',
388
+ },
389
+ })
390
+ const result = validateContracts(manifest, contracts)
391
+ assert.strictEqual(result.valid, true)
392
+ })
393
+
394
+ test('empty manifest with new contracts is valid', () => {
395
+ const manifest: VersionManifest = {
396
+ manifestVersion: 1,
397
+ contracts: {},
398
+ }
399
+ const contracts = makeContracts({
400
+ funcA: { functionKey: 'funcA', version: 1, contractHash: 'aaa' },
401
+ funcB: { functionKey: 'funcB', version: 1, contractHash: 'bbb' },
402
+ })
403
+ const result = validateContracts(manifest, contracts)
404
+ assert.strictEqual(result.valid, true)
405
+ })
406
+ })
407
+ })
408
+
409
+ describe('updateManifest', () => {
410
+ test('adds new function to empty manifest', () => {
411
+ const manifest: VersionManifest = { manifestVersion: 1, contracts: {} }
412
+ const contracts = makeContracts({
413
+ createUser: {
414
+ functionKey: 'createUser',
415
+ version: 1,
416
+ contractHash: 'abc',
417
+ },
418
+ })
419
+ const result = updateManifest(manifest, contracts)
420
+ assert.strictEqual(result.contracts.createUser.latest, 1)
421
+ assert.strictEqual(result.contracts.createUser.versions['1'], 'abc')
422
+ })
423
+
424
+ test('adds new version to existing function and updates latest', () => {
425
+ const manifest: VersionManifest = {
426
+ manifestVersion: 1,
427
+ contracts: {
428
+ createUser: { latest: 1, versions: { '1': 'aaa' } },
429
+ },
430
+ }
431
+ const contracts = makeContracts({
432
+ 'createUser@v1': {
433
+ functionKey: 'createUser',
434
+ version: 1,
435
+ contractHash: 'aaa',
436
+ },
437
+ 'createUser@v2': {
438
+ functionKey: 'createUser',
439
+ version: 2,
440
+ contractHash: 'bbb',
441
+ },
442
+ })
443
+ const result = updateManifest(manifest, contracts)
444
+ assert.strictEqual(result.contracts.createUser.latest, 2)
445
+ assert.strictEqual(result.contracts.createUser.versions['2'], 'bbb')
446
+ assert.strictEqual(result.contracts.createUser.versions['1'], 'aaa')
447
+ })
448
+
449
+ test('preserves deleted functions', () => {
450
+ const manifest: VersionManifest = {
451
+ manifestVersion: 1,
452
+ contracts: {
453
+ removedFunc: { latest: 1, versions: { '1': 'old' } },
454
+ },
455
+ }
456
+ const contracts = makeContracts({
457
+ newFunc: { functionKey: 'newFunc', version: 1, contractHash: 'new' },
458
+ })
459
+ const result = updateManifest(manifest, contracts)
460
+ assert.ok(result.contracts.removedFunc)
461
+ assert.strictEqual(result.contracts.removedFunc.latest, 1)
462
+ assert.ok(result.contracts.newFunc)
463
+ })
464
+
465
+ test('does not mutate input manifest', () => {
466
+ const manifest: VersionManifest = {
467
+ manifestVersion: 1,
468
+ contracts: {
469
+ createUser: { latest: 1, versions: { '1': 'aaa' } },
470
+ },
471
+ }
472
+ const before = JSON.stringify(manifest)
473
+ const contracts = makeContracts({
474
+ 'createUser@v2': {
475
+ functionKey: 'createUser',
476
+ version: 2,
477
+ contractHash: 'bbb',
478
+ },
479
+ })
480
+ updateManifest(manifest, contracts)
481
+ assert.strictEqual(JSON.stringify(manifest), before)
482
+ })
483
+ })
484
+
485
+ describe('extractContractsFromMeta', () => {
486
+ test('extracts entries where contractHash is set', () => {
487
+ const meta: FunctionsMeta = {
488
+ createUser: {
489
+ pikkuFuncId: 'createUser',
490
+ inputSchemaName: 'CreateUserInput',
491
+ outputSchemaName: 'CreateUserOutput',
492
+ contractHash: 'abc123',
493
+ },
494
+ }
495
+ const result = extractContractsFromMeta(meta)
496
+ assert.strictEqual(result.size, 1)
497
+ assert.ok(result.has('createUser'))
498
+ assert.strictEqual(result.get('createUser')!.contractHash, 'abc123')
499
+ assert.strictEqual(result.get('createUser')!.functionKey, 'createUser')
500
+ assert.strictEqual(result.get('createUser')!.version, 1)
501
+ })
502
+
503
+ test('skips remote functions', () => {
504
+ const meta: FunctionsMeta = {
505
+ remoteFunc: {
506
+ pikkuFuncId: 'remoteFunc',
507
+ inputSchemaName: null,
508
+ outputSchemaName: null,
509
+ remote: true,
510
+ contractHash: 'shouldskip',
511
+ },
512
+ }
513
+ const result = extractContractsFromMeta(meta)
514
+ assert.strictEqual(result.size, 0)
515
+ })
516
+
517
+ test('skips functions without contractHash', () => {
518
+ const meta: FunctionsMeta = {
519
+ noHash: {
520
+ pikkuFuncId: 'noHash',
521
+ inputSchemaName: null,
522
+ outputSchemaName: null,
523
+ },
524
+ }
525
+ const result = extractContractsFromMeta(meta)
526
+ assert.strictEqual(result.size, 0)
527
+ })
528
+ })