velocious 1.0.577 → 1.0.578
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 +11 -5
- package/build/background-jobs/main.js +2 -2
- package/build/configuration-types.js +1 -0
- package/build/configuration.js +52 -10
- package/build/database/drivers/mysql/structure-sql.js +70 -9
- package/build/database/query/with-count.js +8 -8
- package/build/database/record/record-not-found-error.js +1 -0
- package/build/frontend-model-controller.js +71 -69
- package/build/frontend-models/base.js +10 -1
- package/build/src/background-jobs/main.js +3 -3
- package/build/src/configuration-types.d.ts +5 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -1
- package/build/src/configuration.d.ts +13 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +52 -11
- package/build/src/database/drivers/mysql/structure-sql.d.ts +11 -0
- package/build/src/database/drivers/mysql/structure-sql.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/structure-sql.js +61 -10
- package/build/src/database/query/with-count.js +8 -8
- package/build/src/database/record/record-not-found-error.d.ts +1 -0
- package/build/src/database/record/record-not-found-error.d.ts.map +1 -1
- package/build/src/database/record/record-not-found-error.js +2 -1
- package/build/src/frontend-model-controller.d.ts +10 -10
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +73 -67
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +11 -2
- package/build/src/velocious-error.d.ts +10 -0
- package/build/src/velocious-error.d.ts.map +1 -1
- package/build/src/velocious-error.js +8 -2
- package/build/velocious-error.js +7 -1
- package/package.json +2 -2
- package/src/background-jobs/main.js +2 -2
- package/src/configuration-types.js +1 -0
- package/src/configuration.js +52 -10
- package/src/database/drivers/mysql/structure-sql.js +70 -9
- package/src/database/query/with-count.js +8 -8
- package/src/database/record/record-not-found-error.js +1 -0
- package/src/frontend-model-controller.js +71 -69
- package/src/frontend-models/base.js +10 -1
- package/src/velocious-error.js +7 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Gap-less positional lists with automatic reordering via `actsAsList`, including models with numeric, string, or UUID primary keys (see [docs/acts-as-list.md](docs/acts-as-list.md))
|
|
23
23
|
* Rails-style nested-attribute writes on frontend-model `save()` (see [docs/nested-attributes.md](docs/nested-attributes.md))
|
|
24
24
|
* Async-aware test-data factories with inherited traits, graph-first native association autosave, metadata-aware override precedence, callbacks, sequences, and linting (see [docs/factories.md](docs/factories.md))
|
|
25
|
-
* Per-row association counts via `.withCount(...)`, including safe batching of structurally identical aggregates, on frontend and backend queries (see [docs/with-count.md](docs/with-count.md))
|
|
25
|
+
* Per-row association counts via `.withCount(...)`, including cohort-safe intersected filters and safe batching of structurally identical aggregates, on frontend and backend queries (see [docs/with-count.md](docs/with-count.md))
|
|
26
26
|
* Consumer-defined per-row SQL aggregates/computations via `.queryData(...)`, with compatible projections sharing a roundtrip while preserving declared alias-overwrite order, on frontend and backend queries (see [docs/query-data.md](docs/query-data.md))
|
|
27
27
|
* Per-record ability checks via `.abilities(...)` on frontend queries + `record.can(action)` (see [docs/abilities.md](docs/abilities.md))
|
|
28
28
|
* Translated model attributes with current-locale relationship sorting (see [docs/translations.md](docs/translations.md))
|
|
@@ -741,7 +741,7 @@ Use `await FrontendModelBase.waitForIdle()` when a test harness or app lifecycle
|
|
|
741
741
|
|
|
742
742
|
Frontend-model HTTP requests always use `credentials: "include"` so shared custom commands can set session cookies without app-level transport overrides.
|
|
743
743
|
|
|
744
|
-
Unexpected frontend-model endpoint failures stay client-safe in production with `errorMessage: "Request failed."
|
|
744
|
+
Unexpected frontend-model endpoint failures stay client-safe in production with `errorType: "internal_error"`, `errorMessage: "Request failed."`, and a server-generated `correlationId` shared with the matching framework-error report. Expected application failures can use `VelociousError.safe(message, {errorType, details, code})`; generated frontend-model callers preserve the server's safe error fields. See [docs/frontend-models.md](docs/frontend-models.md#error-payloads).
|
|
745
745
|
Invalid client query descriptors, such as unknown `select`, `where`, `search`, `joins`, `preload`, `group`, `sort`, `pluck`, or Ransack attributes, return the specific frontend-model query error message with `velocious.code: "frontend-model-query-error"` and are not emitted as framework errors.
|
|
746
746
|
Invalid frontend-model write attributes and attachment names, including attributes rejected by `permittedParams()`, return the specific safe error message with `velocious.code: "frontend-model-attribute-error"` and are not emitted as framework errors.
|
|
747
747
|
In `development` and `test`, Velocious also includes `debugErrorClass`, `debugErrorMessage`, and `debugBacktrace` fields so browser/system-test failures are easier to diagnose without exposing those details in production.
|
|
@@ -934,7 +934,8 @@ Translated models also get a `currentTranslation` `hasOne` relationship scoped t
|
|
|
934
934
|
|
|
935
935
|
Async class APIs initialize record metadata on first use when a model has not
|
|
936
936
|
already been initialized eagerly. See [docs/model-initialization.md](docs/model-initialization.md)
|
|
937
|
-
for the eager and lazy initialization behavior
|
|
937
|
+
for the eager and lazy initialization behavior, including atomic shared bootstrap
|
|
938
|
+
and complete recovery after an eager initialization failure.
|
|
938
939
|
|
|
939
940
|
## Lifecycle callbacks
|
|
940
941
|
|
|
@@ -1347,7 +1348,7 @@ If you need to regenerate missing structure files without rerunning migrations,
|
|
|
1347
1348
|
npx velocious db:schema:dump
|
|
1348
1349
|
```
|
|
1349
1350
|
|
|
1350
|
-
`db:schema:dump` generates a structure SQL file for each configured database identifier under `db/structure-<identifier>.sql`. It only writes files when one or more expected files are missing. The generated file includes the full DDL (tables, indexes, views, triggers, etc.) followed by `INSERT INTO schema_migrations (version) VALUES (...)` for every currently applied migration version.
|
|
1351
|
+
`db:schema:dump` generates a structure SQL file for each configured database identifier under `db/structure-<identifier>.sql`. It only writes files when one or more expected files are missing. The generated file includes the full DDL (tables, indexes, views, triggers, etc.) followed by `INSERT INTO schema_migrations (version) VALUES (...)` for every currently applied migration version. MySQL and MariaDB dumps place same-schema referenced base tables before their dependent tables. The migration ledger preserves applied versions in the checked-in snapshot so fresh databases loaded from it do not re-run migrations that already shaped the schemas in the file.
|
|
1351
1352
|
|
|
1352
1353
|
If you need to load the checked-in structure files for each configured database, use:
|
|
1353
1354
|
|
|
@@ -1857,7 +1858,7 @@ configuration.getErrorEvents().on("all-error", ({error, errorType}) => {
|
|
|
1857
1858
|
})
|
|
1858
1859
|
```
|
|
1859
1860
|
|
|
1860
|
-
Genuinely unexpected frontend-model command failures reach this bus too. The frontend-model controller catches them to return a client-safe `Request failed.`
|
|
1861
|
+
Genuinely unexpected frontend-model command failures reach this bus too. The frontend-model controller catches them to return a client-safe `internal_error` response with `Request failed.` and a correlation ID, then emits them as `framework-error`/`all-error` with the same correlation ID and `context.frontendModelEndpoint === true`. Expected user-flow errors are excluded: validation failures are forwarded with their real message (for example `Name can't be blank`), invalid client query descriptors are returned as frontend-model query errors, and `error.velocious`-annotated / `safeToExpose` errors keep their expected-error status. A raw `errorType` property alone is not considered safe and does not suppress reporting.
|
|
1861
1862
|
|
|
1862
1863
|
Unexpected inbound decoded WebSocket dispatch failures emit one `framework-error` and one matching `all-error`. Established expected client-flow errors remain excluded from both events.
|
|
1863
1864
|
|
|
@@ -2226,6 +2227,11 @@ VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS=5400000
|
|
|
2226
2227
|
|
|
2227
2228
|
New jobs default to `executionMode: "pooled"`: a worker runs them in warm, reusable Node child runners. `pooledRunnerCount` (default: `4`) bounds this independent per-worker pool, and `pooledRunnerConcurrency` (default: `1`) sets how many jobs each child runs at once on its own event loop, so total pooled capacity is `pooledRunnerCount × pooledRunnerConcurrency` — raise concurrency for I/O-bound jobs to get high throughput from a bounded, isolated set of processes. `pooledRunnerCount`, `pooledRunnerConcurrency`, and `pooledRunnerMaxJobs` must be finite positive integers; the RSS and lifetime limits must be finite positive numbers. A child is recycled after an acknowledged job when it reaches `pooledRunnerMaxJobs` (default: `100`), `pooledRunnerMaxRssBytes` (default: `536870912`, or 512 MiB), or `pooledRunnerMaxLifetimeMs` (default: `3600000`, or one hour). `execution_mode` is the single source of truth for a job's runtime — pooled rows persist as `execution_mode = "pooled"` directly. See [execution modes and pooled runners](docs/background-jobs.md#execution-modes-and-pooled-runners).
|
|
2228
2229
|
|
|
2230
|
+
Cold pooled jobs share one atomic model-bootstrap phase. If that phase fails, all
|
|
2231
|
+
waiting jobs receive the failure; a later job in the surviving child cannot run
|
|
2232
|
+
until a complete model-initialization phase succeeds. The pool's configured
|
|
2233
|
+
concurrency and per-job connection scopes are unchanged.
|
|
2234
|
+
|
|
2229
2235
|
`maxConcurrentForkedJobs` (default: `4`) caps how many out-of-process `executionMode: "forked"` or `executionMode: "spawned"` jobs one worker may keep in flight. Forked jobs use `child_process.fork()` with an attached IPC channel. After the main process acknowledges their durable status report, forked and spawned one-shot runners exit without waiting for graceful Beacon/database teardown; the OS closes their process-owned resources. A missing or rejected status acknowledgement makes the runner exit as failed instead of reporting clean success. Spawned jobs use the legacy `background-jobs-runner` CLI process via `child_process.spawn()` and are only for callers that intentionally want that spawned behavior.
|
|
2230
2236
|
|
|
2231
2237
|
`jobTimeoutMs` (or `VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS`, milliseconds; default: disabled) is a wall-clock backstop for `"forked"` and `"pooled"` jobs. A job still running after the timeout is terminated (`SIGTERM`, then `SIGKILL` after the reaping grace) and reported `failed`, so a genuinely-hung job can't pin a worker's capacity — and its whole-app boot and DB connections — indefinitely (notably a retired-release worker draining after a deploy). For a **pooled** job the whole child running it is killed, so its concurrent in-flight siblings on that child are also reported `failed` and requeued — a hung JS job can't be cancelled any other way — before a replacement child is spawned. It's a coarse safety net, not per-job tuning: it applies to every forked and pooled job, so set it well above the longest legitimate job. Omit it, or set `null`/`<= 0`, to disable. `"inline"` jobs are not covered — they share the worker's process and can't be killed without killing the worker. See [docs/background-jobs.md](docs/background-jobs.md#job-timeout-hung-runner-backstop).
|
|
@@ -786,9 +786,9 @@ export default class BackgroundJobsMain {
|
|
|
786
786
|
options: message.options || {}
|
|
787
787
|
})
|
|
788
788
|
|
|
789
|
-
jsonSocket.send({type: "schedule-replaced", ...result})
|
|
790
789
|
this._notifyEnqueued()
|
|
791
790
|
await this._drain()
|
|
791
|
+
jsonSocket.send({type: "schedule-replaced", ...result})
|
|
792
792
|
} catch (error) {
|
|
793
793
|
this._handleClientMutationError({
|
|
794
794
|
context: {jobName: message.jobName, scheduleKey: message.scheduleKey, stage: "background-job-replace-scheduled"},
|
|
@@ -812,9 +812,9 @@ export default class BackgroundJobsMain {
|
|
|
812
812
|
try {
|
|
813
813
|
const result = await this.store.cancelScheduled(message.scheduleKey)
|
|
814
814
|
|
|
815
|
-
jsonSocket.send({type: "schedule-cancelled", ...result})
|
|
816
815
|
this._notifyEnqueued()
|
|
817
816
|
await this._drain()
|
|
817
|
+
jsonSocket.send({type: "schedule-cancelled", ...result})
|
|
818
818
|
} catch (error) {
|
|
819
819
|
this._handleClientMutationError({
|
|
820
820
|
context: {scheduleKey: message.scheduleKey, stage: "background-job-cancel-scheduled"},
|
|
@@ -312,6 +312,7 @@
|
|
|
312
312
|
* @property {string} controller - Controller class name.
|
|
313
313
|
* @property {string} [action] - Controller action or endpoint label.
|
|
314
314
|
* @property {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url" | "custom-command"} [commandType] - Frontend-model command type.
|
|
315
|
+
* @property {string} [correlationId] - Server-generated identifier shared by an unexpected client error and framework reports.
|
|
315
316
|
* @property {boolean} [expectedError] - Whether the error is an expected user-flow failure.
|
|
316
317
|
* @property {boolean} [frontendModelEndpoint] - Whether the error came from the frontend-model endpoint.
|
|
317
318
|
* @property {string} [model] - Frontend-model name from the failed request.
|
package/build/configuration.js
CHANGED
|
@@ -286,6 +286,19 @@ export default class VelociousConfiguration {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
this._isInitialized = false
|
|
289
|
+
this._modelsInitialized = false
|
|
290
|
+
/**
|
|
291
|
+
* Invalidates model phases that started before database connections closed.
|
|
292
|
+
* @type {number}
|
|
293
|
+
*/
|
|
294
|
+
this._modelInitializationGeneration = 0
|
|
295
|
+
/**
|
|
296
|
+
* In-progress `initializeModels()` promise. Model initialization is an
|
|
297
|
+
* atomic bootstrap phase: concurrent callers share it, and a rejection
|
|
298
|
+
* leaves the phase eligible for a later complete attempt.
|
|
299
|
+
* @type {Promise<void> | undefined}
|
|
300
|
+
*/
|
|
301
|
+
this._initializeModelsPromise = undefined
|
|
289
302
|
/**
|
|
290
303
|
* In-progress `initialize()` promise, memoized so concurrent callers await
|
|
291
304
|
* the same bootstrap. Reset to undefined if initialization fails.
|
|
@@ -2020,25 +2033,39 @@ export default class VelociousConfiguration {
|
|
|
2020
2033
|
* @returns {Promise<void>} - Resolves when complete.
|
|
2021
2034
|
*/
|
|
2022
2035
|
async initializeModels(args = {type: "server"}) {
|
|
2023
|
-
if (
|
|
2024
|
-
|
|
2036
|
+
if (this._modelsInitialized) return
|
|
2037
|
+
if (this._initializeModelsPromise) return await this._initializeModelsPromise
|
|
2025
2038
|
|
|
2039
|
+
const modelInitializationGeneration = this._modelInitializationGeneration
|
|
2040
|
+
const initializeModelsPromise = (async () => {
|
|
2026
2041
|
const shouldSkipDummyModelInitialization = process.env.VELOCIOUS_SKIP_DUMMY_MODEL_INITIALIZATION === "1"
|
|
2027
2042
|
&& process.env.VELOCIOUS_BROWSER_TESTS === "true"
|
|
2028
2043
|
&& this.getEnvironment() === "test"
|
|
2029
2044
|
|
|
2030
|
-
if (shouldSkipDummyModelInitialization) {
|
|
2031
|
-
|
|
2045
|
+
if (!shouldSkipDummyModelInitialization) {
|
|
2046
|
+
if (this._initializeModels) {
|
|
2047
|
+
await this._initializeModels({configuration: this, type: args.type})
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
await this.getEnvironmentHandler().initializePackageModels(this)
|
|
2051
|
+
await initializeAuditedModelRelationships(this)
|
|
2052
|
+
|
|
2053
|
+
await this.getEnvironmentHandler().initializeFrontendModelWebsocketPublishers(this)
|
|
2032
2054
|
}
|
|
2033
2055
|
|
|
2034
|
-
if (this.
|
|
2035
|
-
|
|
2056
|
+
if (this._modelInitializationGeneration === modelInitializationGeneration) {
|
|
2057
|
+
this._modelsInitialized = true
|
|
2036
2058
|
}
|
|
2059
|
+
})()
|
|
2037
2060
|
|
|
2038
|
-
|
|
2039
|
-
await initializeAuditedModelRelationships(this)
|
|
2061
|
+
this._initializeModelsPromise = initializeModelsPromise
|
|
2040
2062
|
|
|
2041
|
-
|
|
2063
|
+
try {
|
|
2064
|
+
await initializeModelsPromise
|
|
2065
|
+
} finally {
|
|
2066
|
+
if (this._initializeModelsPromise === initializeModelsPromise) {
|
|
2067
|
+
this._initializeModelsPromise = undefined
|
|
2068
|
+
}
|
|
2042
2069
|
}
|
|
2043
2070
|
}
|
|
2044
2071
|
|
|
@@ -2073,6 +2100,12 @@ export default class VelociousConfiguration {
|
|
|
2073
2100
|
|
|
2074
2101
|
this._initializePromise = (async () => {
|
|
2075
2102
|
await this.initializeModels({type})
|
|
2103
|
+
|
|
2104
|
+
// Model initialization can be invalidated by a concurrent connection close.
|
|
2105
|
+
// If models are not ready, stop without marking the configuration initialized
|
|
2106
|
+
// so the next caller retries a full bootstrap.
|
|
2107
|
+
if (!this._modelsInitialized) return
|
|
2108
|
+
|
|
2076
2109
|
await this.getEnvironmentHandler().autoDiscoverResources(this)
|
|
2077
2110
|
this._mergeDiscoveredAbilityResources()
|
|
2078
2111
|
this._validateResourceRelationshipsOnModels()
|
|
@@ -2104,6 +2137,13 @@ export default class VelociousConfiguration {
|
|
|
2104
2137
|
this._initializePromise = undefined
|
|
2105
2138
|
throw error
|
|
2106
2139
|
}
|
|
2140
|
+
|
|
2141
|
+
// If the inner IIFE returned without marking the configuration initialized
|
|
2142
|
+
// (e.g. because models were invalidated mid-bootstrap), clear the promise so
|
|
2143
|
+
// a later call retries a full bootstrap.
|
|
2144
|
+
if (!this._isInitialized) {
|
|
2145
|
+
this._initializePromise = undefined
|
|
2146
|
+
}
|
|
2107
2147
|
}
|
|
2108
2148
|
|
|
2109
2149
|
/**
|
|
@@ -3244,8 +3284,10 @@ export default class VelociousConfiguration {
|
|
|
3244
3284
|
PoolClass.clearGlobalConnections(this)
|
|
3245
3285
|
}
|
|
3246
3286
|
|
|
3247
|
-
// Allow
|
|
3287
|
+
// Allow full re-initialization after connections are closed.
|
|
3288
|
+
this._modelInitializationGeneration += 1
|
|
3248
3289
|
this._modelsInitialized = false
|
|
3290
|
+
this._isInitialized = false
|
|
3249
3291
|
}
|
|
3250
3292
|
})()
|
|
3251
3293
|
|
|
@@ -20,6 +20,9 @@ export default class VelociousDatabaseDriversMysqlStructureSql {
|
|
|
20
20
|
const {driver} = this
|
|
21
21
|
const isMariaDb = await this._isMariaDb()
|
|
22
22
|
const rows = await driver.query("SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_type, table_name")
|
|
23
|
+
const foreignKeyRows = await driver.query("SELECT table_name, referenced_table_name FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND referenced_table_schema = DATABASE() AND referenced_table_name IS NOT NULL")
|
|
24
|
+
const baseTableNames = []
|
|
25
|
+
const views = []
|
|
23
26
|
const statements = []
|
|
24
27
|
|
|
25
28
|
for (const row of rows) {
|
|
@@ -31,24 +34,82 @@ export default class VelociousDatabaseDriversMysqlStructureSql {
|
|
|
31
34
|
if (!tableName || !tableType) continue
|
|
32
35
|
|
|
33
36
|
if (tableType == "BASE TABLE") {
|
|
34
|
-
|
|
35
|
-
const rawCreateStatement = this._mysqlCreateStatement(createRows?.[0])
|
|
36
|
-
const createStatement = rawCreateStatement ? this._stripAutoIncrement(rawCreateStatement) : null
|
|
37
|
-
|
|
38
|
-
if (createStatement) statements.push(normalizeSqlStatement(createStatement))
|
|
37
|
+
baseTableNames.push(tableName)
|
|
39
38
|
} else if (tableType == "VIEW" || (isMariaDb && tableType == "SYSTEM VIEW")) {
|
|
40
|
-
|
|
41
|
-
const createStatement = this._mysqlCreateStatement(createRows?.[0])
|
|
42
|
-
|
|
43
|
-
if (createStatement) statements.push(normalizeSqlStatement(createStatement))
|
|
39
|
+
views.push(tableName)
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
42
|
|
|
43
|
+
for (const tableName of this._orderBaseTables({foreignKeyRows, tableNames: baseTableNames})) {
|
|
44
|
+
const createRows = await driver.query(`SHOW CREATE TABLE ${driver.quoteTable(tableName)}`)
|
|
45
|
+
const rawCreateStatement = this._mysqlCreateStatement(createRows?.[0])
|
|
46
|
+
const createStatement = rawCreateStatement ? this._stripAutoIncrement(rawCreateStatement) : null
|
|
47
|
+
|
|
48
|
+
if (createStatement) statements.push(normalizeSqlStatement(createStatement))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const tableName of views) {
|
|
52
|
+
const createRows = await driver.query(`SHOW CREATE VIEW ${driver.quoteTable(tableName)}`)
|
|
53
|
+
const createStatement = this._mysqlCreateStatement(createRows?.[0])
|
|
54
|
+
|
|
55
|
+
if (createStatement) statements.push(normalizeSqlStatement(createStatement))
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
if (statements.length == 0) return null
|
|
48
59
|
|
|
49
60
|
return `${statements.join("\n\n")}\n`
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Orders tables so referenced tables are created before their dependents.
|
|
65
|
+
* @param {object} args - Options object.
|
|
66
|
+
* @param {Array<Record<string, ?>>} args.foreignKeyRows - Foreign key metadata rows.
|
|
67
|
+
* @param {string[]} args.tableNames - Base table names in their existing order.
|
|
68
|
+
* @returns {string[]} - Ordered table names.
|
|
69
|
+
*/
|
|
70
|
+
_orderBaseTables({foreignKeyRows, tableNames}) {
|
|
71
|
+
const pendingTableNames = new Set(tableNames)
|
|
72
|
+
/** @type {Record<string, Set<string>>} */
|
|
73
|
+
const dependenciesByTableName = {}
|
|
74
|
+
const orderedTableNames = []
|
|
75
|
+
|
|
76
|
+
for (const tableName of tableNames) {
|
|
77
|
+
dependenciesByTableName[tableName] = new Set()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const row of foreignKeyRows) {
|
|
81
|
+
const tableNameValue = row.table_name || row.TABLE_NAME
|
|
82
|
+
const referencedTableNameValue = row.referenced_table_name || row.REFERENCED_TABLE_NAME
|
|
83
|
+
const tableName = tableNameValue ? String(tableNameValue) : ""
|
|
84
|
+
const referencedTableName = referencedTableNameValue ? String(referencedTableNameValue) : ""
|
|
85
|
+
|
|
86
|
+
if (tableName == referencedTableName || !pendingTableNames.has(tableName) || !pendingTableNames.has(referencedTableName)) continue
|
|
87
|
+
|
|
88
|
+
dependenciesByTableName[tableName].add(referencedTableName)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
while (pendingTableNames.size > 0) {
|
|
92
|
+
const nextTableName = tableNames.find((tableName) => {
|
|
93
|
+
if (!pendingTableNames.has(tableName)) return false
|
|
94
|
+
|
|
95
|
+
return Array.from(dependenciesByTableName[tableName]).every((dependencyTableName) => !pendingTableNames.has(dependencyTableName))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (!nextTableName) {
|
|
99
|
+
for (const tableName of tableNames) {
|
|
100
|
+
if (pendingTableNames.has(tableName)) orderedTableNames.push(tableName)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
orderedTableNames.push(nextTableName)
|
|
107
|
+
pendingTableNames.delete(nextTableName)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return orderedTableNames
|
|
111
|
+
}
|
|
112
|
+
|
|
52
113
|
/**
|
|
53
114
|
* Runs is maria db.
|
|
54
115
|
* @returns {Promise<boolean>} - Resolves with Whether maria db.
|
|
@@ -161,22 +161,22 @@ function queryForEntry({entry, modelClass, parentIds, sourceModel}) {
|
|
|
161
161
|
|
|
162
162
|
const foreignKey = relationship.getForeignKey()
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* Mandatory cohort conditions.
|
|
165
165
|
* @type {Record<string, ?>} */
|
|
166
|
-
const
|
|
166
|
+
const mandatoryWhereConditions = {[foreignKey]: parentIds}
|
|
167
167
|
|
|
168
168
|
if (relationship.getPolymorphic && relationship.getPolymorphic()) {
|
|
169
169
|
const typeColumn = relationship.getPolymorphicTypeColumn()
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (entry.where) {
|
|
174
|
-
Object.assign(whereConditions, entry.where)
|
|
170
|
+
mandatoryWhereConditions[typeColumn] = modelClass.getModelName()
|
|
175
171
|
}
|
|
176
172
|
|
|
177
173
|
const baseQuery = sourceModel.queryForModel(targetModelClass)
|
|
178
174
|
baseQuery._forceQualifyBaseTable = true
|
|
179
|
-
baseQuery.where(
|
|
175
|
+
baseQuery.where(mandatoryWhereConditions)
|
|
176
|
+
|
|
177
|
+
if (entry.where) {
|
|
178
|
+
baseQuery.where(entry.where)
|
|
179
|
+
}
|
|
180
180
|
|
|
181
181
|
const countQuery = relationship.applyScope(baseQuery)
|
|
182
182
|
|