@nemmtor/ts-databuilders 0.0.1-alpha.8 → 0.0.1-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/dist/main.js +6 -3
- package/package.json +25 -12
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ You can also generate configuration file by providing values step by step in an
|
|
|
27
27
|
pnpm ts-databuilders init --wizard
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
### Configure via CLI flags (optional:
|
|
30
|
+
### Configure via CLI flags (optional):
|
|
31
31
|
```bash
|
|
32
32
|
pnpm ts-databuilders --output-dir="src/__generated__" --jsdoc-tag=MyBuilder
|
|
33
33
|
```
|
|
@@ -49,6 +49,9 @@ pnpm ts-databuilders --wizard
|
|
|
49
49
|
|
|
50
50
|
**Priority:** CLI flags > Config file > Built-in defaults
|
|
51
51
|
|
|
52
|
+
#### Debugging
|
|
53
|
+
In order to turn on debug logs pass a flag: `--log-level debug`.
|
|
54
|
+
|
|
52
55
|
## Quick Start
|
|
53
56
|
**1. Annotate your types with JSDoc:**
|
|
54
57
|
```ts
|
|
@@ -133,7 +136,7 @@ Where in reality the only thing specific to this single test is the fact that so
|
|
|
133
136
|
Imagine even more complex scenario:
|
|
134
137
|
```tsx
|
|
135
138
|
it('should show validation error when email is invalid', async () => {
|
|
136
|
-
render(<ProfileForm defaultValues={
|
|
139
|
+
render(<ProfileForm defaultValues={{
|
|
137
140
|
firstName: '',
|
|
138
141
|
lastName: '',
|
|
139
142
|
age: 0,
|
|
@@ -152,7 +155,7 @@ it('should show validation error when email is invalid', async () => {
|
|
|
152
155
|
skills: [],
|
|
153
156
|
bio: '',
|
|
154
157
|
email: 'invalid-email'
|
|
155
|
-
}
|
|
158
|
+
}}
|
|
156
159
|
/>)
|
|
157
160
|
|
|
158
161
|
await submitForm();
|
|
@@ -165,9 +168,13 @@ Again - in reality you should only be worried about email, not about whole form
|
|
|
165
168
|
Here's how above tests could be written with databuilders:
|
|
166
169
|
```ts
|
|
167
170
|
it('should emit a ContentUpdatedEvent', () => {
|
|
168
|
-
const aggregate = DocumentAggregate.create(
|
|
171
|
+
const aggregate = DocumentAggregate.create(
|
|
172
|
+
new CreateDocumentAggregatedPayloadBuilder().build()
|
|
173
|
+
);
|
|
169
174
|
|
|
170
|
-
aggregate.updateContent(
|
|
175
|
+
aggregate.updateContent(
|
|
176
|
+
new UpdateDocumentContentPayloadBuilder().withContent('new-content').build()
|
|
177
|
+
);
|
|
171
178
|
|
|
172
179
|
expect(...);
|
|
173
180
|
})
|
|
@@ -175,7 +182,9 @@ it('should emit a ContentUpdatedEvent', () => {
|
|
|
175
182
|
|
|
176
183
|
```tsx
|
|
177
184
|
it('should show validation error when email is invalid', async () => {
|
|
178
|
-
render(<ProfileForm defaultValues={
|
|
185
|
+
render(<ProfileForm defaultValues={
|
|
186
|
+
new ProfileFormInputBuilder.withEmail('invalid-email').build()} />
|
|
187
|
+
)
|
|
179
188
|
|
|
180
189
|
await submitForm();
|
|
181
190
|
|
|
@@ -185,6 +194,8 @@ it('should show validation error when email is invalid', async () => {
|
|
|
185
194
|
|
|
186
195
|
This not only makes the test code less verbose but also highlights what is really being tested.
|
|
187
196
|
|
|
197
|
+
**Why not use AI for that?** While AI can generate test data, ts-databuilders is fast, free and deterministic.
|
|
198
|
+
|
|
188
199
|
[Read more about data builders.](http://www.natpryce.com/articles/000714.html)
|
|
189
200
|
|
|
190
201
|
## Nested Builders
|
|
@@ -333,7 +344,7 @@ export type User = { name: string };
|
|
|
333
344
|
```
|
|
334
345
|
|
|
335
346
|
### Type Aliases Only
|
|
336
|
-
Currently, only **type aliases** are supported as root builder types. Interfaces, classes
|
|
347
|
+
Currently, only **type aliases** are supported as root builder types. Interfaces, classes etc. are not supported:
|
|
337
348
|
```ts
|
|
338
349
|
// ❌ Not supported
|
|
339
350
|
/** @DataBuilder */
|
|
@@ -354,7 +365,7 @@ export type User = {
|
|
|
354
365
|
};
|
|
355
366
|
```
|
|
356
367
|
|
|
357
|
-
### Unsupported
|
|
368
|
+
### Unsupported TypeScript Features
|
|
358
369
|
|
|
359
370
|
Some TypeScript features are not yet supported and will cause generation errors:
|
|
360
371
|
|
|
@@ -385,12 +396,17 @@ Some TypeScript features are not yet supported and will cause generation errors:
|
|
|
385
396
|
- Test thoroughly before using in production
|
|
386
397
|
|
|
387
398
|
**Found an issue?** Please [report it on GitHub](https://github.com/nemmtor/ts-databuilders/issues) with:
|
|
399
|
+
- A minimal reproducible example (if possible)
|
|
388
400
|
- The type definition causing the issue
|
|
389
401
|
- The error message received
|
|
390
402
|
- Your `ts-databuilders.json` config and any provided CLI flags (if applicable)
|
|
391
403
|
|
|
404
|
+
You can also turn on debug logs by passing `--log-level debug` flag.
|
|
405
|
+
|
|
392
406
|
Your feedback helps improve the library for everyone! 🙏
|
|
393
407
|
|
|
408
|
+
## Similar Projects
|
|
409
|
+
- [effect-builder](https://github.com/slashlifeai/effect-builder) - a runtime library for building objects with Effect Schema validation.
|
|
394
410
|
|
|
395
411
|
## Contributing
|
|
396
412
|
|
package/dist/main.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import*as
|
|
2
|
+
import*as at from"@effect/platform-node/NodeContext";import*as st from"@effect/platform-node/NodeRuntime";import*as ct from"effect/Effect";import*as dt from"effect/Layer";import*as lt from"effect/Logger";import*as ft from"effect/LogLevel";import*as L from"@effect/cli/Command";import*as u from"@effect/cli/Options";import*as X from"effect/Layer";import{Project as Nt}from"ts-morph";import*as De from"@effect/platform/FileSystem";import*as Oe from"@effect/platform/Path";import*as g from"effect/Effect";import*as b from"effect/Match";import*as Ge from"effect/Option";import*as oe from"effect/Schema";import*as he from"@effect/platform/FileSystem";import*as Be from"@effect/platform/Path";import*as Re from"effect/Context";import*as D from"effect/Effect";import*as x from"effect/Option";import*as r from"effect/Schema";var O={jsdocTag:"JSDoc tag used to mark types for data building generation.",outputDir:"Output directory for generated builders.",include:"Glob pattern for files included while searching for jsdoc tag.",fileSuffix:"File suffix for created builder files.",builderSuffix:"Suffix for generated classes.",defaults:"Default values to be used in data builder constructor.",defaultString:"Default string value to be used in data builder constructor.",defaultNumber:"Default number value to be used in data builder constructor.",defaultBoolean:"Default boolean value to be used in data builder constructor."};import*as le from"effect/Effect";var F=class extends le.Service()("@TSDataBuilders/Process",{succeed:{cwd:le.sync(()=>process.cwd())}}){};var mt=e=>e!==void 0,ie=e=>Object.fromEntries(Object.entries(e).filter(([i,s])=>mt(s)));var Ee="ts-databuilders.json",gt=r.Struct({jsdocTag:r.NonEmptyTrimmedString,outputDir:r.NonEmptyTrimmedString,include:r.NonEmptyTrimmedString,fileSuffix:r.NonEmptyTrimmedString,builderSuffix:r.NonEmptyTrimmedString,defaults:r.Struct({string:r.String,number:r.Number,boolean:r.Boolean})}),_=gt.make({jsdocTag:"DataBuilder",outputDir:"generated/builders",include:"src/**/*.ts{,x}",fileSuffix:".builder",builderSuffix:"Builder",defaults:{string:"",number:0,boolean:!1}}),Ce=r.Struct({$schema:r.optional(r.String),jsdocTag:r.String.pipe(r.annotations({description:O.jsdocTag})),outputDir:r.String.pipe(r.annotations({description:O.outputDir})),include:r.String.pipe(r.annotations({description:O.include})),fileSuffix:r.String.pipe(r.annotations({description:O.fileSuffix})),builderSuffix:r.String.pipe(r.annotations({description:O.builderSuffix})),defaults:r.Struct({string:r.String.pipe(r.annotations({description:O.defaultString})),number:r.Number.pipe(r.annotations({description:O.defaultNumber})),boolean:r.Boolean.pipe(r.annotations({description:O.defaultBoolean}))}).pipe(r.partial,r.annotations({description:O.defaults}))}).pipe(r.partial),M=class extends Re.Tag("Configuration")(){},K=r.Struct({jsdocTag:r.NonEmptyTrimmedString,outputDir:r.NonEmptyTrimmedString,include:r.NonEmptyTrimmedString,fileSuffix:r.NonEmptyTrimmedString,builderSuffix:r.NonEmptyTrimmedString,defaultString:r.String,defaultNumber:r.NumberFromString,defaultBoolean:r.BooleanFromString}),Le=e=>D.gen(function*(){yield*D.logDebug("[Configuration]: Loading configuration");let s=yield*(yield*F).cwd,n=(yield*Be.Path).join(s,Ee),o=yield*Tt(n),p=yield*yt({fromCLI:e,fromConfigFile:o});return M.of(p)}),yt=e=>D.gen(function*(){let i=St(e),s=x.flatMap(e.fromConfigFile,p=>x.fromNullable(p.defaults)).pipe(x.map(p=>ie(p)),x.getOrElse(()=>({}))),l=ie({string:e.fromCLI.defaultString.pipe(x.getOrUndefined),number:e.fromCLI.defaultNumber.pipe(x.getOrUndefined),boolean:e.fromCLI.defaultBoolean.pipe(x.getOrUndefined)}),n={...s,...l},o={builderSuffix:yield*i("builderSuffix"),include:yield*i("include"),fileSuffix:yield*i("fileSuffix"),jsdocTag:yield*i("jsdocTag"),outputDir:yield*i("outputDir"),defaults:{..._.defaults,...n}};return yield*D.logDebug(`[Configuration]: Resolving config with value: ${JSON.stringify(o,null,4)}`),o}),St=e=>i=>e.fromCLI[i].pipe(D.orElse(()=>x.flatMap(e.fromConfigFile,s=>x.fromNullable(s[i]))),D.orElseSucceed(()=>_[i])),Tt=e=>D.gen(function*(){let i=yield*he.FileSystem;if(yield*D.orDie(i.exists(e))){yield*D.logDebug("[Configuration]: Found config file - attempting to read it");let l=yield*ht(e),n=yield*r.decodeUnknown(Ce)(l);return x.some(n)}else return yield*D.logDebug("[Configuration]: No config file found"),x.none()}),ht=e=>D.gen(function*(){let i=yield*he.FileSystem,s=yield*D.orDie(i.readFileString(e));return yield*D.try({try:()=>JSON.parse(s),catch:l=>`[FileSystem] Unable to read and parse JSON file from '${e}': ${String(l)}`})}).pipe(D.orDie);import*as P from"effect/Schema";var Ne=P.transform(P.String,P.String.pipe(P.brand("KebabCase")),{decode:e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").replace(/_/g,"-").toLowerCase(),encode:e=>e}),$e=P.transform(P.String,P.String.pipe(P.brand("PascalCase")),{decode:e=>e.split(/[-_\s]+/).flatMap(i=>i.split(/(?=[A-Z])/)).filter(Boolean).map(i=>i.charAt(0).toUpperCase()+i.slice(1).toLowerCase()).join(""),encode:e=>e}),Ue=P.transform(P.String,P.String.pipe(P.brand("CamelCase")),{decode:e=>e.split(/[-_\s]+/).flatMap(i=>i.split(/(?=[A-Z])/)).filter(Boolean).map((i,s)=>{let l=i.toLowerCase();return s===0?l:l.charAt(0).toUpperCase()+l.slice(1)}).join(""),encode:e=>e});import{randomUUID as Ct}from"crypto";import*as fe from"effect/Effect";var J=class extends fe.Service()("@TSDataBuilders/IdGenerator",{succeed:{generateUuid:fe.sync(()=>Ct())}}){};var pe=class extends g.Service()("@TSDataBuilders/BuilderGenerator",{effect:g.gen(function*(){let i=yield*De.FileSystem,s=yield*Oe.Path,l=yield*F,n=yield*M,o=yield*J,{fileSuffix:p,builderSuffix:c,defaults:d}=n,m=T=>Ge.getOrUndefined(T.inlineDefault)??b.value(T).pipe(b.when({kind:"STRING"},()=>`"${d.string}"`),b.when({kind:"NUMBER"},()=>d.number),b.when({kind:"BOOLEAN"},()=>d.boolean),b.when({kind:"UNDEFINED"},()=>"undefined"),b.when({kind:"NULL"},()=>"null"),b.when({kind:"DATE"},()=>"new Date()"),b.when({kind:"ARRAY"},()=>"[]"),b.when({kind:"LITERAL"},y=>y.literalValue),b.when({kind:"TYPE_CAST"},y=>m(y.baseTypeMetadata)),b.when({kind:"TUPLE"},y=>`[${y.members.map(f=>m(f)).map(f=>`${f}`).join(", ")}]`),b.when({kind:"TYPE_LITERAL"},y=>`{${Object.entries(y.metadata).filter(([f,{optional:h}])=>!h).map(([f,h])=>`${f}: ${m(h)}`).join(", ")}}`),b.when({kind:"RECORD"},y=>{if(y.keyType.kind==="STRING"||y.keyType.kind==="NUMBER")return"{}";let a=m(y.keyType),f=m(y.valueType);return`{${a}: ${f}}`}),b.when({kind:"UNION"},y=>{let f=y.members.slice().sort((h,C)=>{let N=je.indexOf(h.kind),I=je.indexOf(C.kind);return(N===-1?1/0:N)-(I===-1?1/0:I)})[0];return f?m(f):"never"}),b.when({kind:"BUILDER"},y=>`new ${y.name}${c}().build()`),b.exhaustive);return{generateBaseBuilder:g.fnUntraced(function*(){let T=s.join(yield*l.cwd,n.outputDir),y=s.resolve(T,"data-builder.ts");yield*g.logDebug(`[Builders]: Creating base builder at ${y}`),yield*g.orDie(i.writeFileString(y,`${Dt}
|
|
3
|
+
`))}),generateBuilder:g.fnUntraced(function*(T){let y=new Nt,a=T.name;yield*g.logDebug(`[Builders]: Creating builder for ${a}`);let f=s.join(yield*l.cwd,n.outputDir),h=s.resolve(f,`${yield*Ne.pipe(oe.decode)(a)}${p}.ts`);yield*g.logDebug(`[Builders]: Creating builder content for ${a}`);let C=y.createSourceFile(`__temp_${yield*o.generateUuid}.ts`,"",{overwrite:!0}),N=s.resolve(T.path),I=s.relative(s.dirname(h),N).replace(/\.ts$/,"");if(C.addImportDeclaration({namedImports:[a],isTypeOnly:!0,moduleSpecifier:I}),C.addImportDeclaration({namedImports:["DataBuilder"],moduleSpecifier:"./data-builder"}),T.shape.kind!=="TYPE_LITERAL")return yield*g.dieMessage("[BuilderGenerator]: Expected root type to be type literal");let v=[...new Set(Ot(T.shape.metadata))];yield*g.forEach(v,$=>Ne.pipe(oe.decode)($).pipe(g.andThen(k=>C.addImportDeclaration({namedImports:[`${$}${c}`],moduleSpecifier:`./${k}${p}`}))),{concurrency:"unbounded"});let Y=Object.entries(T.shape.metadata).filter(([$,{optional:k}])=>!k).map(([$,k])=>`${$}: ${k.kind==="TYPE_CAST"?`${m(k)} as ${a}['${$}']`:m(k)}`),W=yield*g.all(Object.entries(T.shape.metadata).map(([$,{optional:k,kind:de}])=>bt({fieldName:$,optional:k,typeName:a,isNestedBuilder:de==="BUILDER"})),{concurrency:"unbounded"}),ee=`{
|
|
3
4
|
${Y.join(`,
|
|
4
5
|
`)}
|
|
5
|
-
}`;
|
|
6
|
+
}`;C.addClass({name:`${a}${c}`,isExported:!0,extends:`DataBuilder<${a}>`,methods:[{name:"constructor",statements:[`super(${ee});`]},...W]}),yield*g.logDebug(`[Builders]: Saving builder content at ${h}`),yield*i.writeFileString(h,`${C.getText()}
|
|
7
|
+
`)})}}),dependencies:[J.Default]}){},je=["UNDEFINED","BOOLEAN","NUMBER","STRING","DATE","LITERAL","TYPE_LITERAL","ARRAY","TUPLE","RECORD"],Dt=`export abstract class DataBuilder<T> {
|
|
6
8
|
private data: T;
|
|
7
9
|
|
|
8
10
|
constructor(initialData: T) {
|
|
@@ -18,4 +20,5 @@ import*as it from"@effect/platform-node/NodeContext";import*as rt from"@effect/p
|
|
|
18
20
|
return this;
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
|
-
`;function gt(t){let i=[];function c(e){switch(e.kind){case"BUILDER":i.push(e.name);break;case"TYPE_LITERAL":Object.values(e.metadata).forEach(c);break;case"UNION":case"TUPLE":e.members.forEach(c);break;case"RECORD":c(e.keyType),c(e.valueType);break}}return Object.values(t).forEach(c),i}class z extends B.Service()("@TSDataBuilders/Builders",{effect:B.gen(function*(){let t=yield*$e.FileSystem,i=yield*ae,c=yield*$,e=yield*we.Path,g=yield*N;return{create:B.fnUntraced(function*(s){let o=e.join(yield*c.cwd,g.outputDir);if(yield*B.orDie(t.exists(o)))yield*B.logDebug(`[Builders]: Removing already existing output directory at ${o}`),yield*B.orDie(t.remove(o,{recursive:!0}));yield*B.logDebug(`[Builders]: Creating output directory at ${o}`),yield*B.orDie(t.makeDirectory(o,{recursive:!0})),yield*i.generateBaseBuilder();let d=s.map((n)=>n.name),u=d.filter((n,S)=>d.indexOf(n)!==S),a=[...new Set(u)];if(u.length>0)return yield*B.dieMessage(`Duplicated builders: ${a.join(", ")}`);yield*B.all(s.map((n)=>i.generateBuilder(n)),{concurrency:"unbounded"})})}}),dependencies:[ae.Default]}){}import*as l from"@effect/cli/Options";var St=l.text("jsdoc-tag").pipe(l.withDescription(D.jsdocTag),l.withSchema(R.fields.jsdocTag),l.optional),yt=l.text("output-dir").pipe(l.withAlias("o"),l.withDescription(D.outputDir),l.withSchema(R.fields.outputDir),l.optional),Et=l.text("include").pipe(l.withAlias("i"),l.withDescription(D.include),l.withSchema(R.fields.include),l.optional),bt=l.text("file-suffix").pipe(l.withDescription(D.fileSuffix),l.withSchema(R.fields.fileSuffix),l.optional),Ct=l.text("builder-suffix").pipe(l.withDescription(D.builderSuffix),l.withSchema(R.fields.builderSuffix),l.optional),Dt=l.text("default-string").pipe(l.withDescription(D.defaultString),l.withSchema(R.fields.defaultString),l.optional),Tt=l.text("default-number").pipe(l.withDescription(D.defaultNumber),l.withSchema(R.fields.defaultNumber),l.optional),xt=l.text("default-boolean").pipe(l.withDescription(D.defaultBoolean),l.withSchema(R.fields.defaultBoolean),l.optional),Ee={jsdocTag:St,outputDir:yt,include:Et,fileSuffix:bt,builderSuffix:Ct,defaultString:Dt,defaultNumber:Tt,defaultBoolean:xt};import*as Pe from"@effect/platform/FileSystem";import*as Ue from"@effect/platform/Path";import*as ve from"effect/Effect";import*as L from"effect/Option";import*as Ae from"effect/Schema";var Ie=(t)=>ve.gen(function*(){let i=yield*$,c=yield*Pe.FileSystem,e=yield*i.cwd,s=(yield*Ue.Path).join(e,ge),o=ee({string:t.defaultString.pipe(L.getOrUndefined),number:t.defaultNumber.pipe(L.getOrUndefined),boolean:t.defaultBoolean.pipe(L.getOrUndefined)}),m=yield*Ae.decode(he)({$schema:"https://raw.githubusercontent.com/nemmtor/ts-databuilders/refs/heads/main/schema.json",builderSuffix:t.builderSuffix.pipe(L.getOrElse(()=>M.builderSuffix)),fileSuffix:t.fileSuffix.pipe(L.getOrElse(()=>M.fileSuffix)),include:t.include.pipe(L.getOrElse(()=>M.include)),jsdocTag:t.jsdocTag.pipe(L.getOrElse(()=>M.jsdocTag)),outputDir:t.outputDir.pipe(L.getOrElse(()=>M.outputDir)),defaults:{...M.defaults,...o}});yield*c.writeFileString(s,JSON.stringify(m,null,2))});import*as _ from"effect/Chunk";import*as F from"effect/Effect";import*as _e from"effect/Function";import*as V from"effect/Option";import*as ce from"effect/Stream";import*as Le from"effect/Data";import*as G from"effect/Effect";import*as je from"effect/Stream";import{glob as Ft}from"glob";class te extends G.Service()("@TSDataBuilders/TreeWalker",{effect:G.gen(function*(){let t=yield*$;return{walk:G.fnUntraced(function*(i){let c=yield*t.cwd;return yield*G.logDebug(`[TreeWalker]: Walking path: ${c}/${i}`),je.fromAsyncIterable(Ft.stream(i,{cwd:c,nodir:!0}),(e)=>new Me({cause:e}))})}})}){}class Me extends Le.TaggedError("TreeWalkerError"){}import*as Re from"@effect/platform/FileSystem";import*as A from"effect/Effect";import*as P from"effect/Stream";class se extends A.Service()("@TSDataBuilders/FileContentChecker",{effect:A.gen(function*(){let t=yield*Re.FileSystem,i=new TextDecoder;return{check:A.fnUntraced(function*(c){let{content:e,filePath:g}=c;return yield*A.logDebug(`[FileContentChecker](${g}): Checking file content`),yield*P.orDie(t.stream(g,{chunkSize:16384})).pipe(P.map((m)=>i.decode(m,{stream:!0})),P.mapAccum("",(m,d)=>{let u=m+d;return[u.slice(-e.length+1),u.includes(e)]}),P.find(Boolean),P.tap((m)=>m?A.logDebug(`[FileContentChecker](${g}): found expected content`):A.void),P.runCollect)})}})}){}class H extends F.Service()("@TSDataBuilders/Finder",{effect:F.gen(function*(){let t=yield*se,i=yield*te,{include:c,jsdocTag:e}=yield*N,g=`@${e}`;return{find:F.fnUntraced(function*(){yield*F.logDebug("[Finder]: Attempting to find files with builders");let o=yield*(yield*i.walk(c)).pipe(ce.mapEffect((m)=>t.check({filePath:m,content:g}).pipe(F.map(_.map((d)=>d?V.some(m):V.none()))),{concurrency:"unbounded"}),ce.runCollect,F.map(_.flatMap(_e.identity)),F.map(_.filter((m)=>V.isSome(m))),F.map(_.map((m)=>m.value)));return yield*F.logDebug(`[Finder]: Found builders in files: ${o.pipe(_.toArray).join(", ")}`),o},F.catchTag("TreeWalkerError",(s)=>F.die(s)))}}),dependencies:[te.Default,se.Default]}){}import*as Je from"@effect/platform/FileSystem";import*as q from"effect/Data";import*as p from"effect/Effect";import*as re from"effect/Either";import{Project as Ot,SyntaxKind as ue}from"ts-morph";import*as W from"effect/Data";import*as f from"effect/Effect";import*as y from"effect/Match";import*as Ge from"effect/Option";import{Node as Nt,SyntaxKind as E}from"ts-morph";import{randomUUID as kt}from"node:crypto";import*as fe from"effect/Effect";class le extends fe.Service()("@TSDataBuilders/RandomUUID",{succeed:{generate:fe.sync(()=>kt())}}){}class de extends f.Service()("@TSDataBuilders/TypeNodeParser",{effect:f.gen(function*(){let{jsdocTag:t}=yield*N,i=yield*le,c=(g)=>f.gen(function*(){let{type:s,contextNode:o,optional:m}=g,d=s.getProperties();if(!s.isObject()||d.length===0)return yield*new Ke({raw:s.getText(),kind:o.getKind()});let u={};for(let a of d){let n=a.getName(),S=a.getTypeAtLocation(o),h=a.isOptional(),b=o.getProject().createSourceFile(`__temp_${yield*i.generate}.ts`,`type __T = ${S.getText()}`,{overwrite:!0}),O=b.getTypeAliasOrThrow("__T").getTypeNodeOrThrow(),I=yield*f.suspend(()=>e({typeNode:O,optional:h}));u[n]=I,o.getProject().removeSourceFile(b)}return{kind:"TYPE_LITERAL",metadata:u,optional:m}}),e=(g)=>f.gen(function*(){let{typeNode:s,optional:o}=g,m=s.getKind(),d=y.value(m).pipe(y.when(y.is(E.StringKeyword),()=>f.succeed({kind:"STRING",optional:o})),y.when(y.is(E.NumberKeyword),()=>f.succeed({kind:"NUMBER",optional:o})),y.when(y.is(E.BooleanKeyword),()=>f.succeed({kind:"BOOLEAN",optional:o})),y.when(y.is(E.UndefinedKeyword),()=>f.succeed({kind:"UNDEFINED",optional:o})),y.when(y.is(E.ArrayType),()=>f.succeed({kind:"ARRAY",optional:o})),y.when(y.is(E.LiteralType),()=>f.succeed({kind:"LITERAL",literalValue:s.asKindOrThrow(E.LiteralType).getLiteral().getText(),optional:o})),y.when(y.is(E.TypeLiteral),()=>f.gen(function*(){let n=s.asKindOrThrow(E.TypeLiteral).getMembers();return{kind:"TYPE_LITERAL",metadata:yield*f.reduce(n,{},(h,b)=>f.gen(function*(){if(!b.isKind(E.PropertySignature))return h;let O=b.getTypeNode();if(!O)return h;let I=b.getNameNode().getText(),Y=b.hasQuestionToken(),K=yield*f.suspend(()=>e({typeNode:O,optional:Y}));return{...h,[I]:K}})),optional:o}})),y.when(y.is(E.ImportType),()=>f.gen(function*(){let a=s.asKindOrThrow(E.ImportType),n=a.getType(),S=n.getSymbol();if(!S)return yield*f.die(new We);let h=S.getDeclarations();if(h&&h.length>1)return yield*f.die(new be);let[b]=h;if(!b)return yield*f.die(new Ce);return yield*c({type:n,contextNode:a,optional:o})})),y.when(y.is(E.TupleType),()=>f.gen(function*(){let n=s.asKindOrThrow(E.TupleType).getElements(),S=yield*f.all(n.map((h)=>f.suspend(()=>e({typeNode:h,optional:!1}))),{concurrency:"unbounded"});return{kind:"TUPLE",optional:o,members:S}})),y.when(y.is(E.TypeReference),()=>f.gen(function*(){let a=s.asKindOrThrow(E.TypeReference),n=a.getTypeName().getText();if(n==="Date")return{kind:"DATE",optional:o};if(n==="Array")return{kind:"ARRAY",optional:o};let S=a.getTypeArguments();if(n==="Record"){let[J,v]=S;if(!J||!v)return yield*new ie({kind:m,raw:s.getText()});let j=yield*f.suspend(()=>e({typeNode:J,optional:!1})),me=yield*f.suspend(()=>e({typeNode:v,optional:!1}));return{kind:"RECORD",keyType:j,valueType:me,optional:o}}if(["Pick","Omit","Partial","Required","Readonly","Extract","NonNullable"].includes(n))return yield*c({type:a.getType(),contextNode:a,optional:o});let b=a.getType().getAliasSymbol();if(!b)return yield*c({type:a.getType(),contextNode:a,optional:o});let O=b.getDeclarations();if(O&&O.length>1)return yield*f.die(new be);let[I]=O;if(!I)return yield*f.die(new Ce);let Y=b?.getJsDocTags().map((J)=>J.getName()).includes(t);if(!Nt.isTypeAliasDeclaration(I))return yield*f.die(new Ye);let K=I.getTypeNode();if(!K)return yield*new ie({kind:m,raw:s.getText()});if(!Y)return yield*f.suspend(()=>e({typeNode:K,optional:o}));return{kind:"BUILDER",name:I.getName(),optional:o}})),y.when(y.is(E.UnionType),()=>f.gen(function*(){let a=yield*f.all(s.asKindOrThrow(E.UnionType).getTypeNodes().map((n)=>f.suspend(()=>e({typeNode:n,optional:!1}))),{concurrency:"unbounded"});return{kind:"UNION",optional:o,members:a}})),y.when(y.is(E.IntersectionType),()=>f.gen(function*(){let n=s.asKindOrThrow(E.IntersectionType).getTypeNodes(),S=[E.StringKeyword,E.NumberKeyword,E.BooleanKeyword],h=n.find((b)=>S.includes(b.getKind()));if(h&&n.length>1)return{kind:"TYPE_CAST",baseTypeMetadata:yield*f.suspend(()=>e({typeNode:h,optional:!1})),optional:o};return yield*new ie({kind:m,raw:s.getText()})})),y.option);if(Ge.isNone(d))return yield*new ie({kind:m,raw:s.getText()});return yield*d.value});return{generateMetadata:e}}),dependencies:[le.Default]}){}class ie extends W.TaggedError("UnsupportedSyntaxKindError"){}class be extends W.TaggedError("MultipleSymbolDeclarationsError"){}class Ce extends W.TaggedError("MissingSymbolDeclarationError"){}class We extends W.TaggedError("MissingSymbolError"){}class Ye extends W.TaggedError("UnsupportedTypeAliasDeclaration"){}class Ke extends W.TaggedError("CannotBuildTypeReferenceMetadata"){}class Q extends p.Service()("@TSDataBuilders/Parser",{effect:p.gen(function*(){let t=yield*Je.FileSystem,i=yield*de,{jsdocTag:c}=yield*N;return{generateBuildersMetadata:p.fnUntraced(function*(e){yield*p.logDebug(`[Parser](${e}): Generating builder metadata`),yield*p.logDebug(`[Parser](${e}): Reading source code`);let g=yield*p.orDie(t.readFileString(e)),s=yield*p.try({try:()=>{return new Ot().createSourceFile(e,g,{overwrite:!0}).getTypeAliases().filter((n)=>n.getJsDocs().flatMap((S)=>S.getTags().flatMap((h)=>h.getTagName())).includes(c)).map((n)=>{let S=n.getName();if(!n.isExported())return re.left(new ze({path:e,typeName:S}));let h=n.getTypeNode();if(!(h?.isKind(ue.TypeLiteral)||h?.isKind(ue.TypeReference)))return re.left(new Ve({path:e,typeName:n.getName()}));return re.right({name:n.getName(),node:h})}).filter(Boolean)},catch:(d)=>new Ze({cause:d})}),o=yield*p.all(s.map((d)=>d),{concurrency:"unbounded"});return yield*p.logDebug(`[Parser](${e}): Generating metadata for types: ${o.map(({name:d})=>d).join(", ")}`),yield*p.all(o.map(({name:d,node:u})=>i.generateMetadata({typeNode:u,optional:!1}).pipe(p.tap(()=>p.logDebug(`[Parser](${e}): Finished generating metadata for type: ${d}`)),p.map((a)=>({name:d,shape:a,path:e})),p.catchTags({UnsupportedSyntaxKindError:(a)=>p.fail(new He({kind:a.kind,raw:a.raw,path:e,typeName:d})),CannotBuildTypeReferenceMetadata:(a)=>p.fail(new qe({kind:a.kind,raw:a.raw,path:e,typeName:d}))}))),{concurrency:"unbounded"})},p.catchTags({ParserError:(e)=>p.die(e),UnexportedDatabuilderError:(e)=>p.dieMessage(`[Parser](${e.path}): Unexported databuilder ${e.typeName}`),RichUnsupportedSyntaxKindError:(e)=>p.dieMessage(`[Parser](${e.path}): Unsupported syntax kind of id: ${e.kind} with raw type: ${e.raw} found in type ${e.typeName}`),RichCannotBuildTypeReferenceMetadata:(e)=>p.dieMessage(`[Parser](${e.path}): Cannot build type reference metadata with kind of id: ${e.kind} with raw type: ${e.raw} found in type ${e.typeName}. Is it a root of databuilder?`),UnsupportedBuilderTypeError:(e)=>p.dieMessage(`[Parser](${e.path}): Unsupported builder type ${e.typeName}`)}))}}),dependencies:[de.Default]}){}class Ze extends q.TaggedError("ParserError"){}class ze extends q.TaggedError("UnexportedDatabuilderError"){}class Ve extends q.TaggedError("UnsupportedBuilderTypeError"){}class He extends q.TaggedError("RichUnsupportedSyntaxKindError"){}class qe extends q.TaggedError("RichCannotBuildTypeReferenceMetadata"){}import*as Qe from"effect/Chunk";import*as w from"effect/Effect";import*as Xe from"effect/Function";var et=w.gen(function*(){let t=yield*H,i=yield*Q,c=yield*z;yield*w.logInfo("[TSDatabuilders]: Generating builders for your project.");let e=yield*t.find();yield*w.logInfo(`[TSDatabuilders]: Found builders in ${e.length} file(s).`),yield*w.logDebug("[TSDatabuilders]: Attempting to generate builders metadata");let g=yield*w.all(Qe.map(e,(s)=>i.generateBuildersMetadata(s)),{concurrency:"unbounded"}).pipe(w.map((s)=>s.flatMap(Xe.identity)));if(g.length===0)return;yield*w.logDebug("[TSDatabuilders]: Attempting to create builders files"),yield*c.create(g),yield*w.logInfo(`[TSDatabuilders]: Created ${g.length} builder(s).`)});var wt=U.make("init",Ee).pipe(U.withHandler(Ie)),Pt=U.make("ts-databuilders",Ee),tt=Pt.pipe(U.withHandler(()=>et),U.withSubcommands([wt]),U.provide((t)=>X.mergeAll(H.Default,Q.Default,z.Default).pipe(X.provide(X.effect(N,Fe(t))))),U.run({name:"Typescript Databuilders generator",version:"v0.0.1"}));var vt=ot.mergeAll(nt.minimumLogLevel(at.Debug),$.Default,it.layer);tt(process.argv).pipe(Ut.provide(vt),rt.runMain);
|
|
23
|
+
`;function Ot(e){let i=[];function s(l){switch(l.kind){case"BUILDER":i.push(l.name);break;case"TYPE_LITERAL":Object.values(l.metadata).forEach(s);break;case"UNION":case"TUPLE":l.members.forEach(s);break;case"RECORD":s(l.keyType),s(l.valueType);break}}return Object.values(e).forEach(s),i}var bt=e=>g.gen(function*(){let{fieldName:i,optional:s,typeName:l,isNestedBuilder:n}=e,o=i.replaceAll("'","").replaceAll('"',""),p=yield*Ue.pipe(oe.decode)(o),c=`with${yield*$e.pipe(oe.decode)(o)}`,d=[`return this.with({ ${i}: ${p} });`],m=[`return this.with({ ${i}: ${p}.build() });`],T=n?m:d,y=[`if (!${p}) {`,` const { "${o}": _unused, ...rest } = this.build();`," return this.with(rest);","}"],a=s?[...y,...T]:T,f=`${l}['${o}']`;return{name:c,isPublic:!0,parameters:[{name:p,type:n?`DataBuilder<${f}>`:f}],statements:a}}),Z=class extends g.Service()("@TSDataBuilders/BuildersGenerator",{effect:g.gen(function*(){let i=yield*De.FileSystem,s=yield*pe,l=yield*F,n=yield*Oe.Path,o=yield*M;return{create:g.fnUntraced(function*(p){let c=n.join(yield*l.cwd,o.outputDir);(yield*g.orDie(i.exists(c)))&&(yield*g.logDebug(`[Builders]: Removing already existing output directory at ${c}`),yield*g.orDie(i.remove(c,{recursive:!0}))),yield*g.logDebug(`[Builders]: Creating output directory at ${c}`),yield*g.orDie(i.makeDirectory(c,{recursive:!0})),yield*s.generateBaseBuilder();let m=p.map(a=>a.name),T=m.filter((a,f)=>m.indexOf(a)!==f),y=[...new Set(T)];if(T.length>0)return yield*g.dieMessage(`Duplicated builders: ${y.join(", ")}`);yield*g.all(p.map(a=>s.generateBuilder(a)),{concurrency:"unbounded"})})}}),dependencies:[pe.Default]}){};import*as Ke from"@effect/platform/FileSystem";import*as Ye from"@effect/platform/Path";import*as We from"effect/Effect";import*as j from"effect/Option";import*as Je from"effect/Schema";var Ve=e=>We.gen(function*(){let i=yield*F,s=yield*Ke.FileSystem,l=yield*i.cwd,o=(yield*Ye.Path).join(l,Ee),p=ie({string:e.defaultString.pipe(j.getOrUndefined),number:e.defaultNumber.pipe(j.getOrUndefined),boolean:e.defaultBoolean.pipe(j.getOrUndefined)}),c=yield*Je.decode(Ce)({$schema:"https://raw.githubusercontent.com/nemmtor/ts-databuilders/refs/heads/main/schema.json",builderSuffix:e.builderSuffix.pipe(j.getOrElse(()=>_.builderSuffix)),fileSuffix:e.fileSuffix.pipe(j.getOrElse(()=>_.fileSuffix)),include:e.include.pipe(j.getOrElse(()=>_.include)),jsdocTag:e.jsdocTag.pipe(j.getOrElse(()=>_.jsdocTag)),outputDir:e.outputDir.pipe(j.getOrElse(()=>_.outputDir)),defaults:{..._.defaults,...p}});yield*s.writeFileString(o,`${JSON.stringify(c,null,2)}
|
|
24
|
+
`)});import*as q from"effect/Chunk";import*as w from"effect/Effect";import*as me from"effect/Option";import*as ge from"effect/Stream";import*as Ze from"@effect/platform/FileSystem";import*as ze from"effect/Chunk";import*as U from"effect/Effect";import*as B from"effect/Stream";var re=class extends U.Service()("@TSDataBuilders/FileContentChecker",{effect:U.gen(function*(){let i=yield*Ze.FileSystem,s=new TextDecoder;return{check:U.fnUntraced(function*(l){let{content:n,filePath:o}=l;return yield*U.logDebug(`[FileContentChecker](${o}): Checking file content`),yield*B.orDie(i.stream(o,{chunkSize:16*1024})).pipe(B.map(d=>s.decode(d,{stream:!0})),B.mapAccum("",(d,m)=>{let T=d+m;return[T.slice(-n.length+1),T.includes(n)]}),B.find(d=>!!d),B.tap(()=>U.logDebug(`[FileContentChecker](${o}): found expected content`)),B.runCollect,U.map(d=>d.pipe(ze.get(0))))})}})}){};import*as qe from"effect/Data";import*as V from"effect/Effect";import*as He from"effect/Stream";import{glob as wt}from"glob";import*as ue from"effect/Effect";var ae=class extends ue.Service()("@TSDataBuilders/Glob",{succeed:{iterate:i=>ue.sync(()=>wt.iterate(i.path,{cwd:i.cwd,nodir:!0}))}}){};var se=class extends V.Service()("@TSDataBuilders/TreeWalker",{effect:V.gen(function*(){let i=yield*ae,s=yield*F;return{walk:V.fnUntraced(function*(l){let n=yield*s.cwd;return yield*V.logDebug(`[TreeWalker]: Walking path: ${n}/${l}`),He.fromAsyncIterable(yield*i.iterate({path:l,cwd:n}),o=>new be({cause:o}))})}}),dependencies:[ae.Default]}){},be=class extends qe.TaggedError("TreeWalkerError"){};var z=class extends w.Service()("@TSDataBuilders/Finder",{effect:w.gen(function*(){let i=yield*re,s=yield*se,{include:l,jsdocTag:n}=yield*M,o=`@${n}`;return{find:w.gen(function*(){yield*w.logDebug("[Finder]: Attempting to find files with builders");let c=yield*(yield*s.walk(l)).pipe(ge.mapEffect(d=>i.check({filePath:d,content:o}).pipe(w.map(m=>m.pipe(me.map(()=>d)))),{concurrency:"unbounded"}),ge.runCollect,w.map(q.filter(d=>me.isSome(d))),w.map(q.map(d=>d.value)));return yield*w.logDebug(`[Finder]: Found builders in files: ${c.pipe(q.toArray).join(", ")}`),c}).pipe(w.catchTag("TreeWalkerError",p=>w.die(p)))}}),dependencies:[se.Default,re.Default]}){};import{Node as kt,Project as Ft,SyntaxKind as E}from"ts-morph";import*as Xe from"@effect/platform/FileSystem";import*as G from"effect/Data";import*as t from"effect/Effect";import*as ce from"effect/Either";import*as S from"effect/Match";import*as R from"effect/Option";var ye=class extends t.Service()("@TSDataBuilders/TypeNodeParser",{effect:t.gen(function*(){let{jsdocTag:i}=yield*M,s=yield*J,l=o=>t.gen(function*(){let{type:p,contextNode:c,optional:d,inlineDefault:m}=o,T=p.getProperties();if(!p.isObject()||T.length===0)return yield*new we({raw:p.getText(),kind:c.getKind()});let y={};for(let a of T){let f=a.getName(),h=a.getTypeAtLocation(c),C=a.isOptional(),N=c.getProject().createSourceFile(`__temp_${yield*s.generateUuid}.ts`,`type __T = ${h.getText()}`,{overwrite:!0}),I=N.getTypeAliasOrThrow("__T").getTypeNodeOrThrow(),v=yield*t.suspend(()=>n({typeNode:I,optional:C,inlineDefault:R.none()}));y[f]=v,c.getProject().removeSourceFile(N)}return{kind:"TYPE_LITERAL",metadata:y,inlineDefault:m,optional:d}}),n=o=>t.gen(function*(){let{typeNode:p,optional:c,inlineDefault:d}=o,m=p.getKind(),T=S.value(m).pipe(S.when(S.is(E.StringKeyword),()=>t.succeed({kind:"STRING",inlineDefault:d,optional:c})),S.when(S.is(E.NumberKeyword),()=>t.succeed({kind:"NUMBER",inlineDefault:d,optional:c})),S.when(S.is(E.BooleanKeyword),()=>t.succeed({kind:"BOOLEAN",inlineDefault:d,optional:c})),S.when(S.is(E.UndefinedKeyword),()=>t.succeed({kind:"UNDEFINED",inlineDefault:d,optional:c})),S.when(S.is(E.ArrayType),()=>t.succeed({kind:"ARRAY",inlineDefault:d,optional:c})),S.when(S.is(E.LiteralType),()=>{let a=p.asKindOrThrow(E.LiteralType).getLiteral().getText();return a==="null"?t.succeed({kind:"NULL",inlineDefault:d,optional:c}):t.succeed({kind:"LITERAL",inlineDefault:d,literalValue:a,optional:c})}),S.when(S.is(E.TypeLiteral),()=>t.gen(function*(){let f=p.asKindOrThrow(E.TypeLiteral).getMembers(),h=yield*t.reduce(f,{},(C,N)=>t.gen(function*(){if(!N.isKind(E.PropertySignature))return C;let I=N.getTypeNode();if(!I)return C;let v=N.getNameNode().getText(),Y=N.hasQuestionToken(),W=yield*Mt(N),ee=yield*t.suspend(()=>n({typeNode:I,optional:Y,inlineDefault:W}));return{...C,[v]:ee}}));return{kind:"TYPE_LITERAL",inlineDefault:d,metadata:h,optional:c}})),S.when(S.is(E.ImportType),()=>t.gen(function*(){let a=p.asKindOrThrow(E.ImportType),f=a.getType(),h=f.getSymbol(),C=f.getText();if(!h)return yield*new Fe({raw:C});let N=h.getDeclarations();if(N&&N.length>1)return yield*new Te({raw:C});let[I]=N;return I?yield*l({type:f,contextNode:a,inlineDefault:d,optional:c}):yield*new Se({raw:C})})),S.when(S.is(E.TupleType),()=>t.gen(function*(){let f=p.asKindOrThrow(E.TupleType).getElements(),h=yield*t.all(f.map(C=>t.suspend(()=>n({typeNode:C,optional:!1,inlineDefault:R.none()}))),{concurrency:"unbounded"});return{kind:"TUPLE",inlineDefault:d,optional:c,members:h}})),S.when(S.is(E.TypeReference),()=>t.gen(function*(){let a=p.asKindOrThrow(E.TypeReference),f=a.getTypeName().getText();if(f==="Date")return{kind:"DATE",optional:c,inlineDefault:d};if(f==="Array")return{kind:"ARRAY",optional:c,inlineDefault:d};let h=a.getTypeArguments();if(f==="Record"){let[k,de]=h;if(!k||!de)return yield*new H({kind:m,raw:p.getText()});let pt=yield*t.suspend(()=>n({typeNode:k,optional:!1,inlineDefault:R.none()})),ut=yield*t.suspend(()=>n({typeNode:de,optional:!1,inlineDefault:R.none()}));return{kind:"RECORD",keyType:pt,valueType:ut,optional:c,inlineDefault:d}}if(["Pick","Omit","Partial","Required","Readonly","Extract","NonNullable"].includes(f))return yield*l({type:a.getType(),contextNode:a,optional:c,inlineDefault:d});let N=a.getType(),I=N.getText(),v=N.getAliasSymbol();if(!v)return yield*l({type:N,contextNode:a,optional:c,inlineDefault:d});let Y=v.getDeclarations();if(Y&&Y.length>1)return yield*new Te({raw:I});let[W]=Y;if(!W)return yield*new Se({raw:I});let ee=v?.getJsDocTags().map(k=>k.getName()).includes(i);if(!kt.isTypeAliasDeclaration(W))return yield*new xe;let $=W.getTypeNode();return $?ee?{kind:"BUILDER",name:W.getName(),inlineDefault:d,optional:c}:yield*t.suspend(()=>n({typeNode:$,optional:c,inlineDefault:d})):yield*new H({kind:m,raw:I})})),S.when(S.is(E.UnionType),()=>t.gen(function*(){let a=yield*t.all(p.asKindOrThrow(E.UnionType).getTypeNodes().map(f=>t.suspend(()=>n({typeNode:f,optional:!1,inlineDefault:R.none()}))),{concurrency:"unbounded"});return{kind:"UNION",optional:c,members:a,inlineDefault:d}})),S.when(S.is(E.IntersectionType),()=>t.gen(function*(){let f=p.asKindOrThrow(E.IntersectionType).getTypeNodes(),h=[E.StringKeyword,E.NumberKeyword,E.BooleanKeyword],C=f.find(N=>h.includes(N.getKind()));return C&&f.length>1?{kind:"TYPE_CAST",baseTypeMetadata:yield*t.suspend(()=>n({typeNode:C,optional:!1,inlineDefault:d})),inlineDefault:d,optional:c}:yield*new H({kind:m,raw:p.getText()})})),S.option);return R.isNone(T)?yield*new H({kind:m,raw:p.getText()}):yield*T.value});return{generateMetadata:n}}),dependencies:[J.Default]}){},Q=class extends t.Service()("@TSDataBuilders/Parser",{effect:t.gen(function*(){let i=yield*Xe.FileSystem,s=yield*ye,{jsdocTag:l}=yield*M;return{generateBuildersMetadata:n=>t.gen(function*(){yield*t.logDebug(`[Parser](${n}): Generating builder metadata`),yield*t.logDebug(`[Parser](${n}): Reading source code`);let o=yield*t.orDie(i.readFileString(n)),p=yield*t.try({try:()=>new Ft().createSourceFile(n,o,{overwrite:!0}).getTypeAliases().filter(a=>a.getJsDocs().flatMap(f=>f.getTags().flatMap(h=>h.getTagName())).includes(l)).map(a=>{let f=a.getName();if(!a.isExported())return ce.left(new Pe({typeName:f}));let h=a.getTypeNode();return h?.isKind(E.TypeLiteral)||h?.isKind(E.TypeReference)?ce.right({name:a.getName(),node:h}):ce.left(new ke({typeName:a.getName()}))}).filter(Boolean),catch:m=>new Ie({cause:m})}),c=yield*t.all(p.map(m=>m),{concurrency:"unbounded"});return yield*t.logDebug(`[Parser](${n}): Generating metadata for types: ${c.map(({name:m})=>m).join(", ")}`),yield*t.all(c.map(({name:m,node:T})=>s.generateMetadata({typeNode:T,optional:!1,inlineDefault:R.none()}).pipe(t.tap(()=>t.logDebug(`[Parser](${n}): Finished generating metadata for type: ${m}`)),t.map(y=>({name:m,shape:y,path:n})))),{concurrency:"unbounded"})}).pipe(t.catchTags({ParserError:o=>t.die(o),MissingSymbolDeclarationError:o=>t.dieMessage(`[Parser](${n}): Missing symbol declaration for type: ${o.raw}`),UnsupportedTypeAliasDeclarationError:()=>t.dieMessage(`[Parser](${n}): Unsupported type alias declaration`),MultipleSymbolDeclarationsError:o=>t.dieMessage(`[Parser](${n}): Missing symbol declaration error for type: ${o.raw}`),MissingSymbolError:o=>t.dieMessage(`[Parser](${n}): Missing symbol error for type: ${o.raw}`),UnexportedDatabuilderError:o=>t.dieMessage(`[Parser](${n}): Unexported databuilder ${o.typeName}`),UnsupportedSyntaxKindError:o=>t.dieMessage(`[Parser](${n}): Unsupported syntax kind of id: ${o.kind} for type: ${o.raw}`),CannotBuildTypeReferenceMetadataError:o=>t.dieMessage(`[Parser](${n}): Cannot build type reference metadata with kind of id: ${o.kind} for type: ${o.raw}. Is it a root of databuilder?`),UnsupportedBuilderTypeError:o=>t.dieMessage(`[Parser](${n}): Unsupported builder type ${o.typeName}`)}))}}),dependencies:[ye.Default]}){},H=class extends G.TaggedError("UnsupportedSyntaxKindError"){},xe=class extends G.TaggedError("UnsupportedTypeAliasDeclarationError"){},we=class extends G.TaggedError("CannotBuildTypeReferenceMetadataError"){},Ie=class extends G.TaggedError("ParserError"){},Pe=class extends G.TaggedError("UnexportedDatabuilderError"){},ke=class extends G.TaggedError("UnsupportedBuilderTypeError"){},Fe=class extends G.TaggedError("MissingSymbolError"){},Se=class extends G.TaggedError("MissingSymbolDeclarationError"){},Te=class extends G.TaggedError("MultipleSymbolDeclarationsError"){},Mt=t.fnUntraced(function*(e){let i=e.getJsDocs();for(let s of i){let n=s.getTags().find(o=>o.getTagName()==="DataBuilderDefault");if(n){let o=n.getComment();if(typeof o=="string")return R.some(o.trim())}}return R.none()});import*as tt from"effect/Chunk";import*as A from"effect/Effect";import*as it from"effect/Function";var nt=A.gen(function*(){let e=yield*z,i=yield*Q,s=yield*Z;yield*A.logInfo("[TSDatabuilders]: Generating builders for your project.");let l=yield*e.find;yield*A.logInfo(`[TSDatabuilders]: Found builders in ${l.length} file(s).`),yield*A.logDebug("[TSDatabuilders]: Attempting to generate builders metadata");let n=yield*A.all(tt.map(l,o=>i.generateBuildersMetadata(o)),{concurrency:"unbounded"}).pipe(A.map(o=>o.flatMap(it.identity)));n.length!==0&&(yield*A.logDebug("[TSDatabuilders]: Attempting to create builders files"),yield*s.create(n),yield*A.logInfo(`[TSDatabuilders]: Created ${n.length} builder(s).`))});var At=u.text("jsdoc-tag").pipe(u.withDescription(O.jsdocTag),u.withSchema(K.fields.jsdocTag),u.optional),Bt=u.text("output-dir").pipe(u.withAlias("o"),u.withDescription(O.outputDir),u.withSchema(K.fields.outputDir),u.optional),Rt=u.text("include").pipe(u.withAlias("i"),u.withDescription(O.include),u.withSchema(K.fields.include),u.optional),Lt=u.text("file-suffix").pipe(u.withDescription(O.fileSuffix),u.withSchema(K.fields.fileSuffix),u.optional),$t=u.text("builder-suffix").pipe(u.withDescription(O.builderSuffix),u.withSchema(K.fields.builderSuffix),u.optional),Ut=u.text("default-string").pipe(u.withDescription(O.defaultString),u.withSchema(K.fields.defaultString),u.optional),vt=u.text("default-number").pipe(u.withDescription(O.defaultNumber),u.withSchema(K.fields.defaultNumber),u.optional),jt=u.text("default-boolean").pipe(u.withDescription(O.defaultBoolean),u.withSchema(K.fields.defaultBoolean),u.optional),ot={jsdocTag:At,outputDir:Bt,include:Rt,fileSuffix:Lt,builderSuffix:$t,defaultString:Ut,defaultNumber:vt,defaultBoolean:jt},Gt=L.make("init",ot).pipe(L.withHandler(Ve)),_t=L.make("ts-databuilders",ot),rt=_t.pipe(L.withHandler(()=>nt),L.withSubcommands([Gt]),L.provide(e=>X.mergeAll(z.Default,Q.Default,Z.Default).pipe(X.provide(X.effect(M,Le(e))))),L.run({name:"Typescript Databuilders generator",version:"v0.0.1"}));var Kt=dt.mergeAll(lt.minimumLogLevel(ft.Info),F.Default,at.layer);rt(process.argv).pipe(ct.provide(Kt),st.runMain);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@nemmtor/ts-databuilders",
|
|
4
|
-
"version": "0.0.1-alpha.
|
|
4
|
+
"version": "0.0.1-alpha.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"description": "CLI tool that automatically generates builder classes from annotated TypeScript types.",
|
|
@@ -27,8 +27,11 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build
|
|
31
|
-
"
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"start": "tsx src/main.ts",
|
|
32
35
|
"format": "biome format",
|
|
33
36
|
"format:fix": "biome format --write",
|
|
34
37
|
"lint": "biome lint",
|
|
@@ -38,25 +41,35 @@
|
|
|
38
41
|
"check-types": "tsc -b tsconfig.json",
|
|
39
42
|
"check-knip": "knip",
|
|
40
43
|
"check-dep": "depcruise --output-type err-long src",
|
|
41
|
-
"gen:schema": "
|
|
44
|
+
"gen:schema": "tsx scripts/generate-schema.ts"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
|
-
"@biomejs/biome": "^2.2
|
|
45
|
-
"@effect/language-service": "^0.
|
|
47
|
+
"@biomejs/biome": "^2.3.2",
|
|
48
|
+
"@effect/language-service": "^0.55.0",
|
|
49
|
+
"@effect/vitest": "^0.27.0",
|
|
46
50
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
47
|
-
"@types/
|
|
48
|
-
"
|
|
49
|
-
"
|
|
51
|
+
"@types/node": "^24.0.0",
|
|
52
|
+
"@vitest/coverage-v8": "4.0.7",
|
|
53
|
+
"dependency-cruiser": "^17.2.0",
|
|
54
|
+
"knip": "^5.66.4",
|
|
55
|
+
"lefthook": "^2.0.2",
|
|
56
|
+
"tsup": "^8.5.0",
|
|
57
|
+
"tsx": "^4.20.6",
|
|
58
|
+
"vitest": "^4.0.6"
|
|
50
59
|
},
|
|
51
60
|
"peerDependencies": {
|
|
52
61
|
"typescript": "^5.9.3"
|
|
53
62
|
},
|
|
54
63
|
"dependencies": {
|
|
55
|
-
"@effect/cli": "^0.
|
|
56
|
-
"@effect/platform": "^0.
|
|
57
|
-
"@effect/platform-node": "^0.
|
|
64
|
+
"@effect/cli": "^0.72.0",
|
|
65
|
+
"@effect/platform": "^0.93.0",
|
|
66
|
+
"@effect/platform-node": "^0.100.0",
|
|
58
67
|
"effect": "^3.18.4",
|
|
59
68
|
"glob": "^11.0.3",
|
|
60
69
|
"ts-morph": "^27.0.2"
|
|
70
|
+
},
|
|
71
|
+
"packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd",
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=20.0.0"
|
|
61
74
|
}
|
|
62
75
|
}
|