@ic-reactor/codegen 0.11.0 → 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.
- package/README.md +5 -4
- package/dist/chunk-VBCR5IVT.js +609 -0
- package/dist/index.cjs +616 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +17 -1
- package/dist/renderer.cjs +639 -0
- package/dist/renderer.d.cts +33 -0
- package/dist/renderer.d.ts +33 -0
- package/dist/renderer.js +14 -0
- package/llms.txt +28 -0
- package/package.json +32 -13
- package/src/__snapshots__/reactor.test.ts.snap +12 -0
- package/src/bindgen.test.ts +1 -1
- package/src/generators/client.ts +0 -1
- package/src/generators/reactor.ts +12 -0
- package/src/index.ts +3 -0
- package/src/metadata-rules.json +144 -0
- package/src/metadata.ts +157 -0
- package/src/naming.test.ts +1 -1
- package/src/pipeline.test.ts +1 -1
- package/src/reactor.test.ts +4 -1
- package/src/renderer.test.ts +573 -0
- package/src/renderer.ts +515 -0
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CandidMetadata,
|
|
3
|
+
CandidSchema,
|
|
4
|
+
CandidType,
|
|
5
|
+
CandidTypeDeclaration,
|
|
6
|
+
} from "@ic-reactor/parser"
|
|
7
|
+
import {
|
|
8
|
+
BUILT_IN_FORMAT_HELPERS,
|
|
9
|
+
type CustomJSDocFormatTypes,
|
|
10
|
+
normalizeValidationMetadata,
|
|
11
|
+
} from "./metadata.js"
|
|
12
|
+
|
|
13
|
+
export { BUILT_IN_JSDOC_FORMAT_TYPES } from "./metadata.js"
|
|
14
|
+
export { BUILT_IN_FORMAT_HELPERS } from "./metadata.js"
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
CustomJSDocFormatTypes,
|
|
18
|
+
JSDocFormatDefinition,
|
|
19
|
+
} from "./metadata.js"
|
|
20
|
+
|
|
21
|
+
export interface GenerateCodecDeclarationsOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Canister name used to derive the service export name.
|
|
24
|
+
* Converted to SCREAMING_SNAKE_CASE (e.g. `"my-backend"` → `MY_BACKEND`).
|
|
25
|
+
* Ignored when `serviceExportName` is set explicitly.
|
|
26
|
+
*/
|
|
27
|
+
canisterName?: string
|
|
28
|
+
/** Explicit service export name. Takes priority over `canisterName`. */
|
|
29
|
+
serviceExportName?: string
|
|
30
|
+
customJSDocFormatTypes?: CustomJSDocFormatTypes
|
|
31
|
+
includeCompatibilityExports?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const reservedWords = new Set([
|
|
35
|
+
"break",
|
|
36
|
+
"case",
|
|
37
|
+
"catch",
|
|
38
|
+
"class",
|
|
39
|
+
"const",
|
|
40
|
+
"continue",
|
|
41
|
+
"debugger",
|
|
42
|
+
"default",
|
|
43
|
+
"delete",
|
|
44
|
+
"do",
|
|
45
|
+
"else",
|
|
46
|
+
"export",
|
|
47
|
+
"extends",
|
|
48
|
+
"false",
|
|
49
|
+
"finally",
|
|
50
|
+
"for",
|
|
51
|
+
"function",
|
|
52
|
+
"if",
|
|
53
|
+
"import",
|
|
54
|
+
"in",
|
|
55
|
+
"instanceof",
|
|
56
|
+
"new",
|
|
57
|
+
"null",
|
|
58
|
+
"return",
|
|
59
|
+
"super",
|
|
60
|
+
"switch",
|
|
61
|
+
"this",
|
|
62
|
+
"throw",
|
|
63
|
+
"true",
|
|
64
|
+
"try",
|
|
65
|
+
"typeof",
|
|
66
|
+
"var",
|
|
67
|
+
"void",
|
|
68
|
+
"while",
|
|
69
|
+
"with",
|
|
70
|
+
"yield",
|
|
71
|
+
"let",
|
|
72
|
+
"package",
|
|
73
|
+
"private",
|
|
74
|
+
"protected",
|
|
75
|
+
"public",
|
|
76
|
+
"static",
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
function isValidIdentifier(name: string): boolean {
|
|
80
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !reservedWords.has(name)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function assertIdentifier(name: string, label: string): void {
|
|
84
|
+
if (!isValidIdentifier(name)) {
|
|
85
|
+
throw new Error(`${label} must be a valid TypeScript identifier: ${name}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function propertyName(name: string): string {
|
|
90
|
+
return isValidIdentifier(name) ? name : JSON.stringify(name)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeOptions(
|
|
94
|
+
options: string | GenerateCodecDeclarationsOptions = {}
|
|
95
|
+
): GenerateCodecDeclarationsOptions {
|
|
96
|
+
return typeof options === "string" ? { serviceExportName: options } : options
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convert a canister name to a SCREAMING_SNAKE_CASE identifier.
|
|
101
|
+
* Hyphens and spaces are replaced with underscores.
|
|
102
|
+
*/
|
|
103
|
+
function toServiceExportName(canisterName: string): string {
|
|
104
|
+
return canisterName.replace(/[-\s]+/g, "_").toUpperCase()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveServiceExportName(
|
|
108
|
+
options: GenerateCodecDeclarationsOptions,
|
|
109
|
+
declaredTypeNames: Set<string>
|
|
110
|
+
): string {
|
|
111
|
+
if (options.serviceExportName) return options.serviceExportName
|
|
112
|
+
|
|
113
|
+
if (options.canisterName) {
|
|
114
|
+
let name = toServiceExportName(options.canisterName)
|
|
115
|
+
if (declaredTypeNames.has(name)) {
|
|
116
|
+
name = `${name}_SERVICE`
|
|
117
|
+
}
|
|
118
|
+
return name
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return "_SERVICE"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasMetadata(
|
|
125
|
+
metadata: CandidMetadata | undefined
|
|
126
|
+
): metadata is CandidMetadata {
|
|
127
|
+
return metadata != null && Object.keys(metadata).length > 0
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function stripUndefined<T extends Record<string, unknown>>(value: T): T {
|
|
131
|
+
return Object.fromEntries(
|
|
132
|
+
Object.entries(value).filter(([, entry]) => entry !== undefined)
|
|
133
|
+
) as T
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isEmptyObject(value: unknown): boolean {
|
|
137
|
+
return (
|
|
138
|
+
value != null &&
|
|
139
|
+
typeof value === "object" &&
|
|
140
|
+
!Array.isArray(value) &&
|
|
141
|
+
Object.keys(value).length === 0
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function textFormatHelperFor(
|
|
146
|
+
expression: string,
|
|
147
|
+
metadata: CandidMetadata,
|
|
148
|
+
options: GenerateCodecDeclarationsOptions
|
|
149
|
+
): string | undefined {
|
|
150
|
+
const format = metadata.validation?.format
|
|
151
|
+
if (!format || expression !== "c.text()") return undefined
|
|
152
|
+
if (options.customJSDocFormatTypes?.[format.type]) {
|
|
153
|
+
return undefined
|
|
154
|
+
}
|
|
155
|
+
return BUILT_IN_FORMAT_HELPERS[format.type]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hasOnlyDescriptionDocs(
|
|
159
|
+
docs: string[] | undefined,
|
|
160
|
+
description: string | undefined
|
|
161
|
+
): boolean {
|
|
162
|
+
return (
|
|
163
|
+
docs != null &&
|
|
164
|
+
description != null &&
|
|
165
|
+
docs.length === 1 &&
|
|
166
|
+
docs[0] === description
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function metadataForRender(
|
|
171
|
+
metadata: CandidMetadata,
|
|
172
|
+
options: GenerateCodecDeclarationsOptions
|
|
173
|
+
): CandidMetadata {
|
|
174
|
+
if (!metadata.validation) {
|
|
175
|
+
return metadata
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...metadata,
|
|
180
|
+
validation: normalizeValidationMetadata(metadata.validation, {
|
|
181
|
+
customJSDocFormatTypes: options.customJSDocFormatTypes,
|
|
182
|
+
}),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function applyMetadata(
|
|
187
|
+
expression: string,
|
|
188
|
+
metadata: CandidMetadata | undefined,
|
|
189
|
+
options: GenerateCodecDeclarationsOptions
|
|
190
|
+
): string {
|
|
191
|
+
if (!hasMetadata(metadata)) return expression
|
|
192
|
+
|
|
193
|
+
const textFormatHelper = textFormatHelperFor(expression, metadata, options)
|
|
194
|
+
const textFormatMessage = metadata.validation?.format?.message
|
|
195
|
+
const renderedMetadata = metadataForRender(metadata, options)
|
|
196
|
+
const { description, ...metadataRest } = renderedMetadata
|
|
197
|
+
const rest: Partial<CandidMetadata> = { ...metadataRest }
|
|
198
|
+
let result = textFormatHelper
|
|
199
|
+
? `c.${textFormatHelper}(${textFormatMessage ? JSON.stringify(textFormatMessage) : ""})`
|
|
200
|
+
: expression
|
|
201
|
+
|
|
202
|
+
if (textFormatHelper) {
|
|
203
|
+
delete rest.docs
|
|
204
|
+
if (rest.validation) {
|
|
205
|
+
const { format: _format, ...validationRest } = rest.validation
|
|
206
|
+
rest.validation = stripUndefined(validationRest)
|
|
207
|
+
|
|
208
|
+
if (isEmptyObject(rest.validation)) {
|
|
209
|
+
delete rest.validation
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else if (hasOnlyDescriptionDocs(rest.docs, description)) {
|
|
213
|
+
delete rest.docs
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (description) {
|
|
217
|
+
result += `.describe(${JSON.stringify(description)})`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (Object.keys(rest).length > 0) {
|
|
221
|
+
result += `.meta(${JSON.stringify(rest)})`
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getReferencedNames(type: CandidType): string[] {
|
|
228
|
+
const refs: string[] = []
|
|
229
|
+
|
|
230
|
+
function visit(node: CandidType): void {
|
|
231
|
+
switch (node.kind) {
|
|
232
|
+
case "reference":
|
|
233
|
+
refs.push(node.name)
|
|
234
|
+
break
|
|
235
|
+
case "opt":
|
|
236
|
+
case "vec":
|
|
237
|
+
visit(node.type)
|
|
238
|
+
break
|
|
239
|
+
case "record":
|
|
240
|
+
case "variant":
|
|
241
|
+
for (const field of node.fields) {
|
|
242
|
+
visit(field.type)
|
|
243
|
+
}
|
|
244
|
+
break
|
|
245
|
+
case "tuple":
|
|
246
|
+
for (const item of node.types) {
|
|
247
|
+
visit(item)
|
|
248
|
+
}
|
|
249
|
+
break
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
visit(type)
|
|
254
|
+
return refs
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function sortDeclarations(
|
|
258
|
+
declarations: CandidTypeDeclaration[]
|
|
259
|
+
): CandidTypeDeclaration[] {
|
|
260
|
+
const sorted: CandidTypeDeclaration[] = []
|
|
261
|
+
const visited = new Set<string>()
|
|
262
|
+
const visiting: string[] = []
|
|
263
|
+
const declarationByName = new Map(
|
|
264
|
+
declarations.map((decl) => [decl.name, decl])
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
function visit(name: string): void {
|
|
268
|
+
if (visited.has(name)) return
|
|
269
|
+
|
|
270
|
+
const cycleStart = visiting.indexOf(name)
|
|
271
|
+
if (cycleStart !== -1) {
|
|
272
|
+
const cycle = [...visiting.slice(cycleStart), name].join(" -> ")
|
|
273
|
+
throw new Error(`Recursive Candid types are not supported yet: ${cycle}`)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const declaration = declarationByName.get(name)
|
|
277
|
+
if (!declaration) return
|
|
278
|
+
|
|
279
|
+
visiting.push(name)
|
|
280
|
+
for (const dependency of getReferencedNames(declaration.type)) {
|
|
281
|
+
visit(dependency)
|
|
282
|
+
}
|
|
283
|
+
visiting.pop()
|
|
284
|
+
|
|
285
|
+
visited.add(name)
|
|
286
|
+
sorted.push(declaration)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (const declaration of declarations) {
|
|
290
|
+
visit(declaration.name)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return sorted
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function renderType(
|
|
297
|
+
type: CandidType,
|
|
298
|
+
indent = "",
|
|
299
|
+
options: GenerateCodecDeclarationsOptions = {}
|
|
300
|
+
): string {
|
|
301
|
+
const nextIndent = `${indent} `
|
|
302
|
+
let expression: string
|
|
303
|
+
|
|
304
|
+
switch (type.kind) {
|
|
305
|
+
case "null":
|
|
306
|
+
expression = "c.null()"
|
|
307
|
+
break
|
|
308
|
+
case "bool":
|
|
309
|
+
expression = "c.bool()"
|
|
310
|
+
break
|
|
311
|
+
case "nat":
|
|
312
|
+
expression = "c.nat()"
|
|
313
|
+
break
|
|
314
|
+
case "int":
|
|
315
|
+
expression = "c.int()"
|
|
316
|
+
break
|
|
317
|
+
case "nat8":
|
|
318
|
+
expression = "c.nat8()"
|
|
319
|
+
break
|
|
320
|
+
case "nat16":
|
|
321
|
+
expression = "c.nat16()"
|
|
322
|
+
break
|
|
323
|
+
case "nat32":
|
|
324
|
+
expression = "c.nat32()"
|
|
325
|
+
break
|
|
326
|
+
case "nat64":
|
|
327
|
+
expression = "c.nat64()"
|
|
328
|
+
break
|
|
329
|
+
case "int8":
|
|
330
|
+
expression = "c.int8()"
|
|
331
|
+
break
|
|
332
|
+
case "int16":
|
|
333
|
+
expression = "c.int16()"
|
|
334
|
+
break
|
|
335
|
+
case "int32":
|
|
336
|
+
expression = "c.int32()"
|
|
337
|
+
break
|
|
338
|
+
case "int64":
|
|
339
|
+
expression = "c.int64()"
|
|
340
|
+
break
|
|
341
|
+
case "float32":
|
|
342
|
+
expression = "c.float32()"
|
|
343
|
+
break
|
|
344
|
+
case "float64":
|
|
345
|
+
expression = "c.float64()"
|
|
346
|
+
break
|
|
347
|
+
case "text":
|
|
348
|
+
expression = "c.text()"
|
|
349
|
+
break
|
|
350
|
+
case "reserved":
|
|
351
|
+
expression = "c.reserved()"
|
|
352
|
+
break
|
|
353
|
+
case "empty":
|
|
354
|
+
expression = "c.empty()"
|
|
355
|
+
break
|
|
356
|
+
case "principal":
|
|
357
|
+
expression = "c.principal()"
|
|
358
|
+
break
|
|
359
|
+
case "blob":
|
|
360
|
+
expression = "c.blob()"
|
|
361
|
+
break
|
|
362
|
+
case "reference":
|
|
363
|
+
assertIdentifier(type.name, "Type reference")
|
|
364
|
+
expression = type.name
|
|
365
|
+
break
|
|
366
|
+
case "opt":
|
|
367
|
+
expression = `c.opt(${renderType(type.type, indent, options)})`
|
|
368
|
+
break
|
|
369
|
+
case "vec":
|
|
370
|
+
expression = `c.vec(${renderType(type.type, indent, options)})`
|
|
371
|
+
break
|
|
372
|
+
case "record": {
|
|
373
|
+
if (type.fields.length === 0) {
|
|
374
|
+
expression = "c.record({})"
|
|
375
|
+
} else {
|
|
376
|
+
const fields = type.fields
|
|
377
|
+
.map((field) => {
|
|
378
|
+
const fieldExpression = applyMetadata(
|
|
379
|
+
renderType(field.type, nextIndent, options),
|
|
380
|
+
field.metadata,
|
|
381
|
+
options
|
|
382
|
+
)
|
|
383
|
+
return `${nextIndent}${propertyName(field.name)}: ${fieldExpression},`
|
|
384
|
+
})
|
|
385
|
+
.join("\n")
|
|
386
|
+
expression = `c.record({\n${fields}\n${indent}})`
|
|
387
|
+
}
|
|
388
|
+
break
|
|
389
|
+
}
|
|
390
|
+
case "variant": {
|
|
391
|
+
if (type.fields.length === 0) {
|
|
392
|
+
expression = "c.variant({})"
|
|
393
|
+
} else {
|
|
394
|
+
const fields = type.fields
|
|
395
|
+
.map((field) => {
|
|
396
|
+
const fieldExpression = applyMetadata(
|
|
397
|
+
renderType(field.type, nextIndent, options),
|
|
398
|
+
field.metadata,
|
|
399
|
+
options
|
|
400
|
+
)
|
|
401
|
+
return `${nextIndent}${propertyName(field.name)}: ${fieldExpression},`
|
|
402
|
+
})
|
|
403
|
+
.join("\n")
|
|
404
|
+
expression = `c.variant({\n${fields}\n${indent}})`
|
|
405
|
+
}
|
|
406
|
+
break
|
|
407
|
+
}
|
|
408
|
+
case "tuple":
|
|
409
|
+
expression = `c.tuple([${type.types
|
|
410
|
+
.map((item) => renderType(item, indent, options))
|
|
411
|
+
.join(", ")}])`
|
|
412
|
+
break
|
|
413
|
+
case "func":
|
|
414
|
+
case "service":
|
|
415
|
+
case "class":
|
|
416
|
+
case "unknown":
|
|
417
|
+
case "knot":
|
|
418
|
+
case "future":
|
|
419
|
+
expression = `/* c.${type.kind} is not supported */ c.reserved()`
|
|
420
|
+
break
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return applyMetadata(expression, type.metadata, options)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function renderMethodReturn(
|
|
427
|
+
method: NonNullable<CandidSchema["service"]>["methods"][number],
|
|
428
|
+
options: GenerateCodecDeclarationsOptions
|
|
429
|
+
): string {
|
|
430
|
+
if (method.mode === "oneway") return ""
|
|
431
|
+
if (method.returns.length === 0) return ""
|
|
432
|
+
if (method.returns.length === 1) {
|
|
433
|
+
return `, ${renderType(method.returns[0], " ", options)}`
|
|
434
|
+
}
|
|
435
|
+
return `, [${method.returns
|
|
436
|
+
.map((returnType) => renderType(returnType, " ", options))
|
|
437
|
+
.join(", ")}]`
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Converts a structured CandidSchema AST into readable `@ic-reactor/cod`
|
|
442
|
+
* codec declarations.
|
|
443
|
+
*/
|
|
444
|
+
export function generateCodecDeclarations(
|
|
445
|
+
schema: CandidSchema,
|
|
446
|
+
optionsOrServiceExportName: string | GenerateCodecDeclarationsOptions = {}
|
|
447
|
+
): string {
|
|
448
|
+
const options = normalizeOptions(optionsOrServiceExportName)
|
|
449
|
+
const lines: string[] = ['import { c } from "@ic-reactor/cod"', ""]
|
|
450
|
+
|
|
451
|
+
for (const declaration of sortDeclarations(schema.types)) {
|
|
452
|
+
assertIdentifier(declaration.name, "Type declaration name")
|
|
453
|
+
|
|
454
|
+
lines.push(
|
|
455
|
+
`export const ${declaration.name} = ${applyMetadata(
|
|
456
|
+
renderType(declaration.type, "", options),
|
|
457
|
+
declaration.metadata,
|
|
458
|
+
options
|
|
459
|
+
)}`
|
|
460
|
+
)
|
|
461
|
+
lines.push(
|
|
462
|
+
`export type ${declaration.name} = c.infer<typeof ${declaration.name}>`
|
|
463
|
+
)
|
|
464
|
+
lines.push("")
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (schema.service) {
|
|
468
|
+
const includeCompatibilityExports =
|
|
469
|
+
options.includeCompatibilityExports ?? false
|
|
470
|
+
const declaredTypeNames = new Set(schema.types.map((t) => t.name))
|
|
471
|
+
const serviceExportName = resolveServiceExportName(
|
|
472
|
+
options,
|
|
473
|
+
declaredTypeNames
|
|
474
|
+
)
|
|
475
|
+
assertIdentifier(serviceExportName, "Service export name")
|
|
476
|
+
const methods = schema.service.methods
|
|
477
|
+
.map((method) => {
|
|
478
|
+
const args = method.args
|
|
479
|
+
.map((arg) => renderType(arg, " ", options))
|
|
480
|
+
.join(", ")
|
|
481
|
+
const methodExpression = `c.${method.mode}([${args}]${renderMethodReturn(
|
|
482
|
+
method,
|
|
483
|
+
options
|
|
484
|
+
)})`
|
|
485
|
+
return ` ${propertyName(method.name)}: ${applyMetadata(
|
|
486
|
+
methodExpression,
|
|
487
|
+
method.metadata,
|
|
488
|
+
options
|
|
489
|
+
)},`
|
|
490
|
+
})
|
|
491
|
+
.join("\n")
|
|
492
|
+
|
|
493
|
+
const serviceExpression =
|
|
494
|
+
methods.length > 0 ? `c.service({\n${methods}\n})` : "c.service({})"
|
|
495
|
+
lines.push(
|
|
496
|
+
`export const ${serviceExportName} = ${applyMetadata(
|
|
497
|
+
serviceExpression,
|
|
498
|
+
schema.service.metadata,
|
|
499
|
+
options
|
|
500
|
+
)}`
|
|
501
|
+
)
|
|
502
|
+
lines.push("")
|
|
503
|
+
|
|
504
|
+
if (includeCompatibilityExports) {
|
|
505
|
+
lines.push(`export const idlFactory = ${serviceExportName}.idlFactory`)
|
|
506
|
+
lines.push(
|
|
507
|
+
`export type _SERVICE = c.ServiceOf<typeof ${serviceExportName}>`
|
|
508
|
+
)
|
|
509
|
+
lines.push("")
|
|
510
|
+
lines.push(`export const manifest = ${serviceExportName}.manifest()`)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return `${lines.join("\n").trimEnd()}\n`
|
|
515
|
+
}
|