@koltakov/ffa-core 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -22
- package/dist/cli.js +115 -67
- package/dist/index.d.ts +65 -5
- package/dist/index.js +143 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,8 +61,22 @@ export default defineConfig({
|
|
|
61
61
|
title: string().required(),
|
|
62
62
|
body: string().required(),
|
|
63
63
|
status: enumField(['draft', 'published', 'archived']).required(),
|
|
64
|
-
price: number().
|
|
64
|
+
price: number().price().required(),
|
|
65
65
|
authorId: belongsTo('User'),
|
|
66
|
+
}, {
|
|
67
|
+
count: 30,
|
|
68
|
+
meta: {
|
|
69
|
+
// Static values — always present in meta regardless of filters
|
|
70
|
+
currency: 'USD',
|
|
71
|
+
supportedStatuses: ['draft', 'published', 'archived'],
|
|
72
|
+
|
|
73
|
+
// Functions — receive filtered items (before pagination), return computed value
|
|
74
|
+
total: (items) => items.length,
|
|
75
|
+
avgPrice: (items) => +(items.reduce((s, i) => s + (i.price as number), 0) / items.length).toFixed(2),
|
|
76
|
+
byStatus: (items) => Object.fromEntries(
|
|
77
|
+
['draft', 'published', 'archived'].map(s => [s, items.filter(i => i.status === s).length])
|
|
78
|
+
),
|
|
79
|
+
},
|
|
66
80
|
}),
|
|
67
81
|
},
|
|
68
82
|
})
|
|
@@ -144,7 +158,22 @@ GET /posts?search=react&status=published&sort=price&order=asc&page=1&limit=20
|
|
|
144
158
|
```json
|
|
145
159
|
{
|
|
146
160
|
"data": [ { "id": "...", "title": "...", ... } ],
|
|
147
|
-
"meta": {
|
|
161
|
+
"meta": {
|
|
162
|
+
"pagination": { "total": 47, "page": 1, "limit": 10, "pages": 5 }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
With custom meta fields (see [Custom Meta](#custom-meta) below):
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"data": [ ... ],
|
|
171
|
+
"meta": {
|
|
172
|
+
"pagination": { "total": 23, "page": 1, "limit": 10, "pages": 3 },
|
|
173
|
+
"currency": "USD",
|
|
174
|
+
"avgPrice": 149.99,
|
|
175
|
+
"byStatus": { "draft": 5, "published": 15, "archived": 3 }
|
|
176
|
+
}
|
|
148
177
|
}
|
|
149
178
|
```
|
|
150
179
|
|
|
@@ -152,6 +181,62 @@ Also sets `X-Total-Count: 47` header.
|
|
|
152
181
|
|
|
153
182
|
---
|
|
154
183
|
|
|
184
|
+
## Custom Meta
|
|
185
|
+
|
|
186
|
+
Define `meta` on any entity to enrich the list response. Meta values can be **static** (always included as-is) or **functions** (computed on the current filtered dataset, before pagination).
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import type { MetaFn } from '@koltakov/ffa-core'
|
|
190
|
+
|
|
191
|
+
Product: entity({
|
|
192
|
+
price: number().price().required(),
|
|
193
|
+
status: enumField(['active', 'sale', 'soldout']).required(),
|
|
194
|
+
rating: number().rating().required(),
|
|
195
|
+
}, {
|
|
196
|
+
count: 50,
|
|
197
|
+
meta: {
|
|
198
|
+
// Static — always the same regardless of filters
|
|
199
|
+
currency: 'USD',
|
|
200
|
+
apiVersion: 2,
|
|
201
|
+
sortOptions: ['price', 'rating', 'title'],
|
|
202
|
+
|
|
203
|
+
// Functions — receive items after search/filter, before pagination
|
|
204
|
+
total: (items) => items.length,
|
|
205
|
+
avgPrice: (items) => +(items.reduce((s, i) => s + (i.price as number), 0) / items.length).toFixed(2),
|
|
206
|
+
avgRating: (items) => +(items.reduce((s, i) => s + (i.rating as number), 0) / items.length).toFixed(1),
|
|
207
|
+
priceRange: (items) => ({
|
|
208
|
+
min: Math.min(...items.map(i => i.price as number)),
|
|
209
|
+
max: Math.max(...items.map(i => i.price as number)),
|
|
210
|
+
}),
|
|
211
|
+
byStatus: (items) => Object.fromEntries(
|
|
212
|
+
['active', 'sale', 'soldout'].map(s => [s, items.filter(i => i.status === s).length])
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Response when filtering with `?status=active&page=1&limit=10`:
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"data": [ ... ],
|
|
222
|
+
"meta": {
|
|
223
|
+
"pagination": { "total": 31, "page": 1, "limit": 10, "pages": 4 },
|
|
224
|
+
"currency": "USD",
|
|
225
|
+
"apiVersion": 2,
|
|
226
|
+
"sortOptions": ["price", "rating", "title"],
|
|
227
|
+
"total": 31,
|
|
228
|
+
"avgPrice": 124.50,
|
|
229
|
+
"avgRating": 4.1,
|
|
230
|
+
"priceRange": { "min": 9.99, "max": 599.99 },
|
|
231
|
+
"byStatus": { "active": 31, "sale": 0, "soldout": 0 }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
> Meta functions receive only the filtered items — so `avgPrice` and `byStatus` always reflect the current search/filter result, not the full dataset.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
155
240
|
## Relations
|
|
156
241
|
|
|
157
242
|
```ts
|
|
@@ -180,8 +265,8 @@ Returns the related objects inline instead of just ids.
|
|
|
180
265
|
|
|
181
266
|
| Factory | Faker output | Notes |
|
|
182
267
|
|-----------------------|----------------------------------|--------------------------------|
|
|
183
|
-
| `string()` | Smart by field name or word |
|
|
184
|
-
| `number()` | Integer 0–1000 |
|
|
268
|
+
| `string()` | Smart by field name or word | Chainable hints — see below |
|
|
269
|
+
| `number()` | Integer 0–1000 | Chainable hints — see below |
|
|
185
270
|
| `boolean()` | `true` / `false` | |
|
|
186
271
|
| `uuid()` | UUID v4 | |
|
|
187
272
|
| `datetime()` | ISO 8601 string | |
|
|
@@ -189,24 +274,124 @@ Returns the related objects inline instead of just ids.
|
|
|
189
274
|
| `belongsTo('Entity')` | Random id from that entity | |
|
|
190
275
|
| `hasMany('Entity')` | Array of 1–3 ids from that entity| |
|
|
191
276
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## DSL Fake Hints
|
|
280
|
+
|
|
281
|
+
When field name alone isn't enough, chain a hint method to control exactly what gets generated.
|
|
282
|
+
Hints take priority over smart field-name detection.
|
|
283
|
+
|
|
284
|
+
### `string()` hints
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
// Internet
|
|
288
|
+
string().email() // → "user@example.com"
|
|
289
|
+
string().url() // → "https://example.com"
|
|
290
|
+
string().domain() // → "example.com"
|
|
291
|
+
string().ip() // → "192.168.1.1"
|
|
292
|
+
string().username() // → "john_doe"
|
|
293
|
+
|
|
294
|
+
// Media
|
|
295
|
+
string().image() // → "https://loremflickr.com/640/480"
|
|
296
|
+
string().avatar() // → "https://avatars.githubusercontent.com/..."
|
|
297
|
+
|
|
298
|
+
// Person
|
|
299
|
+
string().firstName() // → "Alice"
|
|
300
|
+
string().lastName() // → "Johnson"
|
|
301
|
+
string().fullName() // → "Alice Johnson"
|
|
302
|
+
string().phone() // → "+1-555-234-5678"
|
|
303
|
+
|
|
304
|
+
// Location
|
|
305
|
+
string().city() // → "Berlin"
|
|
306
|
+
string().country() // → "Germany"
|
|
307
|
+
string().address() // → "12 Oak Street"
|
|
308
|
+
string().zip() // → "10115"
|
|
309
|
+
string().locale() // → "DE"
|
|
310
|
+
|
|
311
|
+
// Business
|
|
312
|
+
string().company() // → "Acme Corp"
|
|
313
|
+
string().jobTitle() // → "Senior Engineer"
|
|
314
|
+
string().department() // → "Electronics"
|
|
315
|
+
string().currency() // → "EUR"
|
|
316
|
+
|
|
317
|
+
// Text
|
|
318
|
+
string().word() // → "matrix"
|
|
319
|
+
string().slug() // → "hello-world"
|
|
320
|
+
string().sentence() // → "The quick brown fox."
|
|
321
|
+
string().paragraph() // → "Lorem ipsum dolor..."
|
|
322
|
+
string().bio() // → "Lorem ipsum dolor..."
|
|
323
|
+
|
|
324
|
+
// Visual
|
|
325
|
+
string().color() // → "azure"
|
|
326
|
+
string().hexColor() // → "#A3F5C2"
|
|
327
|
+
|
|
328
|
+
// Id
|
|
329
|
+
string().uuid() // → "a3f7c2b1-..."
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### `number()` hints
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
number().price() // → 19.99
|
|
336
|
+
number().age() // → 34
|
|
337
|
+
number().rating() // → 4
|
|
338
|
+
number().percent() // → 72
|
|
339
|
+
number().lat() // → 51.5074
|
|
340
|
+
number().lng() // → -0.1278
|
|
341
|
+
number().year() // → 2021
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Escape hatch
|
|
345
|
+
|
|
346
|
+
For any case not covered by the methods above, use `.fake(hint)` directly with full TypeScript autocomplete:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
string().fake('hexColor')
|
|
350
|
+
number().fake('rating')
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Example config
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
User: entity({
|
|
357
|
+
name: string().fullName().required(),
|
|
358
|
+
email: string().email().required(),
|
|
359
|
+
avatar: string().avatar().optional(),
|
|
360
|
+
bio: string().paragraph().optional(),
|
|
361
|
+
role: enumField(['admin', 'editor', 'viewer']).required(),
|
|
362
|
+
}),
|
|
363
|
+
|
|
364
|
+
Product: entity({
|
|
365
|
+
title: string().sentence().required(),
|
|
366
|
+
image: string().image().required(),
|
|
367
|
+
price: number().price().required(),
|
|
368
|
+
rating: number().rating().required(),
|
|
369
|
+
company: string().company().required(),
|
|
370
|
+
color: string().color().optional(),
|
|
371
|
+
lat: number().lat().required(),
|
|
372
|
+
lng: number().lng().required(),
|
|
373
|
+
}),
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Smart Faker by Field Name (auto, no hint needed)
|
|
377
|
+
|
|
378
|
+
If the field name matches a known keyword, ffa generates the right value automatically — no hint required:
|
|
379
|
+
|
|
380
|
+
| Field name pattern | Generated value |
|
|
381
|
+
|-----------------------------|-------------------------|
|
|
382
|
+
| `email`, `mail` | Email address |
|
|
383
|
+
| `name`, `firstName` | First name |
|
|
384
|
+
| `lastName`, `surname` | Last name |
|
|
385
|
+
| `phone`, `tel`, `mobile` | Phone number |
|
|
386
|
+
| `city`, `country`, `address`| Location values |
|
|
387
|
+
| `url`, `website`, `link` | URL |
|
|
388
|
+
| `avatar`, `photo` | Avatar URL |
|
|
389
|
+
| `image` | Image URL |
|
|
390
|
+
| `company` | Company name |
|
|
391
|
+
| `title`, `heading` | Short sentence |
|
|
392
|
+
| `description`, `bio`, `text`| Paragraph |
|
|
393
|
+
| `price`, `cost`, `amount` | Price |
|
|
394
|
+
| `color` | Color name |
|
|
210
395
|
|
|
211
396
|
---
|
|
212
397
|
|
|
@@ -219,6 +404,7 @@ Field names drive faker output automatically:
|
|
|
219
404
|
| `.min(n)` | `string`, `number` | Min length / min value |
|
|
220
405
|
| `.max(n)` | `string`, `number` | Max length / max value |
|
|
221
406
|
| `.readonly()` | all | Excluded from create/update validation |
|
|
407
|
+
| `.fake(hint)` | `string`, `number` | Explicit faker hint (see DSL hints above) |
|
|
222
408
|
|
|
223
409
|
---
|
|
224
410
|
|
package/dist/cli.js
CHANGED
|
@@ -63,77 +63,121 @@ async function loadConfig() {
|
|
|
63
63
|
// src/memory-db.ts
|
|
64
64
|
import { faker } from "@faker-js/faker";
|
|
65
65
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
66
|
-
var
|
|
66
|
+
var HINT_MAP = {
|
|
67
|
+
// internet
|
|
67
68
|
email: () => faker.internet.email(),
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
url: () => faker.internet.url(),
|
|
70
|
+
domain: () => faker.internet.domainName(),
|
|
71
|
+
ip: () => faker.internet.ip(),
|
|
72
|
+
username: () => faker.internet.username(),
|
|
73
|
+
// media
|
|
74
|
+
image: () => faker.image.url(),
|
|
75
|
+
avatar: () => faker.image.avatar(),
|
|
76
|
+
// person
|
|
77
|
+
firstName: () => faker.person.firstName(),
|
|
78
|
+
lastName: () => faker.person.lastName(),
|
|
79
|
+
fullName: () => faker.person.fullName(),
|
|
80
|
+
// contact
|
|
77
81
|
phone: () => faker.phone.number(),
|
|
78
|
-
|
|
79
|
-
mobile: () => faker.phone.number(),
|
|
82
|
+
// location
|
|
80
83
|
city: () => faker.location.city(),
|
|
81
84
|
country: () => faker.location.country(),
|
|
82
85
|
address: () => faker.location.streetAddress(),
|
|
83
86
|
zip: () => faker.location.zipCode(),
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
website: () => faker.internet.url(),
|
|
87
|
-
link: () => faker.internet.url(),
|
|
88
|
-
avatar: () => faker.image.avatar(),
|
|
89
|
-
photo: () => faker.image.avatar(),
|
|
90
|
-
image: () => faker.image.avatar(),
|
|
87
|
+
locale: () => faker.location.countryCode("alpha-2"),
|
|
88
|
+
// business
|
|
91
89
|
company: () => faker.company.name(),
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
jobTitle: () => faker.person.jobTitle(),
|
|
91
|
+
department: () => faker.commerce.department(),
|
|
92
|
+
currency: () => faker.finance.currencyCode(),
|
|
93
|
+
// text
|
|
94
|
+
word: () => faker.lorem.word(),
|
|
95
|
+
slug: () => faker.helpers.slugify(faker.lorem.words(2)),
|
|
96
|
+
sentence: () => faker.lorem.sentence(),
|
|
97
|
+
paragraph: () => faker.lorem.paragraph(),
|
|
95
98
|
bio: () => faker.lorem.paragraph(),
|
|
96
|
-
|
|
99
|
+
// visual
|
|
97
100
|
color: () => faker.color.human(),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
hexColor: () => faker.color.rgb({ format: "hex" }),
|
|
102
|
+
// id
|
|
103
|
+
uuid: () => faker.string.uuid(),
|
|
104
|
+
// number hints
|
|
105
|
+
price: () => parseFloat(faker.commerce.price()),
|
|
106
|
+
age: () => faker.number.int({ min: 18, max: 80 }),
|
|
107
|
+
rating: () => faker.number.int({ min: 1, max: 5 }),
|
|
108
|
+
percent: () => faker.number.int({ min: 0, max: 100 }),
|
|
109
|
+
lat: () => parseFloat(faker.location.latitude().toString()),
|
|
110
|
+
lng: () => parseFloat(faker.location.longitude().toString()),
|
|
111
|
+
year: () => faker.number.int({ min: 1990, max: (/* @__PURE__ */ new Date()).getFullYear() })
|
|
112
|
+
};
|
|
113
|
+
var FIELD_NAME_MAP = {
|
|
114
|
+
email: HINT_MAP.email,
|
|
115
|
+
mail: HINT_MAP.email,
|
|
116
|
+
name: HINT_MAP.firstName,
|
|
117
|
+
firstname: HINT_MAP.firstName,
|
|
118
|
+
first_name: HINT_MAP.firstName,
|
|
119
|
+
lastname: HINT_MAP.lastName,
|
|
120
|
+
last_name: HINT_MAP.lastName,
|
|
121
|
+
surname: HINT_MAP.lastName,
|
|
122
|
+
fullname: HINT_MAP.fullName,
|
|
123
|
+
username: HINT_MAP.username,
|
|
124
|
+
phone: HINT_MAP.phone,
|
|
125
|
+
tel: HINT_MAP.phone,
|
|
126
|
+
mobile: HINT_MAP.phone,
|
|
127
|
+
city: HINT_MAP.city,
|
|
128
|
+
country: HINT_MAP.country,
|
|
129
|
+
address: HINT_MAP.address,
|
|
130
|
+
zip: HINT_MAP.zip,
|
|
131
|
+
postal: HINT_MAP.zip,
|
|
132
|
+
url: HINT_MAP.url,
|
|
133
|
+
website: HINT_MAP.url,
|
|
134
|
+
link: HINT_MAP.url,
|
|
135
|
+
avatar: HINT_MAP.avatar,
|
|
136
|
+
photo: HINT_MAP.avatar,
|
|
137
|
+
image: HINT_MAP.image,
|
|
138
|
+
company: HINT_MAP.company,
|
|
139
|
+
title: HINT_MAP.sentence,
|
|
140
|
+
heading: HINT_MAP.sentence,
|
|
141
|
+
description: HINT_MAP.paragraph,
|
|
142
|
+
bio: HINT_MAP.bio,
|
|
143
|
+
text: HINT_MAP.paragraph,
|
|
144
|
+
color: HINT_MAP.color,
|
|
145
|
+
price: HINT_MAP.price,
|
|
146
|
+
cost: HINT_MAP.price,
|
|
147
|
+
amount: HINT_MAP.price
|
|
101
148
|
};
|
|
102
149
|
function generateFakeValue(def, fieldName, db) {
|
|
103
|
-
if (def.
|
|
104
|
-
if (def.rules.enumValues?.length) return faker.helpers.arrayElement(def.rules.enumValues);
|
|
105
|
-
return faker.lorem.word();
|
|
106
|
-
}
|
|
150
|
+
if (def.rules.fakeHint) return HINT_MAP[def.rules.fakeHint]();
|
|
107
151
|
if (def.type === "belongsTo") {
|
|
108
152
|
const target = def.rules.entity;
|
|
109
153
|
if (target && db[target]?.length) {
|
|
110
|
-
|
|
111
|
-
return items[Math.floor(Math.random() * items.length)].id;
|
|
154
|
+
return db[target][Math.floor(Math.random() * db[target].length)].id;
|
|
112
155
|
}
|
|
113
156
|
return faker.string.uuid();
|
|
114
157
|
}
|
|
115
158
|
if (def.type === "hasMany") {
|
|
116
159
|
const target = def.rules.entity;
|
|
117
160
|
if (target && db[target]?.length) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
return [...items].sort(() => Math.random() - 0.5).slice(0, count).map((i) => i.id);
|
|
161
|
+
const count = faker.number.int({ min: 1, max: Math.min(3, db[target].length) });
|
|
162
|
+
return [...db[target]].sort(() => Math.random() - 0.5).slice(0, count).map((i) => i.id);
|
|
121
163
|
}
|
|
122
164
|
return [];
|
|
123
165
|
}
|
|
166
|
+
if (def.type === "enum") {
|
|
167
|
+
if (def.rules.enumValues?.length) return faker.helpers.arrayElement(def.rules.enumValues);
|
|
168
|
+
return faker.lorem.word();
|
|
169
|
+
}
|
|
124
170
|
const normalized = fieldName.toLowerCase().replace(/[-\s]/g, "_");
|
|
125
|
-
|
|
126
|
-
if (mapFn) return mapFn();
|
|
171
|
+
if (FIELD_NAME_MAP[normalized]) return FIELD_NAME_MAP[normalized]();
|
|
127
172
|
for (const key of Object.keys(FIELD_NAME_MAP)) {
|
|
128
173
|
if (normalized.includes(key)) return FIELD_NAME_MAP[key]();
|
|
129
174
|
}
|
|
130
|
-
|
|
131
|
-
switch (type) {
|
|
175
|
+
switch (def.type) {
|
|
132
176
|
case "string":
|
|
133
177
|
return faker.lorem.word();
|
|
134
178
|
case "number": {
|
|
135
|
-
const min = rules.min ?? 0;
|
|
136
|
-
const max = rules.max ?? 1e3;
|
|
179
|
+
const min = def.rules.min ?? 0;
|
|
180
|
+
const max = def.rules.max ?? 1e3;
|
|
137
181
|
return faker.number.int({ min, max });
|
|
138
182
|
}
|
|
139
183
|
case "boolean":
|
|
@@ -160,41 +204,31 @@ function createMemoryDB(entities, persistPath) {
|
|
|
160
204
|
try {
|
|
161
205
|
const raw = readFileSync2(persistPath, "utf-8");
|
|
162
206
|
const saved = JSON.parse(raw);
|
|
163
|
-
for (const name in entities)
|
|
164
|
-
db[name] = saved[name] ?? [];
|
|
165
|
-
}
|
|
207
|
+
for (const name in entities) db[name] = saved[name] ?? [];
|
|
166
208
|
} catch {
|
|
167
209
|
}
|
|
168
210
|
}
|
|
169
|
-
function
|
|
211
|
+
function sortedEntityNames() {
|
|
170
212
|
const names = Object.keys(entities);
|
|
171
|
-
|
|
213
|
+
return [
|
|
172
214
|
...names.filter((n) => !Object.values(entities[n].fields).some((f) => f.type === "belongsTo" || f.type === "hasMany")),
|
|
173
215
|
...names.filter((n) => Object.values(entities[n].fields).some((f) => f.type === "belongsTo" || f.type === "hasMany"))
|
|
174
216
|
];
|
|
175
|
-
|
|
217
|
+
}
|
|
218
|
+
function seedAll() {
|
|
219
|
+
for (const name of sortedEntityNames()) {
|
|
176
220
|
const { fields, count = 10 } = entities[name];
|
|
177
221
|
db[name] = Array.from({ length: count }).map(() => generateRecord(fields, db));
|
|
178
222
|
}
|
|
179
223
|
}
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
...names.filter((n) => !Object.values(entities[n].fields).some((f) => f.type === "belongsTo" || f.type === "hasMany")),
|
|
185
|
-
...names.filter((n) => Object.values(entities[n].fields).some((f) => f.type === "belongsTo" || f.type === "hasMany"))
|
|
186
|
-
];
|
|
187
|
-
for (const name of sorted) {
|
|
188
|
-
if (!db[name]) {
|
|
189
|
-
const { fields, count = 10 } = entities[name];
|
|
190
|
-
db[name] = Array.from({ length: count }).map(() => generateRecord(fields, db));
|
|
191
|
-
}
|
|
224
|
+
for (const name of sortedEntityNames()) {
|
|
225
|
+
if (!db[name]) {
|
|
226
|
+
const { fields, count = 10 } = entities[name];
|
|
227
|
+
db[name] = Array.from({ length: count }).map(() => generateRecord(fields, db));
|
|
192
228
|
}
|
|
193
229
|
}
|
|
194
230
|
function persist() {
|
|
195
|
-
if (persistPath)
|
|
196
|
-
writeFileSync(persistPath, JSON.stringify(db, null, 2), "utf-8");
|
|
197
|
-
}
|
|
231
|
+
if (persistPath) writeFileSync(persistPath, JSON.stringify(db, null, 2), "utf-8");
|
|
198
232
|
}
|
|
199
233
|
return {
|
|
200
234
|
list(entity, options = {}) {
|
|
@@ -214,21 +248,33 @@ function createMemoryDB(entities, persistPath) {
|
|
|
214
248
|
const sortKey = options.sort;
|
|
215
249
|
const order = options.order ?? "asc";
|
|
216
250
|
items.sort((a, b) => {
|
|
217
|
-
const aVal = a[sortKey];
|
|
218
|
-
const bVal = b[sortKey];
|
|
251
|
+
const aVal = a[sortKey], bVal = b[sortKey];
|
|
219
252
|
if (aVal == null) return 1;
|
|
220
253
|
if (bVal == null) return -1;
|
|
221
254
|
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
222
255
|
return order === "desc" ? -cmp : cmp;
|
|
223
256
|
});
|
|
224
257
|
}
|
|
258
|
+
const entityMeta = entities[entity]?.meta;
|
|
259
|
+
const customMeta = {};
|
|
260
|
+
if (entityMeta) {
|
|
261
|
+
for (const [key, value] of Object.entries(entityMeta)) {
|
|
262
|
+
customMeta[key] = typeof value === "function" ? value(items) : value;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
225
265
|
const total = items.length;
|
|
226
266
|
const page = options.page ?? 1;
|
|
227
267
|
const limit = options.limit ?? total;
|
|
228
268
|
const pages = limit > 0 ? Math.ceil(total / limit) : 1;
|
|
229
269
|
const offset = (page - 1) * limit;
|
|
230
270
|
const data = options.limit ? items.slice(offset, offset + limit) : items;
|
|
231
|
-
return {
|
|
271
|
+
return {
|
|
272
|
+
data,
|
|
273
|
+
meta: {
|
|
274
|
+
pagination: { total, page, limit, pages },
|
|
275
|
+
...customMeta
|
|
276
|
+
}
|
|
277
|
+
};
|
|
232
278
|
},
|
|
233
279
|
get(entity, id) {
|
|
234
280
|
return db[entity]?.find((item) => item.id === id);
|
|
@@ -741,7 +787,7 @@ program.command("init").description("Create ffa.config.ts in the current directo
|
|
|
741
787
|
console.log(" ffa.config.ts already exists!");
|
|
742
788
|
process.exit(1);
|
|
743
789
|
}
|
|
744
|
-
const template = `import { defineConfig, entity, string, number, boolean, enumField } from 'ffa-core'
|
|
790
|
+
const template = `import { defineConfig, entity, string, number, boolean, enumField } from '@koltakov/ffa-core'
|
|
745
791
|
|
|
746
792
|
export default defineConfig({
|
|
747
793
|
server: { port: 3333 },
|
|
@@ -765,6 +811,8 @@ program.command("reset").description("Delete persisted data file (ffa-data.json)
|
|
|
765
811
|
return;
|
|
766
812
|
}
|
|
767
813
|
unlinkSync(dataPath);
|
|
768
|
-
console.log(
|
|
814
|
+
console.log(
|
|
815
|
+
" ffa-data.json deleted. Restart the server to regenerate seed data."
|
|
816
|
+
);
|
|
769
817
|
});
|
|
770
818
|
program.parse();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
type FieldType = 'string' | 'number' | 'boolean' | 'uuid' | 'datetime' | 'enum' | 'belongsTo' | 'hasMany';
|
|
2
|
+
type StringFakeHint = 'email' | 'url' | 'domain' | 'ip' | 'username' | 'image' | 'avatar' | 'firstName' | 'lastName' | 'fullName' | 'phone' | 'city' | 'country' | 'address' | 'zip' | 'locale' | 'company' | 'jobTitle' | 'department' | 'currency' | 'word' | 'slug' | 'sentence' | 'paragraph' | 'bio' | 'color' | 'hexColor' | 'uuid';
|
|
3
|
+
type NumberFakeHint = 'price' | 'age' | 'rating' | 'percent' | 'lat' | 'lng' | 'year';
|
|
4
|
+
type FakeHint = StringFakeHint | NumberFakeHint;
|
|
5
|
+
/** Function that receives filtered items (before pagination) and returns any value */
|
|
6
|
+
type MetaFn = (items: Record<string, unknown>[]) => unknown;
|
|
7
|
+
/**
|
|
8
|
+
* A meta value is either:
|
|
9
|
+
* - a static value (string, number, boolean, object, array, …)
|
|
10
|
+
* - a MetaFn computed from the current filtered dataset
|
|
11
|
+
*/
|
|
12
|
+
type MetaValue = MetaFn | unknown;
|
|
2
13
|
interface FieldRules {
|
|
3
14
|
required?: boolean;
|
|
4
15
|
min?: number;
|
|
@@ -7,6 +18,7 @@ interface FieldRules {
|
|
|
7
18
|
readonly?: boolean;
|
|
8
19
|
entity?: string;
|
|
9
20
|
enumValues?: string[];
|
|
21
|
+
fakeHint?: FakeHint;
|
|
10
22
|
}
|
|
11
23
|
interface FieldDefinition {
|
|
12
24
|
type: FieldType;
|
|
@@ -15,6 +27,7 @@ interface FieldDefinition {
|
|
|
15
27
|
interface EntityDefinition {
|
|
16
28
|
fields: Record<string, FieldDefinition>;
|
|
17
29
|
count?: number;
|
|
30
|
+
meta?: Record<string, MetaValue>;
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
interface ServerConfig {
|
|
@@ -33,13 +46,15 @@ declare function defineConfig(config: FfaConfig): FfaConfig;
|
|
|
33
46
|
|
|
34
47
|
declare function entity(fields: Record<string, any>, options?: {
|
|
35
48
|
count?: number;
|
|
49
|
+
meta?: Record<string, MetaValue>;
|
|
36
50
|
}): {
|
|
37
51
|
fields: Record<string, any>;
|
|
38
52
|
count: number | undefined;
|
|
53
|
+
meta: Record<string, unknown> | undefined;
|
|
39
54
|
};
|
|
40
55
|
|
|
41
56
|
declare class FieldBuilder {
|
|
42
|
-
|
|
57
|
+
protected def: FieldDefinition;
|
|
43
58
|
constructor(type: FieldType, extraRules?: Partial<FieldRules>);
|
|
44
59
|
required(): this;
|
|
45
60
|
optional(): this;
|
|
@@ -47,11 +62,56 @@ declare class FieldBuilder {
|
|
|
47
62
|
max(value: number): this;
|
|
48
63
|
default(value: any): this;
|
|
49
64
|
readonly(): this;
|
|
65
|
+
/** Explicit faker hint — overrides smart field-name detection */
|
|
66
|
+
fake(hint: FakeHint): this;
|
|
50
67
|
build(): FieldDefinition;
|
|
51
68
|
}
|
|
69
|
+
declare class StringFieldBuilder extends FieldBuilder {
|
|
70
|
+
constructor();
|
|
71
|
+
private hint;
|
|
72
|
+
email(): this;
|
|
73
|
+
url(): this;
|
|
74
|
+
domain(): this;
|
|
75
|
+
ip(): this;
|
|
76
|
+
username(): this;
|
|
77
|
+
image(): this;
|
|
78
|
+
avatar(): this;
|
|
79
|
+
firstName(): this;
|
|
80
|
+
lastName(): this;
|
|
81
|
+
fullName(): this;
|
|
82
|
+
phone(): this;
|
|
83
|
+
city(): this;
|
|
84
|
+
country(): this;
|
|
85
|
+
address(): this;
|
|
86
|
+
zip(): this;
|
|
87
|
+
locale(): this;
|
|
88
|
+
company(): this;
|
|
89
|
+
jobTitle(): this;
|
|
90
|
+
department(): this;
|
|
91
|
+
currency(): this;
|
|
92
|
+
word(): this;
|
|
93
|
+
slug(): this;
|
|
94
|
+
sentence(): this;
|
|
95
|
+
paragraph(): this;
|
|
96
|
+
bio(): this;
|
|
97
|
+
color(): this;
|
|
98
|
+
hexColor(): this;
|
|
99
|
+
uuid(): this;
|
|
100
|
+
}
|
|
101
|
+
declare class NumberFieldBuilder extends FieldBuilder {
|
|
102
|
+
constructor();
|
|
103
|
+
private hint;
|
|
104
|
+
price(): this;
|
|
105
|
+
age(): this;
|
|
106
|
+
rating(): this;
|
|
107
|
+
percent(): this;
|
|
108
|
+
lat(): this;
|
|
109
|
+
lng(): this;
|
|
110
|
+
year(): this;
|
|
111
|
+
}
|
|
52
112
|
|
|
53
|
-
declare const string: () =>
|
|
54
|
-
declare const number: () =>
|
|
113
|
+
declare const string: () => StringFieldBuilder;
|
|
114
|
+
declare const number: () => NumberFieldBuilder;
|
|
55
115
|
declare const boolean: () => FieldBuilder;
|
|
56
116
|
declare const uuid: () => FieldBuilder;
|
|
57
117
|
declare const datetime: () => FieldBuilder;
|
|
@@ -59,6 +119,6 @@ declare const enumField: (values: [string, ...string[]]) => FieldBuilder;
|
|
|
59
119
|
declare const belongsTo: (entity: string) => FieldBuilder;
|
|
60
120
|
declare const hasMany: (entity: string) => FieldBuilder;
|
|
61
121
|
|
|
62
|
-
declare const __ffa_version = "0.
|
|
122
|
+
declare const __ffa_version = "0.8.0";
|
|
63
123
|
|
|
64
|
-
export { type FfaConfig, __ffa_version, belongsTo, boolean, datetime, defineConfig, entity, enumField, hasMany, number, string, uuid };
|
|
124
|
+
export { type FakeHint, type FfaConfig, type MetaFn, type MetaValue, type NumberFakeHint, type StringFakeHint, __ffa_version, belongsTo, boolean, datetime, defineConfig, entity, enumField, hasMany, number, string, uuid };
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,8 @@ function entity(fields, options) {
|
|
|
14
14
|
}
|
|
15
15
|
return {
|
|
16
16
|
fields: builtFields,
|
|
17
|
-
count: options?.count
|
|
17
|
+
count: options?.count,
|
|
18
|
+
meta: options?.meta
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -22,10 +23,7 @@ function entity(fields, options) {
|
|
|
22
23
|
var FieldBuilder = class {
|
|
23
24
|
def;
|
|
24
25
|
constructor(type, extraRules) {
|
|
25
|
-
this.def = {
|
|
26
|
-
type,
|
|
27
|
-
rules: { ...extraRules }
|
|
28
|
-
};
|
|
26
|
+
this.def = { type, rules: { ...extraRules } };
|
|
29
27
|
}
|
|
30
28
|
required() {
|
|
31
29
|
this.def.rules.required = true;
|
|
@@ -51,14 +49,151 @@ var FieldBuilder = class {
|
|
|
51
49
|
this.def.rules.readonly = true;
|
|
52
50
|
return this;
|
|
53
51
|
}
|
|
52
|
+
/** Explicit faker hint — overrides smart field-name detection */
|
|
53
|
+
fake(hint) {
|
|
54
|
+
this.def.rules.fakeHint = hint;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
54
57
|
build() {
|
|
55
58
|
return structuredClone(this.def);
|
|
56
59
|
}
|
|
57
60
|
};
|
|
61
|
+
var StringFieldBuilder = class extends FieldBuilder {
|
|
62
|
+
constructor() {
|
|
63
|
+
super("string");
|
|
64
|
+
}
|
|
65
|
+
hint(h) {
|
|
66
|
+
this.def.rules.fakeHint = h;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
// Internet
|
|
70
|
+
email() {
|
|
71
|
+
return this.hint("email");
|
|
72
|
+
}
|
|
73
|
+
url() {
|
|
74
|
+
return this.hint("url");
|
|
75
|
+
}
|
|
76
|
+
domain() {
|
|
77
|
+
return this.hint("domain");
|
|
78
|
+
}
|
|
79
|
+
ip() {
|
|
80
|
+
return this.hint("ip");
|
|
81
|
+
}
|
|
82
|
+
username() {
|
|
83
|
+
return this.hint("username");
|
|
84
|
+
}
|
|
85
|
+
// Media
|
|
86
|
+
image() {
|
|
87
|
+
return this.hint("image");
|
|
88
|
+
}
|
|
89
|
+
avatar() {
|
|
90
|
+
return this.hint("avatar");
|
|
91
|
+
}
|
|
92
|
+
// Person
|
|
93
|
+
firstName() {
|
|
94
|
+
return this.hint("firstName");
|
|
95
|
+
}
|
|
96
|
+
lastName() {
|
|
97
|
+
return this.hint("lastName");
|
|
98
|
+
}
|
|
99
|
+
fullName() {
|
|
100
|
+
return this.hint("fullName");
|
|
101
|
+
}
|
|
102
|
+
// Contact
|
|
103
|
+
phone() {
|
|
104
|
+
return this.hint("phone");
|
|
105
|
+
}
|
|
106
|
+
// Location
|
|
107
|
+
city() {
|
|
108
|
+
return this.hint("city");
|
|
109
|
+
}
|
|
110
|
+
country() {
|
|
111
|
+
return this.hint("country");
|
|
112
|
+
}
|
|
113
|
+
address() {
|
|
114
|
+
return this.hint("address");
|
|
115
|
+
}
|
|
116
|
+
zip() {
|
|
117
|
+
return this.hint("zip");
|
|
118
|
+
}
|
|
119
|
+
locale() {
|
|
120
|
+
return this.hint("locale");
|
|
121
|
+
}
|
|
122
|
+
// Business
|
|
123
|
+
company() {
|
|
124
|
+
return this.hint("company");
|
|
125
|
+
}
|
|
126
|
+
jobTitle() {
|
|
127
|
+
return this.hint("jobTitle");
|
|
128
|
+
}
|
|
129
|
+
department() {
|
|
130
|
+
return this.hint("department");
|
|
131
|
+
}
|
|
132
|
+
currency() {
|
|
133
|
+
return this.hint("currency");
|
|
134
|
+
}
|
|
135
|
+
// Text
|
|
136
|
+
word() {
|
|
137
|
+
return this.hint("word");
|
|
138
|
+
}
|
|
139
|
+
slug() {
|
|
140
|
+
return this.hint("slug");
|
|
141
|
+
}
|
|
142
|
+
sentence() {
|
|
143
|
+
return this.hint("sentence");
|
|
144
|
+
}
|
|
145
|
+
paragraph() {
|
|
146
|
+
return this.hint("paragraph");
|
|
147
|
+
}
|
|
148
|
+
bio() {
|
|
149
|
+
return this.hint("bio");
|
|
150
|
+
}
|
|
151
|
+
// Visual
|
|
152
|
+
color() {
|
|
153
|
+
return this.hint("color");
|
|
154
|
+
}
|
|
155
|
+
hexColor() {
|
|
156
|
+
return this.hint("hexColor");
|
|
157
|
+
}
|
|
158
|
+
// Id
|
|
159
|
+
uuid() {
|
|
160
|
+
return this.hint("uuid");
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var NumberFieldBuilder = class extends FieldBuilder {
|
|
164
|
+
constructor() {
|
|
165
|
+
super("number");
|
|
166
|
+
}
|
|
167
|
+
hint(h) {
|
|
168
|
+
this.def.rules.fakeHint = h;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
price() {
|
|
172
|
+
return this.hint("price");
|
|
173
|
+
}
|
|
174
|
+
age() {
|
|
175
|
+
return this.hint("age");
|
|
176
|
+
}
|
|
177
|
+
rating() {
|
|
178
|
+
return this.hint("rating");
|
|
179
|
+
}
|
|
180
|
+
percent() {
|
|
181
|
+
return this.hint("percent");
|
|
182
|
+
}
|
|
183
|
+
lat() {
|
|
184
|
+
return this.hint("lat");
|
|
185
|
+
}
|
|
186
|
+
lng() {
|
|
187
|
+
return this.hint("lng");
|
|
188
|
+
}
|
|
189
|
+
year() {
|
|
190
|
+
return this.hint("year");
|
|
191
|
+
}
|
|
192
|
+
};
|
|
58
193
|
|
|
59
194
|
// src/field/factories.ts
|
|
60
|
-
var string = () => new
|
|
61
|
-
var number = () => new
|
|
195
|
+
var string = () => new StringFieldBuilder();
|
|
196
|
+
var number = () => new NumberFieldBuilder();
|
|
62
197
|
var boolean = () => new FieldBuilder("boolean");
|
|
63
198
|
var uuid = () => new FieldBuilder("uuid");
|
|
64
199
|
var datetime = () => new FieldBuilder("datetime");
|
|
@@ -67,7 +202,7 @@ var belongsTo = (entity2) => new FieldBuilder("belongsTo", { entity: entity2 });
|
|
|
67
202
|
var hasMany = (entity2) => new FieldBuilder("hasMany", { entity: entity2 });
|
|
68
203
|
|
|
69
204
|
// src/index.ts
|
|
70
|
-
var __ffa_version = "0.
|
|
205
|
+
var __ffa_version = "0.8.0";
|
|
71
206
|
export {
|
|
72
207
|
__ffa_version,
|
|
73
208
|
belongsTo,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/defineConfig.ts","../src/entity/entity.ts","../src/field/FieldBuilder.ts","../src/field/factories.ts","../src/index.ts"],"sourcesContent":["import type { EntityDefinition } from '../field/types'\n\nexport interface ServerConfig {\n port: number\n cors?: boolean | { origin: string | string[] }\n persist?: boolean | string\n delay?: number | [number, number]\n}\n\nexport interface FfaConfig {\n server: ServerConfig\n entities: Record<string, EntityDefinition>\n}\n\nexport function defineConfig(config: FfaConfig): FfaConfig {\n return {\n server: config.server ?? { port: 3000 },\n entities: config.entities ?? {},\n }\n}\n","
|
|
1
|
+
{"version":3,"sources":["../src/config/defineConfig.ts","../src/entity/entity.ts","../src/field/FieldBuilder.ts","../src/field/factories.ts","../src/index.ts"],"sourcesContent":["import type { EntityDefinition } from '../field/types'\n\nexport interface ServerConfig {\n port: number\n cors?: boolean | { origin: string | string[] }\n persist?: boolean | string\n delay?: number | [number, number]\n}\n\nexport interface FfaConfig {\n server: ServerConfig\n entities: Record<string, EntityDefinition>\n}\n\nexport function defineConfig(config: FfaConfig): FfaConfig {\n return {\n server: config.server ?? { port: 3000 },\n entities: config.entities ?? {},\n }\n}\n","import type { MetaValue } from '../field/types'\n\nexport function entity(\n fields: Record<string, any>,\n options?: { count?: number; meta?: Record<string, MetaValue> },\n) {\n const builtFields: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(fields)) {\n builtFields[key] = value.build()\n }\n\n return {\n fields: builtFields,\n count: options?.count,\n meta: options?.meta,\n }\n}\n","import type { FieldDefinition, FieldRules, FieldType, FakeHint, StringFakeHint, NumberFakeHint } from './types'\n\n// ─── Base builder ────────────────────────────────────────────────────────────\n\nexport class FieldBuilder {\n protected def: FieldDefinition\n\n constructor(type: FieldType, extraRules?: Partial<FieldRules>) {\n this.def = { type, rules: { ...extraRules } }\n }\n\n required(): this {\n this.def.rules.required = true\n return this\n }\n\n optional(): this {\n this.def.rules.required = false\n return this\n }\n\n min(value: number): this {\n this.def.rules.min = value\n return this\n }\n\n max(value: number): this {\n this.def.rules.max = value\n return this\n }\n\n default(value: any): this {\n this.def.rules.default = value\n return this\n }\n\n readonly(): this {\n this.def.rules.readonly = true\n return this\n }\n\n /** Explicit faker hint — overrides smart field-name detection */\n fake(hint: FakeHint): this {\n this.def.rules.fakeHint = hint\n return this\n }\n\n build(): FieldDefinition {\n return structuredClone(this.def)\n }\n}\n\n// ─── String builder ──────────────────────────────────────────────────────────\n\nexport class StringFieldBuilder extends FieldBuilder {\n constructor() {\n super('string')\n }\n\n private hint(h: StringFakeHint): this {\n this.def.rules.fakeHint = h\n return this\n }\n\n // Internet\n email(): this { return this.hint('email') }\n url(): this { return this.hint('url') }\n domain(): this { return this.hint('domain') }\n ip(): this { return this.hint('ip') }\n username(): this { return this.hint('username') }\n\n // Media\n image(): this { return this.hint('image') }\n avatar(): this { return this.hint('avatar') }\n\n // Person\n firstName(): this { return this.hint('firstName') }\n lastName(): this { return this.hint('lastName') }\n fullName(): this { return this.hint('fullName') }\n\n // Contact\n phone(): this { return this.hint('phone') }\n\n // Location\n city(): this { return this.hint('city') }\n country(): this { return this.hint('country') }\n address(): this { return this.hint('address') }\n zip(): this { return this.hint('zip') }\n locale(): this { return this.hint('locale') }\n\n // Business\n company(): this { return this.hint('company') }\n jobTitle(): this { return this.hint('jobTitle') }\n department(): this { return this.hint('department') }\n currency(): this { return this.hint('currency') }\n\n // Text\n word(): this { return this.hint('word') }\n slug(): this { return this.hint('slug') }\n sentence(): this { return this.hint('sentence') }\n paragraph(): this { return this.hint('paragraph') }\n bio(): this { return this.hint('bio') }\n\n // Visual\n color(): this { return this.hint('color') }\n hexColor(): this { return this.hint('hexColor') }\n\n // Id\n uuid(): this { return this.hint('uuid') }\n}\n\n// ─── Number builder ──────────────────────────────────────────────────────────\n\nexport class NumberFieldBuilder extends FieldBuilder {\n constructor() {\n super('number')\n }\n\n private hint(h: NumberFakeHint): this {\n this.def.rules.fakeHint = h\n return this\n }\n\n price(): this { return this.hint('price') }\n age(): this { return this.hint('age') }\n rating(): this { return this.hint('rating') }\n percent(): this { return this.hint('percent') }\n lat(): this { return this.hint('lat') }\n lng(): this { return this.hint('lng') }\n year(): this { return this.hint('year') }\n}\n","import { FieldBuilder, StringFieldBuilder, NumberFieldBuilder } from './FieldBuilder'\n\nexport const string = (): StringFieldBuilder => new StringFieldBuilder()\nexport const number = (): NumberFieldBuilder => new NumberFieldBuilder()\nexport const boolean = () => new FieldBuilder('boolean')\nexport const uuid = () => new FieldBuilder('uuid')\nexport const datetime = () => new FieldBuilder('datetime')\nexport const enumField = (values: [string, ...string[]]) => new FieldBuilder('enum', { enumValues: values })\nexport const belongsTo = (entity: string) => new FieldBuilder('belongsTo', { entity })\nexport const hasMany = (entity: string) => new FieldBuilder('hasMany', { entity })\n","// config\nexport { defineConfig } from './config/defineConfig'\nexport type { FfaConfig } from './config/defineConfig'\n\n// entity\nexport { entity } from './entity/entity'\n\n// field DSL\nexport { string, number, boolean, uuid, datetime, enumField, belongsTo, hasMany } from './field/factories'\nexport type { FakeHint, StringFakeHint, NumberFakeHint, MetaFn, MetaValue } from './field/types'\n\nexport const __ffa_version = '0.8.0'\n"],"mappings":"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","names":["entity"]}
|