@ic-reactor/codegen 0.12.0 → 0.13.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 +14 -4
- package/dist/index.cjs +415 -699
- package/dist/index.d.cts +119 -28
- package/dist/index.d.ts +119 -28
- package/dist/index.js +404 -98
- package/package.json +9 -15
- package/src/generators/declarations.ts +164 -21
- package/src/generators/reactor.ts +20 -7
- package/src/index.ts +19 -5
- package/src/pipeline.ts +190 -18
- package/src/validate.ts +407 -0
- package/dist/chunk-VBCR5IVT.js +0 -609
- package/dist/renderer.cjs +0 -639
- package/dist/renderer.d.cts +0 -33
- package/dist/renderer.d.ts +0 -33
- package/dist/renderer.js +0 -14
- package/src/__snapshots__/bindgen.test.ts.snap +0 -18
- package/src/__snapshots__/reactor.test.ts.snap +0 -127
- package/src/bindgen.test.ts +0 -85
- package/src/metadata-rules.json +0 -144
- package/src/metadata.ts +0 -157
- package/src/naming.test.ts +0 -45
- package/src/parser.ts +0 -79
- package/src/pipeline.test.ts +0 -355
- package/src/reactor.test.ts +0 -84
- package/src/renderer.test.ts +0 -573
- package/src/renderer.ts +0 -515
package/dist/renderer.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BUILT_IN_FORMAT_HELPERS,
|
|
3
|
-
BUILT_IN_JSDOC_FORMAT_TYPES,
|
|
4
|
-
generateCodecDeclarations,
|
|
5
|
-
renderType,
|
|
6
|
-
sortDeclarations
|
|
7
|
-
} from "./chunk-VBCR5IVT.js";
|
|
8
|
-
export {
|
|
9
|
-
BUILT_IN_FORMAT_HELPERS,
|
|
10
|
-
BUILT_IN_JSDOC_FORMAT_TYPES,
|
|
11
|
-
generateCodecDeclarations,
|
|
12
|
-
renderType,
|
|
13
|
-
sortDeclarations
|
|
14
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
-
|
|
3
|
-
exports[`Bindgen > generateDeclarations creates correct files > js-declarations 1`] = `
|
|
4
|
-
"export const idlFactory = ({ IDL }) => {
|
|
5
|
-
return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], ['query']) });
|
|
6
|
-
};
|
|
7
|
-
export const init = ({ IDL }) => { return []; };"
|
|
8
|
-
`;
|
|
9
|
-
|
|
10
|
-
exports[`Bindgen > generateDeclarations creates correct files > ts-declarations 1`] = `
|
|
11
|
-
"import type { Principal } from '@icp-sdk/core/principal';
|
|
12
|
-
import type { ActorMethod } from '@icp-sdk/core/agent';
|
|
13
|
-
import type { IDL } from '@icp-sdk/core/candid';
|
|
14
|
-
|
|
15
|
-
export interface _SERVICE { 'greet' : ActorMethod<[string], string> }
|
|
16
|
-
export declare const idlFactory: IDL.InterfaceFactory;
|
|
17
|
-
export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];"
|
|
18
|
-
`;
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
-
|
|
3
|
-
exports[`Reactor generator > keeps default behavior as DisplayReactor > display-reactor-index 1`] = `
|
|
4
|
-
"import { createActorHooks } from "@ic-reactor/react"
|
|
5
|
-
import { DisplayReactor } from "@ic-reactor/react"
|
|
6
|
-
import { clientManager } from "../../clients"
|
|
7
|
-
import { idlFactory, type _SERVICE } from "./declarations/backend"
|
|
8
|
-
|
|
9
|
-
export type BackendService = _SERVICE
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Backend Reactor
|
|
13
|
-
*
|
|
14
|
-
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
15
|
-
* This file is overwritten whenever generation runs.
|
|
16
|
-
*
|
|
17
|
-
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
18
|
-
* factory modules). Avoid editing this managed file directly.
|
|
19
|
-
*/
|
|
20
|
-
export const backendReactor = new DisplayReactor<BackendService>({
|
|
21
|
-
clientManager,
|
|
22
|
-
idlFactory,
|
|
23
|
-
name: "backend",
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
export const {
|
|
27
|
-
useActorQuery: useBackendQuery,
|
|
28
|
-
useActorSuspenseQuery: useBackendSuspenseQuery,
|
|
29
|
-
useActorInfiniteQuery: useBackendInfiniteQuery,
|
|
30
|
-
useActorSuspenseInfiniteQuery: useBackendSuspenseInfiniteQuery,
|
|
31
|
-
useActorMutation: useBackendMutation,
|
|
32
|
-
useActorMethod: useBackendMethod,
|
|
33
|
-
} = createActorHooks(backendReactor)
|
|
34
|
-
"
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
exports[`Reactor generator > supports Reactor mode generation > reactor-index 1`] = `
|
|
38
|
-
"import { createActorHooks } from "@ic-reactor/react"
|
|
39
|
-
import { Reactor } from "@ic-reactor/react"
|
|
40
|
-
import { clientManager } from "../../clients"
|
|
41
|
-
import { idlFactory, type _SERVICE } from "./declarations/workflow_engine"
|
|
42
|
-
|
|
43
|
-
export type WorkflowEngineService = _SERVICE
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* WorkflowEngine Reactor
|
|
47
|
-
*
|
|
48
|
-
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
49
|
-
* This file is overwritten whenever generation runs.
|
|
50
|
-
*
|
|
51
|
-
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
52
|
-
* factory modules). Avoid editing this managed file directly.
|
|
53
|
-
*/
|
|
54
|
-
export const workflowEngineReactor = new Reactor<WorkflowEngineService>({
|
|
55
|
-
clientManager,
|
|
56
|
-
idlFactory,
|
|
57
|
-
name: "workflow_engine",
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
export const {
|
|
61
|
-
useActorQuery: useWorkflowEngineQuery,
|
|
62
|
-
useActorSuspenseQuery: useWorkflowEngineSuspenseQuery,
|
|
63
|
-
useActorInfiniteQuery: useWorkflowEngineInfiniteQuery,
|
|
64
|
-
useActorSuspenseInfiniteQuery: useWorkflowEngineSuspenseInfiniteQuery,
|
|
65
|
-
useActorMutation: useWorkflowEngineMutation,
|
|
66
|
-
useActorMethod: useWorkflowEngineMethod,
|
|
67
|
-
} = createActorHooks(workflowEngineReactor)
|
|
68
|
-
"
|
|
69
|
-
`;
|
|
70
|
-
|
|
71
|
-
exports[`Reactor generator > supports candid reactor subclasses > metadata-display-reactor-index 1`] = `
|
|
72
|
-
"import { createActorHooks } from "@ic-reactor/react"
|
|
73
|
-
import { MetadataDisplayReactor } from "@ic-reactor/candid"
|
|
74
|
-
import { clientManager } from "../../clients"
|
|
75
|
-
import { idlFactory, type _SERVICE } from "./declarations/ledger"
|
|
76
|
-
|
|
77
|
-
export type LedgerService = _SERVICE
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Ledger Reactor
|
|
81
|
-
*
|
|
82
|
-
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
83
|
-
* This file is overwritten whenever generation runs.
|
|
84
|
-
*
|
|
85
|
-
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
86
|
-
* factory modules). Avoid editing this managed file directly.
|
|
87
|
-
*/
|
|
88
|
-
export const ledgerReactor = new MetadataDisplayReactor<LedgerService>({
|
|
89
|
-
clientManager,
|
|
90
|
-
idlFactory,
|
|
91
|
-
name: "ledger",
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
export const {
|
|
95
|
-
useActorQuery: useLedgerQuery,
|
|
96
|
-
useActorSuspenseQuery: useLedgerSuspenseQuery,
|
|
97
|
-
useActorInfiniteQuery: useLedgerInfiniteQuery,
|
|
98
|
-
useActorSuspenseInfiniteQuery: useLedgerSuspenseInfiniteQuery,
|
|
99
|
-
useActorMutation: useLedgerMutation,
|
|
100
|
-
useActorMethod: useLedgerMethod,
|
|
101
|
-
} = createActorHooks(ledgerReactor)
|
|
102
|
-
"
|
|
103
|
-
`;
|
|
104
|
-
|
|
105
|
-
exports[`Reactor generator > supports core target generation without React hooks > core-display-reactor-index 1`] = `
|
|
106
|
-
"import { DisplayReactor } from "@ic-reactor/core"
|
|
107
|
-
import { clientManager } from "../../clients"
|
|
108
|
-
import { idlFactory, type _SERVICE } from "./declarations/backend"
|
|
109
|
-
|
|
110
|
-
export type BackendService = _SERVICE
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Backend Reactor
|
|
114
|
-
*
|
|
115
|
-
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
116
|
-
* This file is overwritten whenever generation runs.
|
|
117
|
-
*
|
|
118
|
-
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
119
|
-
* factory modules). Avoid editing this managed file directly.
|
|
120
|
-
*/
|
|
121
|
-
export const backendReactor = new DisplayReactor<BackendService>({
|
|
122
|
-
clientManager,
|
|
123
|
-
idlFactory,
|
|
124
|
-
name: "backend",
|
|
125
|
-
})
|
|
126
|
-
"
|
|
127
|
-
`;
|
package/src/bindgen.test.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
2
|
-
import fs from "node:fs"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
import { generateDeclarations } from "./generators/index.js"
|
|
5
|
-
|
|
6
|
-
describe("Bindgen", () => {
|
|
7
|
-
const mockDidFile = "mock/test.did"
|
|
8
|
-
const mockOutDir = "mock/output"
|
|
9
|
-
const mockCanisterName = "test_canister"
|
|
10
|
-
const validDidContent = `service : {
|
|
11
|
-
greet: (text) -> (text) query;
|
|
12
|
-
}`
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
// Mock fs methods
|
|
16
|
-
vi.spyOn(fs, "existsSync").mockImplementation((p) => {
|
|
17
|
-
// Pretend the source DID file exists
|
|
18
|
-
if (p.toString() === mockDidFile) return true
|
|
19
|
-
return false
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined as any)
|
|
23
|
-
vi.spyOn(fs, "rmSync").mockImplementation(() => undefined)
|
|
24
|
-
vi.spyOn(fs, "readFileSync").mockImplementation(() => validDidContent)
|
|
25
|
-
vi.spyOn(fs, "writeFileSync").mockImplementation(() => undefined)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
vi.restoreAllMocks()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("generateDeclarations creates correct files", async () => {
|
|
33
|
-
const result = await generateDeclarations({
|
|
34
|
-
didFile: mockDidFile,
|
|
35
|
-
outDir: mockOutDir,
|
|
36
|
-
canisterName: mockCanisterName,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
if (!result.success) {
|
|
40
|
-
console.error(result.error)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
expect(result.success).toBe(true)
|
|
44
|
-
expect(result.declarationsDir).toBe(path.join(mockOutDir, "declarations"))
|
|
45
|
-
|
|
46
|
-
// Verify file writes
|
|
47
|
-
const declarationsDir = path.join(mockOutDir, "declarations")
|
|
48
|
-
const jsPath = path.join(declarationsDir, "test.js")
|
|
49
|
-
const dtsPath = path.join(declarationsDir, "test.d.ts")
|
|
50
|
-
|
|
51
|
-
// Should create directory
|
|
52
|
-
expect(fs.mkdirSync).toHaveBeenCalledWith(mockOutDir, { recursive: true })
|
|
53
|
-
expect(fs.mkdirSync).toHaveBeenCalledWith(declarationsDir, {
|
|
54
|
-
recursive: true,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Validate exact content using snapshots
|
|
58
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(jsPath, expect.any(String))
|
|
59
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(dtsPath, expect.any(String))
|
|
60
|
-
|
|
61
|
-
// Get the arguments of the calls to check content
|
|
62
|
-
const jsCall = vi
|
|
63
|
-
.mocked(fs.writeFileSync)
|
|
64
|
-
.mock.calls.find((call) => call[0] === jsPath)
|
|
65
|
-
const dtsCall = vi
|
|
66
|
-
.mocked(fs.writeFileSync)
|
|
67
|
-
.mock.calls.find((call) => call[0] === dtsPath)
|
|
68
|
-
|
|
69
|
-
expect(jsCall?.[1]).toMatchSnapshot("js-declarations")
|
|
70
|
-
expect(dtsCall?.[1]).toMatchSnapshot("ts-declarations")
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it("returns error if DID file missing", async () => {
|
|
74
|
-
vi.spyOn(fs, "existsSync").mockReturnValue(false)
|
|
75
|
-
|
|
76
|
-
const result = await generateDeclarations({
|
|
77
|
-
didFile: "missing.did",
|
|
78
|
-
outDir: mockOutDir,
|
|
79
|
-
canisterName: mockCanisterName,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
expect(result.success).toBe(false)
|
|
83
|
-
expect(result.error).toContain("DID file not found")
|
|
84
|
-
})
|
|
85
|
-
})
|
package/src/metadata-rules.json
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"defaultValidationMessages": {
|
|
3
|
-
"minimum": {
|
|
4
|
-
"template": "Must be at least {value}"
|
|
5
|
-
},
|
|
6
|
-
"maximum": {
|
|
7
|
-
"template": "Must be at most {value}"
|
|
8
|
-
},
|
|
9
|
-
"minLength": {
|
|
10
|
-
"template": "Must be at least {value} character{plural}"
|
|
11
|
-
},
|
|
12
|
-
"maxLength": {
|
|
13
|
-
"template": "Must be at most {value} character{plural}"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"email": {
|
|
17
|
-
"helper": "email",
|
|
18
|
-
"regex": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
|
|
19
|
-
"jsonSchemaFormat": "email",
|
|
20
|
-
"errorMessage": "Must be a valid email address"
|
|
21
|
-
},
|
|
22
|
-
"date-time": {
|
|
23
|
-
"helper": "dateTime",
|
|
24
|
-
"regex": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d))$",
|
|
25
|
-
"jsonSchemaFormat": "date-time",
|
|
26
|
-
"errorMessage": "Must be a valid ISO datetime"
|
|
27
|
-
},
|
|
28
|
-
"datetime": {
|
|
29
|
-
"helper": "datetime",
|
|
30
|
-
"regex": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d))$",
|
|
31
|
-
"jsonSchemaFormat": "date-time",
|
|
32
|
-
"errorMessage": "Must be a valid ISO datetime"
|
|
33
|
-
},
|
|
34
|
-
"date": {
|
|
35
|
-
"helper": "date",
|
|
36
|
-
"regex": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
|
|
37
|
-
"jsonSchemaFormat": "date",
|
|
38
|
-
"errorMessage": "Must be a valid ISO date"
|
|
39
|
-
},
|
|
40
|
-
"time": {
|
|
41
|
-
"helper": "time",
|
|
42
|
-
"regex": "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$",
|
|
43
|
-
"errorMessage": "Must be a valid ISO time"
|
|
44
|
-
},
|
|
45
|
-
"duration": {
|
|
46
|
-
"helper": "duration",
|
|
47
|
-
"regex": "^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$",
|
|
48
|
-
"jsonSchemaFormat": "duration",
|
|
49
|
-
"errorMessage": "Must be a valid ISO duration"
|
|
50
|
-
},
|
|
51
|
-
"url": {
|
|
52
|
-
"helper": "url",
|
|
53
|
-
"regex": "^[a-zA-Z][a-zA-Z\\d+.-]*:.+",
|
|
54
|
-
"jsonSchemaFormat": "uri",
|
|
55
|
-
"errorMessage": "Must be a valid URL"
|
|
56
|
-
},
|
|
57
|
-
"uri": {
|
|
58
|
-
"helper": "uri",
|
|
59
|
-
"regex": "^[a-zA-Z][a-zA-Z\\d+.-]*:.+",
|
|
60
|
-
"jsonSchemaFormat": "uri",
|
|
61
|
-
"errorMessage": "Must be a valid URI"
|
|
62
|
-
},
|
|
63
|
-
"httpsUrl": {
|
|
64
|
-
"helper": "httpsUrl",
|
|
65
|
-
"regex": "^https://.+",
|
|
66
|
-
"jsonSchemaFormat": "uri",
|
|
67
|
-
"errorMessage": "Must be a valid HTTPS URL"
|
|
68
|
-
},
|
|
69
|
-
"ipv4": {
|
|
70
|
-
"helper": "ipv4",
|
|
71
|
-
"regex": "^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.|$)){4}$",
|
|
72
|
-
"jsonSchemaFormat": "ipv4",
|
|
73
|
-
"errorMessage": "Must be a valid IPv4 address"
|
|
74
|
-
},
|
|
75
|
-
"ipv6": {
|
|
76
|
-
"helper": "ipv6",
|
|
77
|
-
"regex": "^((?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|::1|::|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?:(?::[0-9A-Fa-f]{1,4}){1,6}))$",
|
|
78
|
-
"jsonSchemaFormat": "ipv6",
|
|
79
|
-
"errorMessage": "Must be a valid IPv6 address"
|
|
80
|
-
},
|
|
81
|
-
"uuid": {
|
|
82
|
-
"helper": "uuid",
|
|
83
|
-
"regex": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
|
|
84
|
-
"jsonSchemaFormat": "uuid",
|
|
85
|
-
"errorMessage": "Must be a valid UUID"
|
|
86
|
-
},
|
|
87
|
-
"guid": {
|
|
88
|
-
"helper": "guid",
|
|
89
|
-
"regex": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
|
|
90
|
-
"jsonSchemaFormat": "uuid",
|
|
91
|
-
"errorMessage": "Must be a valid GUID"
|
|
92
|
-
},
|
|
93
|
-
"base64": {
|
|
94
|
-
"helper": "base64",
|
|
95
|
-
"regex": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
|
|
96
|
-
"contentEncoding": "base64",
|
|
97
|
-
"errorMessage": "Must be valid base64"
|
|
98
|
-
},
|
|
99
|
-
"base64url": {
|
|
100
|
-
"helper": "base64url",
|
|
101
|
-
"regex": "^[A-Za-z0-9_-]+$",
|
|
102
|
-
"errorMessage": "Must be valid base64url"
|
|
103
|
-
},
|
|
104
|
-
"cuid": {
|
|
105
|
-
"helper": "cuid",
|
|
106
|
-
"regex": "^c[a-z0-9]{24}$",
|
|
107
|
-
"errorMessage": "Must be a valid CUID"
|
|
108
|
-
},
|
|
109
|
-
"cuid2": {
|
|
110
|
-
"helper": "cuid2",
|
|
111
|
-
"regex": "^[a-z][a-z0-9]*$",
|
|
112
|
-
"errorMessage": "Must be a valid CUID2"
|
|
113
|
-
},
|
|
114
|
-
"ulid": {
|
|
115
|
-
"helper": "ulid",
|
|
116
|
-
"regex": "^[0-9A-HJKMNP-TV-Z]{26}$",
|
|
117
|
-
"errorMessage": "Must be a valid ULID"
|
|
118
|
-
},
|
|
119
|
-
"nanoid": {
|
|
120
|
-
"helper": "nanoid",
|
|
121
|
-
"regex": "^[A-Za-z0-9_-]{21}$",
|
|
122
|
-
"errorMessage": "Must be a valid nanoid"
|
|
123
|
-
},
|
|
124
|
-
"emoji": {
|
|
125
|
-
"helper": "emoji",
|
|
126
|
-
"regex": "^\\p{Extended_Pictographic}+$",
|
|
127
|
-
"errorMessage": "Must contain only emoji characters"
|
|
128
|
-
},
|
|
129
|
-
"cidrv4": {
|
|
130
|
-
"helper": "cidrv4",
|
|
131
|
-
"regex": "^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)/(?:3[0-2]|[12]?\\d)$",
|
|
132
|
-
"errorMessage": "Must be a valid IPv4 CIDR range"
|
|
133
|
-
},
|
|
134
|
-
"cidrv6": {
|
|
135
|
-
"helper": "cidrv6",
|
|
136
|
-
"regex": "^[0-9A-Fa-f:]+/(?:12[0-8]|1[01]\\d|\\d?\\d)$",
|
|
137
|
-
"errorMessage": "Must be a valid IPv6 CIDR range"
|
|
138
|
-
},
|
|
139
|
-
"mac": {
|
|
140
|
-
"helper": "mac",
|
|
141
|
-
"regex": "^(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$",
|
|
142
|
-
"errorMessage": "Must be a valid MAC address"
|
|
143
|
-
}
|
|
144
|
-
}
|
package/src/metadata.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import type { CandidMetadata } from "@ic-reactor/parser"
|
|
2
|
-
import metadataRules from "./metadata-rules.json"
|
|
3
|
-
|
|
4
|
-
export type JSDocFormatDefinition = {
|
|
5
|
-
regex?: string
|
|
6
|
-
errorMessage?: string
|
|
7
|
-
jsonSchemaFormat?: string
|
|
8
|
-
contentEncoding?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type CustomJSDocFormatTypes = Record<
|
|
12
|
-
string,
|
|
13
|
-
string | JSDocFormatDefinition
|
|
14
|
-
>
|
|
15
|
-
|
|
16
|
-
type ValidationBoundRule = {
|
|
17
|
-
template?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type MetadataRulesFile = {
|
|
21
|
-
defaultValidationMessages?: Record<string, ValidationBoundRule>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const metadataRulesFile = metadataRules as MetadataRulesFile
|
|
25
|
-
const defaultValidationMessages =
|
|
26
|
-
metadataRulesFile.defaultValidationMessages ?? {}
|
|
27
|
-
|
|
28
|
-
const rawRuleEntries = Object.entries(metadataRules).filter(
|
|
29
|
-
([type]) => type !== "defaultValidationMessages"
|
|
30
|
-
) as Array<
|
|
31
|
-
[
|
|
32
|
-
string,
|
|
33
|
-
{
|
|
34
|
-
helper?: string
|
|
35
|
-
regex?: string
|
|
36
|
-
jsonSchemaFormat?: string
|
|
37
|
-
contentEncoding?: string
|
|
38
|
-
errorMessage?: string
|
|
39
|
-
},
|
|
40
|
-
]
|
|
41
|
-
>
|
|
42
|
-
|
|
43
|
-
export const BUILT_IN_JSDOC_FORMAT_TYPES: Readonly<
|
|
44
|
-
Record<string, JSDocFormatDefinition>
|
|
45
|
-
> = Object.fromEntries(
|
|
46
|
-
rawRuleEntries.map(([type, rule]) => [
|
|
47
|
-
type,
|
|
48
|
-
{
|
|
49
|
-
regex: rule.regex,
|
|
50
|
-
jsonSchemaFormat: rule.jsonSchemaFormat,
|
|
51
|
-
contentEncoding: rule.contentEncoding,
|
|
52
|
-
errorMessage: rule.errorMessage,
|
|
53
|
-
},
|
|
54
|
-
])
|
|
55
|
-
) as Record<string, JSDocFormatDefinition>
|
|
56
|
-
|
|
57
|
-
export const BUILT_IN_FORMAT_HELPERS: Readonly<Record<string, string>> =
|
|
58
|
-
Object.fromEntries(
|
|
59
|
-
rawRuleEntries
|
|
60
|
-
.filter(([, rule]) => rule.helper)
|
|
61
|
-
.map(([type, rule]) => [type, rule.helper as string])
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
export interface FormatResolutionOptions {
|
|
65
|
-
customJSDocFormatTypes?: CustomJSDocFormatTypes
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function normalizeFormatDefinition(
|
|
69
|
-
definition: CustomJSDocFormatTypes[string] | undefined
|
|
70
|
-
): JSDocFormatDefinition | undefined {
|
|
71
|
-
if (!definition) return undefined
|
|
72
|
-
return typeof definition === "string" ? { regex: definition } : definition
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function formatDefinitionFor(
|
|
76
|
-
type: string,
|
|
77
|
-
options: FormatResolutionOptions
|
|
78
|
-
): JSDocFormatDefinition | undefined {
|
|
79
|
-
const builtIn = BUILT_IN_JSDOC_FORMAT_TYPES[type]
|
|
80
|
-
const custom = normalizeFormatDefinition(
|
|
81
|
-
options.customJSDocFormatTypes?.[type]
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
if (!custom) return builtIn
|
|
85
|
-
return {
|
|
86
|
-
...builtIn,
|
|
87
|
-
...custom,
|
|
88
|
-
errorMessage: custom.errorMessage ?? builtIn?.errorMessage,
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function stripUndefined<T extends Record<string, unknown>>(value: T): T {
|
|
93
|
-
return Object.fromEntries(
|
|
94
|
-
Object.entries(value).filter(([, entry]) => entry !== undefined)
|
|
95
|
-
) as T
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function defaultBoundMessage(
|
|
99
|
-
kind: "minimum" | "maximum" | "minLength" | "maxLength",
|
|
100
|
-
value: string
|
|
101
|
-
): string {
|
|
102
|
-
const template =
|
|
103
|
-
defaultValidationMessages[kind]?.template ??
|
|
104
|
-
(kind === "minimum"
|
|
105
|
-
? "Must be at least {value}"
|
|
106
|
-
: kind === "maximum"
|
|
107
|
-
? "Must be at most {value}"
|
|
108
|
-
: kind === "minLength"
|
|
109
|
-
? "Must be at least {value} character{plural}"
|
|
110
|
-
: "Must be at most {value} character{plural}")
|
|
111
|
-
|
|
112
|
-
return template
|
|
113
|
-
.replace("{value}", value)
|
|
114
|
-
.replace("{plural}", value === "1" ? "" : "s")
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function withDefaultBoundMessage<
|
|
118
|
-
T extends { value: string; message?: string } | undefined,
|
|
119
|
-
>(bound: T, kind: "minimum" | "maximum" | "minLength" | "maxLength"): T {
|
|
120
|
-
if (!bound || bound.message !== undefined) return bound
|
|
121
|
-
return {
|
|
122
|
-
...bound,
|
|
123
|
-
message: defaultBoundMessage(kind, bound.value),
|
|
124
|
-
} as T
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function normalizeValidationMetadata(
|
|
128
|
-
validation: NonNullable<CandidMetadata["validation"]>,
|
|
129
|
-
options: FormatResolutionOptions
|
|
130
|
-
): NonNullable<CandidMetadata["validation"]> {
|
|
131
|
-
const normalized = {
|
|
132
|
-
...validation,
|
|
133
|
-
minimum: withDefaultBoundMessage(validation.minimum, "minimum"),
|
|
134
|
-
maximum: withDefaultBoundMessage(validation.maximum, "maximum"),
|
|
135
|
-
minLength: withDefaultBoundMessage(validation.minLength, "minLength"),
|
|
136
|
-
maxLength: withDefaultBoundMessage(validation.maxLength, "maxLength"),
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const format = validation.format
|
|
140
|
-
const formatDefinition = format
|
|
141
|
-
? formatDefinitionFor(format.type, options)
|
|
142
|
-
: undefined
|
|
143
|
-
|
|
144
|
-
if (format && formatDefinition) {
|
|
145
|
-
normalized.format = stripUndefined({
|
|
146
|
-
...format,
|
|
147
|
-
regex: formatDefinition.regex,
|
|
148
|
-
jsonSchemaFormat: formatDefinition.jsonSchemaFormat,
|
|
149
|
-
contentEncoding: formatDefinition.contentEncoding,
|
|
150
|
-
errorMessage: format.message ?? formatDefinition.errorMessage,
|
|
151
|
-
})
|
|
152
|
-
} else if (format) {
|
|
153
|
-
normalized.format = format
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return normalized
|
|
157
|
-
}
|
package/src/naming.test.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import {
|
|
3
|
-
toPascalCase,
|
|
4
|
-
toCamelCase,
|
|
5
|
-
getReactorName,
|
|
6
|
-
getServiceTypeName,
|
|
7
|
-
} from "./naming.js"
|
|
8
|
-
|
|
9
|
-
describe("Naming Utilities", () => {
|
|
10
|
-
describe("Base Conversions", () => {
|
|
11
|
-
it("converts to PascalCase", () => {
|
|
12
|
-
expect(toPascalCase("get_user")).toBe("GetUser")
|
|
13
|
-
expect(toPascalCase("my-canister")).toBe("MyCanister")
|
|
14
|
-
expect(toPascalCase("list_items_v2")).toBe("ListItemsV2")
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it("converts to camelCase", () => {
|
|
18
|
-
expect(toCamelCase("get_user")).toBe("getUser")
|
|
19
|
-
expect(toCamelCase("my-canister")).toBe("myCanister")
|
|
20
|
-
expect(toCamelCase("ListItems")).toBe("listItems")
|
|
21
|
-
})
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
describe("Domain-Specific Naming", () => {
|
|
25
|
-
it("generates reactor names", () => {
|
|
26
|
-
expect(getReactorName("backend")).toBe("backendReactor")
|
|
27
|
-
expect(getReactorName("my-canister")).toBe("myCanisterReactor")
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it("generates service type names", () => {
|
|
31
|
-
expect(getServiceTypeName("backend")).toBe("BackendService")
|
|
32
|
-
expect(getServiceTypeName("update_item")).toBe("UpdateItemService")
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it("generates reactor names", () => {
|
|
36
|
-
expect(getReactorName("backend")).toBe("backendReactor")
|
|
37
|
-
expect(getReactorName("my-canister")).toBe("myCanisterReactor")
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it("generates service type names", () => {
|
|
41
|
-
expect(getServiceTypeName("backend")).toBe("BackendService")
|
|
42
|
-
expect(getServiceTypeName("my-canister")).toBe("MyCanisterService")
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
})
|
package/src/parser.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Candid Parser Utilities
|
|
3
|
-
*
|
|
4
|
-
* Parses Candid .did files to extract service method signatures.
|
|
5
|
-
* Used by the CLI for listing methods and by advanced generators.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { didToJs } from "@ic-reactor/parser"
|
|
9
|
-
import fs from "node:fs"
|
|
10
|
-
|
|
11
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
-
// TYPES
|
|
13
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export type MethodType = "query" | "mutation"
|
|
16
|
-
|
|
17
|
-
export interface MethodInfo {
|
|
18
|
-
/** Method name as it appears in the Candid service */
|
|
19
|
-
name: string
|
|
20
|
-
/** "query" for read-only calls, "mutation" for update calls */
|
|
21
|
-
type: MethodType
|
|
22
|
-
/** True if the method takes at least one argument */
|
|
23
|
-
hasArgs: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
-
// PARSERS
|
|
28
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Extract method information from raw Candid source text.
|
|
32
|
-
*
|
|
33
|
-
* Compiles the Candid to JS and inspects the IDL.Service definition.
|
|
34
|
-
*/
|
|
35
|
-
export function extractMethods(didContent: string): MethodInfo[] {
|
|
36
|
-
try {
|
|
37
|
-
const jsContent = didToJs(didContent)
|
|
38
|
-
|
|
39
|
-
const methods: MethodInfo[] = []
|
|
40
|
-
|
|
41
|
-
// Find the IDL.Service({...}) body
|
|
42
|
-
const serviceMatch = /IDL\.Service\(\{([\s\S]*?)\}\)/.exec(jsContent)
|
|
43
|
-
if (!serviceMatch) return methods
|
|
44
|
-
|
|
45
|
-
const serviceBody = serviceMatch[1]
|
|
46
|
-
|
|
47
|
-
// Match each method: 'methodName': IDL.Func([args], [rets], [annotations])
|
|
48
|
-
const methodRegex =
|
|
49
|
-
/['"]([\w]+)['"]\s*:\s*IDL\.Func\(\s*\[(.*?)\],\s*\[.*?\],\s*\[(.*?)\]\)/g
|
|
50
|
-
|
|
51
|
-
let match: RegExpExecArray | null
|
|
52
|
-
while ((match = methodRegex.exec(serviceBody)) !== null) {
|
|
53
|
-
const [, name, args, annotations] = match
|
|
54
|
-
methods.push({
|
|
55
|
-
name,
|
|
56
|
-
type: annotations.includes("'query'") ? "query" : "mutation",
|
|
57
|
-
hasArgs: args.trim().length > 0,
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return methods
|
|
62
|
-
} catch (error) {
|
|
63
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
64
|
-
throw new Error(`Failed to parse Candid: ${msg}`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Parse a .did file from disk and return its methods.
|
|
70
|
-
*
|
|
71
|
-
* @throws if the file does not exist or fails to parse
|
|
72
|
-
*/
|
|
73
|
-
export function parseDIDFile(didFilePath: string): MethodInfo[] {
|
|
74
|
-
if (!fs.existsSync(didFilePath)) {
|
|
75
|
-
throw new Error(`DID file not found: ${didFilePath}`)
|
|
76
|
-
}
|
|
77
|
-
const content = fs.readFileSync(didFilePath, "utf-8")
|
|
78
|
-
return extractMethods(content)
|
|
79
|
-
}
|