velocious 1.0.464 → 1.0.466

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "build/bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.464",
6
+ "version": "1.0.466",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -3,6 +3,12 @@
3
3
  import BaseCommand from "../../../../../cli/base-command.js"
4
4
  import fs from "node:fs/promises"
5
5
  import path from "node:path"
6
+ import requireContext from "require-context"
7
+
8
+ /**
9
+ * @typedef {(id: string) => {default: typeof import("../../../../../database/record/index.js").default}} ModelFileRequireContextIdFunctionType
10
+ * @typedef {ModelFileRequireContextIdFunctionType & {keys: () => string[]}} ModelFileRequireContextType
11
+ */
6
12
 
7
13
  /**
8
14
  * Lints model relationships: every non-polymorphic belongs-to relationship should have an inverse
@@ -27,7 +33,9 @@ export default class VelociousCliCommandsLintRelationships extends BaseCommand {
27
33
  // current configuration, so make this command's configuration the current one.
28
34
  this.getConfiguration().setCurrent()
29
35
 
30
- await this.getConfiguration().initializeModels()
36
+ if (!await this._registerStaticModelFiles()) {
37
+ await this.getConfiguration().initializeModels()
38
+ }
31
39
 
32
40
  const ignoredRelationships = await this._loadIgnoredRelationships()
33
41
  const offences = []
@@ -66,7 +74,11 @@ export default class VelociousCliCommandsLintRelationships extends BaseCommand {
66
74
  if (candidate.through) return false
67
75
 
68
76
  try {
69
- return candidate.getTargetModelClass() === modelClass
77
+ const candidateTargetModelClass = candidate.getTargetModelClass()
78
+
79
+ if (!candidateTargetModelClass) return false
80
+
81
+ return this._modelClassesMatch(candidateTargetModelClass, modelClass)
70
82
  } catch {
71
83
  // A has-many/has-one with an unresolvable target can't be the inverse of this belongs-to.
72
84
  // It is reported separately when its own model's belongs-to relationships are linted.
@@ -97,6 +109,87 @@ export default class VelociousCliCommandsLintRelationships extends BaseCommand {
97
109
  return {offences}
98
110
  }
99
111
 
112
+ /**
113
+ * Registers model classes from the conventional src/models directory without
114
+ * running the application's full database/server initialization.
115
+ * @returns {Promise<boolean>} Whether static model files were registered.
116
+ */
117
+ async _registerStaticModelFiles() {
118
+ if (this.args.testing) return false
119
+
120
+ const modelsDirectory = path.join(this.directory(), "src/models")
121
+
122
+ try {
123
+ const stats = await fs.stat(modelsDirectory)
124
+
125
+ if (!stats.isDirectory()) return false
126
+ } catch (error) {
127
+ if (/** @type {NodeJS.ErrnoException} */ (error).code == "ENOENT") return false
128
+
129
+ throw error
130
+ }
131
+
132
+ if ((await this._javascriptFilesInDirectory(modelsDirectory)).length === 0) return false
133
+
134
+ /** @type {ModelFileRequireContextType} */
135
+ const requireContextModels = requireContext(modelsDirectory, true, /^(.+)\.js$/)
136
+
137
+ const modelFileNames = requireContextModels.keys()
138
+ for (const fileName of modelFileNames) {
139
+ const modelClassImport = requireContextModels(fileName)
140
+ const modelClass = modelClassImport.default
141
+
142
+ if (!modelClass) {
143
+ throw new Error(`Model wasn't exported from: ${fileName}`)
144
+ }
145
+
146
+ modelClass.registerRecordClass({configuration: this.getConfiguration()})
147
+ }
148
+
149
+ return true
150
+ }
151
+
152
+ /**
153
+ * Finds JavaScript files below a directory.
154
+ * @param {string} directory - Directory to scan.
155
+ * @returns {Promise<string[]>} JavaScript file paths.
156
+ */
157
+ async _javascriptFilesInDirectory(directory) {
158
+ const filePaths = []
159
+ const entries = await fs.readdir(directory, {withFileTypes: true})
160
+
161
+ for (const entry of entries) {
162
+ const entryPath = path.join(directory, entry.name)
163
+
164
+ if (entry.isDirectory()) {
165
+ filePaths.push(...await this._javascriptFilesInDirectory(entryPath))
166
+ continue
167
+ }
168
+
169
+ if (entry.isFile() && entry.name.endsWith(".js")) {
170
+ filePaths.push(entryPath)
171
+ }
172
+ }
173
+
174
+ return filePaths
175
+ }
176
+
177
+ /**
178
+ * Checks whether two model class objects describe the same registered model.
179
+ * @param {typeof import("../../../../../database/record/index.js").default} leftModelClass - Candidate target model class.
180
+ * @param {typeof import("../../../../../database/record/index.js").default} rightModelClass - Belongs-to source model class.
181
+ * @returns {boolean} Whether both model classes represent the same model identity.
182
+ */
183
+ _modelClassesMatch(leftModelClass, rightModelClass) {
184
+ if (leftModelClass === rightModelClass) return true
185
+ // `translates()` creates an internal translation class; apps may also define
186
+ // a concrete class for the same model/table so generated code has a stable
187
+ // file and type name.
188
+ if (leftModelClass.getModelName() != rightModelClass.getModelName()) return false
189
+
190
+ return leftModelClass.tableName() == rightModelClass.tableName()
191
+ }
192
+
100
193
  /**
101
194
  * Loads the ignored relationship keys from the lint config file. The file is optional; when the
102
195
  * default path doesn't exist, no relationships are ignored. An explicitly passed `--config` path
@@ -4254,7 +4254,7 @@ export default class FrontendModelBase {
4254
4254
  * @param {string | number | null} [args.memberId] - Optional member id for member-scoped commands.
4255
4255
  * @param {Record<string, ?>} args.payload - Request payload.
4256
4256
  * @param {string} args.resourcePath - Direct resource path.
4257
- * @returns {Promise<Record<string, ?>>} - Decoded response payload.
4257
+ * @returns {Promise<Record<string, FrontendModelAttributeValue>>} - Decoded response payload.
4258
4258
  */
4259
4259
  static async executeCustomCommand({commandName, commandType, memberId = null, payload, resourcePath}) {
4260
4260
  const serializedPayload = /** @type {Record<string, ?>} */ (serializeFrontendModelTransportValue(payload))
@@ -4279,7 +4279,7 @@ export default class FrontendModelBase {
4279
4279
  scheduleSharedFrontendModelRequestFlush()
4280
4280
  })
4281
4281
 
4282
- const decodedBatchResponse = /** @type {Record<string, ?>} */ (batchResponse)
4282
+ const decodedBatchResponse = /** @type {Record<string, FrontendModelAttributeValue>} */ (batchResponse)
4283
4283
 
4284
4284
  this.throwOnErrorFrontendModelResponse({
4285
4285
  commandType,
@@ -55,3 +55,16 @@ declare module "is-plain-object" {
55
55
  declare module "escape-string-regexp" {
56
56
  export default function escapeStringRegexp(value: string): string
57
57
  }
58
+
59
+ declare module "require-context" {
60
+ export interface RequireContext<TModule = {default?: unknown}> {
61
+ (id: string): TModule
62
+ keys(): string[]
63
+ }
64
+
65
+ export default function requireContext<TModule = {default?: unknown}>(
66
+ directory: string,
67
+ useSubdirectories?: boolean,
68
+ regExp?: RegExp
69
+ ): RequireContext<TModule>
70
+ }