@ghostry/fabricator-extension-faker-v10 0.0.1 → 0.0.3
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 +31 -15
- package/dist/esm/Error.js +2 -2
- package/dist/esm/FakerModules/Build.js +39 -37
- package/dist/esm/FakerModules/Words.js +151 -0
- package/dist/esm/index.js +1 -1
- package/dist/types/Construct.d.ts +1 -1
- package/dist/types/Error.d.ts +2 -1
- package/dist/types/FakerModules/Build.d.ts +11 -5
- package/dist/types/FakerModules/Types.d.ts +4 -1
- package/dist/types/FakerModules/Words.d.ts +71 -0
- package/dist/types/Types.d.ts +2 -2
- package/dist/types/index.d.ts +7 -7
- package/package.json +18 -8
package/README.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://docs.ghostry.dev/fabricator/logo.png" alt="fabricator" width="96" height="96">
|
|
4
|
+
|
|
1
5
|
# @ghostry/fabricator-extension-faker-v10
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
**Seeded faker generators for `@ghostry/fabricator`.**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@ghostry/fabricator-extension-faker-v10)
|
|
10
|
+
[](https://npmx.dev/package/@ghostry/fabricator-extension-faker-v10)
|
|
11
|
+
[](https://jsr.io/@ghostry/fabricator-extension-faker-v10)
|
|
12
|
+
[](https://github.com/ghostry-dev/fabricator)
|
|
13
|
+
[](#)
|
|
14
|
+
[](#)
|
|
15
|
+
[](#)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
[`@faker-js/faker`](https://fakerjs.dev) integration for [`@ghostry/fabricator`](https://www.npmjs.com/package/@ghostry/fabricator). Faker's generators draw from the same stream as every other builder in a schema, so `T.faker.person.fullName()` replays exactly like `T.string` does — nothing additional to track, no drift between runs.
|
|
4
20
|
|
|
5
21
|
Targets `@faker-js/faker` 10.x specifically — hence the `-v10` suffix, matching the adapter packages' convention. A future faker major is a sibling package with its own peer range, not a version bump of this one.
|
|
6
22
|
|
|
@@ -43,21 +59,21 @@ Faker's relative-date methods resolve against the same instance `clock` that `T.
|
|
|
43
59
|
|
|
44
60
|
A faker method could have been wrapped as an opaque producer, and that is the one thing this package refuses to do — an `opaque` schema converts to `Type.Unknown()` in any adapter and is invisible to `combinatorial`/`coverage`. Each builder instead returns the core kind matching the method's own return type:
|
|
45
61
|
|
|
46
|
-
| faker returns
|
|
47
|
-
|
|
|
48
|
-
| `string` (207)
|
|
49
|
-
| `Date` (7)
|
|
50
|
-
| a record shape (7)
|
|
51
|
-
| a literal union (6)
|
|
52
|
-
| `number` (6)
|
|
53
|
-
| `boolean` (1)
|
|
54
|
-
| `bigint` (1)
|
|
55
|
-
| `number
|
|
56
|
-
| `Date[]` (1)
|
|
62
|
+
| faker returns | builder returns | via the TypeBox adapter |
|
|
63
|
+
| ---------------------- | ------------------------------- | -------------------------------------------- |
|
|
64
|
+
| `string` (207) | `T.string` | `Type.String()` |
|
|
65
|
+
| `Date` (7) | `T.date` | `Type.Date()` |
|
|
66
|
+
| a record shape (7) | `T.object({ ... })` | `Type.Object({ ... })` |
|
|
67
|
+
| a literal union (6) | `T.enum.uniform([...])` | `Type.Union([Type.Literal(), …])` |
|
|
68
|
+
| `number` (6) | `T.number` | `Type.Number()` |
|
|
69
|
+
| `boolean` (1) | `T.boolean` | `Type.Boolean()` |
|
|
70
|
+
| `bigint` (1) | `T.bigint` | `Type.BigInt()` |
|
|
71
|
+
| `[number, number]` (1) | `T.tuple([T.number, T.number])` | `Type.Tuple([Type.Number(), Type.Number()])` |
|
|
72
|
+
| `Date[]` (1) | `T.array(T.date)` | `Type.Array(Type.Date())` |
|
|
57
73
|
|
|
58
74
|
Seventeen string methods whose output satisfies a JSON-Schema format regardless of the options passed additionally carry it — `internet.email()` converts to `Type.String({ format: "email" })`, `database.mongodbObjectId()` to a `pattern`.
|
|
59
75
|
|
|
60
|
-
The
|
|
76
|
+
The mirror is hand-written, not generated — one declaration per builder and one implementation, kept in agreement by the compiler. A compile-time assertion checks every entry's value type against faker's own declared return type, so a faker release that changes one fails `tsc` at that entry rather than silently emitting a wrong schema.
|
|
61
77
|
|
|
62
78
|
## Three deviations from faker's own API
|
|
63
79
|
|
|
@@ -75,7 +91,7 @@ T.faker.color.rgb.channels({ includeAlpha: true });
|
|
|
75
91
|
|
|
76
92
|
The same applies to `cmyk`, `hsl`, `hwb`, `lab`, `lch`, and `colorByCSSColorSpace`. `color`'s other four methods are ordinary builders.
|
|
77
93
|
|
|
78
|
-
**
|
|
94
|
+
**Where faker's declared return type is narrower than its JS type, the kind narrows with it.** A literal union becomes an `enum`, not a `string`: `person.sexType()` gives `T.enum.uniform(["female", "generic", "male"])`, so it converts to a union of literals and is enumerable by `combinatorial`/`coverage` rather than an unconstrained string. A fixed-arity array becomes a `tuple`, not an `array`: `location.nearbyGPSCoordinate()` gives `T.tuple([T.number, T.number])`, since no option can change its `[latitude, longitude]` arity — unlike the color channels above, which is why those stay `T.array`.
|
|
79
95
|
|
|
80
96
|
## `use` — for what the mirror doesn't cover
|
|
81
97
|
|
|
@@ -94,7 +110,7 @@ T.faker.use.opaque((f) => f.helpers.arrayElement(["free", "pro"] as const));
|
|
|
94
110
|
|
|
95
111
|
## Notes
|
|
96
112
|
|
|
97
|
-
`faker.seed(...)` is inert here, by design: fabricator's
|
|
113
|
+
`faker.seed(...)` is inert here, by design: fabricator's seeding governs, and a second one competing for control of the same output is the bug this package exists to remove. Change the instance's `clock` — or its `salt`, if it has one — instead.
|
|
98
114
|
|
|
99
115
|
A builder called outside `fabricate()` throws `FakerExtensionError.NoActiveScopeError` — there is no active fabrication to draw from. `FakerExtensionError` extends core's `FabricatorError`, so one `catch` still covers both packages.
|
|
100
116
|
|
package/dist/esm/Error.js
CHANGED
|
@@ -2,14 +2,14 @@ import { FabricatorError } from "@ghostry/fabricator";
|
|
|
2
2
|
class Error_FakerExtensionError extends FabricatorError {
|
|
3
3
|
constructor(){
|
|
4
4
|
super();
|
|
5
|
-
this.name = "FakerExtensionError";
|
|
5
|
+
this.name = "@ghostry/fabricator-extension-faker-v10:FakerExtensionError";
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
(function(FakerExtensionError) {
|
|
9
9
|
class NoActiveScopeError extends FakerExtensionError {
|
|
10
10
|
constructor(){
|
|
11
11
|
super();
|
|
12
|
-
this.name = "NoActiveScopeError";
|
|
12
|
+
this.name = "@ghostry/fabricator-extension-faker-v10:NoActiveScopeError";
|
|
13
13
|
this.message = "A faker builder was invoked outside of fabricate(). Faker builders only draw randomness while the schema they belong to is being fabricated.";
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { toWords } from "./Words.js";
|
|
1
2
|
function build(T, faker, draw) {
|
|
2
|
-
const [f, d] = [
|
|
3
|
+
const [f, d, w] = [
|
|
3
4
|
faker,
|
|
4
|
-
draw
|
|
5
|
+
draw,
|
|
6
|
+
toWords(faker)
|
|
5
7
|
];
|
|
6
8
|
const string = T.string.as(()=>"");
|
|
7
9
|
return {
|
|
@@ -171,7 +173,7 @@ function build(T, faker, draw) {
|
|
|
171
173
|
collation: (...args)=>T.string.as(d(()=>f.database.collation(...args))),
|
|
172
174
|
column: (...args)=>T.string.as(d(()=>f.database.column(...args))),
|
|
173
175
|
engine: (...args)=>T.string.as(d(()=>f.database.engine(...args))),
|
|
174
|
-
mongodbObjectId: (...args)=>T.string.as(d(()=>f.database.mongodbObjectId(...args))
|
|
176
|
+
mongodbObjectId: (...args)=>T.string.as(d(()=>f.database.mongodbObjectId(...args))).admits({
|
|
175
177
|
pattern: "^[0-9a-fA-F]{24}$"
|
|
176
178
|
}),
|
|
177
179
|
type: (...args)=>T.string.as(d(()=>f.database.type(...args)))
|
|
@@ -196,11 +198,11 @@ function build(T, faker, draw) {
|
|
|
196
198
|
accountName: (...args)=>T.string.as(d(()=>f.finance.accountName(...args))),
|
|
197
199
|
accountNumber: (...args)=>T.string.as(d(()=>f.finance.accountNumber(...args))),
|
|
198
200
|
amount: (...args)=>T.string.as(d(()=>f.finance.amount(...args))),
|
|
199
|
-
bic: (...args)=>T.string.as(d(()=>f.finance.bic(...args))
|
|
201
|
+
bic: (...args)=>T.string.as(d(()=>f.finance.bic(...args))).admits({
|
|
200
202
|
pattern: "^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$"
|
|
201
203
|
}),
|
|
202
204
|
bitcoinAddress: (...args)=>T.string.as(d(()=>f.finance.bitcoinAddress(...args))),
|
|
203
|
-
creditCardCVV: (...args)=>T.string.as(d(()=>f.finance.creditCardCVV(...args))
|
|
205
|
+
creditCardCVV: (...args)=>T.string.as(d(()=>f.finance.creditCardCVV(...args))).admits({
|
|
204
206
|
pattern: "^[0-9]{3,4}$"
|
|
205
207
|
}),
|
|
206
208
|
creditCardIssuer: (...args)=>T.string.as(d(()=>f.finance.creditCardIssuer(...args))),
|
|
@@ -250,38 +252,38 @@ function build(T, faker, draw) {
|
|
|
250
252
|
verb: (...args)=>T.string.as(d(()=>f.hacker.verb(...args)))
|
|
251
253
|
},
|
|
252
254
|
image: {
|
|
253
|
-
avatar: (...args)=>T.string.as(d(()=>f.image.avatar(...args))
|
|
255
|
+
avatar: (...args)=>T.string.as(d(()=>f.image.avatar(...args))).admits({
|
|
254
256
|
format: "uri"
|
|
255
257
|
}),
|
|
256
|
-
avatarGitHub: (...args)=>T.string.as(d(()=>f.image.avatarGitHub(...args))
|
|
258
|
+
avatarGitHub: (...args)=>T.string.as(d(()=>f.image.avatarGitHub(...args))).admits({
|
|
257
259
|
format: "uri"
|
|
258
260
|
}),
|
|
259
|
-
dataUri: (...args)=>T.string.as(d(()=>f.image.dataUri(...args))
|
|
261
|
+
dataUri: (...args)=>T.string.as(d(()=>f.image.dataUri(...args))).admits({
|
|
260
262
|
format: "uri"
|
|
261
263
|
}),
|
|
262
|
-
personPortrait: (...args)=>T.string.as(d(()=>f.image.personPortrait(...args))
|
|
264
|
+
personPortrait: (...args)=>T.string.as(d(()=>f.image.personPortrait(...args))).admits({
|
|
263
265
|
format: "uri"
|
|
264
266
|
}),
|
|
265
|
-
url: (...args)=>T.string.as(d(()=>f.image.url(...args))
|
|
267
|
+
url: (...args)=>T.string.as(d(()=>f.image.url(...args))).admits({
|
|
266
268
|
format: "uri"
|
|
267
269
|
}),
|
|
268
270
|
urlLoremFlickr: (...args)=>T.string.as(d(()=>f.image.urlLoremFlickr(...args))),
|
|
269
|
-
urlPicsumPhotos: (...args)=>T.string.as(d(()=>f.image.urlPicsumPhotos(...args))
|
|
271
|
+
urlPicsumPhotos: (...args)=>T.string.as(d(()=>f.image.urlPicsumPhotos(...args))).admits({
|
|
270
272
|
format: "uri"
|
|
271
273
|
})
|
|
272
274
|
},
|
|
273
275
|
internet: {
|
|
274
276
|
displayName: (...args)=>T.string.as(d(()=>f.internet.displayName(...args))),
|
|
275
|
-
domainName: (...args)=>T.string.as(d(()=>f.internet.domainName(...args))
|
|
277
|
+
domainName: (...args)=>T.string.as(d(()=>f.internet.domainName(...args))).admits({
|
|
276
278
|
format: "hostname"
|
|
277
279
|
}),
|
|
278
280
|
domainSuffix: (...args)=>T.string.as(d(()=>f.internet.domainSuffix(...args))),
|
|
279
281
|
domainWord: (...args)=>T.string.as(d(()=>f.internet.domainWord(...args))),
|
|
280
|
-
email: (...args)=>T.string.as(d(()=>f.internet.email(...args))
|
|
282
|
+
email: (...args)=>T.string.as(d(()=>f.internet.email(...args))).admits({
|
|
281
283
|
format: "email"
|
|
282
284
|
}),
|
|
283
285
|
emoji: (...args)=>T.string.as(d(()=>f.internet.emoji(...args))),
|
|
284
|
-
exampleEmail: (...args)=>T.string.as(d(()=>f.internet.exampleEmail(...args))
|
|
286
|
+
exampleEmail: (...args)=>T.string.as(d(()=>f.internet.exampleEmail(...args))).admits({
|
|
285
287
|
format: "email"
|
|
286
288
|
}),
|
|
287
289
|
httpMethod: (...args)=>T.enum.uniform([
|
|
@@ -293,10 +295,10 @@ function build(T, faker, draw) {
|
|
|
293
295
|
]).as(d(()=>f.internet.httpMethod(...args))),
|
|
294
296
|
httpStatusCode: (...args)=>T.number.as(d(()=>f.internet.httpStatusCode(...args))),
|
|
295
297
|
ip: (...args)=>T.string.as(d(()=>f.internet.ip(...args))),
|
|
296
|
-
ipv4: (...args)=>T.string.as(d(()=>f.internet.ipv4(...args))
|
|
298
|
+
ipv4: (...args)=>T.string.as(d(()=>f.internet.ipv4(...args))).admits({
|
|
297
299
|
format: "ipv4"
|
|
298
300
|
}),
|
|
299
|
-
ipv6: (...args)=>T.string.as(d(()=>f.internet.ipv6(...args))
|
|
301
|
+
ipv6: (...args)=>T.string.as(d(()=>f.internet.ipv6(...args))).admits({
|
|
300
302
|
format: "ipv6"
|
|
301
303
|
}),
|
|
302
304
|
jwt: (...args)=>T.string.as(d(()=>f.internet.jwt(...args))),
|
|
@@ -308,7 +310,7 @@ function build(T, faker, draw) {
|
|
|
308
310
|
"http",
|
|
309
311
|
"https"
|
|
310
312
|
]).as(d(()=>f.internet.protocol(...args))),
|
|
311
|
-
url: (...args)=>T.string.as(d(()=>f.internet.url(...args))
|
|
313
|
+
url: (...args)=>T.string.as(d(()=>f.internet.url(...args))).admits({
|
|
312
314
|
format: "uri"
|
|
313
315
|
}),
|
|
314
316
|
userAgent: (...args)=>T.string.as(d(()=>f.internet.userAgent(...args))),
|
|
@@ -344,15 +346,15 @@ function build(T, faker, draw) {
|
|
|
344
346
|
zipCode: (...args)=>T.string.as(d(()=>f.location.zipCode(...args)))
|
|
345
347
|
},
|
|
346
348
|
lorem: {
|
|
347
|
-
lines: (...args)=>T.string.as(d(
|
|
348
|
-
paragraph: (...args)=>T.string.as(d(
|
|
349
|
-
paragraphs: (...args)=>T.string.as(d(
|
|
350
|
-
sentence: (...args)=>T.string.as(d(
|
|
351
|
-
sentences: (...args)=>T.string.as(d(
|
|
352
|
-
slug: (...args)=>T.string.as(d(
|
|
353
|
-
text: (...args)=>T.string.as(d(
|
|
354
|
-
word: (...args)=>T.string.as(d(
|
|
355
|
-
words: (...args)=>T.string.as(d(
|
|
349
|
+
lines: (...args)=>T.string.as(d(w.lorem.lines(...args))),
|
|
350
|
+
paragraph: (...args)=>T.string.as(d(w.lorem.paragraph(...args))),
|
|
351
|
+
paragraphs: (...args)=>T.string.as(d(w.lorem.paragraphs(...args))),
|
|
352
|
+
sentence: (...args)=>T.string.as(d(w.lorem.sentence(...args))),
|
|
353
|
+
sentences: (...args)=>T.string.as(d(w.lorem.sentences(...args))),
|
|
354
|
+
slug: (...args)=>T.string.as(d(w.lorem.slug(...args))),
|
|
355
|
+
text: (...args)=>T.string.as(d(w.lorem.text(...args))),
|
|
356
|
+
word: (...args)=>T.string.as(d(w.lorem.word(...args))),
|
|
357
|
+
words: (...args)=>T.string.as(d(w.lorem.words(...args)))
|
|
356
358
|
},
|
|
357
359
|
music: {
|
|
358
360
|
album: (...args)=>T.string.as(d(()=>f.music.album(...args))),
|
|
@@ -417,7 +419,7 @@ function build(T, faker, draw) {
|
|
|
417
419
|
sample: (...args)=>T.string.as(d(()=>f.string.sample(...args))),
|
|
418
420
|
symbol: (...args)=>T.string.as(d(()=>f.string.symbol(...args))),
|
|
419
421
|
ulid: (...args)=>T.string.as(d(()=>f.string.ulid(...args))),
|
|
420
|
-
uuid: (...args)=>T.string.as(d(()=>f.string.uuid(...args))
|
|
422
|
+
uuid: (...args)=>T.string.as(d(()=>f.string.uuid(...args))).admits({
|
|
421
423
|
format: "uuid"
|
|
422
424
|
})
|
|
423
425
|
},
|
|
@@ -429,7 +431,7 @@ function build(T, faker, draw) {
|
|
|
429
431
|
directoryPath: (...args)=>T.string.as(d(()=>f.system.directoryPath(...args))),
|
|
430
432
|
fileExt: (...args)=>T.string.as(d(()=>f.system.fileExt(...args))),
|
|
431
433
|
fileName: (...args)=>T.string.as(d(()=>f.system.fileName(...args))),
|
|
432
|
-
filePath: (...args)=>T.string.as(d(()=>f.system.filePath(...args))
|
|
434
|
+
filePath: (...args)=>T.string.as(d(()=>f.system.filePath(...args))).admits({
|
|
433
435
|
format: "uri-reference"
|
|
434
436
|
}),
|
|
435
437
|
fileType: (...args)=>T.string.as(d(()=>f.system.fileType(...args))),
|
|
@@ -449,15 +451,15 @@ function build(T, faker, draw) {
|
|
|
449
451
|
vrm: (...args)=>T.string.as(d(()=>f.vehicle.vrm(...args)))
|
|
450
452
|
},
|
|
451
453
|
word: {
|
|
452
|
-
adjective: (...args)=>T.string.as(d(
|
|
453
|
-
adverb: (...args)=>T.string.as(d(
|
|
454
|
-
conjunction: (...args)=>T.string.as(d(
|
|
455
|
-
interjection: (...args)=>T.string.as(d(
|
|
456
|
-
noun: (...args)=>T.string.as(d(
|
|
457
|
-
preposition: (...args)=>T.string.as(d(
|
|
458
|
-
sample: (...args)=>T.string.as(d(
|
|
459
|
-
verb: (...args)=>T.string.as(d(
|
|
460
|
-
words: (...args)=>T.string.as(d(
|
|
454
|
+
adjective: (...args)=>T.string.as(d(w.word.adjective(...args))),
|
|
455
|
+
adverb: (...args)=>T.string.as(d(w.word.adverb(...args))),
|
|
456
|
+
conjunction: (...args)=>T.string.as(d(w.word.conjunction(...args))),
|
|
457
|
+
interjection: (...args)=>T.string.as(d(w.word.interjection(...args))),
|
|
458
|
+
noun: (...args)=>T.string.as(d(w.word.noun(...args))),
|
|
459
|
+
preposition: (...args)=>T.string.as(d(w.word.preposition(...args))),
|
|
460
|
+
sample: (...args)=>T.string.as(d(w.word.sample(...args))),
|
|
461
|
+
verb: (...args)=>T.string.as(d(w.word.verb(...args))),
|
|
462
|
+
words: (...args)=>T.string.as(d(w.word.words(...args)))
|
|
461
463
|
}
|
|
462
464
|
};
|
|
463
465
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { FakerError } from "@faker-js/faker";
|
|
2
|
+
const categories = [
|
|
3
|
+
"adjective",
|
|
4
|
+
"adverb",
|
|
5
|
+
"conjunction",
|
|
6
|
+
"interjection",
|
|
7
|
+
"noun",
|
|
8
|
+
"preposition",
|
|
9
|
+
"verb"
|
|
10
|
+
];
|
|
11
|
+
function filterByLength(list, options = {}) {
|
|
12
|
+
const { length, strategy = "fail" } = "number" == typeof options ? {
|
|
13
|
+
length: options
|
|
14
|
+
} : options;
|
|
15
|
+
if (void 0 === length) {
|
|
16
|
+
if ("shortest" === strategy || "longest" === strategy) return extreme(list, strategy);
|
|
17
|
+
return list;
|
|
18
|
+
}
|
|
19
|
+
const range = "number" == typeof length ? {
|
|
20
|
+
min: length,
|
|
21
|
+
max: length
|
|
22
|
+
} : length;
|
|
23
|
+
const matching = list.filter((word)=>word.length >= range.min && word.length <= range.max);
|
|
24
|
+
if (matching.length > 0) return matching;
|
|
25
|
+
switch(strategy){
|
|
26
|
+
case "fail":
|
|
27
|
+
throw new FakerError("No words found that match the given length.");
|
|
28
|
+
case "closest":
|
|
29
|
+
return closest(list, range);
|
|
30
|
+
case "shortest":
|
|
31
|
+
case "longest":
|
|
32
|
+
return extreme(list, strategy);
|
|
33
|
+
case "any-length":
|
|
34
|
+
return list;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function extreme(list, strategy) {
|
|
38
|
+
const lengths = list.map((word)=>word.length);
|
|
39
|
+
const target = "shortest" === strategy ? Math.min(...lengths) : Math.max(...lengths);
|
|
40
|
+
return list.filter((word)=>word.length === target);
|
|
41
|
+
}
|
|
42
|
+
function closest(list, range) {
|
|
43
|
+
let below = -1 / 0;
|
|
44
|
+
let above = 1 / 0;
|
|
45
|
+
for (const { length } of list){
|
|
46
|
+
if (length < range.min && length > below) below = length;
|
|
47
|
+
if (length > range.max && length < above) above = length;
|
|
48
|
+
}
|
|
49
|
+
const offset = Math.min(range.min - below, above - range.max);
|
|
50
|
+
return list.filter(({ length })=>length === range.min - offset || length === range.max + offset);
|
|
51
|
+
}
|
|
52
|
+
function listOf(read, options) {
|
|
53
|
+
let resolved;
|
|
54
|
+
return ()=>{
|
|
55
|
+
if (void 0 === resolved) resolved = filterByLength(read(), options);
|
|
56
|
+
return resolved;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function pick(random, list) {
|
|
60
|
+
if (0 === list.length) throw new FakerError("Cannot get value from empty dataset.");
|
|
61
|
+
return list[list.length > 1 ? Math.floor(random.next() * list.length) : 0];
|
|
62
|
+
}
|
|
63
|
+
function toWords(faker) {
|
|
64
|
+
const repeat = (count, separator, next)=>faker.helpers.multiple(next, {
|
|
65
|
+
count
|
|
66
|
+
}).join(separator);
|
|
67
|
+
const words = (word, count = 3)=>repeat(count, " ", word);
|
|
68
|
+
const sentence = (word, count = {
|
|
69
|
+
min: 3,
|
|
70
|
+
max: 10
|
|
71
|
+
})=>{
|
|
72
|
+
const text = words(word, count);
|
|
73
|
+
return `${text.charAt(0).toUpperCase()}${text.substring(1)}.`;
|
|
74
|
+
};
|
|
75
|
+
const sentences = (word, count = {
|
|
76
|
+
min: 2,
|
|
77
|
+
max: 6
|
|
78
|
+
}, separator = " ")=>repeat(count, separator, ()=>sentence(word));
|
|
79
|
+
const paragraph = (word, count = 3)=>sentences(word, count);
|
|
80
|
+
const paragraphs = (word, count = 3, separator = "\n")=>repeat(count, separator, ()=>paragraph(word));
|
|
81
|
+
const lines = (word, count = {
|
|
82
|
+
min: 1,
|
|
83
|
+
max: 5
|
|
84
|
+
})=>sentences(word, count, "\n");
|
|
85
|
+
const texts = [
|
|
86
|
+
sentence,
|
|
87
|
+
sentences,
|
|
88
|
+
paragraph,
|
|
89
|
+
paragraphs,
|
|
90
|
+
lines
|
|
91
|
+
];
|
|
92
|
+
const lorem = (compose)=>{
|
|
93
|
+
const list = listOf(()=>faker.definitions.lorem.word, void 0);
|
|
94
|
+
return ({ random })=>compose(()=>pick(random, list()), random);
|
|
95
|
+
};
|
|
96
|
+
const category = (name)=>(options)=>{
|
|
97
|
+
const list = listOf(()=>faker.definitions.word[name], options);
|
|
98
|
+
return ({ random })=>pick(random, list());
|
|
99
|
+
};
|
|
100
|
+
const sampler = (options)=>{
|
|
101
|
+
const lists = categories.map((name)=>listOf(()=>faker.definitions.word[name], options));
|
|
102
|
+
return (random)=>{
|
|
103
|
+
for (const list of faker.helpers.shuffle(lists))try {
|
|
104
|
+
return pick(random, list());
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
throw new FakerError("No matching word data available for the current locale");
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
lorem: {
|
|
113
|
+
lines: (count)=>lorem((word)=>lines(word, count)),
|
|
114
|
+
paragraph: (count)=>lorem((word)=>paragraph(word, count)),
|
|
115
|
+
paragraphs: (count, separator)=>lorem((word)=>paragraphs(word, count, separator)),
|
|
116
|
+
sentence: (count)=>lorem((word)=>sentence(word, count)),
|
|
117
|
+
sentences: (count, separator)=>lorem((word)=>sentences(word, count, separator)),
|
|
118
|
+
slug: (count)=>lorem((word)=>faker.helpers.slugify(words(word, count))),
|
|
119
|
+
text: ()=>lorem((word, random)=>pick(random, texts)(word)),
|
|
120
|
+
word: (options)=>{
|
|
121
|
+
const list = listOf(()=>faker.definitions.lorem.word, options);
|
|
122
|
+
return ({ random })=>pick(random, list());
|
|
123
|
+
},
|
|
124
|
+
words: (count)=>lorem((word)=>words(word, count))
|
|
125
|
+
},
|
|
126
|
+
word: {
|
|
127
|
+
adjective: category("adjective"),
|
|
128
|
+
adverb: category("adverb"),
|
|
129
|
+
conjunction: category("conjunction"),
|
|
130
|
+
interjection: category("interjection"),
|
|
131
|
+
noun: category("noun"),
|
|
132
|
+
preposition: category("preposition"),
|
|
133
|
+
sample: (options)=>{
|
|
134
|
+
const sample = sampler(options);
|
|
135
|
+
return ({ random })=>sample(random);
|
|
136
|
+
},
|
|
137
|
+
verb: category("verb"),
|
|
138
|
+
words: (options = {})=>{
|
|
139
|
+
const { count = {
|
|
140
|
+
min: 1,
|
|
141
|
+
max: 3
|
|
142
|
+
} } = "number" == typeof options ? {
|
|
143
|
+
count: options
|
|
144
|
+
} : options;
|
|
145
|
+
const sample = sampler(void 0);
|
|
146
|
+
return ({ random })=>repeat(count, " ", ()=>sample(random));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export { toWords };
|
package/dist/esm/index.js
CHANGED
|
@@ -22,4 +22,4 @@ import type { FakerExtension, Registry } from "./Types";
|
|
|
22
22
|
* runtime floor on the builder count: the type-level guards read the surface
|
|
23
23
|
* alone and cannot see a spread that drops a module.
|
|
24
24
|
*/
|
|
25
|
-
export declare function construct(T: Registry, faker: Faker, draw: <$T>(produce: () => $T) => (context: ProduceContext) => $T): FakerExtension;
|
|
25
|
+
export declare function construct(T: Registry, faker: Faker, draw: <$T>(produce: (context: ProduceContext) => $T) => (context: ProduceContext) => $T): FakerExtension;
|
package/dist/types/Error.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* exports that name, and importing both into one scope would collide. Follows
|
|
4
4
|
* core's `FabricatorError` shape (abstract base plus a merged namespace of
|
|
5
5
|
* subclasses) so `e instanceof FabricatorError` still catches everything either
|
|
6
|
-
* package throws.
|
|
6
|
+
* package throws. A `name` is prefixed with this package's name, as core's are
|
|
7
|
+
* with its own.
|
|
7
8
|
*/
|
|
8
9
|
import { FabricatorError } from "@ghostry/fabricator";
|
|
9
10
|
export declare abstract class FakerExtensionError extends FabricatorError {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* the faker-bump workflow; an entry added or changed here needs the matching
|
|
10
10
|
* edit there, and vice versa.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Shapes before editing:
|
|
13
13
|
*
|
|
14
14
|
* - Every builder spreads faker's `Parameters<...>` rather than taking a single
|
|
15
15
|
* `options?`. Faker's zero-parameter methods (`animal.bear()`) and its
|
|
@@ -24,16 +24,22 @@
|
|
|
24
24
|
* producer rather than a length bound.
|
|
25
25
|
* - `color`'s 7 split methods are a `{ text, channels }` pair each rather than
|
|
26
26
|
* one ambiguous builder, since their return type depends on their arguments;
|
|
27
|
-
* see the deviation policy in `
|
|
27
|
+
* see the deviation policy in `AGENTS.md`'s "The faker extension". All 7 name
|
|
28
28
|
* `format: "decimal"` on `.channels()`, not only `rgb`: bare is right at
|
|
29
29
|
* runtime for the other 6, but not provable through faker's declarations,
|
|
30
30
|
* whose _first_ overload takes `format?: StringColorFormat` and returns
|
|
31
31
|
* `string`. `'decimal'` is `NumberColorFormat`'s sole member, so naming it is
|
|
32
32
|
* exact.
|
|
33
|
+
* - `lorem`'s and `word`'s builders never call faker's own method: faker's
|
|
34
|
+
* per-word path copies the word list and parses an options object for every
|
|
35
|
+
* word drawn, so they draw through `./Words.ts`, which reproduces faker's
|
|
36
|
+
* output exactly from the same stream.
|
|
33
37
|
*
|
|
34
38
|
* Every node is a plain object or a bare builder function, never a function
|
|
35
39
|
* carrying properties — the invariant `deepMerge` needs to recurse through an
|
|
36
40
|
* extended registry, guarded by `test/Mergeable.test.ts`.
|
|
41
|
+
*
|
|
42
|
+
* @module
|
|
37
43
|
*/
|
|
38
44
|
import type { Faker } from "@faker-js/faker";
|
|
39
45
|
import type { ProduceContext } from "@ghostry/fabricator";
|
|
@@ -41,9 +47,9 @@ import type { Registry } from "../Types";
|
|
|
41
47
|
import type { FakerModules } from "./Types";
|
|
42
48
|
/**
|
|
43
49
|
* `src/index.ts`'s `draw`, by type — the save/restore wrapper that binds a
|
|
44
|
-
* producer to the leaf's own `random`/`clock`. Named here rather than imported
|
|
45
|
-
*
|
|
50
|
+
* producer to the leaf's own `random`/`clock`. Named here rather than imported,
|
|
51
|
+
* since `src/index.ts` imports this file.
|
|
46
52
|
*/
|
|
47
|
-
type Draw = <$T>(produce: () => $T) => (context: ProduceContext) => $T;
|
|
53
|
+
type Draw = <$T>(produce: (context: ProduceContext) => $T) => (context: ProduceContext) => $T;
|
|
48
54
|
export declare function build(T: Registry, faker: Faker, draw: Draw): FakerModules;
|
|
49
55
|
export {};
|
|
@@ -41,11 +41,14 @@
|
|
|
41
41
|
* union rather than a bare `string`.
|
|
42
42
|
* 4. A reordered overload set changes `Parameters<...>` here, which the matching
|
|
43
43
|
* builder's `...args` forwarding then fails to accept.
|
|
44
|
+
* 5. A change to how `lorem` or `word` draws fails `test/Parity.test.ts`, at `bun
|
|
45
|
+
* test` rather than `check`: those builders reimplement faker's method
|
|
46
|
+
* bodies in `./Words.ts`, which must then be brought back in line.
|
|
44
47
|
*
|
|
45
48
|
* Fix the entry in **both** files; the annotation on `build` fails until they
|
|
46
49
|
* agree.
|
|
47
50
|
*
|
|
48
|
-
* These assertions resolve through built `dist/`, not `src/` (`
|
|
51
|
+
* These assertions resolve through built `dist/`, not `src/` (`AGENTS.md`'s
|
|
49
52
|
* test-import convention), so a `src`-only edit proves nothing about faker
|
|
50
53
|
* drift until you rebuild. A disagreement between this file and `./Build.ts` is
|
|
51
54
|
* caught without one, being internal to `src`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Faker 10's `lorem` and `word` method bodies, minus the word-list copy.
|
|
3
|
+
*
|
|
4
|
+
* Faker's per-word path has two costs. With no `length`, its shared
|
|
5
|
+
* `filterWordListByLength` returns `[...wordList]`, copying a whole locale list
|
|
6
|
+
* (up to 1,000 entries in `en`) to index one element. That copy is close to
|
|
7
|
+
* free where the engine shares a literal array's storage on spread (Node 22–26
|
|
8
|
+
* and Bun 1.4.4 with faker's bundled locales), but costs about a microsecond
|
|
9
|
+
* per word on Bun 1.2.14, and on any engine when a locale's lists are built at
|
|
10
|
+
* runtime. Each word then goes through `helpers.arrayElement` → `number.int`,
|
|
11
|
+
* which parses an options object per draw and is most of what remains where the
|
|
12
|
+
* copy is free. `lorem.sentence`/`paragraph`/`text` pay both once per word.
|
|
13
|
+
* Every builder in `./Build.ts`'s `lorem`/`word` modules draws through this
|
|
14
|
+
* file instead and never calls faker's own method.
|
|
15
|
+
*
|
|
16
|
+
* The copy is reported upstream as
|
|
17
|
+
* https://github.com/faker-js/faker/issues/4242. A faker release that drops it
|
|
18
|
+
* is the point to weigh calling faker's methods again, not an automatic
|
|
19
|
+
* removal: the per-draw options parsing and definitions lookup would remain.
|
|
20
|
+
*
|
|
21
|
+
* **The output is faker's exactly, not an approximation.** `pick` reads
|
|
22
|
+
* `random.next()` and indexes with `arrayElement`'s own arithmetic,
|
|
23
|
+
* `Math.floor(u * length)`, from the same stream faker's randomizer reads
|
|
24
|
+
* (`src/index.ts`'s `draw` installs one `context` for both). Every draw made
|
|
25
|
+
* once per group rather than once per word — a `count` resolved through
|
|
26
|
+
* `rangeToNumber`, `word.sample`'s shuffle, `slug`'s `slugify` — goes through
|
|
27
|
+
* faker's own helpers, which keeps their semantics (an inverted range's error,
|
|
28
|
+
* a fractional count's truncation) without restating them, at a cost that is
|
|
29
|
+
* off the hot path. So a seed's data is unchanged, the surface keeps faker's
|
|
30
|
+
* `Parameters<...>`, and nothing about this is a deviation in the sense of
|
|
31
|
+
* `AGENTS.md`'s deviation policy. `test/Parity.test.ts` is the guard: it
|
|
32
|
+
* compares every builder here against faker's own method on an identical
|
|
33
|
+
* stream, so a faker release that changes how these draw fails there.
|
|
34
|
+
*
|
|
35
|
+
* Each filtered list is resolved once per builder call, lazily on the first
|
|
36
|
+
* draw, and a throw is never cached: missing locale data (thrown by faker's
|
|
37
|
+
* definitions proxy) and `strategy: "fail"` still throw from `fabricate()` on
|
|
38
|
+
* every call, as faker's own methods do, rather than from the builder call.
|
|
39
|
+
*
|
|
40
|
+
* @module
|
|
41
|
+
*/
|
|
42
|
+
import type { Faker } from "@faker-js/faker";
|
|
43
|
+
import type { ProduceContext } from "@ghostry/fabricator";
|
|
44
|
+
type Producer = (context: ProduceContext) => string;
|
|
45
|
+
type WordOptions = Parameters<Faker["word"]["noun"]>[0];
|
|
46
|
+
type Count = NonNullable<Parameters<Faker["lorem"]["words"]>[0]>;
|
|
47
|
+
export declare function toWords(faker: Faker): {
|
|
48
|
+
lorem: {
|
|
49
|
+
lines: (count?: Count) => Producer;
|
|
50
|
+
paragraph: (count?: Count) => Producer;
|
|
51
|
+
paragraphs: (count?: Count, separator?: string) => Producer;
|
|
52
|
+
sentence: (count?: Count) => Producer;
|
|
53
|
+
sentences: (count?: Count, separator?: string) => Producer;
|
|
54
|
+
slug: (count?: Count) => Producer;
|
|
55
|
+
text: () => Producer;
|
|
56
|
+
word: (options?: WordOptions) => Producer;
|
|
57
|
+
words: (count?: Count) => Producer;
|
|
58
|
+
};
|
|
59
|
+
word: {
|
|
60
|
+
adjective: (options?: WordOptions) => Producer;
|
|
61
|
+
adverb: (options?: WordOptions) => Producer;
|
|
62
|
+
conjunction: (options?: WordOptions) => Producer;
|
|
63
|
+
interjection: (options?: WordOptions) => Producer;
|
|
64
|
+
noun: (options?: WordOptions) => Producer;
|
|
65
|
+
preposition: (options?: WordOptions) => Producer;
|
|
66
|
+
sample: (options?: WordOptions) => Producer;
|
|
67
|
+
verb: (options?: WordOptions) => Producer;
|
|
68
|
+
words: (options?: Parameters<Faker["word"]["words"]>[0]) => Producer;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export {};
|
package/dist/types/Types.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type Registry = Pick<typeof registry, "array" | "bigint" | "boolean" | "d
|
|
|
12
12
|
* Every module the mirror covers, intersected with `keyof Faker` so every
|
|
13
13
|
* downstream `Faker[$M]` indexing is sound. `FakerModules/Types.ts` is the
|
|
14
14
|
* source of truth — `helpers` is absent from it deliberately (deviation policy
|
|
15
|
-
* in `
|
|
15
|
+
* in `AGENTS.md`'s "The faker extension"), so this type excludes it without
|
|
16
16
|
* saying so twice.
|
|
17
17
|
*
|
|
18
18
|
* `test/index.types.test.ts`'s `_ModulesExhaustive` holds this to faker's own
|
|
@@ -60,7 +60,7 @@ export type FakerExtension = FakerModules & {
|
|
|
60
60
|
* (`src/index.ts`'s `draw`).
|
|
61
61
|
*
|
|
62
62
|
* How `helpers` is reached (omitted from the mirror — deviation policy in
|
|
63
|
-
* `
|
|
63
|
+
* `AGENTS.md`'s "The faker extension"). Kind-tagged forms (`.string`,
|
|
64
64
|
* `.number`, `.date`, `.boolean`, `.bigint`) keep adapter compatibility
|
|
65
65
|
* available there too. `.opaque` is the **only** way to get an `opaque`
|
|
66
66
|
* schema out of this package — honest, since it is the one place the caller
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type { FakerExtension as FakerTypes } from "./Types";
|
|
|
7
7
|
* The mirror's type-level machinery — not needed to _use_ it, but exported so a
|
|
8
8
|
* consumer (or this package's compile-time checks in
|
|
9
9
|
* `test/index.types.test.ts`) can name a module or method without a relative
|
|
10
|
-
* `./Types` path tests otherwise avoid (`
|
|
10
|
+
* `./Types` path tests otherwise avoid (`AGENTS.md`: tests resolve through the
|
|
11
11
|
* package specifier, exercising built `dist/`).
|
|
12
12
|
*
|
|
13
13
|
* `FakerModules` is the surface those checks are made _against_ — every
|
|
@@ -29,9 +29,9 @@ export type FakerConfig = ConstructorParameters<typeof Faker>[0]["config"];
|
|
|
29
29
|
*
|
|
30
30
|
* No `seed` option and no pre-built `Faker`: a pre-built instance's randomizer
|
|
31
31
|
* is fixed at construction, and accepting one would either ignore it silently
|
|
32
|
-
* or let it compete with fabricator's
|
|
33
|
-
* caller builds the instance; it still receives this bridge's `randomizer`,
|
|
34
|
-
* reproducibility survives.
|
|
32
|
+
* or let it compete with fabricator's seeding. `create` is the honest escape:
|
|
33
|
+
* the caller builds the instance; it still receives this bridge's `randomizer`,
|
|
34
|
+
* so reproducibility survives.
|
|
35
35
|
*/
|
|
36
36
|
export type Options = {
|
|
37
37
|
locale: LocaleDefinition | LocaleDefinition[];
|
|
@@ -63,11 +63,11 @@ export type Options = {
|
|
|
63
63
|
* `FakerExtension` (`./Types.ts`) is spelled out explicitly rather than
|
|
64
64
|
* inferred. An inferred type would reach into each kind's `Schema` interface —
|
|
65
65
|
* internal to `@ghostry/fabricator`, unreachable from its `.` export — and
|
|
66
|
-
* declaration emission (`tsgo`, not `tsc --noEmit`; see `
|
|
66
|
+
* declaration emission (`tsgo`, not `tsc --noEmit`; see `AGENTS.md`'s
|
|
67
67
|
* declaration-emit trap) refuses to print an anonymous expansion of one. Even
|
|
68
68
|
* where it printed it would be the wrong contract: inference would surface each
|
|
69
|
-
* builder's incidental narrowness (`Schema<{ produce
|
|
70
|
-
*
|
|
69
|
+
* builder's incidental narrowness (`Schema<{ produce }>`) where the declared
|
|
70
|
+
* surface states `Primitive.string.Schema`.
|
|
71
71
|
*
|
|
72
72
|
* Every node is a plain object or a leaf builder function, never a function
|
|
73
73
|
* carrying properties: `deepMerge` recurses only where `isPlainObject` holds on
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostry/fabricator-extension-faker-v10",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "@faker-js/faker integration for @ghostry/fabricator — faker's data generators, seeded and reproducible through the fabricator instance that calls them.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"factory",
|
|
8
|
+
"reproducible",
|
|
9
|
+
"fixtures",
|
|
10
|
+
"testing",
|
|
11
|
+
"mock",
|
|
12
|
+
"schema",
|
|
8
13
|
"faker",
|
|
9
|
-
"
|
|
14
|
+
"typescript"
|
|
10
15
|
],
|
|
11
16
|
"author": {
|
|
12
17
|
"name": "Patrick Rebsch"
|
|
@@ -16,9 +21,13 @@
|
|
|
16
21
|
"url": "git+https://github.com/ghostry-dev/fabricator",
|
|
17
22
|
"directory": "pkg/fabricator-extension-faker-v10"
|
|
18
23
|
},
|
|
19
|
-
"homepage": "https://
|
|
24
|
+
"homepage": "https://docs.ghostry.dev/fabricator",
|
|
20
25
|
"bugs": "https://github.com/ghostry-dev/fabricator/issues",
|
|
21
26
|
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
22
31
|
"types": "./dist/types/index.d.ts",
|
|
23
32
|
"module": "./dist/esm/index.js",
|
|
24
33
|
"exports": {
|
|
@@ -44,20 +53,21 @@
|
|
|
44
53
|
"check": "tsc --noEmit",
|
|
45
54
|
"check:ts5": "bun --bun ../../node_modules/typescript-5/lib/tsc.js --noEmit",
|
|
46
55
|
"check:jsr": "DENO_BIN_PATH=$PWD/../../node_modules/.bin/deno bunx --no-install jsr publish --dry-run --allow-dirty",
|
|
47
|
-
"format": "
|
|
56
|
+
"format": "bun fmt",
|
|
48
57
|
"test": "bun run check && bun run check:ts5 && bun run check:jsr && bun test"
|
|
49
58
|
},
|
|
50
59
|
"files": [
|
|
51
60
|
"dist"
|
|
52
61
|
],
|
|
53
62
|
"peerDependencies": {
|
|
54
|
-
"@ghostry/fabricator": ">=0.0.
|
|
63
|
+
"@ghostry/fabricator": ">=0.0.14 <0.1.0",
|
|
55
64
|
"@faker-js/faker": "^10.5.0"
|
|
56
65
|
},
|
|
57
66
|
"devDependencies": {
|
|
58
|
-
"@ghostry/fabricator": "0.0.
|
|
59
|
-
"@ghostry/fabricator-adapter-typebox-v0": "0.0.
|
|
60
|
-
"@faker-js/faker": "^10.
|
|
67
|
+
"@ghostry/fabricator": "0.0.14",
|
|
68
|
+
"@ghostry/fabricator-adapter-typebox-v0": "0.0.4",
|
|
69
|
+
"@faker-js/faker": "^10.6.0",
|
|
70
|
+
"@ghostry/harness": "^0.0.6",
|
|
61
71
|
"@sinclair/typebox": "^0.34.52"
|
|
62
72
|
}
|
|
63
73
|
}
|