@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
@@ -215,6 +215,147 @@ describe('resolveLatestVersions', () => {
215
215
  assert.strictEqual(errors.length, 0)
216
216
  })
217
217
 
218
+ test('exposedMeta propagates all versions when base name is exposed', () => {
219
+ const state = makeState({
220
+ 'createUser@v1': {
221
+ pikkuFuncId: 'createUser@v1',
222
+ inputSchemaName: null,
223
+ outputSchemaName: null,
224
+ version: 1,
225
+ },
226
+ createUser: {
227
+ pikkuFuncId: 'createUser',
228
+ inputSchemaName: null,
229
+ outputSchemaName: null,
230
+ },
231
+ })
232
+ state.rpc.exposedMeta['createUser'] = 'createUser'
233
+ state.rpc.internalFiles.set('createUser@v1', {
234
+ path: '/src/user.ts',
235
+ exportedName: 'createUserV1',
236
+ })
237
+ const { logger } = makeLogger()
238
+
239
+ resolveLatestVersions(state, logger)
240
+
241
+ assert.strictEqual(state.rpc.exposedMeta['createUser'], 'createUser@v2')
242
+ assert.strictEqual(state.rpc.exposedMeta['createUser@v1'], 'createUser@v1')
243
+ assert.strictEqual(state.rpc.exposedMeta['createUser@v2'], 'createUser@v2')
244
+ })
245
+
246
+ test('exposedFiles propagated for versioned exposed functions', () => {
247
+ const state = makeState({
248
+ 'createUser@v1': {
249
+ pikkuFuncId: 'createUser@v1',
250
+ inputSchemaName: null,
251
+ outputSchemaName: null,
252
+ version: 1,
253
+ },
254
+ createUser: {
255
+ pikkuFuncId: 'createUser',
256
+ inputSchemaName: null,
257
+ outputSchemaName: null,
258
+ },
259
+ })
260
+ state.rpc.exposedMeta['createUser'] = 'createUser'
261
+ state.rpc.internalFiles.set('createUser@v1', {
262
+ path: '/src/user-v1.ts',
263
+ exportedName: 'createUserV1',
264
+ })
265
+ state.rpc.internalFiles.set('createUser', {
266
+ path: '/src/user.ts',
267
+ exportedName: 'createUser',
268
+ })
269
+ const { logger } = makeLogger()
270
+
271
+ resolveLatestVersions(state, logger)
272
+
273
+ assert.ok(state.rpc.exposedFiles.has('createUser@v1'))
274
+ assert.strictEqual(
275
+ state.rpc.exposedFiles.get('createUser@v1')!.exportedName,
276
+ 'createUserV1'
277
+ )
278
+ assert.ok(state.rpc.exposedFiles.has('createUser@v2'))
279
+ })
280
+
281
+ test('exposedMeta unchanged when base name is not exposed', () => {
282
+ const state = makeState({
283
+ 'createUser@v1': {
284
+ pikkuFuncId: 'createUser@v1',
285
+ inputSchemaName: null,
286
+ outputSchemaName: null,
287
+ version: 1,
288
+ },
289
+ createUser: {
290
+ pikkuFuncId: 'createUser',
291
+ inputSchemaName: null,
292
+ outputSchemaName: null,
293
+ },
294
+ })
295
+ const { logger } = makeLogger()
296
+
297
+ resolveLatestVersions(state, logger)
298
+
299
+ assert.strictEqual(state.rpc.exposedMeta['createUser@v1'], undefined)
300
+ assert.strictEqual(state.rpc.exposedMeta['createUser@v2'], undefined)
301
+ })
302
+
303
+ test('exposedMeta propagates when only explicit versions exist', () => {
304
+ const state = makeState({
305
+ 'createUser@v1': {
306
+ pikkuFuncId: 'createUser@v1',
307
+ inputSchemaName: null,
308
+ outputSchemaName: null,
309
+ version: 1,
310
+ },
311
+ 'createUser@v2': {
312
+ pikkuFuncId: 'createUser@v2',
313
+ inputSchemaName: null,
314
+ outputSchemaName: null,
315
+ version: 2,
316
+ },
317
+ })
318
+ state.rpc.exposedMeta['createUser'] = 'createUser@v1'
319
+ const { logger } = makeLogger()
320
+
321
+ resolveLatestVersions(state, logger)
322
+
323
+ assert.strictEqual(state.rpc.exposedMeta['createUser'], 'createUser@v2')
324
+ assert.strictEqual(state.rpc.exposedMeta['createUser@v1'], 'createUser@v1')
325
+ assert.strictEqual(state.rpc.exposedMeta['createUser@v2'], 'createUser@v2')
326
+ })
327
+
328
+ test('updates HTTP meta pikkuFuncId when renaming unversioned', () => {
329
+ const state = makeState({
330
+ 'createUser@v1': {
331
+ pikkuFuncId: 'createUser@v1',
332
+ inputSchemaName: null,
333
+ outputSchemaName: null,
334
+ version: 1,
335
+ },
336
+ createUser: {
337
+ pikkuFuncId: 'createUser',
338
+ inputSchemaName: null,
339
+ outputSchemaName: null,
340
+ },
341
+ })
342
+ ;(state as any).http = {
343
+ meta: {
344
+ post: {
345
+ '/api/users': { pikkuFuncId: 'createUser', route: '/api/users' },
346
+ },
347
+ },
348
+ }
349
+ const { logger } = makeLogger()
350
+
351
+ resolveLatestVersions(state, logger)
352
+
353
+ assert.strictEqual(
354
+ (state as any).http.meta.post['/api/users'].pikkuFuncId,
355
+ 'createUser@v2'
356
+ )
357
+ })
358
+
218
359
  test('renames files entries when renaming unversioned to versioned', () => {
219
360
  const state = makeState({
220
361
  'createUser@v1': {
@@ -91,6 +91,8 @@ export function resolveLatestVersions(
91
91
  if (state.rpc.exposedMeta[baseName] === oldId) {
92
92
  state.rpc.exposedMeta[baseName] = newId
93
93
  }
94
+
95
+ updateWiringReferences(state, oldId, newId)
94
96
  } else {
95
97
  const latest = group.explicit.reduce((a, b) =>
96
98
  a.version > b.version ? a : b
@@ -98,8 +100,43 @@ export function resolveLatestVersions(
98
100
  state.rpc.internalMeta[baseName] = latest.id
99
101
  }
100
102
 
103
+ if (state.rpc.exposedMeta[baseName]) {
104
+ const latestId = state.rpc.internalMeta[baseName]!
105
+ state.rpc.exposedMeta[baseName] = latestId
106
+ for (const entry of group.explicit) {
107
+ state.rpc.exposedMeta[entry.id] = entry.id
108
+ const fileEntry = state.rpc.internalFiles.get(entry.id)
109
+ if (fileEntry) {
110
+ state.rpc.exposedFiles.set(entry.id, fileEntry)
111
+ }
112
+ }
113
+ if (group.unversioned) {
114
+ state.rpc.exposedMeta[latestId] = latestId
115
+ const fileEntry = state.rpc.internalFiles.get(latestId)
116
+ if (fileEntry) {
117
+ state.rpc.exposedFiles.set(latestId, fileEntry)
118
+ }
119
+ }
120
+ }
121
+
101
122
  for (const entry of group.explicit) {
102
123
  state.rpc.invokedFunctions.add(entry.id)
103
124
  }
104
125
  }
105
126
  }
127
+
128
+ function updateWiringReferences(
129
+ state: InspectorState,
130
+ oldId: string,
131
+ newId: string
132
+ ): void {
133
+ if (state.http) {
134
+ for (const methods of Object.values(state.http.meta)) {
135
+ for (const meta of Object.values(methods)) {
136
+ if (meta.pikkuFuncId === oldId) {
137
+ meta.pikkuFuncId = newId
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
@@ -1,7 +1,7 @@
1
1
  import * as ts from 'typescript'
2
2
  import { dirname, join, resolve } from 'path'
3
3
  import { createGenerator, RootlessError } from 'ts-json-schema-generator'
4
- import { tsImport } from 'tsx/esm/api'
4
+ import { register, tsImport } from 'tsx/esm/api'
5
5
  import * as z from 'zod'
6
6
  import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts'
7
7
  import type { FunctionsMeta, JSONValue } from '@pikku/core'
@@ -60,6 +60,10 @@ function primitiveTypeToSchema(typeStr: string): JSONValue | null {
60
60
  return { type: 'null' }
61
61
  }
62
62
 
63
+ if (normalized === 'any' || normalized === 'unknown') {
64
+ return {}
65
+ }
66
+
63
67
  return null
64
68
  }
65
69
 
@@ -145,7 +149,7 @@ function generateTSSchemas(
145
149
  httpWiringsMeta: HTTPWiringsMeta,
146
150
  additionalTypes?: string[],
147
151
  additionalProperties: boolean = false,
148
- schemaLookup?: Map<string, SchemaRef>
152
+ generatedZodSchemas?: Record<string, JSONValue>
149
153
  ): Record<string, JSONValue> {
150
154
  const schemasSet = new Set(typesMap.customTypes.keys())
151
155
  for (const { inputs, outputs } of Object.values(functionMeta)) {
@@ -182,6 +186,19 @@ function generateTSSchemas(
182
186
  }
183
187
  }
184
188
 
189
+ // Skip ts-json-schema-generator if all schemas are already covered by Zod/primitives.
190
+ // Use generatedZodSchemas (actually converted) rather than schemaLookup (all attempted)
191
+ // so that failed Zod conversions fall through to TS schema generation.
192
+ const uncoveredSchemas = [...schemasSet].filter(
193
+ (s) => !PRIMITIVE_TYPES.has(s) && !generatedZodSchemas?.[s]
194
+ )
195
+ if (uncoveredSchemas.length === 0) {
196
+ return {}
197
+ }
198
+ logger.debug(
199
+ `generateTSSchemas needed for ${uncoveredSchemas.length} types: ${uncoveredSchemas.slice(0, 3).join(', ')}${uncoveredSchemas.length > 3 ? '...' : ''}`
200
+ )
201
+
185
202
  const virtualFilePath = join(
186
203
  dirname(resolve(tsconfig)),
187
204
  '__pikku_virtual_types__.ts'
@@ -210,7 +227,7 @@ function generateTSSchemas(
210
227
  if (PRIMITIVE_TYPES.has(schema)) {
211
228
  return
212
229
  }
213
- if (schemaLookup?.has(schema)) {
230
+ if (generatedZodSchemas?.[schema]) {
214
231
  return
215
232
  }
216
233
  try {
@@ -236,6 +253,97 @@ function generateTSSchemas(
236
253
  return schemas
237
254
  }
238
255
 
256
+ /**
257
+ * Import all source files in parallel using tsx's register() API.
258
+ *
259
+ * tsx's register() sets up the TypeScript loader once, then all subsequent
260
+ * import() calls reuse that loader. This is dramatically faster than calling
261
+ * tsImport() per-file because tsImport() sets up and tears down a fresh
262
+ * compilation context for each call (~170ms each).
263
+ *
264
+ * With register() + parallel import():
265
+ * - 71 files: ~350ms total
266
+ * - vs tsImport loop: ~12,000ms (71 * 170ms)
267
+ *
268
+ * Falls back to serial tsImport() per-file if register() is unavailable.
269
+ */
270
+ async function batchImportWithRegister(
271
+ logger: InspectorLogger,
272
+ sourceFiles: string[]
273
+ ): Promise<Map<string, Record<string, any>> | null> {
274
+ if (sourceFiles.length === 0) return new Map()
275
+
276
+ let unregister: (() => void) | undefined
277
+ try {
278
+ unregister = register()
279
+
280
+ const modules = new Map<string, Record<string, any>>()
281
+ const results = await Promise.allSettled(
282
+ sourceFiles.map(async (srcPath) => {
283
+ const mod = await import(srcPath)
284
+ modules.set(srcPath, mod)
285
+ })
286
+ )
287
+
288
+ const failures = results.filter((r) => r.status === 'rejected')
289
+ if (failures.length > 0) {
290
+ logger.debug(
291
+ `${failures.length}/${sourceFiles.length} files failed to import via register()`
292
+ )
293
+ }
294
+
295
+ return modules
296
+ } catch (e) {
297
+ logger.debug(`tsx register() batch import failed: ${(e as Error).message}`)
298
+ return null
299
+ } finally {
300
+ unregister?.()
301
+ }
302
+ }
303
+
304
+ function processZodSchema(
305
+ schemaName: string,
306
+ zodSchema: any,
307
+ schemas: Record<string, JSONValue>,
308
+ typesMap: TypesMap,
309
+ auxiliaryTypeStore: ReturnType<typeof createAuxiliaryTypeStore>,
310
+ printer: ts.Printer,
311
+ fakeSourceFile: ts.SourceFile,
312
+ logger: InspectorLogger
313
+ ): void {
314
+ const schema = z.toJSONSchema(zodSchema, {
315
+ unrepresentable: 'any',
316
+ override: ({ zodSchema, jsonSchema }) => {
317
+ if ((zodSchema as any)._zod?.def?.type === 'date') {
318
+ ;(jsonSchema as any).type = 'string'
319
+ ;(jsonSchema as any).format = 'date-time'
320
+ }
321
+ },
322
+ }) as any
323
+
324
+ if (schema.required && schema.properties) {
325
+ schema.required = schema.required.filter((fieldName: string) => {
326
+ const prop = schema.properties[fieldName]
327
+ return prop && prop.default === undefined
328
+ })
329
+ if (schema.required.length === 0) {
330
+ delete schema.required
331
+ }
332
+ }
333
+
334
+ const { node: tsType } = zodToTs(zodSchema, { auxiliaryTypeStore })
335
+
336
+ const typeText = printer.printNode(
337
+ ts.EmitHint.Unspecified,
338
+ tsType,
339
+ fakeSourceFile
340
+ )
341
+
342
+ typesMap.addCustomType(schemaName, typeText, [])
343
+ schemas[schemaName] = schema
344
+ logger.debug(`• Generated schema from Zod: ${schemaName}`)
345
+ }
346
+
239
347
  async function generateZodSchemas(
240
348
  logger: InspectorLogger,
241
349
  schemaLookup: Map<string, SchemaRef>,
@@ -252,6 +360,7 @@ async function generateZodSchemas(
252
360
  ts.ScriptKind.TS
253
361
  )
254
362
 
363
+ // Validate all schemas are zod (or unspecified vendor)
255
364
  for (const [schemaName, ref] of schemaLookup.entries()) {
256
365
  if (ref.vendor && ref.vendor !== 'zod') {
257
366
  throw new Error(
@@ -260,55 +369,96 @@ async function generateZodSchemas(
260
369
  `Please use Zod or contribute support for ${ref.vendor}.`
261
370
  )
262
371
  }
372
+ }
263
373
 
264
- try {
265
- const module = await tsImport(ref.sourceFile, import.meta.url)
266
- const zodSchema = module[ref.variableName]
374
+ // Collect unique source files and batch-import them in parallel
375
+ const uniqueSourceFiles = [
376
+ ...new Set([...schemaLookup.values()].map((ref) => ref.sourceFile)),
377
+ ]
378
+ console.log(
379
+ `[TIMING] Zod schemas: ${schemaLookup.size} schemas from ${uniqueSourceFiles.length} files`
380
+ )
381
+
382
+ const importStart = performance.now()
383
+ const importedModules = await batchImportWithRegister(
384
+ logger,
385
+ uniqueSourceFiles
386
+ )
387
+ console.log(
388
+ `[TIMING] Batch import: ${(performance.now() - importStart).toFixed(0)}ms`
389
+ )
390
+
391
+ const processStart = performance.now()
392
+ // Track schemas that need per-file tsImport fallback
393
+ const fallbackSchemas: [string, SchemaRef][] = []
394
+
395
+ for (const [schemaName, ref] of schemaLookup.entries()) {
396
+ const mod = importedModules?.get(ref.sourceFile)
397
+ if (mod) {
398
+ const zodSchema = mod[ref.variableName]
267
399
  if (!zodSchema) {
268
400
  logger.warn(
269
- `Could not find exported schema '${ref.variableName}' in ${ref.sourceFile} for ${schemaName}. Available exports: ${Object.keys(module).join(', ')}`
401
+ `Could not find exported schema '${ref.variableName}' in ${ref.sourceFile} for ${schemaName}. Available exports: ${Object.keys(mod).join(', ')}`
270
402
  )
271
403
  continue
272
404
  }
405
+ try {
406
+ processZodSchema(
407
+ schemaName,
408
+ zodSchema,
409
+ schemas,
410
+ typesMap,
411
+ auxiliaryTypeStore,
412
+ printer,
413
+ fakeSourceFile,
414
+ logger
415
+ )
416
+ } catch (e) {
417
+ logger.warn(
418
+ `Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`
419
+ )
420
+ }
421
+ } else {
422
+ fallbackSchemas.push([schemaName, ref])
423
+ }
424
+ }
273
425
 
274
- const schema = z.toJSONSchema(zodSchema, {
275
- unrepresentable: 'any',
276
- override: ({ zodSchema, jsonSchema }) => {
277
- if ((zodSchema as any)._zod?.def?.type === 'date') {
278
- ;(jsonSchema as any).type = 'string'
279
- ;(jsonSchema as any).format = 'date-time'
280
- }
281
- },
282
- }) as any
283
-
284
- if (schema.required && schema.properties) {
285
- schema.required = schema.required.filter((fieldName: string) => {
286
- const prop = schema.properties[fieldName]
287
- return prop && prop.default === undefined
288
- })
289
- if (schema.required.length === 0) {
290
- delete schema.required
426
+ // Fallback: use tsImport for any schemas that batch import couldn't handle
427
+ if (fallbackSchemas.length > 0) {
428
+ logger.debug(
429
+ `Falling back to tsImport for ${fallbackSchemas.length} schema(s)`
430
+ )
431
+ for (const [schemaName, ref] of fallbackSchemas) {
432
+ try {
433
+ const module = await tsImport(ref.sourceFile, import.meta.url)
434
+ const zodSchema = module[ref.variableName]
435
+ if (!zodSchema) {
436
+ logger.warn(
437
+ `Could not find exported schema '${ref.variableName}' in ${ref.sourceFile} for ${schemaName}. Available exports: ${Object.keys(module).join(', ')}`
438
+ )
439
+ continue
291
440
  }
441
+ processZodSchema(
442
+ schemaName,
443
+ zodSchema,
444
+ schemas,
445
+ typesMap,
446
+ auxiliaryTypeStore,
447
+ printer,
448
+ fakeSourceFile,
449
+ logger
450
+ )
451
+ } catch (e) {
452
+ logger.warn(
453
+ `Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`
454
+ )
292
455
  }
293
-
294
- schemas[schemaName] = schema
295
- const { node: tsType } = zodToTs(zodSchema, { auxiliaryTypeStore })
296
-
297
- const typeText = printer.printNode(
298
- ts.EmitHint.Unspecified,
299
- tsType,
300
- fakeSourceFile
301
- )
302
-
303
- typesMap.addCustomType(schemaName, typeText, [])
304
- logger.debug(`• Generated schema from Zod: ${schemaName}`)
305
- } catch (e) {
306
- logger.warn(
307
- `Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`
308
- )
309
456
  }
310
457
  }
311
458
 
459
+ console.log(
460
+ `[TIMING] Process schemas: ${(performance.now() - processStart).toFixed(0)}ms (${Object.keys(schemas).length} generated)`
461
+ )
312
462
  return schemas
313
463
  }
314
464
 
@@ -347,7 +497,7 @@ export async function generateAllSchemas(
347
497
  state.http.meta,
348
498
  config.schemasFromTypes,
349
499
  config.schema?.additionalProperties,
350
- state.schemaLookup
500
+ zodSchemas
351
501
  )
352
502
 
353
503
  cachedCustomTypesContent = customTypesContent
@@ -45,6 +45,7 @@ export interface SerializableInspectorState {
45
45
  ]
46
46
  >
47
47
  wireServicesMeta: Array<[string, string[]]>
48
+ addonRequiredParentServices: string[]
48
49
  configFactories: Array<
49
50
  [
50
51
  string,
@@ -303,6 +304,7 @@ export function serializeInspectorState(
303
304
  ),
304
305
  wireServicesFactories: Array.from(state.wireServicesFactories.entries()),
305
306
  wireServicesMeta: Array.from(state.wireServicesMeta.entries()),
307
+ addonRequiredParentServices: state.addonRequiredParentServices,
306
308
  configFactories: Array.from(state.configFactories.entries()),
307
309
  filesAndMethods: state.filesAndMethods,
308
310
  filesAndMethodsErrors: Array.from(
@@ -474,6 +476,7 @@ export function deserializeInspectorState(
474
476
  singletonServicesFactories: new Map(data.singletonServicesFactories),
475
477
  wireServicesFactories: new Map(data.wireServicesFactories),
476
478
  wireServicesMeta: new Map(data.wireServicesMeta),
479
+ addonRequiredParentServices: data.addonRequiredParentServices || [],
477
480
  configFactories: new Map(data.configFactories),
478
481
  filesAndMethods: data.filesAndMethods,
479
482
  filesAndMethodsErrors: new Map(
package/src/visit.ts CHANGED
@@ -72,7 +72,8 @@ export const visitSetup = (
72
72
  node,
73
73
  checker,
74
74
  state.singletonServicesFactories,
75
- 'CreateSingletonServices'
75
+ 'CreateSingletonServices',
76
+ state
76
77
  )
77
78
 
78
79
  addFileWithFactory(