@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CandidSchema, CandidType, CandidTypeDeclaration } from '@ic-reactor/parser';
|
|
2
|
+
|
|
3
|
+
type JSDocFormatDefinition = {
|
|
4
|
+
regex?: string;
|
|
5
|
+
errorMessage?: string;
|
|
6
|
+
jsonSchemaFormat?: string;
|
|
7
|
+
contentEncoding?: string;
|
|
8
|
+
};
|
|
9
|
+
type CustomJSDocFormatTypes = Record<string, string | JSDocFormatDefinition>;
|
|
10
|
+
declare const BUILT_IN_JSDOC_FORMAT_TYPES: Readonly<Record<string, JSDocFormatDefinition>>;
|
|
11
|
+
declare const BUILT_IN_FORMAT_HELPERS: Readonly<Record<string, string>>;
|
|
12
|
+
|
|
13
|
+
interface GenerateCodecDeclarationsOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Canister name used to derive the service export name.
|
|
16
|
+
* Converted to SCREAMING_SNAKE_CASE (e.g. `"my-backend"` → `MY_BACKEND`).
|
|
17
|
+
* Ignored when `serviceExportName` is set explicitly.
|
|
18
|
+
*/
|
|
19
|
+
canisterName?: string;
|
|
20
|
+
/** Explicit service export name. Takes priority over `canisterName`. */
|
|
21
|
+
serviceExportName?: string;
|
|
22
|
+
customJSDocFormatTypes?: CustomJSDocFormatTypes;
|
|
23
|
+
includeCompatibilityExports?: boolean;
|
|
24
|
+
}
|
|
25
|
+
declare function sortDeclarations(declarations: CandidTypeDeclaration[]): CandidTypeDeclaration[];
|
|
26
|
+
declare function renderType(type: CandidType, indent?: string, options?: GenerateCodecDeclarationsOptions): string;
|
|
27
|
+
/**
|
|
28
|
+
* Converts a structured CandidSchema AST into readable `@ic-reactor/cod`
|
|
29
|
+
* codec declarations.
|
|
30
|
+
*/
|
|
31
|
+
declare function generateCodecDeclarations(schema: CandidSchema, optionsOrServiceExportName?: string | GenerateCodecDeclarationsOptions): string;
|
|
32
|
+
|
|
33
|
+
export { BUILT_IN_FORMAT_HELPERS, BUILT_IN_JSDOC_FORMAT_TYPES, type CustomJSDocFormatTypes, type GenerateCodecDeclarationsOptions, type JSDocFormatDefinition, generateCodecDeclarations, renderType, sortDeclarations };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CandidSchema, CandidType, CandidTypeDeclaration } from '@ic-reactor/parser';
|
|
2
|
+
|
|
3
|
+
type JSDocFormatDefinition = {
|
|
4
|
+
regex?: string;
|
|
5
|
+
errorMessage?: string;
|
|
6
|
+
jsonSchemaFormat?: string;
|
|
7
|
+
contentEncoding?: string;
|
|
8
|
+
};
|
|
9
|
+
type CustomJSDocFormatTypes = Record<string, string | JSDocFormatDefinition>;
|
|
10
|
+
declare const BUILT_IN_JSDOC_FORMAT_TYPES: Readonly<Record<string, JSDocFormatDefinition>>;
|
|
11
|
+
declare const BUILT_IN_FORMAT_HELPERS: Readonly<Record<string, string>>;
|
|
12
|
+
|
|
13
|
+
interface GenerateCodecDeclarationsOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Canister name used to derive the service export name.
|
|
16
|
+
* Converted to SCREAMING_SNAKE_CASE (e.g. `"my-backend"` → `MY_BACKEND`).
|
|
17
|
+
* Ignored when `serviceExportName` is set explicitly.
|
|
18
|
+
*/
|
|
19
|
+
canisterName?: string;
|
|
20
|
+
/** Explicit service export name. Takes priority over `canisterName`. */
|
|
21
|
+
serviceExportName?: string;
|
|
22
|
+
customJSDocFormatTypes?: CustomJSDocFormatTypes;
|
|
23
|
+
includeCompatibilityExports?: boolean;
|
|
24
|
+
}
|
|
25
|
+
declare function sortDeclarations(declarations: CandidTypeDeclaration[]): CandidTypeDeclaration[];
|
|
26
|
+
declare function renderType(type: CandidType, indent?: string, options?: GenerateCodecDeclarationsOptions): string;
|
|
27
|
+
/**
|
|
28
|
+
* Converts a structured CandidSchema AST into readable `@ic-reactor/cod`
|
|
29
|
+
* codec declarations.
|
|
30
|
+
*/
|
|
31
|
+
declare function generateCodecDeclarations(schema: CandidSchema, optionsOrServiceExportName?: string | GenerateCodecDeclarationsOptions): string;
|
|
32
|
+
|
|
33
|
+
export { BUILT_IN_FORMAT_HELPERS, BUILT_IN_JSDOC_FORMAT_TYPES, type CustomJSDocFormatTypes, type GenerateCodecDeclarationsOptions, type JSDocFormatDefinition, generateCodecDeclarations, renderType, sortDeclarations };
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
};
|
package/llms.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @ic-reactor/codegen — AI Quick Guide
|
|
2
|
+
|
|
3
|
+
Shared generation pipeline used by `@ic-reactor/cli` and
|
|
4
|
+
`@ic-reactor/vite-plugin`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Most apps should install CLI or Vite plugin instead of this package directly.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add -D @ic-reactor/codegen
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Main Use Case
|
|
15
|
+
|
|
16
|
+
Use `runCanisterPipeline(...)` when you need programmatic generation in custom
|
|
17
|
+
tooling/automation.
|
|
18
|
+
|
|
19
|
+
## Generated File Model
|
|
20
|
+
|
|
21
|
+
- `index.generated.ts` is managed output
|
|
22
|
+
- `index.ts` is a stable wrapper
|
|
23
|
+
- app-specific logic should live in wrappers or adjacent modules
|
|
24
|
+
|
|
25
|
+
## Full References
|
|
26
|
+
|
|
27
|
+
- Full AI guide: https://ic-reactor.b3pay.net/llms-full.txt
|
|
28
|
+
- Codegen docs: https://ic-reactor.b3pay.net/v3/packages/codegen
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Shared code generation utilities for IC Reactor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -8,14 +8,32 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./renderer": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/renderer.d.ts",
|
|
23
|
+
"default": "./dist/renderer.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/renderer.d.cts",
|
|
27
|
+
"default": "./dist/renderer.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
15
31
|
},
|
|
16
32
|
"files": [
|
|
17
33
|
"dist",
|
|
18
|
-
"src"
|
|
34
|
+
"src",
|
|
35
|
+
"README.md",
|
|
36
|
+
"llms.txt"
|
|
19
37
|
],
|
|
20
38
|
"keywords": [
|
|
21
39
|
"internet-computer",
|
|
@@ -32,21 +50,22 @@
|
|
|
32
50
|
"url": "git+https://github.com/B3Pay/ic-reactor.git",
|
|
33
51
|
"directory": "packages/codegen"
|
|
34
52
|
},
|
|
53
|
+
"homepage": "https://ic-reactor.b3pay.net/v3/packages/codegen",
|
|
35
54
|
"dependencies": {
|
|
36
55
|
"change-case": "^5.4.4",
|
|
37
|
-
"@ic-reactor/parser": "0.4.
|
|
56
|
+
"@ic-reactor/parser": "0.4.7"
|
|
38
57
|
},
|
|
39
58
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^
|
|
59
|
+
"@types/node": "^26.1.1",
|
|
41
60
|
"tsup": "^8.5.1",
|
|
42
|
-
"typescript": "^
|
|
43
|
-
"vitest": "^4.1.
|
|
61
|
+
"typescript": "^6.0.3",
|
|
62
|
+
"vitest": "^4.1.10"
|
|
44
63
|
},
|
|
45
64
|
"scripts": {
|
|
46
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
|
|
47
|
-
"dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json",
|
|
65
|
+
"build": "tsup src/index.ts src/renderer.ts --format esm,cjs --dts --tsconfig tsconfig.json",
|
|
66
|
+
"dev": "tsup src/index.ts src/renderer.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json",
|
|
48
67
|
"test": "vitest run",
|
|
49
68
|
"test:watch": "vitest",
|
|
50
|
-
"typecheck": "tsc --noEmit"
|
|
69
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
51
70
|
}
|
|
52
71
|
}
|
|
@@ -13,6 +13,9 @@ export type BackendService = _SERVICE
|
|
|
13
13
|
*
|
|
14
14
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
15
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.
|
|
16
19
|
*/
|
|
17
20
|
export const backendReactor = new DisplayReactor<BackendService>({
|
|
18
21
|
clientManager,
|
|
@@ -44,6 +47,9 @@ export type WorkflowEngineService = _SERVICE
|
|
|
44
47
|
*
|
|
45
48
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
46
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.
|
|
47
53
|
*/
|
|
48
54
|
export const workflowEngineReactor = new Reactor<WorkflowEngineService>({
|
|
49
55
|
clientManager,
|
|
@@ -75,6 +81,9 @@ export type LedgerService = _SERVICE
|
|
|
75
81
|
*
|
|
76
82
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
77
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.
|
|
78
87
|
*/
|
|
79
88
|
export const ledgerReactor = new MetadataDisplayReactor<LedgerService>({
|
|
80
89
|
clientManager,
|
|
@@ -105,6 +114,9 @@ export type BackendService = _SERVICE
|
|
|
105
114
|
*
|
|
106
115
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
107
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.
|
|
108
120
|
*/
|
|
109
121
|
export const backendReactor = new DisplayReactor<BackendService>({
|
|
110
122
|
clientManager,
|
package/src/bindgen.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
2
2
|
import fs from "node:fs"
|
|
3
3
|
import path from "node:path"
|
|
4
|
-
import { generateDeclarations } from "./generators"
|
|
4
|
+
import { generateDeclarations } from "./generators/index.js"
|
|
5
5
|
|
|
6
6
|
describe("Bindgen", () => {
|
|
7
7
|
const mockDidFile = "mock/test.did"
|
package/src/generators/client.ts
CHANGED
|
@@ -116,6 +116,9 @@ export type ${serviceName} = _SERVICE
|
|
|
116
116
|
*
|
|
117
117
|
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
118
118
|
* This file is overwritten whenever generation runs.
|
|
119
|
+
*
|
|
120
|
+
* Keep app-specific logic in the stable \`index.ts\` wrapper (or adjacent
|
|
121
|
+
* factory modules). Avoid editing this managed file directly.
|
|
119
122
|
*/
|
|
120
123
|
export const ${reactorName} = new ${reactorClass}<${serviceName}>({
|
|
121
124
|
clientManager,
|
|
@@ -133,6 +136,15 @@ export function generateReactorEntryFile(): string {
|
|
|
133
136
|
*
|
|
134
137
|
* Created once by @ic-reactor/codegen and safe to customize.
|
|
135
138
|
* Keep the re-export below if you want generated exports and types to stay in sync.
|
|
139
|
+
*
|
|
140
|
+
* Recommended customization points:
|
|
141
|
+
* - define reusable query/mutation factories
|
|
142
|
+
* - add app-specific hooks and cache invalidation wiring
|
|
143
|
+
* - compose generated APIs into route loaders/actions
|
|
144
|
+
*
|
|
145
|
+
* Do not edit \`index.generated.ts\`; it is regenerated on each codegen run.
|
|
146
|
+
* AI guide: https://ic-reactor.b3pay.net/llms-full.txt
|
|
147
|
+
* Skill install: npx skills add B3Pay/ic-reactor-skills --full-depth --skill ic-reactor-hooks
|
|
136
148
|
*/
|
|
137
149
|
export * from "./index.generated"
|
|
138
150
|
`
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
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
CHANGED
package/src/pipeline.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs"
|
|
|
2
2
|
import os from "node:os"
|
|
3
3
|
import path from "node:path"
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest"
|
|
5
|
-
import { runCanisterPipeline } from "./pipeline"
|
|
5
|
+
import { runCanisterPipeline } from "./pipeline.js"
|
|
6
6
|
|
|
7
7
|
describe("Codegen pipeline", () => {
|
|
8
8
|
const tempDirs: string[] = []
|
package/src/reactor.test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
generateReactorEntryFile,
|
|
4
|
+
generateReactorFile,
|
|
5
|
+
} from "./generators/index.js"
|
|
3
6
|
|
|
4
7
|
describe("Reactor generator", () => {
|
|
5
8
|
it("keeps default behavior as DisplayReactor", () => {
|