@radio-garden/ditojs-server 2.85.2-0.5067ad799
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 +6 -0
- package/package.json +95 -0
- package/src/app/Application.js +1186 -0
- package/src/app/Validator.js +405 -0
- package/src/app/index.js +2 -0
- package/src/cli/console.js +152 -0
- package/src/cli/db/createMigration.js +241 -0
- package/src/cli/db/index.js +7 -0
- package/src/cli/db/listAssetConfig.js +10 -0
- package/src/cli/db/migrate.js +12 -0
- package/src/cli/db/reset.js +23 -0
- package/src/cli/db/rollback.js +12 -0
- package/src/cli/db/seed.js +80 -0
- package/src/cli/db/unlock.js +9 -0
- package/src/cli/index.js +72 -0
- package/src/controllers/AdminController.js +322 -0
- package/src/controllers/CollectionController.js +274 -0
- package/src/controllers/Controller.js +657 -0
- package/src/controllers/ControllerAction.js +370 -0
- package/src/controllers/MemberAction.js +27 -0
- package/src/controllers/ModelController.js +63 -0
- package/src/controllers/RelationController.js +93 -0
- package/src/controllers/UsersController.js +64 -0
- package/src/controllers/index.js +5 -0
- package/src/errors/AssetError.js +7 -0
- package/src/errors/AuthenticationError.js +7 -0
- package/src/errors/AuthorizationError.js +7 -0
- package/src/errors/ControllerError.js +14 -0
- package/src/errors/DatabaseError.js +37 -0
- package/src/errors/GraphError.js +7 -0
- package/src/errors/ModelError.js +12 -0
- package/src/errors/NotFoundError.js +7 -0
- package/src/errors/NotImplementedError.js +7 -0
- package/src/errors/QueryBuilderError.js +7 -0
- package/src/errors/RelationError.js +21 -0
- package/src/errors/ResponseError.js +56 -0
- package/src/errors/ValidationError.js +7 -0
- package/src/errors/index.js +13 -0
- package/src/graph/DitoGraphProcessor.js +213 -0
- package/src/graph/expression.js +53 -0
- package/src/graph/graph.js +258 -0
- package/src/graph/index.js +3 -0
- package/src/index.js +9 -0
- package/src/lib/EventEmitter.js +66 -0
- package/src/lib/KnexHelper.js +30 -0
- package/src/lib/index.js +2 -0
- package/src/middleware/attachLogger.js +8 -0
- package/src/middleware/createTransaction.js +33 -0
- package/src/middleware/extendContext.js +10 -0
- package/src/middleware/findRoute.js +20 -0
- package/src/middleware/handleConnectMiddleware.js +99 -0
- package/src/middleware/handleError.js +29 -0
- package/src/middleware/handleRoute.js +23 -0
- package/src/middleware/handleSession.js +77 -0
- package/src/middleware/handleUser.js +31 -0
- package/src/middleware/index.js +11 -0
- package/src/middleware/logRequests.js +125 -0
- package/src/middleware/setupRequestStorage.js +14 -0
- package/src/mixins/AssetMixin.js +78 -0
- package/src/mixins/SessionMixin.js +17 -0
- package/src/mixins/TimeStampedMixin.js +41 -0
- package/src/mixins/UserMixin.js +171 -0
- package/src/mixins/index.js +4 -0
- package/src/models/AssetModel.js +4 -0
- package/src/models/Model.js +1205 -0
- package/src/models/RelationAccessor.js +41 -0
- package/src/models/SessionModel.js +4 -0
- package/src/models/TimeStampedModel.js +4 -0
- package/src/models/UserModel.js +4 -0
- package/src/models/definitions/assets.js +5 -0
- package/src/models/definitions/filters.js +121 -0
- package/src/models/definitions/hooks.js +8 -0
- package/src/models/definitions/index.js +22 -0
- package/src/models/definitions/modifiers.js +5 -0
- package/src/models/definitions/options.js +5 -0
- package/src/models/definitions/properties.js +73 -0
- package/src/models/definitions/relations.js +5 -0
- package/src/models/definitions/schema.js +5 -0
- package/src/models/definitions/scopes.js +36 -0
- package/src/models/index.js +5 -0
- package/src/query/QueryBuilder.js +1077 -0
- package/src/query/QueryFilters.js +66 -0
- package/src/query/QueryParameters.js +79 -0
- package/src/query/Registry.js +29 -0
- package/src/query/index.js +3 -0
- package/src/schema/formats/_empty.js +4 -0
- package/src/schema/formats/_required.js +4 -0
- package/src/schema/formats/index.js +2 -0
- package/src/schema/index.js +5 -0
- package/src/schema/keywords/_computed.js +7 -0
- package/src/schema/keywords/_foreign.js +7 -0
- package/src/schema/keywords/_hidden.js +7 -0
- package/src/schema/keywords/_index.js +7 -0
- package/src/schema/keywords/_instanceof.js +45 -0
- package/src/schema/keywords/_primary.js +7 -0
- package/src/schema/keywords/_range.js +18 -0
- package/src/schema/keywords/_relate.js +13 -0
- package/src/schema/keywords/_specificType.js +7 -0
- package/src/schema/keywords/_unique.js +7 -0
- package/src/schema/keywords/_unsigned.js +7 -0
- package/src/schema/keywords/_validate.js +73 -0
- package/src/schema/keywords/index.js +12 -0
- package/src/schema/relations.js +324 -0
- package/src/schema/relations.test.js +177 -0
- package/src/schema/schema.js +289 -0
- package/src/schema/schema.test.js +720 -0
- package/src/schema/types/_asset.js +31 -0
- package/src/schema/types/_color.js +4 -0
- package/src/schema/types/index.js +2 -0
- package/src/services/Service.js +35 -0
- package/src/services/index.js +1 -0
- package/src/storage/AssetFile.js +81 -0
- package/src/storage/DiskStorage.js +114 -0
- package/src/storage/S3Storage.js +169 -0
- package/src/storage/Storage.js +231 -0
- package/src/storage/index.js +9 -0
- package/src/utils/duration.js +15 -0
- package/src/utils/emitter.js +8 -0
- package/src/utils/fs.js +10 -0
- package/src/utils/function.js +17 -0
- package/src/utils/function.test.js +77 -0
- package/src/utils/handler.js +17 -0
- package/src/utils/json.js +3 -0
- package/src/utils/model.js +35 -0
- package/src/utils/net.js +17 -0
- package/src/utils/object.js +82 -0
- package/src/utils/object.test.js +86 -0
- package/src/utils/scope.js +7 -0
- package/types/index.d.ts +3547 -0
- package/types/tests/application.test-d.ts +26 -0
- package/types/tests/controller.test-d.ts +113 -0
- package/types/tests/errors.test-d.ts +53 -0
- package/types/tests/fixtures.ts +19 -0
- package/types/tests/model.test-d.ts +193 -0
- package/types/tests/query-builder.test-d.ts +106 -0
- package/types/tests/relation.test-d.ts +83 -0
- package/types/tests/storage.test-d.ts +113 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs/promises'
|
|
3
|
+
import pico from 'picocolors'
|
|
4
|
+
import {
|
|
5
|
+
isObject,
|
|
6
|
+
isArray,
|
|
7
|
+
isString,
|
|
8
|
+
deindent,
|
|
9
|
+
capitalize
|
|
10
|
+
} from '@ditojs/utils'
|
|
11
|
+
import {
|
|
12
|
+
getRelationClass,
|
|
13
|
+
isThroughRelationClass
|
|
14
|
+
} from '../../schema/relations.js'
|
|
15
|
+
import { exists } from '../../utils/fs.js'
|
|
16
|
+
|
|
17
|
+
const typeToKnex = {
|
|
18
|
+
number: 'double',
|
|
19
|
+
object: 'json',
|
|
20
|
+
array: 'json'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const defaultValues = {
|
|
24
|
+
'now()': `knex.raw('CURRENT_TIMESTAMP')`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function createMigration(app, name, ...modelNames) {
|
|
28
|
+
const migrationDir = path.join(app.basePath, 'migrations')
|
|
29
|
+
const models = modelNames.map(modelName => {
|
|
30
|
+
const modelClass = app.models[modelName]
|
|
31
|
+
if (!modelClass) {
|
|
32
|
+
throw new Error(`Model class with name '${modelName}' does not exist`)
|
|
33
|
+
}
|
|
34
|
+
return modelClass
|
|
35
|
+
})
|
|
36
|
+
const tables = []
|
|
37
|
+
for (const modelClass of models) {
|
|
38
|
+
collectModelTables(modelClass, app, tables)
|
|
39
|
+
}
|
|
40
|
+
for (const modelClass of models) {
|
|
41
|
+
collectThroughTables(modelClass, app, tables)
|
|
42
|
+
}
|
|
43
|
+
const createTables = []
|
|
44
|
+
const dropTables = []
|
|
45
|
+
for (const { tableName, statements } of tables) {
|
|
46
|
+
createTables.push(deindent`
|
|
47
|
+
.createTable('${tableName}', table => {
|
|
48
|
+
${statements.join('\n')}
|
|
49
|
+
})`)
|
|
50
|
+
dropTables.unshift(deindent`
|
|
51
|
+
.dropTableIfExists('${tableName}')`)
|
|
52
|
+
}
|
|
53
|
+
const getCode = tables =>
|
|
54
|
+
tables.length > 0
|
|
55
|
+
? deindent`
|
|
56
|
+
await knex.schema
|
|
57
|
+
${tables.join('\n')}`
|
|
58
|
+
: ''
|
|
59
|
+
const filename = `${getTimestamp()}_${name}.js`
|
|
60
|
+
const file = path.join(migrationDir, filename)
|
|
61
|
+
if (await exists(file)) {
|
|
62
|
+
// This should never happen, but let's be on the safe side here:
|
|
63
|
+
console.info(pico.red(`Migration '${filename}' already exists.`))
|
|
64
|
+
return false
|
|
65
|
+
} else {
|
|
66
|
+
await fs.writeFile(
|
|
67
|
+
file,
|
|
68
|
+
deindent`
|
|
69
|
+
/** @param {import('knex').Knex} knex */
|
|
70
|
+
export async function up(knex) {
|
|
71
|
+
${getCode(createTables)}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @param {import('knex').Knex} knex */
|
|
75
|
+
export async function down(knex) {
|
|
76
|
+
${getCode(dropTables)}
|
|
77
|
+
}
|
|
78
|
+
`
|
|
79
|
+
)
|
|
80
|
+
console.info(pico.cyan(`Migration '${filename}' successfully created.`))
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function collectModelTables(modelClass, app, tables) {
|
|
86
|
+
const tableName = app.normalizeIdentifier(modelClass.tableName)
|
|
87
|
+
const { properties, relations } = modelClass.definition
|
|
88
|
+
const statements = []
|
|
89
|
+
tables.push({ tableName, statements })
|
|
90
|
+
const uniqueComposites = {}
|
|
91
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
92
|
+
const column = app.normalizeIdentifier(name)
|
|
93
|
+
let {
|
|
94
|
+
description,
|
|
95
|
+
type,
|
|
96
|
+
specificType,
|
|
97
|
+
unsigned,
|
|
98
|
+
computed,
|
|
99
|
+
nullable,
|
|
100
|
+
required,
|
|
101
|
+
primary,
|
|
102
|
+
foreign,
|
|
103
|
+
unique,
|
|
104
|
+
index,
|
|
105
|
+
default: _default
|
|
106
|
+
} = property
|
|
107
|
+
const knexType = typeToKnex[type] || type
|
|
108
|
+
if (!computed) {
|
|
109
|
+
if (description) {
|
|
110
|
+
statements.push(`// ${description.replace(/\s{2,}/g, ' ').trim()}`)
|
|
111
|
+
}
|
|
112
|
+
if (isString(unique)) {
|
|
113
|
+
// To declare composite foreign keys as unique, you can give each
|
|
114
|
+
// property the same string value in the `unique` keywords, e.g.:
|
|
115
|
+
// `unique: 'customerId_name'
|
|
116
|
+
const composites = (
|
|
117
|
+
uniqueComposites[unique] ||
|
|
118
|
+
(uniqueComposites[unique] = [])
|
|
119
|
+
)
|
|
120
|
+
composites.push(column)
|
|
121
|
+
unique = false
|
|
122
|
+
}
|
|
123
|
+
const statement = primary
|
|
124
|
+
? [`table.increments('${column}').primary()`]
|
|
125
|
+
: specificType
|
|
126
|
+
? [`table.specificType('${column}', '${specificType}')`]
|
|
127
|
+
: [`table.${knexType}('${column}')`]
|
|
128
|
+
statement.push(
|
|
129
|
+
unsigned && 'unsigned()',
|
|
130
|
+
!primary && required && 'notNullable()',
|
|
131
|
+
nullable && 'nullable()',
|
|
132
|
+
unique && 'unique()',
|
|
133
|
+
index && 'index()'
|
|
134
|
+
)
|
|
135
|
+
if (_default !== undefined) {
|
|
136
|
+
let value = defaultValues[_default]
|
|
137
|
+
if (!value) {
|
|
138
|
+
value =
|
|
139
|
+
isArray(_default) || isObject(_default)
|
|
140
|
+
? JSON.stringify(_default)
|
|
141
|
+
: _default
|
|
142
|
+
if (isString(value)) {
|
|
143
|
+
value = `'${value}'`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
statement.push(`defaultTo(${value})`)
|
|
147
|
+
}
|
|
148
|
+
if (foreign) {
|
|
149
|
+
for (const relation of Object.values(relations)) {
|
|
150
|
+
// TODO: Support composite keys for foreign references:
|
|
151
|
+
// Use `asArray(from)`, `asArray(to)`
|
|
152
|
+
const { from, to, owner } = relation
|
|
153
|
+
const [fromClass, fromProperty] = from?.split('.') || []
|
|
154
|
+
if (fromProperty === name) {
|
|
155
|
+
if (fromClass !== modelClass.name) {
|
|
156
|
+
throw Error(`Invalid relation declaration: ${relation}`)
|
|
157
|
+
}
|
|
158
|
+
const [toClass, toProperty] = to?.split('.') || []
|
|
159
|
+
statement.push(
|
|
160
|
+
'\n',
|
|
161
|
+
`references('${app.normalizeIdentifier(toProperty)}')`,
|
|
162
|
+
`inTable('${app.normalizeIdentifier(toClass)}')`,
|
|
163
|
+
// Only relations that aren't owners of their data should cascade
|
|
164
|
+
// on delete.
|
|
165
|
+
// TODO: Find the reverse relation of this in the other class and
|
|
166
|
+
// only add `onDelete('CASCADE')` if that one's an owner.
|
|
167
|
+
!owner && `onDelete('CASCADE')`
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
statements.push(
|
|
173
|
+
statement
|
|
174
|
+
.filter(str => !!str)
|
|
175
|
+
.join('.')
|
|
176
|
+
.replace(/\.\n\./g, '\n .')
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const composites of Object.values(uniqueComposites)) {
|
|
181
|
+
statements.push(
|
|
182
|
+
`table.unique([${
|
|
183
|
+
composites.map(column => `'${column}'`).join(', ')
|
|
184
|
+
}])`
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function collectThroughTables(modelClass, app, tables) {
|
|
190
|
+
const { relations } = modelClass.definition
|
|
191
|
+
for (const relation of Object.values(relations)) {
|
|
192
|
+
const { from, to, inverse } = relation
|
|
193
|
+
const relationClass = getRelationClass(relation.relation)
|
|
194
|
+
if (isThroughRelationClass(relationClass) && !inverse) {
|
|
195
|
+
// TODO: Support composite keys for foreign references:
|
|
196
|
+
// Use `asArray(from)`, `asArray(to)`
|
|
197
|
+
const [fromClass, fromProperty] = from?.split('.') || []
|
|
198
|
+
const [toClass, toProperty] = to?.split('.') || []
|
|
199
|
+
const statements = []
|
|
200
|
+
// See convertRelations()
|
|
201
|
+
const tableName = app.normalizeIdentifier(`${fromClass}${toClass}`)
|
|
202
|
+
const fromId = app.normalizeIdentifier(
|
|
203
|
+
`${fromClass}${capitalize(fromProperty)}`
|
|
204
|
+
)
|
|
205
|
+
const toId = app.normalizeIdentifier(
|
|
206
|
+
`${toClass}${capitalize(toProperty)}`
|
|
207
|
+
)
|
|
208
|
+
tables.push({ tableName, statements })
|
|
209
|
+
statements.push(`table.increments('id').primary()`)
|
|
210
|
+
statements.push(deindent`
|
|
211
|
+
table.integer('${fromId}').unsigned().index()
|
|
212
|
+
.references('${app.normalizeIdentifier(fromProperty)}')\\
|
|
213
|
+
.inTable('${app.normalizeIdentifier(fromClass)}')\\
|
|
214
|
+
.onDelete('CASCADE')`)
|
|
215
|
+
statements.push(deindent`
|
|
216
|
+
table.integer('${toId}').unsigned().index()
|
|
217
|
+
.references('${app.normalizeIdentifier(toProperty)}')\\
|
|
218
|
+
.inTable('${app.normalizeIdentifier(toClass)}')\\
|
|
219
|
+
.onDelete('CASCADE')`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Ensure that we have 2 places for each of the date segments.
|
|
225
|
+
function padDate(segment) {
|
|
226
|
+
return segment.toString().padStart(2, '0')
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Get a date object in the correct format, without requiring a full out library
|
|
230
|
+
// like "moment.js".
|
|
231
|
+
function getTimestamp() {
|
|
232
|
+
const d = new Date()
|
|
233
|
+
return (
|
|
234
|
+
d.getFullYear().toString() +
|
|
235
|
+
padDate(d.getMonth() + 1) +
|
|
236
|
+
padDate(d.getDate()) +
|
|
237
|
+
padDate(d.getHours()) +
|
|
238
|
+
padDate(d.getMinutes()) +
|
|
239
|
+
padDate(d.getSeconds())
|
|
240
|
+
)
|
|
241
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { formatJson } from '../../utils/json.js'
|
|
2
|
+
|
|
3
|
+
export async function listAssetConfig(app, ...args) {
|
|
4
|
+
const assetConfig = app.getAssetConfig({
|
|
5
|
+
models: args.length > 0 ? args : Object.keys(app.models),
|
|
6
|
+
normalizeDbNames: true
|
|
7
|
+
})
|
|
8
|
+
console.info(formatJson(assetConfig))
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pico from 'picocolors'
|
|
2
|
+
|
|
3
|
+
export async function migrate(knex) {
|
|
4
|
+
const [batch, log] = await knex.migrate.latest()
|
|
5
|
+
console.info(
|
|
6
|
+
log.length === 0
|
|
7
|
+
? pico.cyan('Already up to date')
|
|
8
|
+
: pico.green(`Batch ${batch} run: ${log.length} migrations\n`) +
|
|
9
|
+
pico.cyan(log.join('\n'))
|
|
10
|
+
)
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pico from 'picocolors'
|
|
2
|
+
import { migrate } from './migrate.js'
|
|
3
|
+
|
|
4
|
+
export async function reset(knex) {
|
|
5
|
+
const batches = []
|
|
6
|
+
const migrations = []
|
|
7
|
+
while (true) {
|
|
8
|
+
const [batch, log] = await knex.migrate.rollback()
|
|
9
|
+
if (log.length === 0) break
|
|
10
|
+
batches.push(batch)
|
|
11
|
+
migrations.push(...log)
|
|
12
|
+
}
|
|
13
|
+
console.info(
|
|
14
|
+
migrations.length === 0
|
|
15
|
+
? pico.cyan('Already at the base migration')
|
|
16
|
+
: pico.green(
|
|
17
|
+
`${batches.length > 1 ? 'Batches' : 'Batch'} ${batches} ` +
|
|
18
|
+
`rolled back: ${migrations.length} migrations\n`
|
|
19
|
+
) + pico.cyan(migrations.join('\n'))
|
|
20
|
+
)
|
|
21
|
+
await migrate(knex)
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pico from 'picocolors'
|
|
2
|
+
|
|
3
|
+
export async function rollback(knex) {
|
|
4
|
+
const [batch, log] = await knex.migrate.rollback()
|
|
5
|
+
console.info(
|
|
6
|
+
log.length === 0
|
|
7
|
+
? pico.cyan('Already at the base migration')
|
|
8
|
+
: pico.green(`Batch ${batch} rolled back: ${log.length} migrations\n`) +
|
|
9
|
+
pico.cyan(log.join('\n'))
|
|
10
|
+
)
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs/promises'
|
|
3
|
+
import pico from 'picocolors'
|
|
4
|
+
import util from 'util'
|
|
5
|
+
import pluralize from 'pluralize'
|
|
6
|
+
import { isFunction, isArray, camelize } from '@ditojs/utils'
|
|
7
|
+
|
|
8
|
+
export async function seed(app) {
|
|
9
|
+
const seedDir = path.join(app.basePath, 'seeds')
|
|
10
|
+
const files = await fs.readdir(seedDir)
|
|
11
|
+
const seeds = []
|
|
12
|
+
// Create a lookup table with sort indices per model name.
|
|
13
|
+
const modelIndices = Object.keys(app.models).reduce(
|
|
14
|
+
(indices, name, index) => {
|
|
15
|
+
indices[name] = index
|
|
16
|
+
return indices
|
|
17
|
+
},
|
|
18
|
+
{}
|
|
19
|
+
)
|
|
20
|
+
// Collect all seed, and separate between seed functions and model see data:
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
const { name, ext, base } = path.parse(file)
|
|
23
|
+
if (!name.startsWith('.') && ['.js', '.json'].includes(ext)) {
|
|
24
|
+
const object = await import(path.resolve(seedDir, file))
|
|
25
|
+
const seed = object.default || object
|
|
26
|
+
// Try to determine the related model from the seed name, and use it also
|
|
27
|
+
// to determine seed sequence based on its index in `app.models`.
|
|
28
|
+
const modelClass = (
|
|
29
|
+
app.models[name] ||
|
|
30
|
+
app.models[camelize(pluralize.singular(name), true)]
|
|
31
|
+
)
|
|
32
|
+
const index = modelClass ? modelIndices[modelClass.name] : Infinity
|
|
33
|
+
seeds.push({
|
|
34
|
+
base,
|
|
35
|
+
seed,
|
|
36
|
+
modelClass,
|
|
37
|
+
index
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Now sort the seed model data according to `app.models` sorting,
|
|
42
|
+
// as determined by `Application.sortModels()`:
|
|
43
|
+
seeds.sort((entry1, entry2) => entry1.index - entry2.index)
|
|
44
|
+
for (const { base, seed, modelClass } of seeds) {
|
|
45
|
+
await handleSeed(app, base, seed, modelClass)
|
|
46
|
+
}
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function handleSeed(app, base, seed, modelClass) {
|
|
51
|
+
try {
|
|
52
|
+
let res
|
|
53
|
+
if (isFunction(seed)) {
|
|
54
|
+
res = await seed(app.models)
|
|
55
|
+
} else if (modelClass) {
|
|
56
|
+
await modelClass.truncate({ cascade: true })
|
|
57
|
+
res = await modelClass.insertGraph(seed)
|
|
58
|
+
}
|
|
59
|
+
if (isArray(res)) {
|
|
60
|
+
console.info(
|
|
61
|
+
pico.green(`${base}:`),
|
|
62
|
+
pico.cyan(`${res.length} seed records created.`)
|
|
63
|
+
)
|
|
64
|
+
} else {
|
|
65
|
+
console.info(
|
|
66
|
+
pico.red(`${base}:`),
|
|
67
|
+
pico.cyan('No seed records created.')
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(
|
|
72
|
+
pico.red(`${base}:`),
|
|
73
|
+
util.inspect(err, {
|
|
74
|
+
colors: true,
|
|
75
|
+
depth: null,
|
|
76
|
+
maxArrayLength: null
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import pico from 'picocolors'
|
|
6
|
+
import Knex from 'knex'
|
|
7
|
+
import { isPlainObject, isFunction, camelize } from '@ditojs/utils'
|
|
8
|
+
import * as db from './db/index.js'
|
|
9
|
+
import startConsole from './console.js'
|
|
10
|
+
|
|
11
|
+
const commands = { db, console: startConsole }
|
|
12
|
+
|
|
13
|
+
function getCommand(commands, parts) {
|
|
14
|
+
const part = parts.shift()
|
|
15
|
+
return commands && part
|
|
16
|
+
? getCommand(commands[camelize(part)], parts)
|
|
17
|
+
: commands
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setSilent(silent) {
|
|
21
|
+
const wasSilent = process.env.DITO_SILENT
|
|
22
|
+
process.env.DITO_SILENT = silent
|
|
23
|
+
return wasSilent
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function execute() {
|
|
27
|
+
try {
|
|
28
|
+
// Dynamically load app or config from the path provided package.json script
|
|
29
|
+
const [, , command, importPath, ...args] = process.argv
|
|
30
|
+
const execute = command && getCommand(commands, command.split(':'))
|
|
31
|
+
if (!isFunction(execute)) {
|
|
32
|
+
throw new Error(`Unknown command: ${command}`)
|
|
33
|
+
}
|
|
34
|
+
const silent = setSilent(true)
|
|
35
|
+
let arg
|
|
36
|
+
try {
|
|
37
|
+
arg = (await import(path.resolve(importPath))).default
|
|
38
|
+
} finally {
|
|
39
|
+
setSilent(silent)
|
|
40
|
+
}
|
|
41
|
+
if (isFunction(arg)) {
|
|
42
|
+
arg = await arg()
|
|
43
|
+
}
|
|
44
|
+
if (isPlainObject(arg) && arg.knex) {
|
|
45
|
+
// A config object with a knex field was passed in, create a knex object
|
|
46
|
+
// from it to pass on to the execute function.
|
|
47
|
+
arg = Knex(arg.knex)
|
|
48
|
+
}
|
|
49
|
+
const res = await execute(arg, ...args)
|
|
50
|
+
process.exit(res === true ? 0 : 1)
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof Error) {
|
|
53
|
+
console.error(
|
|
54
|
+
pico.red(`${err.detail ? `${err.detail}\n` : ''}${err.stack}`)
|
|
55
|
+
)
|
|
56
|
+
} else {
|
|
57
|
+
console.error(pico.red(err))
|
|
58
|
+
}
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Start the console if `node ./cli/index.js`
|
|
64
|
+
|
|
65
|
+
// See module was not imported but called directly
|
|
66
|
+
const path1 = fs.realpathSync(import.meta.url.replace(/^file:\/\//, ''))
|
|
67
|
+
const path2 = fs.realpathSync(process.argv[1])
|
|
68
|
+
if (path1 === path2) {
|
|
69
|
+
execute()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default execute
|