s3db.js 12.1.0 → 12.2.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/README.md +212 -196
- package/dist/s3db.cjs.js +1041 -1941
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1039 -1941
- package/dist/s3db.es.js.map +1 -1
- package/package.json +6 -1
- package/src/cli/index.js +954 -43
- package/src/cli/migration-manager.js +270 -0
- package/src/concerns/calculator.js +0 -4
- package/src/concerns/metadata-encoding.js +1 -21
- package/src/concerns/plugin-storage.js +17 -4
- package/src/concerns/typescript-generator.d.ts +171 -0
- package/src/concerns/typescript-generator.js +275 -0
- package/src/database.class.js +171 -28
- package/src/index.js +15 -9
- package/src/plugins/api/index.js +5 -2
- package/src/plugins/api/routes/resource-routes.js +86 -1
- package/src/plugins/api/server.js +79 -3
- package/src/plugins/api/utils/openapi-generator.js +195 -5
- package/src/plugins/backup/multi-backup-driver.class.js +0 -1
- package/src/plugins/backup.plugin.js +7 -14
- package/src/plugins/concerns/plugin-dependencies.js +73 -19
- package/src/plugins/eventual-consistency/analytics.js +0 -2
- package/src/plugins/eventual-consistency/consolidation.js +2 -13
- package/src/plugins/eventual-consistency/index.js +0 -1
- package/src/plugins/eventual-consistency/install.js +1 -1
- package/src/plugins/geo.plugin.js +5 -6
- package/src/plugins/importer/index.js +1 -1
- package/src/plugins/index.js +2 -1
- package/src/plugins/relation.plugin.js +11 -11
- package/src/plugins/replicator.plugin.js +12 -21
- package/src/plugins/s3-queue.plugin.js +4 -4
- package/src/plugins/scheduler.plugin.js +10 -12
- package/src/plugins/state-machine.plugin.js +8 -12
- package/src/plugins/tfstate/README.md +1 -1
- package/src/plugins/tfstate/errors.js +3 -3
- package/src/plugins/tfstate/index.js +41 -67
- package/src/plugins/ttl.plugin.js +3 -3
- package/src/resource.class.js +263 -61
- package/src/schema.class.js +0 -2
- package/src/testing/factory.class.js +286 -0
- package/src/testing/index.js +15 -0
- package/src/testing/seeder.class.js +183 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory - Test Data Factory Pattern for s3db.js
|
|
3
|
+
*
|
|
4
|
+
* Simplifies test data creation with:
|
|
5
|
+
* - Automatic field generation
|
|
6
|
+
* - Sequence support
|
|
7
|
+
* - Relationships
|
|
8
|
+
* - Traits/states
|
|
9
|
+
* - Batch creation
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const UserFactory = Factory.define('users', {
|
|
13
|
+
* email: ({ seq }) => `user${seq}@example.com`,
|
|
14
|
+
* name: 'Test User',
|
|
15
|
+
* isActive: true
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const user = await UserFactory.create();
|
|
19
|
+
* const users = await UserFactory.createMany(10);
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export class Factory {
|
|
23
|
+
/**
|
|
24
|
+
* Global sequence counter
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
static _sequences = new Map();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Registered factories
|
|
31
|
+
* @private
|
|
32
|
+
*/
|
|
33
|
+
static _factories = new Map();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Database instance (set globally)
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
static _database = null;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a new factory definition
|
|
43
|
+
* @param {string} resourceName - Resource name
|
|
44
|
+
* @param {Object|Function} definition - Field definitions or function
|
|
45
|
+
* @param {Object} options - Factory options
|
|
46
|
+
* @returns {Factory} Factory instance
|
|
47
|
+
*/
|
|
48
|
+
static define(resourceName, definition, options = {}) {
|
|
49
|
+
const factory = new Factory(resourceName, definition, options);
|
|
50
|
+
Factory._factories.set(resourceName, factory);
|
|
51
|
+
return factory;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set global database instance
|
|
56
|
+
* @param {Database} database - s3db.js Database instance
|
|
57
|
+
*/
|
|
58
|
+
static setDatabase(database) {
|
|
59
|
+
Factory._database = database;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get factory by resource name
|
|
64
|
+
* @param {string} resourceName - Resource name
|
|
65
|
+
* @returns {Factory} Factory instance
|
|
66
|
+
*/
|
|
67
|
+
static get(resourceName) {
|
|
68
|
+
return Factory._factories.get(resourceName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Reset all sequences
|
|
73
|
+
*/
|
|
74
|
+
static resetSequences() {
|
|
75
|
+
Factory._sequences.clear();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reset all factories
|
|
80
|
+
*/
|
|
81
|
+
static reset() {
|
|
82
|
+
Factory._sequences.clear();
|
|
83
|
+
Factory._factories.clear();
|
|
84
|
+
Factory._database = null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Constructor
|
|
89
|
+
* @param {string} resourceName - Resource name
|
|
90
|
+
* @param {Object|Function} definition - Field definitions
|
|
91
|
+
* @param {Object} options - Factory options
|
|
92
|
+
*/
|
|
93
|
+
constructor(resourceName, definition, options = {}) {
|
|
94
|
+
this.resourceName = resourceName;
|
|
95
|
+
this.definition = definition;
|
|
96
|
+
this.options = options;
|
|
97
|
+
this.traits = new Map();
|
|
98
|
+
this.afterCreateCallbacks = [];
|
|
99
|
+
this.beforeCreateCallbacks = [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get next sequence number
|
|
104
|
+
* @param {string} name - Sequence name (default: factory name)
|
|
105
|
+
* @returns {number} Next sequence number
|
|
106
|
+
*/
|
|
107
|
+
sequence(name = this.resourceName) {
|
|
108
|
+
const current = Factory._sequences.get(name) || 0;
|
|
109
|
+
const next = current + 1;
|
|
110
|
+
Factory._sequences.set(name, next);
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Define a trait (state variation)
|
|
116
|
+
* @param {string} name - Trait name
|
|
117
|
+
* @param {Object|Function} attributes - Trait attributes
|
|
118
|
+
* @returns {Factory} This factory (for chaining)
|
|
119
|
+
*/
|
|
120
|
+
trait(name, attributes) {
|
|
121
|
+
this.traits.set(name, attributes);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register after create callback
|
|
127
|
+
* @param {Function} callback - Callback function
|
|
128
|
+
* @returns {Factory} This factory (for chaining)
|
|
129
|
+
*/
|
|
130
|
+
afterCreate(callback) {
|
|
131
|
+
this.afterCreateCallbacks.push(callback);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Register before create callback
|
|
137
|
+
* @param {Function} callback - Callback function
|
|
138
|
+
* @returns {Factory} This factory (for chaining)
|
|
139
|
+
*/
|
|
140
|
+
beforeCreate(callback) {
|
|
141
|
+
this.beforeCreateCallbacks.push(callback);
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build attributes without creating in database
|
|
147
|
+
* @param {Object} overrides - Override attributes
|
|
148
|
+
* @param {Object} options - Build options
|
|
149
|
+
* @returns {Promise<Object>} Built attributes
|
|
150
|
+
*/
|
|
151
|
+
async build(overrides = {}, options = {}) {
|
|
152
|
+
const { traits = [] } = options;
|
|
153
|
+
const seq = this.sequence();
|
|
154
|
+
|
|
155
|
+
// Base attributes
|
|
156
|
+
let attributes = typeof this.definition === 'function'
|
|
157
|
+
? await this.definition({ seq, factory: this })
|
|
158
|
+
: { ...this.definition };
|
|
159
|
+
|
|
160
|
+
// Apply traits
|
|
161
|
+
for (const traitName of traits) {
|
|
162
|
+
const trait = this.traits.get(traitName);
|
|
163
|
+
if (!trait) {
|
|
164
|
+
throw new Error(`Trait '${traitName}' not found in factory '${this.resourceName}'`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const traitAttrs = typeof trait === 'function'
|
|
168
|
+
? await trait({ seq, factory: this })
|
|
169
|
+
: trait;
|
|
170
|
+
|
|
171
|
+
attributes = { ...attributes, ...traitAttrs };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Apply overrides
|
|
175
|
+
attributes = { ...attributes, ...overrides };
|
|
176
|
+
|
|
177
|
+
// Resolve functions
|
|
178
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
179
|
+
if (typeof value === 'function') {
|
|
180
|
+
attributes[key] = await value({ seq, factory: this });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return attributes;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create resource in database
|
|
189
|
+
* @param {Object} overrides - Override attributes
|
|
190
|
+
* @param {Object} options - Create options
|
|
191
|
+
* @returns {Promise<Object>} Created resource
|
|
192
|
+
*/
|
|
193
|
+
async create(overrides = {}, options = {}) {
|
|
194
|
+
const { database = Factory._database } = options;
|
|
195
|
+
|
|
196
|
+
if (!database) {
|
|
197
|
+
throw new Error('Database not set. Use Factory.setDatabase(db) or pass database option');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Build attributes
|
|
201
|
+
let attributes = await this.build(overrides, options);
|
|
202
|
+
|
|
203
|
+
// Before create callbacks
|
|
204
|
+
for (const callback of this.beforeCreateCallbacks) {
|
|
205
|
+
attributes = await callback(attributes) || attributes;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Get resource
|
|
209
|
+
const resource = database.resources[this.resourceName];
|
|
210
|
+
if (!resource) {
|
|
211
|
+
throw new Error(`Resource '${this.resourceName}' not found in database`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create in database
|
|
215
|
+
let created = await resource.insert(attributes);
|
|
216
|
+
|
|
217
|
+
// After create callbacks
|
|
218
|
+
for (const callback of this.afterCreateCallbacks) {
|
|
219
|
+
created = await callback(created, { database }) || created;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return created;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create multiple resources
|
|
227
|
+
* @param {number} count - Number of resources to create
|
|
228
|
+
* @param {Object} overrides - Override attributes
|
|
229
|
+
* @param {Object} options - Create options
|
|
230
|
+
* @returns {Promise<Object[]>} Created resources
|
|
231
|
+
*/
|
|
232
|
+
async createMany(count, overrides = {}, options = {}) {
|
|
233
|
+
const resources = [];
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < count; i++) {
|
|
236
|
+
const resource = await this.create(overrides, options);
|
|
237
|
+
resources.push(resource);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return resources;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Build multiple resources without creating
|
|
245
|
+
* @param {number} count - Number of resources to build
|
|
246
|
+
* @param {Object} overrides - Override attributes
|
|
247
|
+
* @param {Object} options - Build options
|
|
248
|
+
* @returns {Promise<Object[]>} Built resources
|
|
249
|
+
*/
|
|
250
|
+
async buildMany(count, overrides = {}, options = {}) {
|
|
251
|
+
const resources = [];
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < count; i++) {
|
|
254
|
+
const resource = await this.build(overrides, options);
|
|
255
|
+
resources.push(resource);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return resources;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create with specific traits
|
|
263
|
+
* @param {string|string[]} traits - Trait name(s)
|
|
264
|
+
* @param {Object} overrides - Override attributes
|
|
265
|
+
* @param {Object} options - Create options
|
|
266
|
+
* @returns {Promise<Object>} Created resource
|
|
267
|
+
*/
|
|
268
|
+
async createWithTraits(traits, overrides = {}, options = {}) {
|
|
269
|
+
const traitArray = Array.isArray(traits) ? traits : [traits];
|
|
270
|
+
return this.create(overrides, { ...options, traits: traitArray });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Build with specific traits
|
|
275
|
+
* @param {string|string[]} traits - Trait name(s)
|
|
276
|
+
* @param {Object} overrides - Override attributes
|
|
277
|
+
* @param {Object} options - Build options
|
|
278
|
+
* @returns {Promise<Object>} Built resource
|
|
279
|
+
*/
|
|
280
|
+
async buildWithTraits(traits, overrides = {}, options = {}) {
|
|
281
|
+
const traitArray = Array.isArray(traits) ? traits : [traits];
|
|
282
|
+
return this.build(overrides, { ...options, traits: traitArray });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export default Factory;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing Utilities for s3db.js
|
|
3
|
+
*
|
|
4
|
+
* Provides factory pattern and seeding utilities for tests.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Factory } from './factory.class.js';
|
|
8
|
+
import { Seeder } from './seeder.class.js';
|
|
9
|
+
|
|
10
|
+
export { Factory, Seeder };
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
Factory,
|
|
14
|
+
Seeder
|
|
15
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeder - Database Seeding for Tests
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for seeding test databases with factories.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const seeder = new Seeder(database);
|
|
8
|
+
*
|
|
9
|
+
* await seeder.seed({
|
|
10
|
+
* users: 10,
|
|
11
|
+
* posts: 50,
|
|
12
|
+
* comments: 100
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* await seeder.truncate(['users', 'posts']);
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Factory } from './factory.class.js';
|
|
19
|
+
|
|
20
|
+
export class Seeder {
|
|
21
|
+
/**
|
|
22
|
+
* Constructor
|
|
23
|
+
* @param {Database} database - s3db.js Database instance
|
|
24
|
+
* @param {Object} options - Seeder options
|
|
25
|
+
*/
|
|
26
|
+
constructor(database, options = {}) {
|
|
27
|
+
this.database = database;
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.verbose = options.verbose !== false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Log message (if verbose)
|
|
34
|
+
* @param {string} message - Message to log
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
log(message) {
|
|
38
|
+
if (this.verbose) {
|
|
39
|
+
console.log(`[Seeder] ${message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Seed resources using factories
|
|
45
|
+
* @param {Object} specs - Seed specifications { resourceName: count }
|
|
46
|
+
* @returns {Promise<Object>} Created resources by resource name
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* const created = await seeder.seed({
|
|
50
|
+
* users: 10,
|
|
51
|
+
* posts: 50
|
|
52
|
+
* });
|
|
53
|
+
*/
|
|
54
|
+
async seed(specs) {
|
|
55
|
+
const created = {};
|
|
56
|
+
|
|
57
|
+
for (const [resourceName, count] of Object.entries(specs)) {
|
|
58
|
+
this.log(`Seeding ${count} ${resourceName}...`);
|
|
59
|
+
|
|
60
|
+
const factory = Factory.get(resourceName);
|
|
61
|
+
if (!factory) {
|
|
62
|
+
throw new Error(`Factory for '${resourceName}' not found. Define it with Factory.define()`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
created[resourceName] = await factory.createMany(count, {}, { database: this.database });
|
|
66
|
+
|
|
67
|
+
this.log(`✅ Created ${count} ${resourceName}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return created;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Seed with custom callback
|
|
75
|
+
* @param {Function} callback - Seeding callback
|
|
76
|
+
* @returns {Promise<any>} Result of callback
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* await seeder.call(async (db) => {
|
|
80
|
+
* const user = await UserFactory.create();
|
|
81
|
+
* const posts = await PostFactory.createMany(5, { userId: user.id });
|
|
82
|
+
* return { user, posts };
|
|
83
|
+
* });
|
|
84
|
+
*/
|
|
85
|
+
async call(callback) {
|
|
86
|
+
this.log('Running custom seeder...');
|
|
87
|
+
const result = await callback(this.database);
|
|
88
|
+
this.log('✅ Custom seeder completed');
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Truncate resources (delete all data)
|
|
94
|
+
* @param {string[]} resourceNames - Resource names to truncate
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* await seeder.truncate(['users', 'posts']);
|
|
99
|
+
*/
|
|
100
|
+
async truncate(resourceNames) {
|
|
101
|
+
for (const resourceName of resourceNames) {
|
|
102
|
+
this.log(`Truncating ${resourceName}...`);
|
|
103
|
+
|
|
104
|
+
const resource = this.database.resources[resourceName];
|
|
105
|
+
if (!resource) {
|
|
106
|
+
this.log(`⚠️ Resource '${resourceName}' not found, skipping`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// List all IDs
|
|
111
|
+
const ids = await resource.listIds();
|
|
112
|
+
|
|
113
|
+
// Delete all
|
|
114
|
+
if (ids.length > 0) {
|
|
115
|
+
await resource.deleteMany(ids);
|
|
116
|
+
this.log(`✅ Deleted ${ids.length} ${resourceName}`);
|
|
117
|
+
} else {
|
|
118
|
+
this.log(`✅ ${resourceName} already empty`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Truncate all resources
|
|
125
|
+
* @returns {Promise<void>}
|
|
126
|
+
*/
|
|
127
|
+
async truncateAll() {
|
|
128
|
+
const resourceNames = Object.keys(this.database.resources);
|
|
129
|
+
await this.truncate(resourceNames);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Run multiple seeders in order
|
|
134
|
+
* @param {Function[]} seeders - Array of seeder functions
|
|
135
|
+
* @returns {Promise<Object[]>} Results of each seeder
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* await seeder.run([
|
|
139
|
+
* async (db) => await UserFactory.createMany(10),
|
|
140
|
+
* async (db) => await PostFactory.createMany(50)
|
|
141
|
+
* ]);
|
|
142
|
+
*/
|
|
143
|
+
async run(seeders) {
|
|
144
|
+
const results = [];
|
|
145
|
+
|
|
146
|
+
for (const seederFn of seeders) {
|
|
147
|
+
this.log(`Running seeder ${seederFn.name || 'anonymous'}...`);
|
|
148
|
+
const result = await seederFn(this.database);
|
|
149
|
+
results.push(result);
|
|
150
|
+
this.log(`✅ Completed ${seederFn.name || 'anonymous'}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return results;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Seed and return specific resources
|
|
158
|
+
* @param {Object} specs - Seed specifications
|
|
159
|
+
* @returns {Promise<Object>} Created resources
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* const { users, posts } = await seeder.seedAndReturn({
|
|
163
|
+
* users: 5,
|
|
164
|
+
* posts: 10
|
|
165
|
+
* });
|
|
166
|
+
*/
|
|
167
|
+
async seedAndReturn(specs) {
|
|
168
|
+
return await this.seed(specs);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Reset database (truncate all and reset sequences)
|
|
173
|
+
* @returns {Promise<void>}
|
|
174
|
+
*/
|
|
175
|
+
async reset() {
|
|
176
|
+
this.log('Resetting database...');
|
|
177
|
+
await this.truncateAll();
|
|
178
|
+
Factory.resetSequences();
|
|
179
|
+
this.log('✅ Database reset complete');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export default Seeder;
|