velocious 1.0.485 → 1.0.487
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 +2 -0
- package/build/configuration-types.js +19 -1
- package/build/configuration.js +25 -2
- package/build/database/query/create-index-base.js +1 -1
- package/build/database/query/create-table-base.js +2 -2
- package/build/database/record/auditing.js +407 -0
- package/build/database/record/index.js +101 -0
- package/build/environment-handlers/base.js +9 -0
- package/build/environment-handlers/node.js +105 -19
- package/build/packages/velocious-package.js +115 -0
- package/build/src/configuration-types.d.ts +57 -2
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +18 -2
- package/build/src/configuration.d.ts +9 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +21 -3
- package/build/src/database/query/create-index-base.js +2 -2
- package/build/src/database/query/create-table-base.js +4 -4
- package/build/src/database/record/auditing.d.ts +156 -0
- package/build/src/database/record/auditing.d.ts.map +1 -0
- package/build/src/database/record/auditing.js +349 -0
- package/build/src/database/record/index.d.ts +69 -0
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +89 -1
- package/build/src/environment-handlers/base.d.ts +6 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +9 -1
- package/build/src/environment-handlers/node.d.ts +18 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +94 -19
- package/build/src/packages/velocious-package.d.ts +69 -0
- package/build/src/packages/velocious-package.d.ts.map +1 -0
- package/build/src/packages/velocious-package.js +101 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/configuration-types.js +19 -1
- package/src/configuration.js +25 -2
- package/src/database/query/create-index-base.js +1 -1
- package/src/database/query/create-table-base.js +2 -2
- package/src/database/record/auditing.js +407 -0
- package/src/database/record/index.js +101 -0
- package/src/environment-handlers/base.js +9 -0
- package/src/environment-handlers/node.js +105 -19
- package/src/packages/velocious-package.js +115 -0
|
@@ -31,6 +31,8 @@ import * as inflection from "inflection"
|
|
|
31
31
|
import path from "path"
|
|
32
32
|
import {AsyncLocalStorage as NodeAsyncLocalStorage} from "node:async_hooks"
|
|
33
33
|
import {timingSafeEqual} from "node:crypto"
|
|
34
|
+
import requireContext from "require-context"
|
|
35
|
+
import InitializerFromRequireContext from "../database/initializer-from-require-context.js"
|
|
34
36
|
import toImportSpecifier from "../utils/to-import-specifier.js"
|
|
35
37
|
import {validateTimeZone} from "../time-zone.js"
|
|
36
38
|
|
|
@@ -103,7 +105,7 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
103
105
|
for (const backendProject of backendProjects) {
|
|
104
106
|
if (backendProject.frontendModels) continue
|
|
105
107
|
|
|
106
|
-
const resourcesDir = path.join(backendProject.path, "src", "resources")
|
|
108
|
+
const resourcesDir = backendProject.resourcesPath || path.join(backendProject.path, "src", "resources")
|
|
107
109
|
let files
|
|
108
110
|
|
|
109
111
|
try {
|
|
@@ -145,6 +147,43 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Loads models contributed by registered packages into the model registry,
|
|
152
|
+
* after the app's own `initializeModels` hook. A package whose models directory
|
|
153
|
+
* is absent is skipped; a package model whose name collides with an
|
|
154
|
+
* already-registered different class throws. Node-only (uses the filesystem), so
|
|
155
|
+
* it lives here rather than in the browser-bundled Configuration.
|
|
156
|
+
* @param {import("../configuration.js").default} configuration - Configuration instance.
|
|
157
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
158
|
+
*/
|
|
159
|
+
async initializePackageModels(configuration) {
|
|
160
|
+
for (const velociousPackage of configuration.getPackages()) {
|
|
161
|
+
const modelsPath = velociousPackage.getModelsPath()
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await fs.access(modelsPath)
|
|
165
|
+
} catch {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const packageRequireContext = /** @type {import("../database/initializer-from-require-context.js").ModelClassRequireContextType} */ (requireContext(modelsPath, true, /^(.+)\.js$/))
|
|
170
|
+
const modelClasses = configuration.getModelClasses()
|
|
171
|
+
|
|
172
|
+
for (const fileName of packageRequireContext.keys()) {
|
|
173
|
+
const modelClass = packageRequireContext(fileName)?.default
|
|
174
|
+
const existing = modelClass && modelClasses[modelClass.getModelName()]
|
|
175
|
+
|
|
176
|
+
if (existing && existing !== modelClass) {
|
|
177
|
+
throw new Error(`Package "${velociousPackage.getName()}" model "${modelClass.getModelName()}" collides with an already-registered model.`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await configuration.ensureConnections({name: `Initialize ${velociousPackage.getName()} package models`}, async () => {
|
|
182
|
+
await new InitializerFromRequireContext({requireContext: packageRequireContext}).initialize({configuration})
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
148
187
|
/**
|
|
149
188
|
* Runs set configuration.
|
|
150
189
|
* @param {import("../configuration.js").default} newConfiguration - New configuration.
|
|
@@ -761,32 +800,79 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
761
800
|
* @returns {Promise<Array<import("./base.js").MigrationObjectType>>} - Resolves with the migrations.
|
|
762
801
|
*/
|
|
763
802
|
async findMigrations() {
|
|
764
|
-
const
|
|
765
|
-
const
|
|
766
|
-
|
|
803
|
+
const configuration = this.getConfiguration()
|
|
804
|
+
const migrationDirectories = [`${configuration.getDirectory()}/src/database/migrations`]
|
|
805
|
+
|
|
806
|
+
for (const velociousPackage of configuration.getPackages()) {
|
|
807
|
+
migrationDirectories.push(velociousPackage.getMigrationsPath())
|
|
808
|
+
}
|
|
767
809
|
|
|
768
|
-
|
|
769
|
-
|
|
810
|
+
/** @type {Array<import("./base.js").MigrationObjectType>} */
|
|
811
|
+
const files = []
|
|
770
812
|
|
|
771
|
-
|
|
813
|
+
for (const migrationsPath of migrationDirectories) {
|
|
814
|
+
await this._collectMigrationsFromDirectory(migrationsPath, files)
|
|
815
|
+
}
|
|
772
816
|
|
|
773
|
-
|
|
817
|
+
this._ensureNoMigrationTimestampCollisions(files)
|
|
774
818
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const migrationClassName = inflection.camelize(migrationName.replaceAll("-", "_"))
|
|
819
|
+
return files.sort((migration1, migration2) => migration1.date - migration2.date)
|
|
820
|
+
}
|
|
778
821
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
822
|
+
/**
|
|
823
|
+
* Collects migration files from one directory into `files`, preserving each
|
|
824
|
+
* file's real absolute path (so app and package migrations keep their own
|
|
825
|
+
* source location). A missing directory is skipped.
|
|
826
|
+
* @param {string} migrationsPath - Directory to scan.
|
|
827
|
+
* @param {Array<import("./base.js").MigrationObjectType>} files - Accumulator to push into.
|
|
828
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
829
|
+
*/
|
|
830
|
+
async _collectMigrationsFromDirectory(migrationsPath, files) {
|
|
831
|
+
const glob = await fs.glob(`${migrationsPath}/**/*.js`)
|
|
832
|
+
|
|
833
|
+
try {
|
|
834
|
+
for await (const fullPath of glob) {
|
|
835
|
+
const file = await path.basename(fullPath)
|
|
836
|
+
const match = file.match(/^(\d{14})-(.+)\.js$/)
|
|
837
|
+
|
|
838
|
+
if (!match) continue
|
|
839
|
+
|
|
840
|
+
const date = parseInt(match[1])
|
|
841
|
+
const migrationName = match[2]
|
|
842
|
+
const migrationClassName = inflection.camelize(migrationName.replaceAll("-", "_"))
|
|
843
|
+
|
|
844
|
+
files.push({file, fullPath, date, migrationClassName})
|
|
845
|
+
}
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (/** @type {NodeJS.ErrnoException} */ (error)?.code !== "ENOENT") {
|
|
848
|
+
throw error
|
|
849
|
+
}
|
|
785
850
|
}
|
|
851
|
+
}
|
|
786
852
|
|
|
787
|
-
|
|
853
|
+
/**
|
|
854
|
+
* Throws if two migrations from different files share the same 14-digit
|
|
855
|
+
* timestamp. The `schema_migrations` ledger keys on the timestamp, so a silent
|
|
856
|
+
* collision (e.g. between the app and a package, or two packages) would leave
|
|
857
|
+
* the second migration un-run — a data bug. Fail loudly instead.
|
|
858
|
+
* @param {Array<import("./base.js").MigrationObjectType>} files - Collected migrations.
|
|
859
|
+
* @returns {void} - No return value.
|
|
860
|
+
*/
|
|
861
|
+
_ensureNoMigrationTimestampCollisions(files) {
|
|
862
|
+
/** @type {Map<number, string>} */
|
|
863
|
+
const pathsByDate = new Map()
|
|
788
864
|
|
|
789
|
-
|
|
865
|
+
for (const migration of files) {
|
|
866
|
+
if (!migration.fullPath) continue
|
|
867
|
+
|
|
868
|
+
const existing = pathsByDate.get(migration.date)
|
|
869
|
+
|
|
870
|
+
if (existing && existing !== migration.fullPath) {
|
|
871
|
+
throw new Error(`Two migrations share the timestamp ${migration.date}: ${existing} and ${migration.fullPath}. Migration timestamps must be unique across the app and all packages.`)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
pathsByDate.set(migration.date, migration.fullPath)
|
|
875
|
+
}
|
|
790
876
|
}
|
|
791
877
|
|
|
792
878
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import restArgsError from "../utils/rest-args-error.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A Velocious package (engine): an external npm package that contributes data
|
|
7
|
+
* models, frontend-model resources and migrations to a consuming app. The app
|
|
8
|
+
* lists packages in `Configuration({packages: [...]})`; the framework then loads
|
|
9
|
+
* the package's `src/models`, discovers its `src/resources`, runs its
|
|
10
|
+
* `src/database/migrations`, and generates its frontend models into the app.
|
|
11
|
+
*/
|
|
12
|
+
export default class VelociousPackage {
|
|
13
|
+
/**
|
|
14
|
+
* Wraps a plain descriptor as a VelociousPackage (or returns it unchanged when
|
|
15
|
+
* it already is one), so packages can be listed without importing this class.
|
|
16
|
+
* @param {VelociousPackage | import("../configuration-types.js").VelociousPackageDescriptor} descriptor - Package or plain descriptor.
|
|
17
|
+
* @returns {VelociousPackage} - The package instance.
|
|
18
|
+
*/
|
|
19
|
+
static from(descriptor) {
|
|
20
|
+
if (descriptor instanceof VelociousPackage) {
|
|
21
|
+
return descriptor
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return new VelociousPackage(descriptor)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Derives the containing directory of a module url without Node's `path`/`url`
|
|
29
|
+
* modules, so this class stays safe to import in browser/Expo bundles.
|
|
30
|
+
* @param {string} url - A module url (usually `import.meta.url`).
|
|
31
|
+
* @returns {string} - The directory path that contains the module.
|
|
32
|
+
*/
|
|
33
|
+
static _directoryFromUrl(url) {
|
|
34
|
+
const directoryUrl = new URL(".", url)
|
|
35
|
+
|
|
36
|
+
return decodeURIComponent(directoryUrl.pathname).replace(/\/$/, "")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Runs constructor.
|
|
41
|
+
* @param {import("../configuration-types.js").VelociousPackageDescriptor} args - Package descriptor.
|
|
42
|
+
*/
|
|
43
|
+
constructor({name, url, path, modelsPath, resourcesPath, migrationsPath, ...restArgs}) {
|
|
44
|
+
restArgsError(restArgs)
|
|
45
|
+
|
|
46
|
+
if (!name) {
|
|
47
|
+
throw new Error("A velocious package requires a name.")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!path && !url) {
|
|
51
|
+
throw new Error(`Velocious package "${name}" requires a "path" or a "url" (usually import.meta.url).`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this._name = name
|
|
55
|
+
this._path = path || VelociousPackage._directoryFromUrl(/** @type {string} */ (url))
|
|
56
|
+
this._modelsPath = modelsPath
|
|
57
|
+
this._resourcesPath = resourcesPath
|
|
58
|
+
this._migrationsPath = migrationsPath
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Runs get name.
|
|
63
|
+
* @returns {string} - The package name.
|
|
64
|
+
*/
|
|
65
|
+
getName() {
|
|
66
|
+
return this._name
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Runs get path.
|
|
71
|
+
* @returns {string} - The package root directory (the one that contains `src`).
|
|
72
|
+
*/
|
|
73
|
+
getPath() {
|
|
74
|
+
return this._path
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Runs get models path.
|
|
79
|
+
* @returns {string} - The package's models directory.
|
|
80
|
+
*/
|
|
81
|
+
getModelsPath() {
|
|
82
|
+
return this._modelsPath || `${this._path}/src/models`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Runs get resources path.
|
|
87
|
+
* @returns {string} - The package's frontend-model resources directory.
|
|
88
|
+
*/
|
|
89
|
+
getResourcesPath() {
|
|
90
|
+
return this._resourcesPath || `${this._path}/src/resources`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Runs get migrations path.
|
|
95
|
+
* @returns {string} - The package's migrations directory.
|
|
96
|
+
*/
|
|
97
|
+
getMigrationsPath() {
|
|
98
|
+
return this._migrationsPath || `${this._path}/src/database/migrations`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Derives the internal backend-project entry the framework appends so the
|
|
103
|
+
* existing resource-discovery + frontend-model generation machinery picks up
|
|
104
|
+
* this package. Generated frontend models are written to the app's output.
|
|
105
|
+
* @param {{frontendModelsOutputPath: string | undefined}} args - The app's frontend-models output path.
|
|
106
|
+
* @returns {import("../configuration-types.js").BackendProjectConfiguration} - The derived backend project.
|
|
107
|
+
*/
|
|
108
|
+
toBackendProjectConfiguration({frontendModelsOutputPath}) {
|
|
109
|
+
return {
|
|
110
|
+
frontendModelsOutputPath,
|
|
111
|
+
path: this.getPath(),
|
|
112
|
+
resourcesPath: this.getResourcesPath()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|