@skmdev/prisma-fixtures 0.1.1
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/LICENSE +21 -0
- package/NOTICE +29 -0
- package/README.md +631 -0
- package/dist/cleanup-options.d.ts +5 -0
- package/dist/cleanup-options.js +19 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +342 -0
- package/dist/fixture-config.d.ts +14 -0
- package/dist/fixture-config.js +95 -0
- package/dist/fixture-document.d.ts +23 -0
- package/dist/fixture-document.js +349 -0
- package/dist/fixture-error.d.ts +22 -0
- package/dist/fixture-error.js +54 -0
- package/dist/fixture-reference.d.ts +17 -0
- package/dist/fixture-reference.js +234 -0
- package/dist/fixture-schema.d.ts +10 -0
- package/dist/fixture-schema.js +243 -0
- package/dist/fixture-template.d.ts +14 -0
- package/dist/fixture-template.js +239 -0
- package/dist/generator.d.ts +2 -0
- package/dist/generator.js +25 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +257 -0
- package/dist/load-options.d.ts +11 -0
- package/dist/load-options.js +47 -0
- package/dist/prisma-config.d.ts +8 -0
- package/dist/prisma-config.js +111 -0
- package/package.json +83 -0
- package/schema/fixture.schema.json +119 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GeneratorOptions } from '@prisma/generator-helper';
|
|
2
|
+
type Dmmf = GeneratorOptions['dmmf'];
|
|
3
|
+
type JsonSchema = Record<string, unknown>;
|
|
4
|
+
type FixtureSchema = JsonSchema & {
|
|
5
|
+
properties: Record<string, JsonSchema>;
|
|
6
|
+
definitions: Record<string, JsonSchema>;
|
|
7
|
+
allOf?: JsonSchema[];
|
|
8
|
+
};
|
|
9
|
+
export declare function buildFixtureSchema(dmmf: Dmmf): FixtureSchema;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildFixtureSchema = buildFixtureSchema;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const baseFixtureSchema = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(__dirname, '../schema/fixture.schema.json'), 'utf8'));
|
|
10
|
+
const dynamicString = () => ({
|
|
11
|
+
type: 'string',
|
|
12
|
+
pattern: '(?:^@(?!@)|<%|\\{\\{|<\\{|\\(\\$current(?:[+*/-][0-9]+)?\\))',
|
|
13
|
+
});
|
|
14
|
+
const pointer = (value) => value.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
15
|
+
function buildFixtureSchema(dmmf) {
|
|
16
|
+
const schema = structuredClone(baseFixtureSchema);
|
|
17
|
+
const definitions = schema.definitions;
|
|
18
|
+
const inputs = new Map();
|
|
19
|
+
const inputsByName = new Map();
|
|
20
|
+
const enums = new Map();
|
|
21
|
+
const enumsByName = new Map();
|
|
22
|
+
const rootRelations = new Map();
|
|
23
|
+
const addInputs = (namespace, values) => {
|
|
24
|
+
for (const input of values) {
|
|
25
|
+
const info = { input, namespace };
|
|
26
|
+
inputs.set(`${namespace}:${input.name}`, info);
|
|
27
|
+
if (!inputsByName.has(input.name))
|
|
28
|
+
inputsByName.set(input.name, info);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
addInputs('model', dmmf.schema.inputObjectTypes.model ?? []);
|
|
32
|
+
addInputs('prisma', dmmf.schema.inputObjectTypes.prisma ?? []);
|
|
33
|
+
const addEnums = (namespace, values) => {
|
|
34
|
+
for (const enumType of values) {
|
|
35
|
+
const info = { enumType, namespace };
|
|
36
|
+
enums.set(`${namespace}:${enumType.name}`, info);
|
|
37
|
+
if (!enumsByName.has(enumType.name))
|
|
38
|
+
enumsByName.set(enumType.name, info);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
addEnums('model', dmmf.schema.enumTypes.model ?? []);
|
|
42
|
+
addEnums('prisma', dmmf.schema.enumTypes.prisma);
|
|
43
|
+
for (const model of dmmf.datamodel.models) {
|
|
44
|
+
const fields = new Map(model.fields
|
|
45
|
+
.filter((field) => field.kind === 'object')
|
|
46
|
+
.map((field) => [field.name, field.isList]));
|
|
47
|
+
rootRelations.set(`${model.name}CreateInput`, fields);
|
|
48
|
+
rootRelations.set(`${model.name}UncheckedCreateInput`, fields);
|
|
49
|
+
}
|
|
50
|
+
const connectionRecord = () => {
|
|
51
|
+
const name = 'PrismaFixtureConnectionRecord';
|
|
52
|
+
if (!(name in definitions)) {
|
|
53
|
+
definitions[name] = {
|
|
54
|
+
allOf: [
|
|
55
|
+
internalRef('safeObject'),
|
|
56
|
+
{
|
|
57
|
+
type: 'object',
|
|
58
|
+
required: ['id'],
|
|
59
|
+
properties: { id: internalRef('jsonValue') },
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return internalRef(name);
|
|
65
|
+
};
|
|
66
|
+
const resolveInput = (reference) => (reference.namespace
|
|
67
|
+
? inputs.get(`${reference.namespace}:${reference.type}`)
|
|
68
|
+
: undefined) ?? inputsByName.get(reference.type);
|
|
69
|
+
const resolveEnum = (reference) => (reference.namespace
|
|
70
|
+
? enums.get(`${reference.namespace}:${reference.type}`)
|
|
71
|
+
: undefined) ?? enumsByName.get(reference.type);
|
|
72
|
+
const ensureEnum = (info) => {
|
|
73
|
+
const name = enumDefinitionName(info);
|
|
74
|
+
if (!(name in definitions)) {
|
|
75
|
+
definitions[name] = { type: 'string', enum: [...info.enumType.values] };
|
|
76
|
+
}
|
|
77
|
+
return name;
|
|
78
|
+
};
|
|
79
|
+
const ensureInput = (info) => {
|
|
80
|
+
const name = inputDefinitionName(info);
|
|
81
|
+
if (name in definitions)
|
|
82
|
+
return name;
|
|
83
|
+
definitions[name] = {};
|
|
84
|
+
const properties = {};
|
|
85
|
+
const required = [];
|
|
86
|
+
const relations = rootRelations.get(info.input.name);
|
|
87
|
+
for (const field of info.input.fields) {
|
|
88
|
+
const alternatives = field.inputTypes.map((reference) => {
|
|
89
|
+
let value;
|
|
90
|
+
if (reference.location === 'scalar') {
|
|
91
|
+
value = scalarSchema(reference.type);
|
|
92
|
+
}
|
|
93
|
+
else if (reference.location === 'inputObjectTypes') {
|
|
94
|
+
const input = resolveInput(reference);
|
|
95
|
+
value = input
|
|
96
|
+
? internalRef(ensureInput(input))
|
|
97
|
+
: internalRef('jsonValue');
|
|
98
|
+
}
|
|
99
|
+
else if (reference.location === 'enumTypes') {
|
|
100
|
+
const enumType = resolveEnum(reference);
|
|
101
|
+
value = enumType
|
|
102
|
+
? internalRef(ensureEnum(enumType))
|
|
103
|
+
: { type: 'string' };
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
value = internalRef('jsonValue');
|
|
107
|
+
}
|
|
108
|
+
return reference.isList
|
|
109
|
+
? { type: 'array', items: { anyOf: [value, dynamicString()] } }
|
|
110
|
+
: value;
|
|
111
|
+
});
|
|
112
|
+
const relationList = relations?.get(field.name);
|
|
113
|
+
if (relationList !== undefined) {
|
|
114
|
+
const record = connectionRecord();
|
|
115
|
+
alternatives.push(record);
|
|
116
|
+
if (relationList) {
|
|
117
|
+
alternatives.push({
|
|
118
|
+
type: 'array',
|
|
119
|
+
items: { anyOf: [dynamicString(), record] },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
alternatives.push(dynamicString());
|
|
124
|
+
if (field.isNullable)
|
|
125
|
+
alternatives.push({ type: 'null' });
|
|
126
|
+
properties[field.name] = {
|
|
127
|
+
...(field.comment ? { description: field.comment } : {}),
|
|
128
|
+
anyOf: alternatives,
|
|
129
|
+
};
|
|
130
|
+
if (field.isRequired)
|
|
131
|
+
required.push(field.name);
|
|
132
|
+
}
|
|
133
|
+
definitions[name] = {
|
|
134
|
+
type: 'object',
|
|
135
|
+
additionalProperties: false,
|
|
136
|
+
properties,
|
|
137
|
+
...(required.length ? { required } : {}),
|
|
138
|
+
};
|
|
139
|
+
return name;
|
|
140
|
+
};
|
|
141
|
+
const entityNames = [
|
|
142
|
+
...new Set(dmmf.datamodel.models.flatMap(({ name }) => [
|
|
143
|
+
name,
|
|
144
|
+
name[0].toLowerCase() + name.slice(1),
|
|
145
|
+
])),
|
|
146
|
+
];
|
|
147
|
+
schema.properties.entity = entityNames.length
|
|
148
|
+
? {
|
|
149
|
+
description: 'Prisma model or lowercase-first delegate name.',
|
|
150
|
+
allOf: [{ $ref: '#/definitions/entityName' }],
|
|
151
|
+
enum: entityNames,
|
|
152
|
+
}
|
|
153
|
+
: {
|
|
154
|
+
description: 'No Prisma models are available.',
|
|
155
|
+
not: {},
|
|
156
|
+
};
|
|
157
|
+
const modelConditions = [];
|
|
158
|
+
for (const model of dmmf.datamodel.models) {
|
|
159
|
+
const aliases = [
|
|
160
|
+
...new Set([
|
|
161
|
+
model.name,
|
|
162
|
+
model.name[0].toLowerCase() + model.name.slice(1),
|
|
163
|
+
]),
|
|
164
|
+
];
|
|
165
|
+
const relationNames = model.fields
|
|
166
|
+
.filter((field) => field.kind === 'object')
|
|
167
|
+
.map((field) => field.name);
|
|
168
|
+
const roots = [
|
|
169
|
+
inputs.get(`prisma:${model.name}CreateInput`) ??
|
|
170
|
+
inputsByName.get(`${model.name}CreateInput`),
|
|
171
|
+
inputs.get(`prisma:${model.name}UncheckedCreateInput`) ??
|
|
172
|
+
inputsByName.get(`${model.name}UncheckedCreateInput`),
|
|
173
|
+
].filter((value) => value !== undefined);
|
|
174
|
+
const itemSchemas = roots.map((root) => internalRef(ensureInput(root)));
|
|
175
|
+
const thenSchema = {
|
|
176
|
+
properties: {
|
|
177
|
+
connectedFields: relationNames.length
|
|
178
|
+
? { items: { enum: relationNames } }
|
|
179
|
+
: { maxItems: 0 },
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
if (itemSchemas.length) {
|
|
183
|
+
thenSchema.allOf = [
|
|
184
|
+
{
|
|
185
|
+
if: { not: { required: ['processor'] } },
|
|
186
|
+
then: {
|
|
187
|
+
properties: {
|
|
188
|
+
items: {
|
|
189
|
+
additionalProperties: itemSchemas.length === 1
|
|
190
|
+
? itemSchemas[0]
|
|
191
|
+
: { anyOf: itemSchemas },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
modelConditions.push({
|
|
199
|
+
if: {
|
|
200
|
+
required: ['entity'],
|
|
201
|
+
properties: { entity: { enum: aliases } },
|
|
202
|
+
},
|
|
203
|
+
then: thenSchema,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
schema.allOf = [...(schema.allOf ?? []), ...modelConditions];
|
|
207
|
+
return schema;
|
|
208
|
+
}
|
|
209
|
+
const inputDefinitionName = ({ input, namespace }) => `PrismaInput_${namespace}_${input.name}`;
|
|
210
|
+
const enumDefinitionName = ({ enumType, namespace }) => `PrismaEnum_${namespace}_${enumType.name}`;
|
|
211
|
+
const internalRef = (name) => ({
|
|
212
|
+
$ref: `#/definitions/${pointer(name)}`,
|
|
213
|
+
});
|
|
214
|
+
const scalarSchema = (type) => {
|
|
215
|
+
if (type === 'Int')
|
|
216
|
+
return { type: 'integer' };
|
|
217
|
+
if (type === 'Float')
|
|
218
|
+
return { type: 'number' };
|
|
219
|
+
if (type === 'Decimal') {
|
|
220
|
+
return {
|
|
221
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (type === 'String' || type === 'DateTime')
|
|
225
|
+
return { type: 'string' };
|
|
226
|
+
if (type === 'Boolean')
|
|
227
|
+
return { type: 'boolean' };
|
|
228
|
+
if (type === 'BigInt') {
|
|
229
|
+
return {
|
|
230
|
+
anyOf: [{ type: 'integer' }, { type: 'string', pattern: '^-?[0-9]+$' }],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (type === 'Json')
|
|
234
|
+
return internalRef('jsonValue');
|
|
235
|
+
if (type === 'Null')
|
|
236
|
+
return { type: 'null' };
|
|
237
|
+
if (type === 'Bytes') {
|
|
238
|
+
return {
|
|
239
|
+
description: 'Bytes constructors are validated by Prisma at runtime; fixture structure remains permissive.',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return internalRef('jsonValue');
|
|
243
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FixtureDefinition } from './fixture-document';
|
|
2
|
+
export type FixtureProcessor = {
|
|
3
|
+
preProcess?: (name: string, data: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
4
|
+
};
|
|
5
|
+
export type ProcessorConstructor = new () => FixtureProcessor;
|
|
6
|
+
type FixtureRandomizer = {
|
|
7
|
+
next(): number;
|
|
8
|
+
seed(value: number | number[]): void;
|
|
9
|
+
};
|
|
10
|
+
export declare function renderFixtureTemplates(fixture: FixtureDefinition, randomizer: FixtureRandomizer, refDate?: string): Promise<Record<string, unknown>>;
|
|
11
|
+
export declare function createFixtureRandomizer(seed?: number): Promise<FixtureRandomizer>;
|
|
12
|
+
export declare function loadFixtureProcessor(processorPath: string): Promise<ProcessorConstructor>;
|
|
13
|
+
export declare function runFixtureProcessor(constructor: ProcessorConstructor | undefined, fixture: FixtureDefinition, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderFixtureTemplates = renderFixtureTemplates;
|
|
7
|
+
exports.createFixtureRandomizer = createFixtureRandomizer;
|
|
8
|
+
exports.loadFixtureProcessor = loadFixtureProcessor;
|
|
9
|
+
exports.runFixtureProcessor = runFixtureProcessor;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_module_1 = require("node:module");
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const node_url_1 = require("node:url");
|
|
14
|
+
const ejs_1 = __importDefault(require("ejs"));
|
|
15
|
+
const fixture_error_1 = require("./fixture-error");
|
|
16
|
+
const fixture_document_1 = require("./fixture-document");
|
|
17
|
+
const requireProcessor = (0, node_module_1.createRequire)(__filename);
|
|
18
|
+
const PROCESSOR_EXTENSIONS = ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts'];
|
|
19
|
+
async function renderFixtureTemplates(fixture, randomizer, refDate) {
|
|
20
|
+
const fakerModule = await import('@faker-js/faker');
|
|
21
|
+
let locale = fakerModule.en;
|
|
22
|
+
if (fixture.locale) {
|
|
23
|
+
if (!Object.hasOwn(fakerModule.allLocales, fixture.locale)) {
|
|
24
|
+
throw (0, fixture_error_1.createFixtureError)('FIXTURE_TEMPLATE_FAILED', 'Unknown Faker locale', (0, fixture_document_1.fixtureErrorContext)(fixture, 'rendering templates'));
|
|
25
|
+
}
|
|
26
|
+
locale =
|
|
27
|
+
fakerModule.allLocales[fixture.locale];
|
|
28
|
+
}
|
|
29
|
+
const generator = new fakerModule.Faker({
|
|
30
|
+
locale: locale === fakerModule.en ? [locale] : [locale, fakerModule.en],
|
|
31
|
+
randomizer,
|
|
32
|
+
});
|
|
33
|
+
if (refDate !== undefined)
|
|
34
|
+
generator.setDefaultRefDate(refDate);
|
|
35
|
+
const data = renderValue(fixture.data, fixture, generator, '');
|
|
36
|
+
if (!(0, fixture_document_1.isFixtureRecord)(data)) {
|
|
37
|
+
throw (0, fixture_error_1.createFixtureError)('FIXTURE_TEMPLATE_FAILED', 'Fixture template produced invalid data', (0, fixture_document_1.fixtureErrorContext)(fixture, 'rendering templates'));
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
(0, fixture_document_1.assertSafeFixtureValue)(data);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw (0, fixture_error_1.wrapFixtureError)(error, 'FIXTURE_TEMPLATE_FAILED', 'Fixture template produced invalid data', (0, fixture_document_1.fixtureErrorContext)(fixture, 'rendering templates'));
|
|
44
|
+
}
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
async function createFixtureRandomizer(seed) {
|
|
48
|
+
const { generateMersenne53Randomizer } = await import('@faker-js/faker');
|
|
49
|
+
return generateMersenne53Randomizer(seed);
|
|
50
|
+
}
|
|
51
|
+
function renderValue(value, fixture, faker, path) {
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.map((item, index) => renderValue(item, fixture, faker, (0, fixture_error_1.appendFixturePath)(path, index)));
|
|
54
|
+
}
|
|
55
|
+
if ((0, fixture_document_1.isFixtureRecord)(value)) {
|
|
56
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
57
|
+
key,
|
|
58
|
+
renderValue(item, fixture, faker, (0, fixture_error_1.appendFixturePath)(path, key)),
|
|
59
|
+
]));
|
|
60
|
+
}
|
|
61
|
+
if (typeof value !== 'string')
|
|
62
|
+
return value;
|
|
63
|
+
try {
|
|
64
|
+
const template = value.includes('<%') ? ejs_1.default.render(value, fixture) : value;
|
|
65
|
+
const providerPattern = /\{\{([\s\S]*?)\}\}/g;
|
|
66
|
+
const providers = [...template.matchAll(providerPattern)];
|
|
67
|
+
const singleProvider = providers.length === 1 && providers[0][0].length === template.length
|
|
68
|
+
? providers[0]
|
|
69
|
+
: undefined;
|
|
70
|
+
const generated = singleProvider
|
|
71
|
+
? fakeValue(singleProvider[1], faker)
|
|
72
|
+
: template.replace(providerPattern, (_token, provider) => String(fakeValue(provider, faker)));
|
|
73
|
+
if (typeof generated !== 'string') {
|
|
74
|
+
(0, fixture_document_1.assertSafeFixtureValue)(generated);
|
|
75
|
+
return generated;
|
|
76
|
+
}
|
|
77
|
+
return generated.replace(/<\{(.*?)\}>/g, (_token, key) => String(parameterValue(fixture.parameters, key) ?? ''));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw (0, fixture_error_1.wrapFixtureError)(error, 'FIXTURE_TEMPLATE_FAILED', 'Fixture template failed', (0, fixture_document_1.fixtureErrorContext)(fixture, 'rendering templates', path));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function fakeValue(expression, faker) {
|
|
84
|
+
const match = expression.trim().match(/^(\w+)\.(\w+)(?:\(([\s\S]*)\))?$/);
|
|
85
|
+
if (!match)
|
|
86
|
+
throw new Error('Invalid Faker provider');
|
|
87
|
+
const [group, method] = modernProvider(match[1], match[2]);
|
|
88
|
+
const argument = match[3];
|
|
89
|
+
if (fixture_document_1.DANGEROUS_KEYS.has(group) || fixture_document_1.DANGEROUS_KEYS.has(method)) {
|
|
90
|
+
throw new Error('Invalid Faker provider');
|
|
91
|
+
}
|
|
92
|
+
const providerGroup = faker[group];
|
|
93
|
+
if (providerGroup === null || typeof providerGroup !== 'object') {
|
|
94
|
+
throw new Error('Unknown Faker provider');
|
|
95
|
+
}
|
|
96
|
+
const provider = providerGroup[method];
|
|
97
|
+
if (typeof provider !== 'function')
|
|
98
|
+
throw new Error('Unknown Faker provider');
|
|
99
|
+
if (argument === undefined || !argument.trim())
|
|
100
|
+
return provider.call(providerGroup);
|
|
101
|
+
let parameter = argument;
|
|
102
|
+
try {
|
|
103
|
+
parameter = JSON.parse(argument);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// The upstream syntax accepts a plain string as the single provider argument.
|
|
107
|
+
}
|
|
108
|
+
if (group === 'date' && method === 'past' && typeof parameter === 'number') {
|
|
109
|
+
parameter = { years: parameter };
|
|
110
|
+
}
|
|
111
|
+
return provider.call(providerGroup, parameter);
|
|
112
|
+
}
|
|
113
|
+
function modernProvider(group, method) {
|
|
114
|
+
if (group === 'name')
|
|
115
|
+
return ['person', method === 'title' ? 'jobTitle' : method];
|
|
116
|
+
if (group === 'address')
|
|
117
|
+
return ['location', method];
|
|
118
|
+
if (group === 'internet' && method === 'userName')
|
|
119
|
+
return [group, 'username'];
|
|
120
|
+
if (group === 'random' && method === 'number')
|
|
121
|
+
return ['number', 'int'];
|
|
122
|
+
if (group === 'random' && method === 'alphaNumeric')
|
|
123
|
+
return ['string', 'alphanumeric'];
|
|
124
|
+
if (group === 'random' && method === 'arrayElement')
|
|
125
|
+
return ['helpers', 'arrayElement'];
|
|
126
|
+
if (group === 'random' && method === 'word')
|
|
127
|
+
return ['word', 'sample'];
|
|
128
|
+
if (group === 'datatype' && method === 'number')
|
|
129
|
+
return ['number', 'int'];
|
|
130
|
+
if (group === 'datatype' && method === 'float')
|
|
131
|
+
return ['number', 'float'];
|
|
132
|
+
if (group === 'datatype' && method === 'datetime')
|
|
133
|
+
return ['date', 'anytime'];
|
|
134
|
+
if (group === 'datatype' && method === 'string')
|
|
135
|
+
return ['string', 'sample'];
|
|
136
|
+
return [group, method];
|
|
137
|
+
}
|
|
138
|
+
function parameterValue(parameters, key) {
|
|
139
|
+
const parts = key.split('.');
|
|
140
|
+
if (parts.some((part) => fixture_document_1.DANGEROUS_KEYS.has(part))) {
|
|
141
|
+
throw (0, fixture_error_1.createFixtureError)('FIXTURE_TEMPLATE_FAILED', 'Unknown fixture parameter', { stage: 'rendering templates' });
|
|
142
|
+
}
|
|
143
|
+
let value = parameters;
|
|
144
|
+
let found = true;
|
|
145
|
+
for (const part of parts) {
|
|
146
|
+
if (!(0, fixture_document_1.isFixtureRecord)(value) || !Object.hasOwn(value, part)) {
|
|
147
|
+
found = false;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
value = value[part];
|
|
151
|
+
}
|
|
152
|
+
if (found && value !== undefined)
|
|
153
|
+
return value;
|
|
154
|
+
if (parts.length === 3 && parts[0] === 'process' && parts[1] === 'env') {
|
|
155
|
+
const variable = parts[2];
|
|
156
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable)) {
|
|
157
|
+
const environmentValue = process.env[variable];
|
|
158
|
+
if (environmentValue !== undefined)
|
|
159
|
+
return environmentValue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw (0, fixture_error_1.createFixtureError)('FIXTURE_TEMPLATE_FAILED', 'Unknown fixture parameter', { stage: 'rendering templates' });
|
|
163
|
+
}
|
|
164
|
+
async function loadFixtureProcessor(processorPath) {
|
|
165
|
+
const resolvedPath = resolveProcessorPath(processorPath);
|
|
166
|
+
let loaded;
|
|
167
|
+
try {
|
|
168
|
+
loaded = requireProcessor(resolvedPath);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (!requiresNativeImport(error))
|
|
172
|
+
throw error;
|
|
173
|
+
loaded = await import((0, node_url_1.pathToFileURL)(resolvedPath).href);
|
|
174
|
+
}
|
|
175
|
+
let constructor = loaded;
|
|
176
|
+
for (let depth = 0; depth < 2 && (0, fixture_document_1.isFixtureRecord)(constructor); depth += 1) {
|
|
177
|
+
if (!Object.hasOwn(constructor, 'default'))
|
|
178
|
+
break;
|
|
179
|
+
constructor = constructor.default;
|
|
180
|
+
}
|
|
181
|
+
if (typeof constructor !== 'function') {
|
|
182
|
+
throw new Error('Fixture processor must export a default class');
|
|
183
|
+
}
|
|
184
|
+
return constructor;
|
|
185
|
+
}
|
|
186
|
+
function requiresNativeImport(error) {
|
|
187
|
+
if (error === null || typeof error !== 'object' || !('code' in error)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const code = error.code;
|
|
191
|
+
return code === 'ERR_REQUIRE_ESM' || code === 'ERR_REQUIRE_ASYNC_MODULE';
|
|
192
|
+
}
|
|
193
|
+
function resolveProcessorPath(processorPath) {
|
|
194
|
+
if (node_path_1.default.extname(processorPath))
|
|
195
|
+
return processorPath;
|
|
196
|
+
try {
|
|
197
|
+
return requireProcessor.resolve(processorPath);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
for (const extension of PROCESSOR_EXTENSIONS) {
|
|
201
|
+
const candidate = `${processorPath}${extension}`;
|
|
202
|
+
try {
|
|
203
|
+
if (node_fs_1.default.statSync(candidate).isFile())
|
|
204
|
+
return candidate;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Continue through the bounded extension list.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw new Error('Fixture processor module not found');
|
|
212
|
+
}
|
|
213
|
+
async function runFixtureProcessor(constructor, fixture, data) {
|
|
214
|
+
let result = data;
|
|
215
|
+
try {
|
|
216
|
+
if (constructor) {
|
|
217
|
+
const processor = new constructor();
|
|
218
|
+
if (processor.preProcess !== undefined) {
|
|
219
|
+
if (typeof processor.preProcess !== 'function') {
|
|
220
|
+
throw new Error('Invalid fixture processor hook');
|
|
221
|
+
}
|
|
222
|
+
result = await processor.preProcess(fixture.name, data);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw (0, fixture_error_1.wrapFixtureError)(error, 'FIXTURE_PROCESSOR_FAILED', 'Fixture processor failed', (0, fixture_document_1.fixtureErrorContext)(fixture, 'processing fixture'));
|
|
228
|
+
}
|
|
229
|
+
if (!(0, fixture_document_1.isFixtureRecord)(result)) {
|
|
230
|
+
throw (0, fixture_error_1.createFixtureError)('FIXTURE_PROCESSOR_FAILED', 'Fixture processor returned invalid data', (0, fixture_document_1.fixtureErrorContext)(fixture, 'processing fixture'));
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
(0, fixture_document_1.assertSafeFixtureValue)(result);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw (0, fixture_error_1.wrapFixtureError)(error, 'FIXTURE_PROCESSOR_FAILED', 'Fixture processor returned invalid data', (0, fixture_document_1.fixtureErrorContext)(fixture, 'processing fixture'));
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const generator_helper_1 = require("@prisma/generator-helper");
|
|
10
|
+
const fixture_schema_1 = require("./fixture-schema");
|
|
11
|
+
(0, generator_helper_1.generatorHandler)({
|
|
12
|
+
onManifest() {
|
|
13
|
+
return {
|
|
14
|
+
prettyName: 'Prisma Fixtures',
|
|
15
|
+
defaultOutput: './generated/fixtures',
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
async onGenerate(options) {
|
|
19
|
+
const output = options.generator.output?.value;
|
|
20
|
+
if (!output)
|
|
21
|
+
throw new Error('Prisma Fixtures generator requires an output path');
|
|
22
|
+
node_fs_1.default.mkdirSync(output, { recursive: true });
|
|
23
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(output, 'schema.json'), `${JSON.stringify((0, fixture_schema_1.buildFixtureSchema)(options.dmmf), null, 2)}\n`);
|
|
24
|
+
},
|
|
25
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type FixtureDefinition, readFixtureDocuments } from './fixture-document';
|
|
2
|
+
import { type FixtureCleanupOptions } from './cleanup-options';
|
|
3
|
+
import { type FixtureLoadOptions, type FixtureResetOptions } from './load-options';
|
|
4
|
+
export type { FixtureDefinition } from './fixture-document';
|
|
5
|
+
export { FixtureError } from './fixture-error';
|
|
6
|
+
export type { FixtureErrorCode, FixtureErrorContext } from './fixture-error';
|
|
7
|
+
export type { FixtureProcessor } from './fixture-template';
|
|
8
|
+
export type { FixtureCleanupOptions, FixturePreservedTable, } from './cleanup-options';
|
|
9
|
+
export type { FixtureLoadOptions, FixtureResetOptions } from './load-options';
|
|
10
|
+
export declare const readFixtureDefinitions: typeof readFixtureDocuments;
|
|
11
|
+
export declare class PrismaFixtures {
|
|
12
|
+
load(client: object): Promise<Record<string, Record<string, unknown>>>;
|
|
13
|
+
}
|
|
14
|
+
export type FixtureWriter = (fixture: FixtureDefinition, data: Record<string, unknown>) => Promise<unknown>;
|
|
15
|
+
export declare function loadFixtures(client: object, definitions: FixtureDefinition[], write?: FixtureWriter): Promise<Record<string, Record<string, unknown>>>;
|
|
16
|
+
export declare function loadFixtures(client: object, definitions: FixtureDefinition[], options?: FixtureLoadOptions, write?: FixtureWriter): Promise<Record<string, Record<string, unknown>>>;
|
|
17
|
+
export declare function cleanFixtures(client: object, options?: FixtureCleanupOptions): Promise<void>;
|
|
18
|
+
export declare function resetFixtures(client: object, definitions: FixtureDefinition[], write?: FixtureWriter): Promise<Record<string, Record<string, unknown>>>;
|
|
19
|
+
export declare function resetFixtures(client: object, definitions: FixtureDefinition[], options?: FixtureResetOptions, write?: FixtureWriter): Promise<Record<string, Record<string, unknown>>>;
|