@atcute/lex-cli 1.1.2 → 2.0.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/cli.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import './dist/index.js';
2
+ import './dist/cli.js';
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,77 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { Builtins, Command, Option, Program } from '@externdefs/collider';
4
+ import pc from 'picocolors';
5
+ import { generateLexiconApi } from './codegen.js';
6
+ import { lexiconDoc } from './schema.js';
7
+ const program = new Program({ binaryName: 'lex-cli' });
8
+ program.register(Builtins.HelpCommand);
9
+ program.register(class GenerateCommand extends Command {
10
+ static paths = [['generate']];
11
+ static usage = Command.Usage({
12
+ description: `Generates TypeScript schema`,
13
+ });
14
+ config = Option.String(['-c', '--config'], {
15
+ required: true,
16
+ description: `Config file`,
17
+ });
18
+ async execute() {
19
+ const configFilename = path.resolve(this.config);
20
+ const configDirname = path.dirname(configFilename);
21
+ let config;
22
+ try {
23
+ const mod = (await import(path.resolve(configFilename)));
24
+ config = mod.default;
25
+ }
26
+ catch (err) {
27
+ console.error(pc.bold(pc.red(`failed to import config:`)));
28
+ console.error(err);
29
+ return 1;
30
+ }
31
+ const documents = [];
32
+ for await (const filename of fs.glob(config.files, { cwd: configDirname })) {
33
+ let source;
34
+ try {
35
+ source = await fs.readFile(path.join(configDirname, filename), 'utf8');
36
+ }
37
+ catch (err) {
38
+ console.error(pc.bold(pc.red(`file read error with "${filename}"`)));
39
+ console.error(err);
40
+ return 1;
41
+ }
42
+ let json;
43
+ try {
44
+ json = JSON.parse(source);
45
+ }
46
+ catch (err) {
47
+ console.error(pc.bold(pc.red(`json parse error in "${filename}"`)));
48
+ console.error(err);
49
+ return 1;
50
+ }
51
+ const result = lexiconDoc.try(json, { mode: 'strip' });
52
+ if (!result.ok) {
53
+ console.error(pc.bold(pc.red(`schema validation failed for "${filename}"`)));
54
+ for (const issue of result.issues) {
55
+ console.log(`- ${issue.code} at .${issue.path.join('.')}`);
56
+ }
57
+ return 1;
58
+ }
59
+ documents.push(result.value);
60
+ }
61
+ const result = await generateLexiconApi({
62
+ documents: documents,
63
+ mappings: config.mappings ?? [],
64
+ prettier: {},
65
+ });
66
+ const outdir = path.join(configDirname, config.outdir);
67
+ for (const file of result.files) {
68
+ const filename = path.join(outdir, file.filename);
69
+ const dirname = path.dirname(filename);
70
+ await fs.mkdir(dirname, { recursive: true });
71
+ await fs.writeFile(filename, file.code);
72
+ }
73
+ }
74
+ });
75
+ const exitCode = await program.run(process.argv.slice(2));
76
+ process.exitCode = exitCode;
77
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { LexiconDoc } from './schema.js';
2
+ export interface SourceFile {
3
+ filename: string;
4
+ code: string;
5
+ }
6
+ export interface ImportMapping {
7
+ nsid: string[];
8
+ imports: string | ((nsid: string) => {
9
+ type: 'named' | 'namespace';
10
+ from: string;
11
+ });
12
+ }
13
+ export interface LexiconApiOptions {
14
+ documents: LexiconDoc[];
15
+ mappings: ImportMapping[];
16
+ prettier?: {
17
+ cwd?: string;
18
+ };
19
+ }
20
+ export interface LexiconApiResult {
21
+ files: SourceFile[];
22
+ }
23
+ export declare const generateLexiconApi: (opts: LexiconApiOptions) => Promise<LexiconApiResult>;
@@ -0,0 +1,552 @@
1
+ import { dirname as getDirname, relative as getRelativePath } from 'node:path/posix';
2
+ import * as prettier from 'prettier';
3
+ const lit = JSON.stringify;
4
+ const resolveExternalImport = (nsid, mappings) => {
5
+ return mappings.find((mapping) => {
6
+ return mapping.nsid.some((pattern) => {
7
+ if (pattern.endsWith('.*')) {
8
+ return nsid.startsWith(pattern.slice(0, -1));
9
+ }
10
+ return nsid === pattern;
11
+ });
12
+ });
13
+ };
14
+ const PURE = `/*#__PURE__*/`;
15
+ export const generateLexiconApi = async (opts) => {
16
+ const documents = opts.documents.toSorted((a, b) => {
17
+ if (a.id < b.id) {
18
+ return -1;
19
+ }
20
+ if (a.id > b.id) {
21
+ return 1;
22
+ }
23
+ return 0;
24
+ });
25
+ const map = new Map(documents.map((doc) => [doc.id, doc]));
26
+ const files = [];
27
+ for (const doc of documents) {
28
+ const filename = `types/${doc.id.replaceAll('.', '/')}.ts`;
29
+ const file = {
30
+ imports: '',
31
+ rawschemas: '',
32
+ schemadefs: '',
33
+ schemas: '',
34
+ interfaces: '',
35
+ exports: '',
36
+ ambients: '',
37
+ };
38
+ file.imports += `import type {} from '@atcute/lexicons';\n`;
39
+ file.imports += `import * as v from '@atcute/lexicons/validations';\n`;
40
+ const imports = new Set();
41
+ const sortedDefIds = Object.keys(doc.defs).toSorted((a, b) => {
42
+ if (a < b) {
43
+ return -1;
44
+ }
45
+ if (a > b) {
46
+ return 1;
47
+ }
48
+ return 0;
49
+ });
50
+ for (const defId of sortedDefIds) {
51
+ const def = doc.defs[defId];
52
+ const defUri = `${doc.id}#${defId}`;
53
+ const camelcased = toCamelCase(defId);
54
+ const varname = `${camelcased}Schema`;
55
+ let result;
56
+ switch (def.type) {
57
+ case 'query': {
58
+ result = generateXrpcQuery(imports, defUri, def);
59
+ file.imports += `import type {} from '@atcute/lexicons/ambient';\n`;
60
+ file.ambients += `declare module '@atcute/lexicons/ambient' {\n`;
61
+ file.ambients += ` interface XRPCQueries {\n`;
62
+ file.ambients += ` ${lit(stripMainHash(defUri))}: ${camelcased}Schema;\n`;
63
+ file.ambients += ` }\n`;
64
+ file.ambients += `}`;
65
+ break;
66
+ }
67
+ case 'procedure': {
68
+ result = generateXrpcProcedure(imports, defUri, def);
69
+ file.imports += `import type {} from '@atcute/lexicons/ambient';\n`;
70
+ file.ambients += `declare module '@atcute/lexicons/ambient' {\n`;
71
+ file.ambients += ` interface XRPCProcedures {\n`;
72
+ file.ambients += ` ${lit(stripMainHash(defUri))}: ${camelcased}Schema;\n`;
73
+ file.ambients += ` }\n`;
74
+ file.ambients += `}`;
75
+ break;
76
+ }
77
+ case 'subscription': {
78
+ result = generateXrpcSubscription(imports, defUri, def);
79
+ file.imports += `import type {} from '@atcute/lexicons/ambient';\n`;
80
+ file.ambients += `declare module '@atcute/lexicons/ambient' {\n`;
81
+ file.ambients += ` interface XRPCSubscriptions {\n`;
82
+ file.ambients += ` ${lit(stripMainHash(defUri))}: ${camelcased}Schema;\n`;
83
+ file.ambients += ` }\n`;
84
+ file.ambients += `}`;
85
+ break;
86
+ }
87
+ case 'object': {
88
+ result = generateObject(imports, defUri, def);
89
+ break;
90
+ }
91
+ case 'record': {
92
+ result = generateRecord(imports, defUri, def);
93
+ file.imports += `import type {} from '@atcute/lexicons/ambient';\n`;
94
+ file.ambients += `declare module '@atcute/lexicons/ambient' {\n`;
95
+ file.ambients += ` interface Records {\n`;
96
+ file.ambients += ` ${lit(stripMainHash(defUri))}: ${camelcased}Schema;\n`;
97
+ file.ambients += ` }\n`;
98
+ file.ambients += `}`;
99
+ break;
100
+ }
101
+ case 'token': {
102
+ result = `${PURE} v.literal(${lit(stripMainHash(defUri))})`;
103
+ break;
104
+ }
105
+ default: {
106
+ result = generateType(imports, defUri, def);
107
+ break;
108
+ }
109
+ }
110
+ file.rawschemas += `const _${varname} = ${result};\n`;
111
+ file.schemadefs += `type ${camelcased}$schematype = typeof _${varname};\n`;
112
+ file.schemas += `export interface ${camelcased}Schema extends ${camelcased}$schematype {}\n`;
113
+ file.exports += `export const ${varname} = _${varname} as ${camelcased}Schema;\n`;
114
+ switch (def.type) {
115
+ case 'array':
116
+ case 'object':
117
+ case 'record':
118
+ case 'unknown': {
119
+ file.interfaces += `export interface ${toTitleCase(defId)} extends v.InferInput<typeof ${varname}> {}\n`;
120
+ break;
121
+ }
122
+ case 'blob':
123
+ case 'boolean':
124
+ case 'bytes':
125
+ case 'cid-link':
126
+ case 'integer':
127
+ case 'string':
128
+ case 'token': {
129
+ file.interfaces += `export type ${toTitleCase(defId)} = v.InferInput<typeof ${varname}>;\n`;
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ {
135
+ const dirname = getDirname(filename);
136
+ const sortedImports = [...imports].toSorted((a, b) => {
137
+ if (a < b) {
138
+ return -1;
139
+ }
140
+ if (a > b) {
141
+ return 1;
142
+ }
143
+ return 0;
144
+ });
145
+ for (const ns of sortedImports) {
146
+ const local = map.get(ns);
147
+ if (local) {
148
+ const target = `types/${ns.replaceAll('.', '/')}.js`;
149
+ let relative = getRelativePath(dirname, target);
150
+ if (!relative.startsWith('.')) {
151
+ relative = `./${relative}`;
152
+ }
153
+ file.imports += `import * as ${toTitleCase(ns)} from ${lit(relative)};\n`;
154
+ continue;
155
+ }
156
+ const external = resolveExternalImport(ns, opts.mappings);
157
+ if (external) {
158
+ if (typeof external.imports === 'function') {
159
+ const res = external.imports(ns);
160
+ if (res.type === 'named') {
161
+ file.imports += `import { ${toTitleCase(ns)} } from ${lit(res.from)};\n`;
162
+ }
163
+ else if (res.type === 'namespace') {
164
+ file.imports += `import * as ${toTitleCase(ns)} from ${lit(res.from)};\n`;
165
+ }
166
+ }
167
+ else {
168
+ file.imports += `import { ${toTitleCase(ns)} } from ${lit(external.imports)};\n`;
169
+ }
170
+ continue;
171
+ }
172
+ throw new Error(`'${doc.id}' referenced non-existent '${ns}' namespace`);
173
+ }
174
+ }
175
+ files.push({
176
+ filename: filename,
177
+ code: file.imports +
178
+ `\n\n` +
179
+ file.rawschemas +
180
+ `\n\n` +
181
+ file.schemadefs +
182
+ `\n\n` +
183
+ file.schemas +
184
+ `\n\n` +
185
+ file.exports +
186
+ `\n\n` +
187
+ file.interfaces +
188
+ `\n\n` +
189
+ file.ambients,
190
+ });
191
+ }
192
+ {
193
+ let code = ``;
194
+ for (const doc of map.values()) {
195
+ code += `export * as ${toTitleCase(doc.id)} from ${lit(`./types/${doc.id.replaceAll('.', '/')}.js`)};\n`;
196
+ }
197
+ files.push({
198
+ filename: 'index.ts',
199
+ code: code,
200
+ });
201
+ }
202
+ if (opts.prettier) {
203
+ const config = await prettier.resolveConfig(opts.prettier.cwd ?? process.cwd(), { editorconfig: true });
204
+ for (const file of files) {
205
+ const formatted = await prettier.format(file.code, { ...config, parser: 'typescript' });
206
+ file.code = formatted;
207
+ }
208
+ }
209
+ return { files };
210
+ };
211
+ const generateXrpcQuery = (imports, defUri, spec) => {
212
+ const params = generateXrpcParameters(imports, defUri, spec.parameters);
213
+ const output = generateXrpcBody(imports, defUri, spec.output);
214
+ return `${PURE} v.query(${lit(stripMainHash(defUri))}, {\n"params": ${params}, "output": ${output} })`;
215
+ };
216
+ const generateXrpcProcedure = (imports, defUri, spec) => {
217
+ const params = generateXrpcParameters(imports, defUri, spec.parameters);
218
+ const input = generateXrpcBody(imports, defUri, spec.input);
219
+ const output = generateXrpcBody(imports, defUri, spec.output);
220
+ return `${PURE} v.procedure(${lit(stripMainHash(defUri))}, {\n"params": ${params}, "input": ${input}, "output": ${output} })`;
221
+ };
222
+ const generateXrpcSubscription = (imports, defUri, spec) => {
223
+ const schema = spec.message?.schema;
224
+ const params = generateXrpcParameters(imports, defUri, spec.parameters);
225
+ let inner = ``;
226
+ inner += `"params": ${params},`;
227
+ if (schema) {
228
+ if (schema.type === 'object') {
229
+ const res = generateObject(imports, defUri, schema, 'none');
230
+ inner += `"message": ${res},`;
231
+ }
232
+ else {
233
+ const res = generateType(imports, defUri, schema);
234
+ inner += `get "message" () { return ${res} },`;
235
+ }
236
+ }
237
+ else {
238
+ inner += `"message": null,`;
239
+ }
240
+ return `${PURE} v.subscription(${lit(stripMainHash(defUri))}, {\n${inner}})`;
241
+ };
242
+ const generateXrpcBody = (imports, defUri, spec) => {
243
+ if (spec === undefined) {
244
+ return `null`;
245
+ }
246
+ const schema = spec.schema;
247
+ const encoding = spec.encoding;
248
+ if (schema) {
249
+ let inner = ``;
250
+ inner += `"type": "lex",`;
251
+ if (schema.type === 'object') {
252
+ const res = generateObject(imports, defUri, schema, 'none');
253
+ inner += `"schema": ${res},`;
254
+ }
255
+ else {
256
+ const res = generateType(imports, defUri, schema);
257
+ inner += `get "schema" () { return ${res} },`;
258
+ }
259
+ return `{\n${inner}}`;
260
+ }
261
+ if (encoding) {
262
+ return `{\n"type": "blob" }`;
263
+ }
264
+ return `null`;
265
+ };
266
+ const generateXrpcParameters = (imports, defUri, spec) => {
267
+ if (spec === undefined) {
268
+ return `null`;
269
+ }
270
+ const mask = {
271
+ type: 'object',
272
+ description: spec.description,
273
+ required: spec.required,
274
+ properties: spec.properties,
275
+ };
276
+ return generateObject(imports, defUri, mask, 'none');
277
+ };
278
+ const generateRecord = (imports, defUri, spec) => {
279
+ const schema = generateObject(imports, defUri, spec.record, 'required');
280
+ let key = `${PURE} v.string()`;
281
+ if (spec.key) {
282
+ if (spec.key === 'tid') {
283
+ key = `${PURE} v.tidString()`;
284
+ }
285
+ else if (spec.key === 'nsid') {
286
+ key = `${PURE} v.nsidString()`;
287
+ }
288
+ else if (spec.key.startsWith('literal:')) {
289
+ key = `${PURE} v.literal(${lit(spec.key.slice('literal:'.length))})`;
290
+ }
291
+ }
292
+ return `${PURE} v.record(${key}, ${schema})`;
293
+ };
294
+ const generateObject = (imports, defUri, spec, writeType = 'optional') => {
295
+ const required = new Set(spec.required);
296
+ const nullable = new Set(spec.nullable);
297
+ let inner = ``;
298
+ switch (writeType) {
299
+ case 'optional': {
300
+ inner += `"$type": ${PURE} v.optional(${PURE} v.literal(${lit(stripMainHash(defUri))})),`;
301
+ break;
302
+ }
303
+ case 'required': {
304
+ inner += `"$type": ${PURE} v.literal(${lit(stripMainHash(defUri))}),`;
305
+ break;
306
+ }
307
+ }
308
+ for (const [prop, propSpec] of Object.entries(spec.properties ?? {})) {
309
+ const lazy = isRefVariant(propSpec.type === 'array' ? propSpec.items : propSpec);
310
+ const optional = !required.has(prop) && !('default' in propSpec);
311
+ const nulled = nullable.has(prop);
312
+ let call = generateType(imports, defUri, propSpec, lazy);
313
+ if (nulled) {
314
+ call = `${PURE} v.nullable(${call})`;
315
+ }
316
+ if (optional) {
317
+ call = `${PURE} v.optional(${call})`;
318
+ }
319
+ if (lazy) {
320
+ inner += `get ${lit(prop)} () { return ${call} },`;
321
+ }
322
+ else {
323
+ inner += `${lit(prop)}: ${call},`;
324
+ }
325
+ }
326
+ return `${PURE} v.object({\n${inner}})`;
327
+ };
328
+ const generateType = (imports, defUri, spec, lazy = false) => {
329
+ switch (spec.type) {
330
+ // LexRefVariant
331
+ case 'ref': {
332
+ const ref = spec.ref;
333
+ if (ref.startsWith('#')) {
334
+ const id = ref.slice(1);
335
+ return `${toCamelCase(id)}Schema`;
336
+ }
337
+ else {
338
+ const [ns, id = 'main'] = ref.split('#');
339
+ if (ns === stripHash(defUri)) {
340
+ return `${toCamelCase(id)}Schema`;
341
+ }
342
+ imports.add(ns);
343
+ return `${toTitleCase(ns)}.${toCamelCase(id)}Schema`;
344
+ }
345
+ }
346
+ case 'union': {
347
+ const refs = spec.refs.map((ref) => {
348
+ if (ref.startsWith('#')) {
349
+ const id = ref.slice(1);
350
+ return `${toCamelCase(id)}Schema`;
351
+ }
352
+ else {
353
+ const [ns, id = 'main'] = ref.split('#');
354
+ if (ns === stripHash(defUri)) {
355
+ return `${toCamelCase(id)}Schema`;
356
+ }
357
+ imports.add(ns);
358
+ return `${toTitleCase(ns)}.${toCamelCase(id)}Schema`;
359
+ }
360
+ });
361
+ return `${PURE} v.variant([${refs.join(', ')}]${spec.closed ? `, true` : ``})`;
362
+ }
363
+ // LexArray
364
+ case 'array': {
365
+ let item = generateType(imports, defUri, spec.items);
366
+ if (!lazy && (spec.items.type === 'ref' || spec.items.type === 'union')) {
367
+ item = `(() => { return ${item}; })`;
368
+ }
369
+ let pipe = [];
370
+ if ((spec.minLength ?? 0) > 0 || spec.maxLength !== undefined) {
371
+ if (spec.maxLength === undefined) {
372
+ pipe.push(`${PURE} v.arrayLength(${lit(spec.minLength ?? 0)})`);
373
+ }
374
+ else {
375
+ pipe.push(`${PURE} v.arrayLength(${lit(spec.minLength ?? 0)}, ${lit(spec.maxLength)})`);
376
+ }
377
+ }
378
+ if (pipe.length === 0) {
379
+ return `${PURE} v.array(${item})`;
380
+ }
381
+ else {
382
+ return `${PURE} v.constrain(v.array(${item}), [ ${pipe.join(', ')} ])`;
383
+ }
384
+ }
385
+ // LexPrimitive
386
+ case 'boolean': {
387
+ if (spec.const !== undefined) {
388
+ return `${PURE} v.literal(${spec.const})`;
389
+ }
390
+ let call = `${PURE} v.boolean()`;
391
+ if (spec.default !== undefined) {
392
+ call = `${PURE} v.optional(${call}, ${lit(spec.default)})`;
393
+ }
394
+ return call;
395
+ }
396
+ case 'integer': {
397
+ if (spec.const !== undefined) {
398
+ return `${PURE} v.literal(${lit(spec.const)})`;
399
+ }
400
+ if (spec.enum !== undefined) {
401
+ return `${PURE} v.literalEnum(${lit(spec.enum)})`;
402
+ }
403
+ let pipe = [];
404
+ if ((spec.minimum ?? 0) > 0 || spec.maximum !== undefined) {
405
+ if (spec.maximum === undefined) {
406
+ pipe.push(`${PURE} v.integerRange(${lit(spec.minimum ?? 0)})`);
407
+ }
408
+ else {
409
+ pipe.push(`${PURE} v.integerRange(${lit(spec.minimum ?? 0)}, ${lit(spec.maximum)})`);
410
+ }
411
+ }
412
+ let call = `${PURE} v.integer()`;
413
+ if (pipe.length !== 0) {
414
+ call = `${PURE} v.constrain(${call}, [ ${pipe.join(', ')} ])`;
415
+ }
416
+ if (spec.default !== undefined) {
417
+ call = `${PURE} v.optional(${call}, ${lit(spec.default)})`;
418
+ }
419
+ return call;
420
+ }
421
+ case 'string': {
422
+ if (spec.const !== undefined) {
423
+ return `${PURE} v.literal(${lit(spec.const)})`;
424
+ }
425
+ if (spec.enum !== undefined) {
426
+ return `${PURE} v.literalEnum(${lit(spec.enum)})`;
427
+ }
428
+ let pipe = [];
429
+ if ((spec.minLength ?? 0) > 0 || spec.maxLength !== undefined) {
430
+ if (spec.maxLength === undefined) {
431
+ pipe.push(`${PURE} v.stringLength(${lit(spec.minLength ?? 0)})`);
432
+ }
433
+ else {
434
+ pipe.push(`${PURE} v.stringLength(${lit(spec.minLength ?? 0)}, ${lit(spec.maxLength)})`);
435
+ }
436
+ }
437
+ if ((spec.minGraphemes ?? 0) > 0 || spec.maxGraphemes !== undefined) {
438
+ if (spec.maxGraphemes === undefined) {
439
+ pipe.push(`${PURE} v.stringGraphemes(${lit(spec.minGraphemes ?? 0)})`);
440
+ }
441
+ else {
442
+ pipe.push(`${PURE} v.stringGraphemes(${lit(spec.minGraphemes ?? 0)}, ${lit(spec.maxGraphemes)})`);
443
+ }
444
+ }
445
+ let call = `${PURE} v.string()`;
446
+ if (spec.knownValues?.length) {
447
+ call = `${PURE} v.string<${spec.knownValues.map(lit).join(' | ')} | (string & {})>()`;
448
+ }
449
+ switch (spec.format) {
450
+ case 'at-identifier': {
451
+ call = `${PURE} v.actorIdentifierString()`;
452
+ break;
453
+ }
454
+ case 'at-uri': {
455
+ call = `${PURE} v.resourceUriString()`;
456
+ break;
457
+ }
458
+ case 'datetime': {
459
+ call = `${PURE} v.datetimeString()`;
460
+ break;
461
+ }
462
+ case 'did': {
463
+ call = `${PURE} v.didString()`;
464
+ break;
465
+ }
466
+ case 'handle': {
467
+ call = `${PURE} v.handleString()`;
468
+ break;
469
+ }
470
+ case 'language': {
471
+ call = `${PURE} v.languageCodeString()`;
472
+ break;
473
+ }
474
+ case 'nsid': {
475
+ call = `${PURE} v.nsidString()`;
476
+ break;
477
+ }
478
+ case 'record-key': {
479
+ call = `${PURE} v.recordKeyString()`;
480
+ break;
481
+ }
482
+ case 'tid': {
483
+ call = `${PURE} v.tidString()`;
484
+ break;
485
+ }
486
+ case 'uri': {
487
+ call = `${PURE} v.genericUriString()`;
488
+ break;
489
+ }
490
+ }
491
+ if (pipe.length !== 0) {
492
+ call = `${PURE} v.constrain(${call}, [ ${pipe.join(', ')} ])`;
493
+ }
494
+ if (spec.default !== undefined) {
495
+ call = `${PURE} v.optional(${call}, ${lit(spec.default)})`;
496
+ }
497
+ return call;
498
+ }
499
+ case 'unknown': {
500
+ return `${PURE} v.unknown()`;
501
+ }
502
+ // LexBlob
503
+ case 'blob': {
504
+ return `${PURE} v.blob()`;
505
+ }
506
+ // LexIpldType
507
+ case 'bytes': {
508
+ let pipe = [];
509
+ if ((spec.minLength ?? 0) > 0 || spec.maxLength !== undefined) {
510
+ if (spec.maxLength === undefined) {
511
+ pipe.push(`${PURE} v.bytesSize(${lit(spec.minLength ?? 0)})`);
512
+ }
513
+ else {
514
+ pipe.push(`${PURE} v.bytesSize(${lit(spec.minLength ?? 0)}, ${lit(spec.maxLength)})`);
515
+ }
516
+ }
517
+ let call = `${PURE} v.bytes()`;
518
+ if (pipe.length !== 0) {
519
+ call = `${PURE} v.constrain(${call}, [ ${pipe.join(', ')} ])`;
520
+ }
521
+ return call;
522
+ }
523
+ case 'cid-link': {
524
+ return `${PURE} v.cidLink()`;
525
+ }
526
+ }
527
+ };
528
+ const isRefVariant = (spec) => {
529
+ const type = spec.type;
530
+ return type === 'ref' || type === 'union';
531
+ };
532
+ const stripHash = (defUri) => {
533
+ const index = defUri.indexOf('#');
534
+ if (index === -1) {
535
+ return defUri;
536
+ }
537
+ return defUri.slice(0, index);
538
+ };
539
+ const stripMainHash = (defUri) => {
540
+ return defUri.endsWith('#main') ? defUri.slice(0, -'#main'.length) : defUri;
541
+ };
542
+ const toTitleCase = (v) => {
543
+ v = v.replace(/^([a-z])/gi, (_, g) => g.toUpperCase());
544
+ v = v.replace(/[.#-]([a-z])/gi, (_, g) => g.toUpperCase());
545
+ return v.replace(/[.-]/g, '');
546
+ };
547
+ const toCamelCase = (v) => {
548
+ v = v.replace(/^([A-Z])/gi, (_, g) => g.toLowerCase());
549
+ v = v.replace(/[.#-]([a-z])/gi, (_, g) => g.toUpperCase());
550
+ return v.replace(/[.-]/g, '');
551
+ };
552
+ //# sourceMappingURL=codegen.js.map