@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 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().min(0).max(9999).required(),
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": { "total": 47, "page": 1, "limit": 20, "pages": 3 }
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 | See smart faker below |
184
- | `number()` | Integer 0–1000 | Respects `.min()` / `.max()` |
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
- ### Smart Faker by Field Name
193
-
194
- Field names drive faker output automatically:
195
-
196
- | Field name pattern | Generated value |
197
- |-----------------------------|-------------------------------|
198
- | `email`, `mail` | `faker.internet.email()` |
199
- | `name`, `firstName` | `faker.person.firstName()` |
200
- | `lastName`, `surname` | `faker.person.lastName()` |
201
- | `phone`, `tel`, `mobile` | `faker.phone.number()` |
202
- | `city`, `country`, `address`| Location values |
203
- | `url`, `website`, `link` | `faker.internet.url()` |
204
- | `avatar`, `photo`, `image` | `faker.image.avatar()` |
205
- | `company` | `faker.company.name()` |
206
- | `title`, `heading` | Short sentence |
207
- | `description`, `bio`, `text`| Paragraph |
208
- | `price`, `cost`, `amount` | `faker.commerce.price()` |
209
- | `color` | `faker.color.human()` |
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 FIELD_NAME_MAP = {
66
+ var HINT_MAP = {
67
+ // internet
67
68
  email: () => faker.internet.email(),
68
- mail: () => faker.internet.email(),
69
- name: () => faker.person.firstName(),
70
- firstname: () => faker.person.firstName(),
71
- first_name: () => faker.person.firstName(),
72
- lastname: () => faker.person.lastName(),
73
- last_name: () => faker.person.lastName(),
74
- surname: () => faker.person.lastName(),
75
- fullname: () => faker.person.fullName(),
76
- username: () => faker.person.fullName(),
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
- tel: () => faker.phone.number(),
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
- postal: () => faker.location.zipCode(),
85
- url: () => faker.internet.url(),
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
- title: () => faker.lorem.sentence(3),
93
- heading: () => faker.lorem.sentence(3),
94
- description: () => faker.lorem.paragraph(),
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
- text: () => faker.lorem.paragraph(),
99
+ // visual
97
100
  color: () => faker.color.human(),
98
- price: () => faker.commerce.price(),
99
- cost: () => faker.commerce.price(),
100
- amount: () => faker.commerce.price()
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.type === "enum") {
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
- const items = db[target];
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 items = db[target];
119
- const count = faker.number.int({ min: 1, max: Math.min(3, items.length) });
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
- const mapFn = FIELD_NAME_MAP[normalized];
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
- const { type, rules } = def;
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 seedAll() {
211
+ function sortedEntityNames() {
170
212
  const names = Object.keys(entities);
171
- const sorted = [
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
- for (const name of sorted) {
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 needsSeed = Object.keys(entities).filter((n) => !db[n]);
181
- if (needsSeed.length > 0) {
182
- const names = Object.keys(entities);
183
- const sorted = [
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 { data, meta: { total, page, limit, pages } };
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(" ffa-data.json deleted. Restart the server to regenerate seed data.");
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
- private def;
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: () => FieldBuilder;
54
- declare const number: () => FieldBuilder;
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.6.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 FieldBuilder("string");
61
- var number = () => new FieldBuilder("number");
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.6.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","// src/entity/entity.ts\nexport function entity(fields: Record<string, any>, options?: { count?: number }) {\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 }\n}\n","import { FieldDefinition, FieldRules, FieldType } from './types'\n\nexport class FieldBuilder {\n private def: FieldDefinition\n\n constructor(type: FieldType, extraRules?: Partial<FieldRules>) {\n this.def = {\n type,\n rules: { ...extraRules },\n }\n }\n\n required() {\n this.def.rules.required = true\n return this\n }\n\n optional() {\n this.def.rules.required = false\n return this\n }\n\n min(value: number) {\n this.def.rules.min = value\n return this\n }\n\n max(value: number) {\n this.def.rules.max = value\n return this\n }\n\n default(value: any) {\n this.def.rules.default = value\n return this\n }\n\n readonly() {\n this.def.rules.readonly = true\n return this\n }\n\n build(): FieldDefinition {\n return structuredClone(this.def)\n }\n}\n","import { FieldBuilder } from './FieldBuilder'\n\nexport const string = () => new FieldBuilder('string')\nexport const number = () => new FieldBuilder('number')\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'\n\nexport const __ffa_version = '0.6.0'\n"],"mappings":";AAcO,SAAS,aAAa,QAA8B;AACzD,SAAO;AAAA,IACL,QAAQ,OAAO,UAAU,EAAE,MAAM,IAAK;AAAA,IACtC,UAAU,OAAO,YAAY,CAAC;AAAA,EAChC;AACF;;;AClBO,SAAS,OAAO,QAA6B,SAA8B;AAChF,QAAM,cAAmC,CAAC;AAE1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,gBAAY,GAAG,IAAI,MAAM,MAAM;AAAA,EACjC;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,OAAO,SAAS;AAAA,EAClB;AACF;;;ACVO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EAER,YAAY,MAAiB,YAAkC;AAC7D,SAAK,MAAM;AAAA,MACT;AAAA,MACA,OAAO,EAAE,GAAG,WAAW;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,WAAW;AACT,SAAK,IAAI,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,WAAW;AACT,SAAK,IAAI,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,OAAe;AACjB,SAAK,IAAI,MAAM,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,OAAe;AACjB,SAAK,IAAI,MAAM,MAAM;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,OAAY;AAClB,SAAK,IAAI,MAAM,UAAU;AACzB,WAAO;AAAA,EACT;AAAA,EAEA,WAAW;AACT,SAAK,IAAI,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,QAAyB;AACvB,WAAO,gBAAgB,KAAK,GAAG;AAAA,EACjC;AACF;;;AC3CO,IAAM,SAAS,MAAM,IAAI,aAAa,QAAQ;AAC9C,IAAM,SAAS,MAAM,IAAI,aAAa,QAAQ;AAC9C,IAAM,UAAU,MAAM,IAAI,aAAa,SAAS;AAChD,IAAM,OAAO,MAAM,IAAI,aAAa,MAAM;AAC1C,IAAM,WAAW,MAAM,IAAI,aAAa,UAAU;AAClD,IAAM,YAAY,CAAC,WAAkC,IAAI,aAAa,QAAQ,EAAE,YAAY,OAAO,CAAC;AACpG,IAAM,YAAY,CAACA,YAAmB,IAAI,aAAa,aAAa,EAAE,QAAAA,QAAO,CAAC;AAC9E,IAAM,UAAU,CAACA,YAAmB,IAAI,aAAa,WAAW,EAAE,QAAAA,QAAO,CAAC;;;ACC1E,IAAM,gBAAgB;","names":["entity"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koltakov/ffa-core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Instant mock REST API for frontend development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",