fakelab 0.0.19 โ 0.0.20
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 +51 -9
- package/lib/cli.js +4 -2
- package/lib/main.d.ts +1 -6
- package/lib/main.js +61 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- ๐ Instant mock server
|
|
8
8
|
- ๐๏ธ Mock from Typescript files
|
|
9
|
-
- ๐ฆ Lightweight
|
|
9
|
+
- ๐ฆ Lightweight
|
|
10
10
|
- ๐งช Perfect for local development, prototyping, and frontend testing
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
@@ -88,9 +88,9 @@ export interface User {
|
|
|
88
88
|
|
|
89
89
|
## Fakelab Runtime
|
|
90
90
|
|
|
91
|
-
`fakelab/
|
|
91
|
+
`fakelab/browser` enables `fakelab` module at runtime, allowing your frontend or Node environment to communicate with the running Fakelab mock server.
|
|
92
92
|
|
|
93
|
-
## `fakelab.url
|
|
93
|
+
## `fakelab.url`
|
|
94
94
|
|
|
95
95
|
The base URL of the running Fakelab server.
|
|
96
96
|
|
|
@@ -119,7 +119,7 @@ fakelab.fetch(name: string, count?: number): Promise<T>
|
|
|
119
119
|
### Basic example
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
|
-
import { fakelab } from "fakelab/
|
|
122
|
+
import { fakelab } from "fakelab/browser";
|
|
123
123
|
|
|
124
124
|
const users = await fakelab.fetch("User", 10);
|
|
125
125
|
|
|
@@ -128,7 +128,7 @@ console.log(users);
|
|
|
128
128
|
// or
|
|
129
129
|
|
|
130
130
|
// can be enabled as a global object
|
|
131
|
-
import "fakelab/
|
|
131
|
+
import "fakelab/browser";
|
|
132
132
|
|
|
133
133
|
const users = await fakelab.fetch("User", 10);
|
|
134
134
|
|
|
@@ -159,6 +159,52 @@ export type DatabaseOptions = {
|
|
|
159
159
|
export default defineConfig({
|
|
160
160
|
database: { enabled: true, dest: "db" },
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
import { database } from "fakelab/browser";
|
|
164
|
+
|
|
165
|
+
const users = await database.get("User");
|
|
166
|
+
|
|
167
|
+
console.log(users);
|
|
168
|
+
|
|
169
|
+
// or insert fresh data to database
|
|
170
|
+
await database.post("User");
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Database Seeding
|
|
174
|
+
|
|
175
|
+
Fakelab supports database seeding to initialize mock data.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
type SeedOptions = {
|
|
179
|
+
count?: number;
|
|
180
|
+
strategy?: "reset" | "once" | "merge";
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Options
|
|
185
|
+
|
|
186
|
+
| Name | Type | Description |
|
|
187
|
+
| ---------- | ------------------------ | ----------------------------------------------------------------------------- |
|
|
188
|
+
| `count` | `number` | Number of records to generate |
|
|
189
|
+
| `strategy` | `reset`, `once`, `merge` | Defines how seeding interacts with existing database data. default is `reset` |
|
|
190
|
+
|
|
191
|
+
- `reset`: Removes all existing data and recreates it from scratch.
|
|
192
|
+
- `once`: Seeds data only if the database is empty.
|
|
193
|
+
- `merge`: Inserts new records and updates existing ones. The total number of items per table is limited to `1000` records.
|
|
194
|
+
|
|
195
|
+
### Basic example
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
export default defineConfig({
|
|
199
|
+
database: { enabled: true, dest: "db" },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
import { database } from "fakelab/browser";
|
|
203
|
+
|
|
204
|
+
await database.seed("User", { count: 10, strategy: "once" });
|
|
205
|
+
|
|
206
|
+
// to flush the database
|
|
207
|
+
await database.flush("User");
|
|
162
208
|
```
|
|
163
209
|
|
|
164
210
|
## Network Simulation
|
|
@@ -231,7 +277,3 @@ npx fakelab serve --pathPrefix /v1 --locale fr
|
|
|
231
277
|
## Related
|
|
232
278
|
|
|
233
279
|
Fakelab is powered by [Fakerjs](https://fakerjs.dev/) library.
|
|
234
|
-
|
|
235
|
-
## License
|
|
236
|
-
|
|
237
|
-
[MIT](https://choosealicense.com/licenses/mit/)
|
package/lib/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import y from'path';import T from'fs-extra';import {Command}from'commander';import P from'express';import Z from'cors';import ee from'express-ejs-layouts';import te from'http';import {Project}from'ts-morph';import m from'winston';import w from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import H from'qs';import re from'figlet';import {bundleRequire}from'bundle-require';import X from'joycon';function _(o){switch(o){case "info":return w.blueBright(o.toUpperCase());case "error":return w.redBright(o.toUpperCase());case "warn":return w.yellowBright(o.toUpperCase());default:return o.toUpperCase()}}var $=m.format.printf(({level:o,message:e,timestamp:t})=>`${w.dim(`[${t}]`)} ${_(o)} ${e}`),f=m.createLogger({format:m.format.combine(m.format.timestamp(),m.format.splat(),$),transports:[new m.transports.Console]}),M=new Intl.ListFormat("en",{style:"long",type:"unit"}),p=class{static info(e,...t){return f.info(e,...t)}static warn(e,...t){return f.warn(e,...t)}static error(e,...t){return f.error(e,...t)}static debug(e,...t){if(!(typeof process>"u"||!process.env.DEBUG))return f.info(e,...t)}static list(e){return M.format(e)}static close(){f.removeAllListeners(),f.close();}};var v=class{constructor(e){this.faker=e;}JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;boolMapping={true:true,false:false};string(e){return this.execute(e,this.faker.word.noun)}int(e){return this.execute(e,this.faker.number.int)}bool(e){return this.execute(e,this.faker.datatype.boolean)}bigInt(e){return this.execute(e,this.faker.number.bigInt)}litbool(e){return this.boolMapping[e]}async object(e,t){let r={};return await Promise.all(e.map(async s=>{let a=s.getTypeAtLocation(s.getValueDeclarationOrThrow());r[s.getName()]=await t(a,this,this.readJSDocTags(s));})),r}async union(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}async intersection(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let t=e.getJsDocTags().filter(r=>r.getName()===this.JSDOC_FAKER_FIELD);return t.length===0?[]:t.map(r=>{let[s]=r.getText();if(!s)return;let a=s.text.trim().match(this.FAKER_TAG_REGEX);if(!a)return;let[,i,n]=a,c=this.evalArgs(n);return {path:i,args:c}})}execute(e,t){if(!e)return t();let r=e.path.split("."),s=this.faker;for(let a of r)s=s[a],s||(p.error("Invalid faker module path: (%s)",e.path),process.exit(1));typeof s!="function"&&(p.error("Unresolvable faker function. (%s)",e.path),process.exit(1));try{return e.args?s(e.args):s()}catch{return p.error("Passed invalid arguments to faker function."),s()}}};var d=y.dirname(fileURLToPath(import.meta.url)),b=process.cwd();var x=class{constructor(e,t,r){this.files=e;this.config=t;this.database=r;let a=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=a.flatMap(i=>{let n=i.getInterfaces(),c=i.getTypeAliases(),l=i.getExportDeclarations().flatMap(u=>u.getNamedExports().flatMap(g=>g.getLocalTargetDeclarations()));return [...n,...c,...l]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(e){return await e()}normalizePath(e){return e.split(y.sep).join(y.posix.sep)}generateInFileEntitiyMap(e){let t=[...new Set(e.map(a=>{let i=a.getName(),n=a.getSourceFile().getFilePath();return `${i}: import("${n}").${i}`}))],{expose:r}=this.config.browserOpts(),s=`
|
|
2
2
|
interface Runtime$ {
|
|
3
3
|
${t.join(`
|
|
4
4
|
`)}
|
|
5
5
|
}`;r.mode==="global"&&(s=`
|
|
6
6
|
declare global {${s}
|
|
7
|
-
}`),O.appendFile(g.resolve(u,this.config.RUNTIME_DECL_FILENAME),s);}address(e,t){let r=this.normalizePath(L);return `${e.replace(r,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let r=t.getName().toLowerCase(),s=t.getType(),i=this.normalizePath(t.getSourceFile().getDirectoryPath()),n=t.getSourceFile().getBaseName(),a=this.address(i,n),c=this.config.database.directoryPath(),l=g.resolve(c,`${r}.json`),f=this.address(this.normalizePath(c),g.basename(l)),y=await JSONFilePreset(l,[]);return [r,{type:s,filepath:a,table:y,tablepath:f}]}));return new Map(e)}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function h(o,e,t=[],r=0){if(o.isString())return e.string(t[r]);if(o.isNumber())return e.int(t[r]);if(o.isBoolean())return e.bool(t[r]);if(o.isBigInt())return e.bigInt(t[r]);if(o.isBooleanLiteral())return e.litbool(o.getText());if(o.isLiteral())return o.getLiteralValue();if(!o.isUndefined()){if(o.isUnion()){let s=o.getUnionTypes();return await e.union(s.map((i,n)=>h(i,e,t,n)))}if(o.isIntersection()){let s=o.getIntersectionTypes();return await e.intersection(s.map((i,n)=>h(i,e,t,n)))}if(o.isArray()){let s=o.getArrayElementTypeOrThrow();return [await h(s,e,t,r)]}if(o.isObject()){let s=o.getProperties();return await e.object(s,(i,n,a)=>h(i,n,a,r))}return null}}function J({each:o}){return {resolve:async t=>await Promise.all(Array.from({length:t},o))}}async function F(o,e){let t=await o.files(e.source),r=new v(t,o),s=await r.entities(),i=await r.initFakerLibrary(o.fakerOpts(e.locale)),n=new w(i);async function a(c,l){let f=J({each:()=>h(c,n)}),y=await(l.count?f.resolve(parseInt(l.count)):h(c,n)),j=JSON.stringify(y,null,2);return {data:y,json:j}}return {entities:s,build:a}}var k=class{constructor(e,t,r){this.builder=e;this.config=t;this.pkg=r;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.config.database.enabled()});}}preview(e){return async(t,r)=>{let s=`${t.protocol}://${t.host}/`,i=t.params.name,n=await this.handleQueries(t),a=U.stringify(n,{addQueryPrefix:true}),c=this.builder.entities.get(i.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,n),f=c.filepath;r.render("preview",{name:i,filepath:f,address:s,search:a,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.config.database.enabled()});}else r.redirect("/");}}database(){return (e,t)=>{this.config.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,r)=>{let s=`${t.protocol}://${t.host}/`,i=t.params.name,n=this.builder.entities.get(i.toLowerCase());if(!this.config.database.enabled())r.redirect("/");else if(n){await n.table.read();let c=n.table.data.length>0,l=JSON.stringify(n.table.data,null,2),f=n.filepath;r.render("table",{name:i,filepath:f,address:s,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else r.redirect("/database");}}};var x=class{constructor(e,t){this.builder=e;this.network=t;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:r}=this.network.state("offline");return e.status(t).json({message:r}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:r}=this.network.state("error");return e.status(t).json({message:r}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,s);t.status(200).json(n);}else t.status(400).json({message:"The entity is not exists"});}catch(r){t.status(500).send(r);}}}getTable(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),t.status(200).json(s.table.data)):t.status(400).json({message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}updateTable(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,s);await i.table.update(a=>a.push(...Array.isArray(n)?n:[n])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}seedTable(){return async(e,t)=>{try{let r=e.body.count||1,s=e.params.name,i=this.builder.entities.get(s.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,{count:r});await i.table.update(a=>a.push(...Array.isArray(n)?n:[n])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){console.log({error:r}),t.status(500).send(r);}}}_update(){return async(e,t)=>{try{let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,s);await i.table.update(a=>a.push(n)),t.status(301).redirect(`/database/${r.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),s.table.data.length>0&&await s.table.update(i=>i.length=0),t.status(301).redirect(`/database/${r.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var G=O.readJSONSync(g.join(u,"../package.json")),T=class{constructor(e,t,r,s){this.router=e;this.config=t;this.opts=r;this.network=s;let{pathPrefix:i}=this.config.serverOpts(this.opts.pathPrefix,this.opts.port);this.prefix=i;}prefix;async register(){let e=await F(this.config,this.opts),t=new k(e,this.config,G),r=new x(e,this.network);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.database()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,r.entity()),this.router.get(`/${this.prefix}/database/:name`,r.getTable()),this.router.post(`/${this.prefix}/database/:name`,r.updateTable()),this.router.post(`/${this.prefix}/database/seed/:name`,r.seedTable()),this.router.post("/__update/:name",r._update()),this.router.post("/__delete/:name",r._clear());}};var C=class o{constructor(e){this.config=e;this.options=this.config.networkOpts(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new o(e)}timeout(){let e=this.chance(this.options?.timeoutRate);return e&&p.debug("Network timeout..."),e}error(){let e=this.chance(this.options?.errorRate);return e&&p.debug("Network error..."),e}state(e){switch(e){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(p.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options?.offline??false}middleware(e,t,r){let s=this.options?.errorRate||0,i=this.options?.timeoutRate||0,n=this.options?.offline??false,a=`delay=${this.resolveDelay()},error=${s},timeout=${i},offline=${n}`;t.setHeader("X-Fakelab-Network",a),r();}resolveDelay(){let e=this.options?.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};async function N(){try{let e=await new Q().resolve({files:["fakelab.config.ts"]});return e||(p.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{p.error("Could not load the config file."),process.exit(1);}}var E=class o{constructor(e){this.options=e;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(e){return new o(e)}async start(){let e=R(),t=R.Router(),r=W.createServer(e),s=await N(),i=C.initHandlers(s);this.setupApplication(e,i),this.setupTemplateEngine(e),await s.generateInFileRuntimeConfig(u,this.options),await new T(t,s,this.options,i).register(),e.use(t),this.run(r,s,this.options);}setupApplication(e,t){e.disable("x-powered-by"),e.use(R.json()),e.use(K({methods:"GET"})),e.use(R.static(u+"/public")),e.use(this.xPoweredMiddleware),e.use(t.middleware);}setupTemplateEngine(e){e.set("views",g.join(u,"views")),e.set("view engine","ejs"),e.use(X),e.set("layout","layouts/main");}listen(e,t){e.database.enabled()&&p.info("database: %s",e.database.directoryPath()),p.info("server: http://localhost:%d",t),console.log(Z.textSync("FAKELAB"));}run(e,t,r){let{port:s}=t.serverOpts(r.pathPrefix,r.port);e.listen(s,"localhost",()=>this.listen(t,s)),e.on("close",()=>{p.close();});}xPoweredMiddleware(e,t,r){t.setHeader("x-powered-by","fakelab"),r();}loadLocalEnv(){}};var A=new Command,P=O.readJSONSync(g.join(u,"../package.json"));A.name(P.name).description(P.description).version(P.version);A.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").action(async o=>{E.init(o).start();});A.parse();
|
|
7
|
+
}`),T.appendFile(y.resolve(d,this.config.RUNTIME_DECL_FILENAME),s);}address(e,t){let r=this.normalizePath(b);return `${e.replace(r,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let r=t.getName().toLowerCase(),s=t.getType(),a=this.normalizePath(t.getSourceFile().getDirectoryPath()),i=t.getSourceFile().getBaseName(),n=this.address(a,i),c=this.database.directoryPath(),l=y.resolve(c,`${r}.json`),u=this.address(this.normalizePath(c),y.basename(l)),g=await JSONFilePreset(l,[]);return [r,{type:s,filepath:n,table:g,tablepath:u}]}));return new Map(e)}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function h(o,e,t=[],r=0){if(o.isString())return e.string(t[r]);if(o.isNumber())return e.int(t[r]);if(o.isBoolean())return e.bool(t[r]);if(o.isBigInt())return e.bigInt(t[r]);if(o.isBooleanLiteral())return e.litbool(o.getText());if(o.isLiteral())return o.getLiteralValue();if(!o.isUndefined()){if(o.isUnion()){let s=o.getUnionTypes();return await e.union(s.map((a,i)=>h(a,e,t,i)))}if(o.isIntersection()){let s=o.getIntersectionTypes();return await e.intersection(s.map((a,i)=>h(a,e,t,i)))}if(o.isArray()){let s=o.getArrayElementTypeOrThrow();return [await h(s,e,t,r)]}if(o.isObject()){let s=o.getProperties();return await e.object(s,(a,i,n)=>h(a,i,n,r))}return null}}function G({each:o}){return {resolve:async t=>await Promise.all(Array.from({length:t},o))}}async function N(o,e,t){let r=await o.files(e.source),s=new x(r,o,t),a=await s.entities(),i=await s.initFakerLibrary(o.fakerOpts(e.locale)),n=new v(i);async function c(l,u){let g=G({each:()=>h(l,n)}),j=await(u.count?g.resolve(parseInt(u.count)):h(l,n)),I=JSON.stringify(j,null,2);return {data:j,json:I}}return {entities:a,build:c}}var k=class{constructor(e,t,r){this.builder=e;this.database=t;this.pkg=r;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(e){return async(t,r)=>{let s=`${t.protocol}://${t.host}/`,a=t.params.name,i=await this.handleQueries(t),n=H.stringify(i,{addQueryPrefix:true}),c=this.builder.entities.get(a.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,i),u=c.filepath;r.render("preview",{name:a,filepath:u,address:s,search:n,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else r.redirect("/");}}db(){return (e,t)=>{this.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,r)=>{let s=`${t.protocol}://${t.host}/`,a=t.params.name,i=this.builder.entities.get(a.toLowerCase());if(!this.database.enabled())r.redirect("/");else if(i){await i.table.read();let c=i.table.data.length>0,l=JSON.stringify(i.table.data,null,2),u=i.filepath;r.render("table",{name:a,filepath:u,address:s,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else r.redirect("/database");}}};var E=class{constructor(e,t,r){this.builder=e;this.network=t;this.database=r;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:r}=this.network.state("offline");return e.status(t).json({message:r}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:r}=this.network.state("error");return e.status(t).json({message:r}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),a=this.builder.entities.get(r.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,s);t.status(200).json(i);}else t.status(400).json({message:"The entity is not exists"});}catch(r){t.status(500).send(r);}}}getTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),t.status(200).json(s.table.data)):t.status(400).json({message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}updateTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),a=this.builder.entities.get(r.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,s);await a.table.update(n=>n.push(...Array.isArray(i)?i:[i])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.body?.count||1,s=e.body?.strategy||"reset",a=e.params.name,i=this.builder.entities.get(a.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,{count:r});if(await i.table.read(),s==="once"&&i.table.data.length>0)return t.status(200).json({message:`${a} entity was seeded once before.`});await i.table.update(c=>{s!=="merge"&&(c.length=0);let l=Array.isArray(n)?n:[n];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.update(a=>a.length=0),t.status(200).json({success:!0})):t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){console.log({error:r}),t.status(500).send(r);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=await this.handleQueries(e),a=this.builder.entities.get(r.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,s);await a.table.update(n=>n.push(i)),t.status(301).redirect(`/database/${r.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),s.table.data.length>0&&await s.table.update(a=>a.length=0),t.status(301).redirect(`/database/${r.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var V=T.readJSONSync(y.join(d,"../package.json")),C=class{constructor(e,t,r,s,a){this.router=e;this.config=t;this.network=r;this.database=s;this.opts=a;let{pathPrefix:i}=this.config.serverOpts(this.opts.pathPrefix,this.opts.port);this.prefix=i;}prefix;async register(){let e=await N(this.config,this.opts,this.database),t=new k(e,this.database,V),r=new E(e,this.network,this.database);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.db()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,r.entity()),this.router.get(`/${this.prefix}/database/:name`,r.getTable()),this.router.post(`/${this.prefix}/database/:name`,r.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,r.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,r.flush()),this.router.post("/__update/:name",r._update()),this.router.post("/__delete/:name",r._clear());}};var R=class o{constructor(e){this.config=e;this.options=this.config.networkOpts(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new o(e)}timeout(){let e=this.chance(this.options?.timeoutRate);return e&&p.debug("Network timeout..."),e}error(){let e=this.chance(this.options?.errorRate);return e&&p.debug("Network error..."),e}state(e){switch(e){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(p.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options?.offline??false}middleware(e,t,r){let s=this.options?.errorRate||0,a=this.options?.timeoutRate||0,i=this.options?.offline??false,n=`delay=${this.resolveDelay()},error=${s},timeout=${a},offline=${i}`;t.setHeader("X-Fakelab-Network",n),r();}resolveDelay(){let e=this.options?.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};async function S(){try{let e=await new X().resolve({files:["fakelab.config.ts"]});return e||(p.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{p.error("Could not load the config file."),process.exit(1);}}var D=class o{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.directoryPath=this.directoryPath.bind(this),this.options=this.config.databaseOpts(),this.options.enabled||T.rmSync(this.directoryPath(),{force:true,recursive:true});}options;static register(e){return new o(e)}enabled(){return this.options.enabled??false}directoryPath(){return y.resolve(b,this.options.dest)}async initialize(){if(this.enabled())try{await T.ensureDir(this.directoryPath()),await this.modifyGitignoreFile(this.options.dest);}catch{p.error("Could not create database.");}}async modifyGitignoreFile(e){try{let t=y.resolve(b,".gitignore");if((await T.readFile(t,{encoding:"utf8"})).split(`
|
|
8
|
+
`).some(s=>s.trim()===e.trim()))return;await T.appendFile(t,`
|
|
9
|
+
${e}`);}catch{}}};var L=class o{constructor(e){this.options=e;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(e){return new o(e)}async start(){let e=P(),t=P.Router(),r=te.createServer(e),s=await S(),a=R.initHandlers(s),i=D.register(s);this.setupApplication(e,a),this.setupTemplateEngine(e),await s.generateInFileRuntimeConfig(d,this.options),await i.initialize(),await new C(t,s,a,i,this.options).register(),e.use(t),this.run(r,s,i,this.options);}setupApplication(e,t){e.disable("x-powered-by"),e.use(P.json()),e.use(Z({methods:"GET"})),e.use(P.static(d+"/public")),e.use(this.xPoweredMiddleware),e.use(t.middleware);}setupTemplateEngine(e){e.set("views",y.join(d,"views")),e.set("view engine","ejs"),e.use(ee),e.set("layout","layouts/main");}listen(e,t){e.enabled()&&p.info("database: %s",e.directoryPath()),p.info("server: http://localhost:%d",t),console.log(re.textSync("FAKELAB"));}run(e,t,r,s){let{port:a}=t.serverOpts(s.pathPrefix,s.port);e.listen(a,"localhost",()=>this.listen(r,a)),e.on("close",()=>{p.close();});}xPoweredMiddleware(e,t,r){t.setHeader("x-powered-by","fakelab"),r();}loadLocalEnv(){}};var F=new Command,A=T.readJSONSync(y.join(d,"../package.json"));F.name(A.name).description(A.description).version(A.version);F.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").action(async o=>{L.init(o).start();});F.parse();
|
package/lib/main.d.ts
CHANGED
|
@@ -127,15 +127,10 @@ declare class Config {
|
|
|
127
127
|
files(_sourcePath?: string): Promise<string[]>;
|
|
128
128
|
serverOpts(prefix?: string, port?: number): Required<ServerOptions>;
|
|
129
129
|
browserOpts(name?: string, mode?: "module" | "global"): Required<BrowserOptions>;
|
|
130
|
+
databaseOpts(): Required<DatabaseOptions>;
|
|
130
131
|
networkOpts(): NetworkOptions;
|
|
131
132
|
fakerOpts(locale?: FakerLocale): Required<FakerEngineOptions>;
|
|
132
133
|
generateInFileRuntimeConfig(dirname: string, options: ServerCLIOptions): Promise<void>;
|
|
133
|
-
get database(): {
|
|
134
|
-
enabled: () => boolean;
|
|
135
|
-
directoryPath: () => string;
|
|
136
|
-
};
|
|
137
|
-
private initializeDatabase;
|
|
138
|
-
private modifyGitignoreFile;
|
|
139
134
|
private tryStat;
|
|
140
135
|
private isReadable;
|
|
141
136
|
private resolveSourcePath;
|
package/lib/main.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import N from'fast-glob';import p from'path';import L from'fs-extra';import {stat,access,constants}from'fs/promises';import M from'is-glob';import l from'winston';import f from'picocolors';import {transform}from'esbuild';function F(o){switch(o){case "info":return f.blueBright(o.toUpperCase());case "error":return f.redBright(o.toUpperCase());case "warn":return f.yellowBright(o.toUpperCase());default:return o.toUpperCase()}}var x=l.format.printf(({level:o,message:t,timestamp:e})=>`${f.dim(`[${e}]`)} ${F(o)} ${t}`),c=l.createLogger({format:l.format.combine(l.format.timestamp(),l.format.splat(),x),transports:[new l.transports.Console]}),C=new Intl.ListFormat("en",{style:"long",type:"unit"}),a=class{static info(t,...e){return c.info(t,...e)}static warn(t,...e){return c.warn(t,...e)}static error(t,...e){return c.error(t,...e)}static debug(t,...e){if(!(typeof process>"u"||!process.env.DEBUG))return c.info(t,...e)}static list(t){return C.format(t)}static close(){c.removeAllListeners(),c.close();}};var y="fakelab",g="module",h=["af","ar","az","bn","cs","cy","da","de","dv","el","en","eo","es","fa","fi","fr","he","hr","hu","hy","id","it","ja","ka","ko","ku","lv","mk","nb","ne","nl","pl","pt","ro"];function w(){let o=Intl.DateTimeFormat().resolvedOptions().locale;if(!o)return "en";let[t]=o.split("-"),e=t.toLowerCase();return h.includes(e)?e:"en"}var O=`let fl = {};
|
|
2
2
|
let db = {};
|
|
3
3
|
fl.url = () => "http://localhost:PORT/PREFIX/";
|
|
4
4
|
fl.fetch = async function (name, count) {
|
|
5
5
|
const search = count ? "?count=" + count : "";
|
|
6
6
|
|
|
7
|
-
const response = await fetch(fl.url() + name + search
|
|
7
|
+
const response = await fetch(fl.url() + name + search);
|
|
8
8
|
|
|
9
9
|
if (!response.ok) throw new Error("[fakelab] Failed to retreived mock data.");
|
|
10
10
|
|
|
@@ -14,10 +14,9 @@ fl.fetch = async function (name, count) {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
db.enabled = () => ENABLED_COND;
|
|
17
|
-
db.get = async function (name) {
|
|
18
|
-
if (!db.enabled()) throw new Error("[fakelab] Database is not enabled.");
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
db.get = async function (name) {
|
|
19
|
+
const response = await fetch(NAME.url() + "database/" + name);
|
|
21
20
|
|
|
22
21
|
if (!response.ok) throw new Error("[fakelab] Failed to retreived data from database.");
|
|
23
22
|
|
|
@@ -26,28 +25,29 @@ db.get = async function (name) {
|
|
|
26
25
|
return result;
|
|
27
26
|
};
|
|
28
27
|
db.post = async function (name) {
|
|
29
|
-
if (!db.enabled()) throw new Error("[fakelab] Database is not enabled.");
|
|
30
|
-
|
|
31
28
|
const response = await fetch(NAME.url() + "database/" + name, { method: "POST", headers: {"Content-Type": "application/json" } });
|
|
32
29
|
|
|
33
30
|
if (!response.ok) throw new Error("[fakelab] Failed to post data to database.");
|
|
34
31
|
};
|
|
35
|
-
db.seed = async function (name,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const response = await fetch(NAME.url() + "database/seed/" + name, { method: "POST", body: JSON.stringify({ count }), headers: {"Content-Type": "application/json" } });
|
|
32
|
+
db.seed = async function (name, options) {
|
|
33
|
+
const response = await fetch(NAME.url() + "database/insert/" + name, { method: "POST", body: JSON.stringify(options), headers: {"Content-Type": "application/json" } });
|
|
39
34
|
|
|
40
35
|
if (!response.ok) throw new Error("[fakelab] Failed to seed data to database.");
|
|
41
36
|
};
|
|
37
|
+
db.flush = async function (name) {
|
|
38
|
+
const response = await fetch(NAME.url() + "database/flush/" + name, { method: "POST" });
|
|
42
39
|
|
|
40
|
+
if (!response.ok) throw new Error("[fakelab] Failed to flush seeded data from database.");
|
|
41
|
+
};
|
|
43
42
|
|
|
44
43
|
const NAME = Object.freeze(fl);
|
|
45
44
|
const database = Object.freeze(db);
|
|
46
45
|
|
|
47
|
-
export { NAME, database };`,
|
|
48
|
-
declare function get<T extends keyof Runtime$>(name: T): Promise<Runtime$[T]
|
|
46
|
+
export { NAME, database };`,T=`declare function fetch<T extends keyof Runtime$, CT extends number | undefined = undefined>(name: T, count?: CT): Promise<Result$<Runtime$[T], CT>>;
|
|
47
|
+
declare function get<T extends keyof Runtime$>(name: T): Promise<Array<Runtime$[T]>>;
|
|
49
48
|
declare function post<T extends keyof Runtime$>(name: T): Promise<void>;
|
|
50
|
-
declare function seed<T extends keyof Runtime$>(name: T,
|
|
49
|
+
declare function seed<T extends keyof Runtime$>(name: T, options?: SeedOptions): Promise<void>;
|
|
50
|
+
declare function flush<T extends keyof Runtime$>(name: T): Promise<void>;
|
|
51
51
|
declare function enabled(): boolean;
|
|
52
52
|
declare function url(): string;
|
|
53
53
|
declare const NAME: {
|
|
@@ -58,18 +58,34 @@ declare const database: {
|
|
|
58
58
|
get: typeof get;
|
|
59
59
|
post: typeof post;
|
|
60
60
|
seed: typeof seed;
|
|
61
|
+
flush: typeof flush;
|
|
61
62
|
enabled: typeof enabled;
|
|
62
63
|
};
|
|
64
|
+
type SeedOptions = {
|
|
65
|
+
/**
|
|
66
|
+
* Number of records to generate.
|
|
67
|
+
*/
|
|
68
|
+
count?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Defines how seeding interacts with existing database data.
|
|
71
|
+
* - \`"reset"\`: Removes all existing data and recreates it from scratch.
|
|
72
|
+
* - \`"once"\`: Seeds data only if the database is empty.
|
|
73
|
+
* - \`"merge"\`: Inserts new records and updates existing ones. The total number of items per table
|
|
74
|
+
* is limited to \`1000\` records.
|
|
75
|
+
* @default "reset"
|
|
76
|
+
*/
|
|
77
|
+
strategy?: "reset" | "once" | "merge"
|
|
78
|
+
}
|
|
63
79
|
type Result$<T, CT> = CT extends number ? (CT extends 0 ? T : T[]) : T;
|
|
64
80
|
interface Runtime$ {}
|
|
65
81
|
|
|
66
|
-
export { NAME, database };`,
|
|
82
|
+
export { NAME, database };`,A=`global.NAME = {};
|
|
67
83
|
global.NAME.database = {};
|
|
68
84
|
global.NAME.url = () => "http://localhost:PORT/PREFIX/";
|
|
69
85
|
global.NAME.fetch = async function (name, count) {
|
|
70
86
|
const search = count ? "?count=" + count : "";
|
|
71
87
|
|
|
72
|
-
const response = await fetch(global.NAME.url() + name + search
|
|
88
|
+
const response = await fetch(global.NAME.url() + name + search);
|
|
73
89
|
|
|
74
90
|
if (!response.ok) throw new Error("[fakelab] Failed to retreived mock data.");
|
|
75
91
|
|
|
@@ -79,9 +95,7 @@ global.NAME.fetch = async function (name, count) {
|
|
|
79
95
|
};
|
|
80
96
|
global.NAME.database.enabled = () => ENABLED_COND;
|
|
81
97
|
global.NAME.database.get = async function (name) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const response = await fetch(global.NAME.url() + "database/" + name, { headers: {"Content-Type": "application/json" } });
|
|
98
|
+
const response = await fetch(global.NAME.url() + "database/" + name);
|
|
85
99
|
|
|
86
100
|
if (!response.ok) throw new Error("[fakelab] Failed to retreived data from database.");
|
|
87
101
|
|
|
@@ -90,37 +104,54 @@ global.NAME.database.get = async function (name) {
|
|
|
90
104
|
return result;
|
|
91
105
|
};
|
|
92
106
|
global.NAME.database.post = async function (name) {
|
|
93
|
-
if (!global.NAME.database.enabled()) throw new Error("[fakelab] Database is not enabled.");
|
|
94
|
-
|
|
95
107
|
const response = await fetch(global.NAME.url() + "database/" + name, { method: "POST", headers: {"Content-Type": "application/json" } });
|
|
96
108
|
|
|
97
109
|
if (!response.ok) throw new Error("[fakelab] Failed to post data to database.");
|
|
98
110
|
};
|
|
99
|
-
global.NAME.database.seed = async function (name,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const response = await fetch(global.NAME.url() + "database/seed/" + name, { method: "POST", body: JSON.stringify({ count }), headers: {"Content-Type": "application/json" } });
|
|
111
|
+
global.NAME.database.seed = async function (name, options) {
|
|
112
|
+
const response = await fetch(global.NAME.url() + "database/insert/" + name, { method: "POST", body: JSON.stringify(options), headers: {"Content-Type": "application/json" } });
|
|
103
113
|
|
|
104
114
|
if (!response.ok) throw new Error("[fakelab] Failed to seed data to database.");
|
|
105
115
|
};
|
|
116
|
+
global.NAME.database.flush = async function (name) {
|
|
117
|
+
const response = await fetch(global.NAME.url() + "database/flush/" + name, { method: "POST" });
|
|
118
|
+
|
|
119
|
+
if (!response.ok) throw new Error("[fakelab] Failed to flush seeded data from database.");
|
|
120
|
+
};
|
|
106
121
|
|
|
107
122
|
const NAME = Object.freeze(fl);
|
|
108
123
|
const database = Object.freeze(db);
|
|
109
124
|
|
|
110
|
-
export { NAME, database };`,
|
|
125
|
+
export { NAME, database };`,k=`export {};
|
|
111
126
|
|
|
112
127
|
declare global {
|
|
113
128
|
const database: {
|
|
114
|
-
|
|
115
|
-
get<T extends keyof Runtime$>(name: T): Promise<Runtime$[T]>;
|
|
129
|
+
get<T extends keyof Runtime$>(name: T): Promise<Array<Runtime$[T]>>;
|
|
116
130
|
post(name: keyof Runtime$): Promise<void>;
|
|
117
|
-
seed(name: keyof Runtime$,
|
|
131
|
+
seed(name: keyof Runtime$, options?: SeedOptions): Promise<void>;
|
|
132
|
+
flush(name: keyof Runtime$): Promise<void>;
|
|
133
|
+
enabled(): boolean;
|
|
118
134
|
};
|
|
119
135
|
const NAME: {
|
|
120
136
|
fetch<T extends keyof Runtime$, CT extends number | undefined = undefined>(name: T, count?: CT): Promise<Result$<Runtime$[T], CT>>;
|
|
121
137
|
url(): string;
|
|
122
138
|
database: typeof database;
|
|
123
139
|
};
|
|
140
|
+
type SeedOptions = {
|
|
141
|
+
/**
|
|
142
|
+
* Number of records to generate.
|
|
143
|
+
*/
|
|
144
|
+
count?: number;
|
|
145
|
+
/**
|
|
146
|
+
* Defines how seeding interacts with existing database data.
|
|
147
|
+
* - \`"reset"\`: Removes all existing data and recreates it from scratch.
|
|
148
|
+
* - \`"once"\`: Seeds data only if the database is empty.
|
|
149
|
+
* - \`"merge"\`: Inserts new records and updates existing ones. The total number of items per table
|
|
150
|
+
* is limited to \`1000\` records.
|
|
151
|
+
* @default "reset"
|
|
152
|
+
*/
|
|
153
|
+
strategy?: "reset" | "once" | "merge"
|
|
154
|
+
}
|
|
124
155
|
type Result$<T, CT> = CT extends number ? (CT extends 0 ? T : T[]) : T;
|
|
125
156
|
interface Runtime$ {}
|
|
126
157
|
interface Window {
|
|
@@ -132,6 +163,4 @@ declare global {
|
|
|
132
163
|
NAME: typeof NAME;
|
|
133
164
|
}
|
|
134
165
|
}
|
|
135
|
-
}`;var
|
|
136
|
-
`).some(a=>a.trim()===e.trim()))return;await d.appendFile(t,`
|
|
137
|
-
${e}`);}catch{s.error("Could not modify .gitignore file.");}}async tryStat(e){try{return await stat(e)}catch{return null}}async isReadable(e){try{return await access(e,constants.R_OK),!0}catch{return false}}resolveSourcePath(e){return (Array.isArray(e)?e:[e]).map(r=>C(r,{strict:true})?r:i.resolve(r))}async resolveTSFiles(e){if(C(e,{strict:true}))return s.info("source: %s",e),L(e,{absolute:true,ignore:["**/*.d.ts"]});let t=i.resolve(e),r=t.endsWith(".ts")?t:t+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("source: %s",r),[r]):(await this.tryStat(t))?.isDirectory()?(s.info("source: %s",t),L("**/*.ts",{cwd:t,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",i.basename(r)),[])}};function U(o){return new m(o)}export{U as defineConfig};
|
|
166
|
+
}`;var E=class{constructor(t,e,r,s){this.port=t;this.prefix=e;this.browserOptions=r;this.dbOptions=s;this.name=this.browserOptions?.expose?.name||"fakelab",this.databaseEnabled=this.dbOptions?.enabled??true?"true":"false";}name;databaseEnabled;transformOptions={minify:true,platform:"browser",target:"es2022"};replacer(t,e){let r=t;for(let s in e)r=r.replace(new RegExp(s,"g"),e[s].toString().trim());return r}async prepareGlobalSource(){let t=this.replacer(A,{NAME:this.name,PORT:this.port,PREFIX:this.prefix,ENABLED_COND:this.databaseEnabled});try{let{code:e}=await transform(t,this.transformOptions);return e}catch(e){return e instanceof Error&&a.warn(e.message),t}}globalDeclaration(){return this.replacer(k,{NAME:this.name})}},u=class o extends E{constructor(e,r,s,i){super(e,r,s,i);this.port=e;this.prefix=r;this.browserOptions=s;this.dbOptions=i;}static init(e,r,s,i){let b=new o(e,r,s,i);return new Proxy(b,{get(n,d){return d==="prepareSource"?s?.expose?.mode==="global"?n.prepareGlobalSource:n.prepareSource:d==="declaration"?s?.expose?.mode==="global"?n.globalDeclaration:n.declaration:n[d]}})}async prepareSource(){let e=this.replacer(O,{NAME:this.name,PORT:this.port,PREFIX:this.prefix,ENABLED_COND:this.databaseEnabled});try{let{code:r}=await transform(e,this.transformOptions);return r}catch(r){return r instanceof Error&&a.warn(r.message),e}}declaration(){return this.replacer(T,{NAME:this.name})}};var m=class{constructor(t){this.opts=t;this.files=this.files.bind(this),this.serverOpts=this.serverOpts.bind(this),this.fakerOpts=this.fakerOpts.bind(this),this.browserOpts=this.browserOpts.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.opts.network?.delay||0,errorRate:this.opts.network?.errorRate||0,timeoutRate:this.opts.network?.timeoutRate||0,offline:this.opts.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";RUNTIME_DECL_FILENAME="runtime.d.ts";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;async files(t){let e=this.resolveSourcePath(t||this.opts.sourcePath),r=Array.from(new Set((await Promise.all(e.map(s=>this.resolveTSFiles(s)))).flat()));return r.length===0&&(a.error("No Typescript files found in: %s",a.list(e.map(s=>p.basename(s)))),process.exit(1)),r}serverOpts(t,e){return {pathPrefix:t||this.opts.server?.pathPrefix||"api",port:e||this.opts.server?.port||5200}}browserOpts(t,e){return {expose:{mode:e||this.opts.browser?.expose?.mode||g,name:t||this.opts.browser?.expose?.name||y}}}databaseOpts(){return {enabled:this.opts.database?.enabled??false,dest:this.opts?.database?.dest||"db"}}networkOpts(){let t=this.opts.network?.preset,e=this.opts.network?.presets??{};return !t||!e[t]?this.NETWORK_DEFAULT_OPTIONS:{...e[t],...this.opts.network??{}}}fakerOpts(t){let e=(t||this.opts.faker?.locale)?.toLowerCase();return e&&h.includes(e)?{locale:e}:{locale:w()}}async generateInFileRuntimeConfig(t,e){let{port:r,pathPrefix:s}=this.serverOpts(e.pathPrefix,e.port),i=p.resolve(t,this.RUNTIME_SOURCE_FILENAME),b=p.resolve(t,this.RUNTIME_DECL_FILENAME),n=u.init(r,s,this.opts.browser,this.opts.database),d=await n.prepareSource();await Promise.all([L.writeFile(i,d),L.writeFile(b,n.declaration())]);}async tryStat(t){try{return await stat(t)}catch{return null}}async isReadable(t){try{return await access(t,constants.R_OK),!0}catch{return false}}resolveSourcePath(t){return (Array.isArray(t)?t:[t]).map(r=>M(r,{strict:true})?r:p.resolve(r))}async resolveTSFiles(t){if(M(t,{strict:true}))return a.info("source: %s",t),N(t,{absolute:true,ignore:["**/*.d.ts"]});let e=p.resolve(t),r=e.endsWith(".ts")?e:e+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(a.error("Cannot read file: %s",r),process.exit(1)),a.info("source: %s",r),[r]):(await this.tryStat(e))?.isDirectory()?(a.info("source: %s",e),N("**/*.ts",{cwd:e,absolute:true,ignore:["**/*.d.ts"]})):(a.warn("invalid source: [REDACTED]/%s",p.basename(r)),[])}};function U(o){return new m(o)}export{U as defineConfig};
|