velocious 1.0.441 → 1.0.443
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/build/authorization/base-resource.js +2 -2
- package/build/beacon/client.js +13 -7
- package/build/beacon/server.js +11 -0
- package/build/cli/commands/lint/relationships.js +12 -0
- package/build/configuration-types.js +5 -1
- package/build/controller.js +1 -1
- package/build/database/drivers/base.js +4 -1
- package/build/database/record/index.js +46 -34
- package/build/database/record/relationships/belongs-to.js +1 -1
- package/build/database/record/relationships/has-many.js +3 -1
- package/build/database/record/relationships/has-one.js +3 -1
- package/build/environment-handlers/base.js +10 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +2 -2
- package/build/environment-handlers/node/cli/commands/lint/relationships.js +144 -0
- package/build/environment-handlers/node.js +10 -0
- package/build/frontend-model-resource/base-resource.js +9 -10
- package/build/frontend-models/base.js +6 -6
- package/build/frontend-models/query.js +2 -2
- package/build/src/authorization/base-resource.d.ts +4 -4
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +3 -3
- package/build/src/beacon/client.d.ts.map +1 -1
- package/build/src/beacon/client.js +13 -8
- package/build/src/beacon/server.d.ts +5 -0
- package/build/src/beacon/server.d.ts.map +1 -1
- package/build/src/beacon/server.js +11 -1
- package/build/src/cli/commands/lint/relationships.d.ts +5 -0
- package/build/src/cli/commands/lint/relationships.d.ts.map +1 -0
- package/build/src/cli/commands/lint/relationships.js +12 -0
- package/build/src/configuration-types.d.ts +7 -3
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +5 -2
- package/build/src/controller.d.ts +3 -3
- package/build/src/controller.d.ts.map +1 -1
- package/build/src/controller.js +2 -2
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +5 -2
- package/build/src/database/record/index.d.ts +43 -37
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +45 -35
- package/build/src/database/record/relationships/belongs-to.js +2 -2
- package/build/src/database/record/relationships/has-many.d.ts.map +1 -1
- package/build/src/database/record/relationships/has-many.js +3 -2
- package/build/src/database/record/relationships/has-one.d.ts.map +1 -1
- package/build/src/database/record/relationships/has-one.js +3 -2
- package/build/src/environment-handlers/base.d.ts +7 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +10 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +3 -3
- package/build/src/environment-handlers/node/cli/commands/lint/relationships.d.ts +34 -0
- package/build/src/environment-handlers/node/cli/commands/lint/relationships.d.ts.map +1 -0
- package/build/src/environment-handlers/node/cli/commands/lint/relationships.js +123 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +10 -1
- package/build/src/frontend-model-resource/base-resource.d.ts +15 -16
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +10 -11
- package/build/src/frontend-models/base.d.ts +18 -12
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +7 -7
- package/build/src/frontend-models/query.d.ts +4 -4
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +3 -3
- package/build/src/utils/is-date.d.ts +10 -0
- package/build/src/utils/is-date.d.ts.map +1 -0
- package/build/src/utils/is-date.js +13 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/is-date.js +13 -0
- package/package.json +1 -1
- package/src/authorization/base-resource.js +2 -2
- package/src/beacon/client.js +13 -7
- package/src/beacon/server.js +11 -0
- package/src/cli/commands/lint/relationships.js +12 -0
- package/src/configuration-types.js +5 -1
- package/src/controller.js +1 -1
- package/src/database/drivers/base.js +4 -1
- package/src/database/record/index.js +46 -34
- package/src/database/record/relationships/belongs-to.js +1 -1
- package/src/database/record/relationships/has-many.js +3 -1
- package/src/database/record/relationships/has-one.js +3 -1
- package/src/environment-handlers/base.js +10 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +2 -2
- package/src/environment-handlers/node/cli/commands/lint/relationships.js +144 -0
- package/src/environment-handlers/node.js +10 -0
- package/src/frontend-model-resource/base-resource.js +9 -10
- package/src/frontend-models/base.js +6 -6
- package/src/frontend-models/query.js +2 -2
- package/src/utils/is-date.js +13 -0
|
@@ -111,7 +111,7 @@ export default class AuthorizationBaseResource {
|
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* Runs current user.
|
|
114
|
-
* @returns {
|
|
114
|
+
* @returns {unknown} - Current user from context.
|
|
115
115
|
*/
|
|
116
116
|
currentUser() {
|
|
117
117
|
return this.context.currentUser
|
|
@@ -127,7 +127,7 @@ export default class AuthorizationBaseResource {
|
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
129
|
* Runs params.
|
|
130
|
-
* @returns {import("../configuration-types.js").
|
|
130
|
+
* @returns {import("../configuration-types.js").VelociousParams | undefined} - Params from context.
|
|
131
131
|
*/
|
|
132
132
|
params() {
|
|
133
133
|
return this.context.params
|
package/build/beacon/client.js
CHANGED
|
@@ -225,14 +225,20 @@ export default class BeaconClient extends EventEmitter {
|
|
|
225
225
|
this._reconnectTimer = undefined
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
const socket = this._socket
|
|
228
|
+
const socket = this._socket
|
|
230
229
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
230
|
+
if (!socket) return
|
|
231
|
+
|
|
232
|
+
this._socket = undefined
|
|
233
|
+
this._jsonSocket = undefined
|
|
234
|
+
|
|
235
|
+
if (socket.destroyed) return
|
|
236
|
+
|
|
237
|
+
await new Promise((resolve) => {
|
|
238
|
+
socket.once("close", () => resolve(undefined))
|
|
239
|
+
socket.end()
|
|
240
|
+
socket.destroySoon()
|
|
241
|
+
})
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
/**
|
package/build/beacon/server.js
CHANGED
|
@@ -37,6 +37,11 @@ export default class BeaconServer {
|
|
|
37
37
|
* Narrows the runtime value to the documented type.
|
|
38
38
|
@type {net.Server | undefined} */
|
|
39
39
|
this.server = undefined
|
|
40
|
+
/**
|
|
41
|
+
* Accepted sockets, including connections that have not completed the hello handshake yet.
|
|
42
|
+
* @type {Set<net.Socket>}
|
|
43
|
+
*/
|
|
44
|
+
this.sockets = new Set()
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
/**
|
|
@@ -68,6 +73,10 @@ export default class BeaconServer {
|
|
|
68
73
|
peer.close()
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
for (const socket of this.sockets) {
|
|
77
|
+
socket.destroy()
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
if (!this.server) return
|
|
72
81
|
|
|
73
82
|
const {server} = this
|
|
@@ -97,6 +106,7 @@ export default class BeaconServer {
|
|
|
97
106
|
* @returns {void}
|
|
98
107
|
*/
|
|
99
108
|
_handleConnection(socket) {
|
|
109
|
+
this.sockets.add(socket)
|
|
100
110
|
const jsonSocket = new JsonSocket(socket)
|
|
101
111
|
/**
|
|
102
112
|
* Defines peerId.
|
|
@@ -104,6 +114,7 @@ export default class BeaconServer {
|
|
|
104
114
|
let peerId
|
|
105
115
|
|
|
106
116
|
const cleanup = () => {
|
|
117
|
+
this.sockets.delete(socket)
|
|
107
118
|
this.peers.delete(jsonSocket)
|
|
108
119
|
}
|
|
109
120
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import BaseCommand from "../../base-command.js"
|
|
2
|
+
|
|
3
|
+
/** Lints model relationships (e.g. belongs-to relationships missing an inverse on the target model). */
|
|
4
|
+
export default class VelociousCliCommandsLintRelationships extends BaseCommand {
|
|
5
|
+
/**
|
|
6
|
+
* Runs execute.
|
|
7
|
+
* @returns {Promise<?>} - Resolves with the command result.
|
|
8
|
+
*/
|
|
9
|
+
async execute() {
|
|
10
|
+
return await this.getConfiguration().getEnvironmentHandler().cliCommandsLintRelationships(this)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -202,7 +202,11 @@
|
|
|
202
202
|
*/
|
|
203
203
|
|
|
204
204
|
/**
|
|
205
|
-
* @typedef {Record<string,
|
|
205
|
+
* @typedef {Record<string, string>} VelociousParams
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @typedef {Record<string, unknown> & {configuration?: import("./configuration.js").default, currentUser?: unknown, params?: VelociousParams, request?: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default}} VelociousLooseObject
|
|
206
210
|
*/
|
|
207
211
|
|
|
208
212
|
/**
|
package/build/controller.js
CHANGED
|
@@ -34,7 +34,7 @@ export default class VelociousController {
|
|
|
34
34
|
* @param {string} args.action - Action.
|
|
35
35
|
* @param {import("./configuration.js").default} args.configuration - Configuration instance.
|
|
36
36
|
* @param {string} args.controller - Controller.
|
|
37
|
-
* @param {
|
|
37
|
+
* @param {Record<string, ?>} args.params - Parameters object.
|
|
38
38
|
* @param {import("./http-server/client/request.js").default} args.request - Request object.
|
|
39
39
|
* @param {import("./http-server/client/response.js").default} args.response - Response object.
|
|
40
40
|
* @param {string} args.viewPath - View path.
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
|
|
104
104
|
import BacktraceCleaner from "../../utils/backtrace-cleaner.js"
|
|
105
105
|
import { getDatabaseAnnotations } from "../annotations.js"
|
|
106
|
+
import isDate from "../../utils/is-date.js"
|
|
106
107
|
import Logger from "../../logger.js"
|
|
107
108
|
import Query from "../query/index.js"
|
|
108
109
|
import Handler from "../handler.js"
|
|
@@ -644,7 +645,9 @@ export default class VelociousDatabaseDriversBase {
|
|
|
644
645
|
return value ? 1 : 0
|
|
645
646
|
}
|
|
646
647
|
|
|
647
|
-
|
|
648
|
+
// isDate instead of instanceof: a Date created in another realm (e.g. the console REPL) would
|
|
649
|
+
// fail instanceof, skip this conversion, and serialize as an empty SQL value downstream.
|
|
650
|
+
if (isDate(value)) {
|
|
648
651
|
return strftime("%F %T.%L", value)
|
|
649
652
|
}
|
|
650
653
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* Model class constructor type used for static `this` typing.
|
|
16
16
|
* @template {VelociousDatabaseRecord} T
|
|
17
|
-
* @typedef {{new (
|
|
17
|
+
* @typedef {{new (changes?: Record<string, unknown>): T}} ModelConstructor
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import timeout from "awaitery/build/timeout.js"
|
|
@@ -31,6 +31,7 @@ import HasOneRelationship from "./relationships/has-one.js"
|
|
|
31
31
|
import RecordAttachmentHandle from "./attachments/handle.js"
|
|
32
32
|
import * as inflection from "inflection"
|
|
33
33
|
import deburrColumnName from "../../utils/deburr-column-name.js"
|
|
34
|
+
import isDate from "../../utils/is-date.js"
|
|
34
35
|
import ModelClassQuery from "../query/model-class-query.js"
|
|
35
36
|
import Preloader from "../query/preloader.js"
|
|
36
37
|
import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQueryData, setPayloadAssociationCount, setPayloadComputedAbility, setPayloadQueryData} from "../../record-payload-values.js"
|
|
@@ -500,9 +501,9 @@ class VelociousDatabaseRecord {
|
|
|
500
501
|
|
|
501
502
|
/**
|
|
502
503
|
* Runs before validation.
|
|
503
|
-
* @template {VelociousDatabaseRecord}
|
|
504
|
-
* @this {
|
|
505
|
-
* @param {LifecycleCallbackType<
|
|
504
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
505
|
+
* @this {MC}
|
|
506
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
506
507
|
* @returns {void}
|
|
507
508
|
*/
|
|
508
509
|
static beforeValidation(callback) {
|
|
@@ -511,9 +512,9 @@ class VelociousDatabaseRecord {
|
|
|
511
512
|
|
|
512
513
|
/**
|
|
513
514
|
* Runs before save.
|
|
514
|
-
* @template {VelociousDatabaseRecord}
|
|
515
|
-
* @this {
|
|
516
|
-
* @param {LifecycleCallbackType<
|
|
515
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
516
|
+
* @this {MC}
|
|
517
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
517
518
|
* @returns {void}
|
|
518
519
|
*/
|
|
519
520
|
static beforeSave(callback) {
|
|
@@ -522,9 +523,9 @@ class VelociousDatabaseRecord {
|
|
|
522
523
|
|
|
523
524
|
/**
|
|
524
525
|
* Runs before create.
|
|
525
|
-
* @template {VelociousDatabaseRecord}
|
|
526
|
-
* @this {
|
|
527
|
-
* @param {LifecycleCallbackType<
|
|
526
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
527
|
+
* @this {MC}
|
|
528
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
528
529
|
* @returns {void}
|
|
529
530
|
*/
|
|
530
531
|
static beforeCreate(callback) {
|
|
@@ -533,9 +534,9 @@ class VelociousDatabaseRecord {
|
|
|
533
534
|
|
|
534
535
|
/**
|
|
535
536
|
* Runs before update.
|
|
536
|
-
* @template {VelociousDatabaseRecord}
|
|
537
|
-
* @this {
|
|
538
|
-
* @param {LifecycleCallbackType<
|
|
537
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
538
|
+
* @this {MC}
|
|
539
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
539
540
|
* @returns {void}
|
|
540
541
|
*/
|
|
541
542
|
static beforeUpdate(callback) {
|
|
@@ -544,9 +545,9 @@ class VelociousDatabaseRecord {
|
|
|
544
545
|
|
|
545
546
|
/**
|
|
546
547
|
* Runs before destroy.
|
|
547
|
-
* @template {VelociousDatabaseRecord}
|
|
548
|
-
* @this {
|
|
549
|
-
* @param {LifecycleCallbackType<
|
|
548
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
549
|
+
* @this {MC}
|
|
550
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
550
551
|
* @returns {void}
|
|
551
552
|
*/
|
|
552
553
|
static beforeDestroy(callback) {
|
|
@@ -555,9 +556,9 @@ class VelociousDatabaseRecord {
|
|
|
555
556
|
|
|
556
557
|
/**
|
|
557
558
|
* Runs after save.
|
|
558
|
-
* @template {VelociousDatabaseRecord}
|
|
559
|
-
* @this {
|
|
560
|
-
* @param {LifecycleCallbackType<
|
|
559
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
560
|
+
* @this {MC}
|
|
561
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
561
562
|
* @returns {void}
|
|
562
563
|
*/
|
|
563
564
|
static afterSave(callback) {
|
|
@@ -566,9 +567,9 @@ class VelociousDatabaseRecord {
|
|
|
566
567
|
|
|
567
568
|
/**
|
|
568
569
|
* Runs after create.
|
|
569
|
-
* @template {VelociousDatabaseRecord}
|
|
570
|
-
* @this {
|
|
571
|
-
* @param {LifecycleCallbackType<
|
|
570
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
571
|
+
* @this {MC}
|
|
572
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
572
573
|
* @returns {void}
|
|
573
574
|
*/
|
|
574
575
|
static afterCreate(callback) {
|
|
@@ -577,9 +578,9 @@ class VelociousDatabaseRecord {
|
|
|
577
578
|
|
|
578
579
|
/**
|
|
579
580
|
* Runs after update.
|
|
580
|
-
* @template {VelociousDatabaseRecord}
|
|
581
|
-
* @this {
|
|
582
|
-
* @param {LifecycleCallbackType<
|
|
581
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
582
|
+
* @this {MC}
|
|
583
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
583
584
|
* @returns {void}
|
|
584
585
|
*/
|
|
585
586
|
static afterUpdate(callback) {
|
|
@@ -588,9 +589,9 @@ class VelociousDatabaseRecord {
|
|
|
588
589
|
|
|
589
590
|
/**
|
|
590
591
|
* Runs after destroy.
|
|
591
|
-
* @template {VelociousDatabaseRecord}
|
|
592
|
-
* @this {
|
|
593
|
-
* @param {LifecycleCallbackType<
|
|
592
|
+
* @template {typeof VelociousDatabaseRecord} MC
|
|
593
|
+
* @this {MC}
|
|
594
|
+
* @param {LifecycleCallbackType<InstanceType<MC>>} callback - Callback function or instance method name.
|
|
594
595
|
* @returns {void}
|
|
595
596
|
*/
|
|
596
597
|
static afterDestroy(callback) {
|
|
@@ -2234,7 +2235,7 @@ class VelociousDatabaseRecord {
|
|
|
2234
2235
|
normalizedValue = this._normalizeDateStringForInsert(normalizedValue)
|
|
2235
2236
|
}
|
|
2236
2237
|
|
|
2237
|
-
if (normalizedValue
|
|
2238
|
+
if (isDate(normalizedValue)) {
|
|
2238
2239
|
const configuration = this._getConfiguration()
|
|
2239
2240
|
const offsetMinutes = configuration.getEnvironmentHandler().getTimezoneOffsetMinutes(configuration)
|
|
2240
2241
|
const offsetMs = offsetMinutes * 60 * 1000
|
|
@@ -2437,7 +2438,7 @@ class VelociousDatabaseRecord {
|
|
|
2437
2438
|
if (model.isChanged()) {
|
|
2438
2439
|
await model.save()
|
|
2439
2440
|
|
|
2440
|
-
const foreignKey =
|
|
2441
|
+
const foreignKey = this._relationshipForeignKeyAttribute(instanceRelationship)
|
|
2441
2442
|
|
|
2442
2443
|
this.setAttribute(foreignKey, model.id())
|
|
2443
2444
|
|
|
@@ -2492,7 +2493,7 @@ class VelociousDatabaseRecord {
|
|
|
2492
2493
|
|
|
2493
2494
|
if (loaded) {
|
|
2494
2495
|
for (const model of loaded) {
|
|
2495
|
-
const foreignKey =
|
|
2496
|
+
const foreignKey = model._relationshipForeignKeyAttribute(instanceRelationship)
|
|
2496
2497
|
|
|
2497
2498
|
model.setAttribute(foreignKey, this.id())
|
|
2498
2499
|
|
|
@@ -2509,6 +2510,17 @@ class VelociousDatabaseRecord {
|
|
|
2509
2510
|
return relationships
|
|
2510
2511
|
}
|
|
2511
2512
|
|
|
2513
|
+
/**
|
|
2514
|
+
* Resolves a relationship foreign-key column to this model's public attribute name.
|
|
2515
|
+
* @param {import("./instance-relationships/base.js").default<?, ?>} instanceRelationship - Relationship instance.
|
|
2516
|
+
* @returns {string} Attribute name accepted by setAttribute/assign.
|
|
2517
|
+
*/
|
|
2518
|
+
_relationshipForeignKeyAttribute(instanceRelationship) {
|
|
2519
|
+
const foreignKey = instanceRelationship.getForeignKey()
|
|
2520
|
+
|
|
2521
|
+
return this.getModelClass().getColumnNameToAttributeNameMap()[foreignKey] || foreignKey
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2512
2524
|
/**
|
|
2513
2525
|
* Runs auto save has many and has one relationships.
|
|
2514
2526
|
* @param {object} args - Options object.
|
|
@@ -2534,7 +2546,7 @@ class VelociousDatabaseRecord {
|
|
|
2534
2546
|
}
|
|
2535
2547
|
|
|
2536
2548
|
for (const model of loaded) {
|
|
2537
|
-
const foreignKey =
|
|
2549
|
+
const foreignKey = model._relationshipForeignKeyAttribute(instanceRelationship)
|
|
2538
2550
|
|
|
2539
2551
|
model.setAttribute(foreignKey, this.id())
|
|
2540
2552
|
|
|
@@ -3904,7 +3916,7 @@ class VelociousDatabaseRecord {
|
|
|
3904
3916
|
const offsetMinutes = configuration.getEnvironmentHandler().getTimezoneOffsetMinutes(configuration)
|
|
3905
3917
|
const offsetMs = offsetMinutes * 60 * 1000
|
|
3906
3918
|
|
|
3907
|
-
if (value
|
|
3919
|
+
if (isDate(value)) {
|
|
3908
3920
|
return new Date(value.getTime() + offsetMs)
|
|
3909
3921
|
}
|
|
3910
3922
|
|
|
@@ -4063,7 +4075,7 @@ class VelociousDatabaseRecord {
|
|
|
4063
4075
|
|
|
4064
4076
|
const value = data[columnName]
|
|
4065
4077
|
|
|
4066
|
-
if (!(value
|
|
4078
|
+
if (!isDate(value)) continue
|
|
4067
4079
|
|
|
4068
4080
|
data[columnName] = new Date(value.getTime() - offsetMs)
|
|
4069
4081
|
}
|
|
@@ -13,7 +13,9 @@ export default class VelociousDatabaseRecordHasManyRelationship extends BaseRela
|
|
|
13
13
|
this.foreignKey = `${inflection.underscore(this.modelClass.getModelName())}_id`
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const targetModelClass = this.className || this.klass ? this.getTargetModelClass() : undefined
|
|
17
|
+
|
|
18
|
+
return targetModelClass?.getAttributeNameToColumnNameMap()[this.foreignKey] || this.foreignKey
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -13,7 +13,9 @@ export default class VelociousDatabaseRecordHasOneRelationship extends BaseRelat
|
|
|
13
13
|
this.foreignKey = `${inflection.underscore(this.modelClass.getModelName())}_id`
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const targetModelClass = this.className || this.klass ? this.getTargetModelClass() : undefined
|
|
17
|
+
|
|
18
|
+
return targetModelClass?.getAttributeNameToColumnNameMap()[this.foreignKey] || this.foreignKey
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -243,6 +243,16 @@ export default class VelociousEnvironmentHandlerBase {
|
|
|
243
243
|
throw new Error("cliCommandsGenerateModel not implemented")
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Runs cli commands lint relationships.
|
|
248
|
+
* @abstract
|
|
249
|
+
* @param {import("../cli/base-command.js").default} _command - Command.
|
|
250
|
+
* @returns {Promise<?>} - Resolves with the command result.
|
|
251
|
+
*/
|
|
252
|
+
async cliCommandsLintRelationships(_command) {
|
|
253
|
+
throw new Error("cliCommandsLintRelationships not implemented")
|
|
254
|
+
}
|
|
255
|
+
|
|
246
256
|
/**
|
|
247
257
|
* Runs cli commands routes.
|
|
248
258
|
* @abstract
|
|
@@ -373,14 +373,14 @@ export default class DbGenerateFrontendModels extends BaseCommand {
|
|
|
373
373
|
|
|
374
374
|
fileContent += "\n"
|
|
375
375
|
fileContent += ` /** @returns {${attributesTypeName}[${JSON.stringify(attribute.name)}]} - Attribute value. */\n`
|
|
376
|
-
fileContent += ` ${camelizedAttribute}() { return this.readAttribute(${JSON.stringify(attribute.name)}) }\n`
|
|
376
|
+
fileContent += ` ${camelizedAttribute}() { return /** @type {${attributesTypeName}[${JSON.stringify(attribute.name)}]} */ (this.readAttribute(${JSON.stringify(attribute.name)})) }\n`
|
|
377
377
|
|
|
378
378
|
fileContent += "\n"
|
|
379
379
|
fileContent += " /**\n"
|
|
380
380
|
fileContent += ` * @param {${attributesTypeName}[${JSON.stringify(attribute.name)}]} newValue - New attribute value.\n`
|
|
381
381
|
fileContent += ` * @returns {${attributesTypeName}[${JSON.stringify(attribute.name)}]} - Assigned value.\n`
|
|
382
382
|
fileContent += " */\n"
|
|
383
|
-
fileContent += ` set${camelizedAttributeUpper}(newValue) { return this.setAttribute(${JSON.stringify(attribute.name)}, newValue) }\n`
|
|
383
|
+
fileContent += ` set${camelizedAttributeUpper}(newValue) { return /** @type {${attributesTypeName}[${JSON.stringify(attribute.name)}]} */ (this.setAttribute(${JSON.stringify(attribute.name)}, newValue)) }\n`
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
for (const methodName of Object.keys(collectionCommands)) {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import BaseCommand from "../../../../../cli/base-command.js"
|
|
4
|
+
import fs from "node:fs/promises"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Lints model relationships: every non-polymorphic belongs-to relationship should have an inverse
|
|
9
|
+
* has-many or has-one relationship declared on its target model class. A missing inverse usually
|
|
10
|
+
* means the target model was never told about the association (e.g. an Event model missing
|
|
11
|
+
* `hasMany("priceCategorySettings")` while PriceCategorySetting declares `belongsTo("event")`).
|
|
12
|
+
*
|
|
13
|
+
* Specific relationships can be ignored through a JSON config file (default:
|
|
14
|
+
* `relationship-lint.json` in the project directory, overridable with `--config <path>`):
|
|
15
|
+
*
|
|
16
|
+
* {"ignore": ["PriceCategorySetting#event"]}
|
|
17
|
+
*
|
|
18
|
+
* where each entry is `<model class name>#<belongs-to relationship name>`.
|
|
19
|
+
*/
|
|
20
|
+
export default class VelociousCliCommandsLintRelationships extends BaseCommand {
|
|
21
|
+
/**
|
|
22
|
+
* Runs execute.
|
|
23
|
+
* @returns {Promise<{offences: Array<{ignoreKey: string, message: string}>}>} - Resolves with the found offences (empty when the lint passes).
|
|
24
|
+
*/
|
|
25
|
+
async execute() {
|
|
26
|
+
// Relationship target resolution (getTargetModelClass) looks model classes up through the
|
|
27
|
+
// current configuration, so make this command's configuration the current one.
|
|
28
|
+
this.getConfiguration().setCurrent()
|
|
29
|
+
|
|
30
|
+
await this.getConfiguration().initializeModels()
|
|
31
|
+
|
|
32
|
+
const ignoredRelationships = await this._loadIgnoredRelationships()
|
|
33
|
+
const offences = []
|
|
34
|
+
const modelClasses = Object.values(this.getConfiguration().getModelClasses())
|
|
35
|
+
|
|
36
|
+
for (const modelClass of modelClasses) {
|
|
37
|
+
for (const relationship of modelClass.getRelationships()) {
|
|
38
|
+
if (relationship.getType() != "belongsTo") continue
|
|
39
|
+
if (relationship.getPolymorphic()) continue
|
|
40
|
+
|
|
41
|
+
const ignoreKey = `${modelClass.name}#${relationship.getRelationshipName()}`
|
|
42
|
+
|
|
43
|
+
if (ignoredRelationships.has(ignoreKey)) continue
|
|
44
|
+
|
|
45
|
+
let targetModelClass
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
targetModelClass = relationship.getTargetModelClass()
|
|
49
|
+
} catch (error) {
|
|
50
|
+
offences.push({
|
|
51
|
+
ignoreKey,
|
|
52
|
+
message: `${ignoreKey}: couldn't resolve the target model class: ${error instanceof Error ? error.message : error}`
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!targetModelClass) {
|
|
59
|
+
offences.push({ignoreKey, message: `${ignoreKey}: couldn't resolve the target model class`})
|
|
60
|
+
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const inverseRelationship = targetModelClass.getRelationships().find((candidate) => {
|
|
65
|
+
if (candidate.getType() != "hasMany" && candidate.getType() != "hasOne") return false
|
|
66
|
+
if (candidate.through) return false
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return candidate.getTargetModelClass() === modelClass
|
|
70
|
+
} catch {
|
|
71
|
+
// A has-many/has-one with an unresolvable target can't be the inverse of this belongs-to.
|
|
72
|
+
// It is reported separately when its own model's belongs-to relationships are linted.
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (inverseRelationship) continue
|
|
78
|
+
|
|
79
|
+
offences.push({
|
|
80
|
+
ignoreKey,
|
|
81
|
+
message: `${targetModelClass.name} is missing an inverse hasMany/hasOne relationship for ${ignoreKey} (belongsTo). ` +
|
|
82
|
+
`Declare the inverse on ${targetModelClass.name} or add "${ignoreKey}" to the ignore config.`
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const offence of offences) {
|
|
88
|
+
console.error(offence.message)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (offences.length > 0) {
|
|
92
|
+
throw new Error(`Relationship lint failed with ${offences.length} offence(s):\n${offences.map((offence) => offence.message).join("\n")}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(`Relationship lint passed for ${modelClasses.length} model(s).`)
|
|
96
|
+
|
|
97
|
+
return {offences}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Loads the ignored relationship keys from the lint config file. The file is optional; when the
|
|
102
|
+
* default path doesn't exist, no relationships are ignored. An explicitly passed `--config` path
|
|
103
|
+
* must exist.
|
|
104
|
+
* @returns {Promise<Set<string>>} - Ignored `<model>#<relationship>` keys.
|
|
105
|
+
*/
|
|
106
|
+
async _loadIgnoredRelationships() {
|
|
107
|
+
const configArgIndex = this.processArgs?.indexOf("--config") ?? -1
|
|
108
|
+
const explicitConfigPath = configArgIndex >= 0 ? this.processArgs?.[configArgIndex + 1] : undefined
|
|
109
|
+
|
|
110
|
+
if (configArgIndex >= 0 && !explicitConfigPath) {
|
|
111
|
+
throw new Error("--config was given without a path argument")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const configPath = explicitConfigPath
|
|
115
|
+
? path.resolve(this.directory(), explicitConfigPath)
|
|
116
|
+
: path.join(this.directory(), "relationship-lint.json")
|
|
117
|
+
|
|
118
|
+
let configContent
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
configContent = await fs.readFile(configPath, "utf8")
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (!explicitConfigPath && /** @type {NodeJS.ErrnoException} */ (error).code == "ENOENT") {
|
|
124
|
+
return new Set()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const config = JSON.parse(configContent)
|
|
131
|
+
|
|
132
|
+
if (config === null || typeof config != "object" || Array.isArray(config)) {
|
|
133
|
+
throw new Error(`Relationship lint config must be a JSON object: ${configPath}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const ignore = config.ignore ?? []
|
|
137
|
+
|
|
138
|
+
if (!Array.isArray(ignore) || ignore.some((entry) => typeof entry != "string")) {
|
|
139
|
+
throw new Error(`Relationship lint config "ignore" must be an array of "<model>#<relationship>" strings: ${configPath}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return new Set(ignore)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -8,6 +8,7 @@ import CliCommandsGenerateBaseModels from "./node/cli/commands/generate/base-mod
|
|
|
8
8
|
import CliCommandsGenerateFrontendModels from "./node/cli/commands/generate/frontend-models.js"
|
|
9
9
|
import CliCommandsGenerateMigration from "./node/cli/commands/generate/migration.js"
|
|
10
10
|
import CliCommandsGenerateModel from "./node/cli/commands/generate/model.js"
|
|
11
|
+
import CliCommandsLintRelationships from "./node/cli/commands/lint/relationships.js"
|
|
11
12
|
import CliCommandsRoutes from "./node/cli/commands/routes.js"
|
|
12
13
|
import CliCommandsServer from "./node/cli/commands/server.js"
|
|
13
14
|
import CliCommandsTest from "./node/cli/commands/test.js"
|
|
@@ -509,6 +510,15 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
509
510
|
return await this.forwardCommand(command, CliCommandsGenerateModel)
|
|
510
511
|
}
|
|
511
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Runs cli commands lint relationships.
|
|
515
|
+
* @param {import("../cli/base-command.js").default} command - Command.
|
|
516
|
+
* @returns {Promise<?>} - Resolves with the command result.
|
|
517
|
+
*/
|
|
518
|
+
async cliCommandsLintRelationships(command) {
|
|
519
|
+
return await this.forwardCommand(command, CliCommandsLintRelationships)
|
|
520
|
+
}
|
|
521
|
+
|
|
512
522
|
/**
|
|
513
523
|
* Runs cli commands routes.
|
|
514
524
|
* @param {import("../cli/base-command.js").default} command - Command.
|
|
@@ -9,7 +9,7 @@ import * as inflection from "inflection"
|
|
|
9
9
|
* @property {import("../controller.js").default} controller - Frontend-model controller instance.
|
|
10
10
|
* @property {typeof import("../database/record/index.js").default} modelClass - Backing model class.
|
|
11
11
|
* @property {string} modelName - Model name.
|
|
12
|
-
* @property {import("../configuration-types.js").
|
|
12
|
+
* @property {import("../configuration-types.js").VelociousParams} params - Request params.
|
|
13
13
|
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} resourceConfiguration - Normalized resource configuration (or raw input shape during early bootstrap).
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -21,13 +21,13 @@ import * as inflection from "inflection"
|
|
|
21
21
|
* @property {import("../configuration-types.js").VelociousLooseObject} [locals] - Ability locals.
|
|
22
22
|
* @property {typeof import("../database/record/index.js").default} [modelClass] - Optional backing model class override.
|
|
23
23
|
* @property {string} [modelName] - Optional model name override.
|
|
24
|
-
* @property {import("../configuration-types.js").
|
|
24
|
+
* @property {import("../configuration-types.js").VelociousParams} [params] - Optional params override.
|
|
25
25
|
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} [resourceConfiguration] - Optional normalized resource configuration.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Base class for backend frontend-model resources.
|
|
30
|
-
* @template {typeof import("../database/record/index.js").default} [
|
|
30
|
+
* @template {typeof import("../database/record/index.js").default} [TModelClass=typeof import("../database/record/index.js").default]
|
|
31
31
|
*/
|
|
32
32
|
export default class FrontendModelBaseResource extends AuthorizationBaseResource {
|
|
33
33
|
/**
|
|
@@ -99,10 +99,10 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
99
99
|
/**
|
|
100
100
|
* Runs typed controller instance.
|
|
101
101
|
* @returns {import("../controller.js").default & {
|
|
102
|
-
* frontendModelAuthorizedQuery: (action: "index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url") => import("../database/query/model-class-query.js").default<
|
|
102
|
+
* frontendModelAuthorizedQuery: (action: "index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url") => import("../database/query/model-class-query.js").default<TModelClass>,
|
|
103
103
|
* frontendModelAbilityAction: (action: string) => string,
|
|
104
104
|
* currentAbility: () => import("../authorization/ability.js").default | undefined,
|
|
105
|
-
* frontendModelIndexQuery: () => import("../database/query/model-class-query.js").default<
|
|
105
|
+
* frontendModelIndexQuery: () => import("../database/query/model-class-query.js").default<TModelClass>,
|
|
106
106
|
* frontendModelPreload: () => import("../database/query/index.js").NestedPreloadRecord | null,
|
|
107
107
|
* serializeFrontendModel: (model: import("../database/record/index.js").default) => Promise<Record<string, unknown>>
|
|
108
108
|
* }} - Controller instance with frontend-model helpers.
|
|
@@ -176,9 +176,9 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
178
|
* Runs params.
|
|
179
|
-
* @returns {import("../configuration-types.js").
|
|
179
|
+
* @returns {import("../configuration-types.js").VelociousParams} - Params.
|
|
180
180
|
*/
|
|
181
|
-
params() { return this.paramsValue || super.params() || {} }
|
|
181
|
+
params() { return /** @type {import("../configuration-types.js").VelociousParams} */ (this.paramsValue || super.params() || {}) }
|
|
182
182
|
|
|
183
183
|
/**
|
|
184
184
|
* Runs resource configuration.
|
|
@@ -243,11 +243,10 @@ export default class FrontendModelBaseResource extends AuthorizationBaseResource
|
|
|
243
243
|
/**
|
|
244
244
|
* Runs authorized query.
|
|
245
245
|
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Ability action.
|
|
246
|
-
* @
|
|
247
|
-
* @returns {import("../database/query/model-class-query.js").default<MC>} - Authorized query.
|
|
246
|
+
* @returns {import("../database/query/model-class-query.js").default<TModelClass>} - Authorized query.
|
|
248
247
|
*/
|
|
249
248
|
authorizedQuery(action) {
|
|
250
|
-
return
|
|
249
|
+
return this.typedControllerInstance().frontendModelAuthorizedQuery(action)
|
|
251
250
|
}
|
|
252
251
|
|
|
253
252
|
|