velocious 1.0.643 → 1.0.645
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 +21 -10
- package/build/authorization/ability.js +36 -13
- package/build/background-jobs/adapter.js +9 -0
- package/build/background-jobs/main.js +35 -10
- package/build/background-jobs/store.js +217 -38
- package/build/background-jobs/types.js +15 -0
- package/build/configuration-types.js +1 -1
- package/build/configuration.js +45 -9
- package/build/database/drivers/base-column.js +8 -0
- package/build/database/drivers/mssql/sql/update.js +1 -3
- package/build/database/drivers/mysql/sql/update.js +1 -3
- package/build/database/drivers/pgsql/column.js +22 -0
- package/build/database/drivers/pgsql/sql/update.js +1 -3
- package/build/database/drivers/sqlite/sql/alter-table.js +11 -1
- package/build/database/drivers/sqlite/sql/update.js +1 -3
- package/build/database/query/index.js +2 -2
- package/build/database/query/model-class-query.js +26 -10
- package/build/database/query/preloader/has-many.js +4 -1
- package/build/database/query/query-data.js +2 -1
- package/build/database/query/update-base.js +14 -0
- package/build/database/query/with-count.js +3 -1
- package/build/database/record/acts-as-list.js +37 -13
- package/build/database/record/attachments/store.js +336 -26
- package/build/database/record/auditing.js +6 -3
- package/build/database/record/index.js +197 -42
- package/build/database/record/instance-relationships/belongs-to.js +2 -1
- package/build/database/record/validators/uniqueness.js +8 -5
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
- package/build/frontend-model-controller.js +136 -53
- package/build/frontend-model-resource/base-resource.js +87 -37
- package/build/frontend-model-resource/velocious-attachment-resource.js +98 -31
- package/build/frontend-models/base.js +307 -59
- package/build/frontend-models/preloader.js +6 -3
- package/build/frontend-models/query.js +15 -6
- package/build/frontend-models/resource-definition.js +21 -2
- package/build/frontend-models/use-destroyed-event.js +1 -1
- package/build/frontend-models/use-model-class-event.js +2 -2
- package/build/frontend-models/use-updated-event.js +1 -1
- package/build/frontend-models/websocket-channel.js +256 -93
- package/build/frontend-models/websocket-publishers.js +218 -18
- package/build/http-server/client/websocket-session.js +13 -1
- package/build/http-server/websocket-channel.js +15 -2
- package/build/src/authorization/ability.d.ts +9 -3
- package/build/src/authorization/ability.d.ts.map +1 -1
- package/build/src/authorization/ability.js +33 -14
- package/build/src/background-jobs/adapter.d.ts +6 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +9 -1
- package/build/src/background-jobs/main.d.ts +6 -0
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +34 -11
- package/build/src/background-jobs/store.d.ts +66 -12
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +191 -35
- package/build/src/background-jobs/types.d.ts +55 -0
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +16 -1
- package/build/src/configuration-types.d.ts +3 -3
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -2
- package/build/src/configuration.d.ts +18 -8
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +38 -7
- package/build/src/database/drivers/base-column.d.ts +5 -0
- package/build/src/database/drivers/base-column.d.ts.map +1 -1
- package/build/src/database/drivers/base-column.js +8 -1
- package/build/src/database/drivers/mssql/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/sql/update.js +2 -4
- package/build/src/database/drivers/mysql/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/sql/update.js +2 -4
- package/build/src/database/drivers/pgsql/column.d.ts +5 -0
- package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/column.js +19 -1
- package/build/src/database/drivers/pgsql/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/sql/update.js +2 -4
- package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/sql/alter-table.js +6 -2
- package/build/src/database/drivers/sqlite/sql/update.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/sql/update.js +2 -4
- package/build/src/database/query/index.d.ts +4 -4
- package/build/src/database/query/index.d.ts.map +1 -1
- package/build/src/database/query/index.js +3 -3
- package/build/src/database/query/model-class-query.d.ts +8 -2
- package/build/src/database/query/model-class-query.d.ts.map +1 -1
- package/build/src/database/query/model-class-query.js +25 -11
- package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-many.js +4 -2
- package/build/src/database/query/query-data.d.ts.map +1 -1
- package/build/src/database/query/query-data.js +3 -2
- package/build/src/database/query/update-base.d.ts +7 -0
- package/build/src/database/query/update-base.d.ts.map +1 -1
- package/build/src/database/query/update-base.js +13 -1
- package/build/src/database/query/with-count.d.ts.map +1 -1
- package/build/src/database/query/with-count.js +3 -2
- package/build/src/database/record/acts-as-list.d.ts.map +1 -1
- package/build/src/database/record/acts-as-list.js +35 -14
- package/build/src/database/record/attachments/store.d.ts +67 -0
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +301 -25
- package/build/src/database/record/auditing.d.ts.map +1 -1
- package/build/src/database/record/auditing.js +7 -4
- package/build/src/database/record/index.d.ts +78 -26
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +171 -42
- package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/belongs-to.js +3 -2
- package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
- package/build/src/database/record/validators/uniqueness.js +8 -6
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +4 -4
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -12
- package/build/src/frontend-model-controller.d.ts +29 -8
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +122 -51
- package/build/src/frontend-model-resource/base-resource.d.ts +51 -24
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +83 -37
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +45 -26
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/velocious-attachment-resource.js +89 -31
- package/build/src/frontend-models/base.d.ts +53 -20
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +275 -59
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +6 -4
- package/build/src/frontend-models/query.d.ts +2 -2
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +14 -7
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +20 -3
- package/build/src/frontend-models/use-destroyed-event.d.ts +1 -1
- package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-destroyed-event.js +2 -2
- package/build/src/frontend-models/use-model-class-event.d.ts +2 -2
- package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-model-class-event.js +3 -3
- package/build/src/frontend-models/use-updated-event.d.ts +1 -1
- package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
- package/build/src/frontend-models/use-updated-event.js +2 -2
- package/build/src/frontend-models/websocket-channel.d.ts +60 -41
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +225 -85
- package/build/src/frontend-models/websocket-publishers.d.ts +9 -0
- package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-publishers.js +190 -19
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +11 -2
- package/build/src/http-server/websocket-channel.d.ts +20 -8
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +15 -3
- package/build/src/sync/assets/cache.d.ts +288 -0
- package/build/src/sync/assets/cache.d.ts.map +1 -0
- package/build/src/sync/assets/cache.js +883 -0
- package/build/src/sync/assets/types.d.ts +199 -0
- package/build/src/sync/assets/types.d.ts.map +1 -0
- package/build/src/sync/assets/types.js +56 -0
- package/build/src/sync/sync-api-client.d.ts.map +1 -1
- package/build/src/sync/sync-api-client.js +4 -3
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +3 -2
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +6 -5
- package/build/src/sync/sync-publisher.d.ts.map +1 -1
- package/build/src/sync/sync-publisher.js +4 -3
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts +2 -2
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts.map +1 -1
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +3 -3
- package/build/src/testing/test-profiler.d.ts +4 -4
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +3 -3
- package/build/src/testing/test-runner.d.ts +279 -117
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +503 -933
- package/build/src/testing/test.d.ts +11 -72
- package/build/src/testing/test.d.ts.map +1 -1
- package/build/src/testing/test.js +131 -284
- package/build/src/testing/velocious-attempt-executor.d.ts +54 -0
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -0
- package/build/src/testing/velocious-attempt-executor.js +408 -0
- package/build/src/testing/velocious-runner-reporter.d.ts +67 -0
- package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -0
- package/build/src/testing/velocious-runner-reporter.js +240 -0
- package/build/src/testing/velocious-suite-hook-executor.d.ts +19 -0
- package/build/src/testing/velocious-suite-hook-executor.d.ts.map +1 -0
- package/build/src/testing/velocious-suite-hook-executor.js +41 -0
- package/build/src/testing/velocious-test-arguments.d.ts +39 -0
- package/build/src/testing/velocious-test-arguments.d.ts.map +1 -0
- package/build/src/testing/velocious-test-arguments.js +55 -0
- package/build/src/utils/model-primary-key.d.ts +73 -0
- package/build/src/utils/model-primary-key.d.ts.map +1 -0
- package/build/src/utils/model-primary-key.js +144 -0
- package/build/src/utils/sha256-bytes-hex.d.ts +7 -0
- package/build/src/utils/sha256-bytes-hex.d.ts.map +1 -0
- package/build/src/utils/sha256-bytes-hex.js +117 -0
- package/build/src/utils/sha256-hex.d.ts +2 -4
- package/build/src/utils/sha256-hex.d.ts.map +1 -1
- package/build/src/utils/sha256-hex.js +6 -120
- package/build/sync/assets/cache.js +998 -0
- package/build/sync/assets/types.js +62 -0
- package/build/sync/sync-api-client.js +3 -2
- package/build/sync/sync-client.js +2 -1
- package/build/sync/sync-envelope-replay-service.js +5 -4
- package/build/sync/sync-publisher.js +3 -2
- package/build/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
- package/build/testing/test-profiler.js +2 -2
- package/build/testing/test-runner.js +496 -990
- package/build/testing/test.js +164 -306
- package/build/testing/velocious-attempt-executor.js +441 -0
- package/build/testing/velocious-runner-reporter.js +264 -0
- package/build/testing/velocious-suite-hook-executor.js +43 -0
- package/build/testing/velocious-test-arguments.js +64 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/model-primary-key.js +171 -0
- package/build/utils/sha256-bytes-hex.js +132 -0
- package/build/utils/sha256-hex.js +7 -135
- package/package.json +5 -4
- package/src/authorization/ability.js +36 -13
- package/src/background-jobs/adapter.js +9 -0
- package/src/background-jobs/main.js +35 -10
- package/src/background-jobs/store.js +217 -38
- package/src/background-jobs/types.js +15 -0
- package/src/configuration-types.js +1 -1
- package/src/configuration.js +45 -9
- package/src/database/drivers/base-column.js +8 -0
- package/src/database/drivers/mssql/sql/update.js +1 -3
- package/src/database/drivers/mysql/sql/update.js +1 -3
- package/src/database/drivers/pgsql/column.js +22 -0
- package/src/database/drivers/pgsql/sql/update.js +1 -3
- package/src/database/drivers/sqlite/sql/alter-table.js +11 -1
- package/src/database/drivers/sqlite/sql/update.js +1 -3
- package/src/database/query/index.js +2 -2
- package/src/database/query/model-class-query.js +26 -10
- package/src/database/query/preloader/has-many.js +4 -1
- package/src/database/query/query-data.js +2 -1
- package/src/database/query/update-base.js +14 -0
- package/src/database/query/with-count.js +3 -1
- package/src/database/record/acts-as-list.js +37 -13
- package/src/database/record/attachments/store.js +336 -26
- package/src/database/record/auditing.js +6 -3
- package/src/database/record/index.js +197 -42
- package/src/database/record/instance-relationships/belongs-to.js +2 -1
- package/src/database/record/validators/uniqueness.js +8 -5
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
- package/src/frontend-model-controller.js +136 -53
- package/src/frontend-model-resource/base-resource.js +87 -37
- package/src/frontend-model-resource/velocious-attachment-resource.js +98 -31
- package/src/frontend-models/base.js +307 -59
- package/src/frontend-models/preloader.js +6 -3
- package/src/frontend-models/query.js +15 -6
- package/src/frontend-models/resource-definition.js +21 -2
- package/src/frontend-models/use-destroyed-event.js +1 -1
- package/src/frontend-models/use-model-class-event.js +2 -2
- package/src/frontend-models/use-updated-event.js +1 -1
- package/src/frontend-models/websocket-channel.js +256 -93
- package/src/frontend-models/websocket-publishers.js +218 -18
- package/src/http-server/client/websocket-session.js +13 -1
- package/src/http-server/websocket-channel.js +15 -2
- package/src/sync/assets/cache.js +998 -0
- package/src/sync/assets/types.js +62 -0
- package/src/sync/sync-api-client.js +3 -2
- package/src/sync/sync-client.js +2 -1
- package/src/sync/sync-envelope-replay-service.js +5 -4
- package/src/sync/sync-publisher.js +3 -2
- package/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
- package/src/testing/test-profiler.js +2 -2
- package/src/testing/test-runner.js +496 -990
- package/src/testing/test.js +164 -306
- package/src/testing/velocious-attempt-executor.js +441 -0
- package/src/testing/velocious-runner-reporter.js +264 -0
- package/src/testing/velocious-suite-hook-executor.js +43 -0
- package/src/testing/velocious-test-arguments.js +64 -0
- package/src/utils/model-primary-key.js +171 -0
- package/src/utils/sha256-bytes-hex.js +132 -0
- package/src/utils/sha256-hex.js +7 -135
- package/build/src/testing/testing-package-adapter.d.ts +0 -9
- package/build/src/testing/testing-package-adapter.d.ts.map +0 -1
- package/build/src/testing/testing-package-adapter.js +0 -69
- package/build/testing/testing-package-adapter.js +0 -76
- package/src/testing/testing-package-adapter.js +0 -76
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import isPlainObject from "./plain-object.js"
|
|
4
|
+
|
|
5
|
+
/** @typedef {string | number} ModelPrimaryKeyScalar */
|
|
6
|
+
/** @typedef {string | string[]} ModelPrimaryKeyDefinition */
|
|
7
|
+
/** @typedef {Record<string, ModelPrimaryKeyScalar>} CompositeModelPrimaryKeyValue */
|
|
8
|
+
/** @typedef {ModelPrimaryKeyScalar | CompositeModelPrimaryKeyValue} ModelPrimaryKeyValue */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validates an untrusted composite identity and returns its definition-ordered values.
|
|
12
|
+
* @param {string[]} primaryKey - Composite primary-key definition.
|
|
13
|
+
* @param {ReturnType<typeof JSON.parse>} value - Candidate identity.
|
|
14
|
+
* @returns {CompositeModelPrimaryKeyValue} - Validated identity.
|
|
15
|
+
*/
|
|
16
|
+
export function validatedCompositePrimaryKeyValue(primaryKey, value) {
|
|
17
|
+
if (!isPlainObject(value)) {
|
|
18
|
+
throw new TypeError("Expected composite primary key identity to be a plain object.")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const valueKeys = Object.keys(value)
|
|
22
|
+
|
|
23
|
+
if (valueKeys.length !== primaryKey.length || primaryKey.some((key) => !Object.prototype.hasOwnProperty.call(value, key))) {
|
|
24
|
+
throw new TypeError(`Expected composite primary key identity fields: ${primaryKey.join(", ")}.`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @type {CompositeModelPrimaryKeyValue} */
|
|
28
|
+
const validatedValue = {}
|
|
29
|
+
|
|
30
|
+
for (const key of primaryKey) {
|
|
31
|
+
const keyValue = value[key]
|
|
32
|
+
|
|
33
|
+
if (typeof keyValue !== "string" && typeof keyValue !== "number") {
|
|
34
|
+
throw new TypeError(`Expected composite primary key identity field '${key}' to be a string or number.`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
validatedValue[key] = keyValue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return validatedValue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns a scalar primary-key definition or fails at a feature boundary that cannot represent composite identities.
|
|
45
|
+
* @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
|
|
46
|
+
* @param {string} operation - Operation requiring a scalar identity.
|
|
47
|
+
* @returns {string} - Scalar primary-key column or attribute name.
|
|
48
|
+
*/
|
|
49
|
+
export function scalarModelPrimaryKey(primaryKey, operation) {
|
|
50
|
+
if (Array.isArray(primaryKey)) {
|
|
51
|
+
throw new Error(`${operation} does not support composite primary keys.`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return primaryKey
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns a scalar identity or fails at a feature boundary that cannot represent composite identities.
|
|
59
|
+
* @param {ModelPrimaryKeyValue} value - Model identity.
|
|
60
|
+
* @param {string} operation - Operation requiring a scalar identity.
|
|
61
|
+
* @returns {ModelPrimaryKeyScalar} - Scalar identity.
|
|
62
|
+
*/
|
|
63
|
+
export function scalarModelPrimaryKeyValue(value, operation) {
|
|
64
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
65
|
+
throw new Error(`${operation} does not support composite primary keys.`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Builds query conditions from a scalar or composite model identity.
|
|
73
|
+
* @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
|
|
74
|
+
* @param {ReturnType<typeof JSON.parse>} value - Candidate identity.
|
|
75
|
+
* @returns {Record<string, ModelPrimaryKeyScalar>} - Primary-key query conditions.
|
|
76
|
+
*/
|
|
77
|
+
export function modelPrimaryKeyConditions(primaryKey, value) {
|
|
78
|
+
if (Array.isArray(primaryKey)) return validatedCompositePrimaryKeyValue(primaryKey, value)
|
|
79
|
+
|
|
80
|
+
return {[primaryKey]: /** @type {ModelPrimaryKeyScalar} */ (value)}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds a qualified SQL predicate matching any supplied composite identity.
|
|
85
|
+
* @param {object} args - Arguments.
|
|
86
|
+
* @param {typeof import("../database/record/index.js").default} args.modelClass - Model class owning the identity attributes.
|
|
87
|
+
* @param {string[]} args.primaryKey - Composite primary-key definition.
|
|
88
|
+
* @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Query receiving the predicate.
|
|
89
|
+
* @param {ModelPrimaryKeyValue[]} args.values - Candidate identities.
|
|
90
|
+
* @returns {string} - Qualified SQL predicate.
|
|
91
|
+
*/
|
|
92
|
+
export function compositeModelPrimaryKeyCohortSql({modelClass, primaryKey, query, values}) {
|
|
93
|
+
if (values.length === 0) return "(1 = 0)"
|
|
94
|
+
|
|
95
|
+
const tableSql = query.driver.quoteTable(query.getTableReferenceForJoin())
|
|
96
|
+
|
|
97
|
+
return `(${values.map((value) => {
|
|
98
|
+
const conditions = modelPrimaryKeyConditions(primaryKey, value)
|
|
99
|
+
const identitySql = primaryKey.map((attributeName) => {
|
|
100
|
+
const columnName = modelClass.getColumnNameForAttributeName(attributeName)
|
|
101
|
+
|
|
102
|
+
return `${tableSql}.${query.driver.quoteColumn(columnName)} = ${query.driver.quote(conditions[attributeName])}`
|
|
103
|
+
}).join(" AND ")
|
|
104
|
+
|
|
105
|
+
return `(${identitySql})`
|
|
106
|
+
}).join(" OR ")})`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Reads a scalar or composite identity through the owning model's value reader.
|
|
111
|
+
* @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
|
|
112
|
+
* @param {(key: string) => ReturnType<typeof JSON.parse>} read - Primary-key value reader.
|
|
113
|
+
* @returns {ModelPrimaryKeyValue} - Model identity.
|
|
114
|
+
*/
|
|
115
|
+
export function readModelPrimaryKeyValue(primaryKey, read) {
|
|
116
|
+
if (!Array.isArray(primaryKey)) return /** @type {ModelPrimaryKeyScalar} */ (read(primaryKey))
|
|
117
|
+
|
|
118
|
+
/** @type {CompositeModelPrimaryKeyValue} */
|
|
119
|
+
const value = {}
|
|
120
|
+
|
|
121
|
+
for (const key of primaryKey) value[key] = /** @type {ModelPrimaryKeyScalar} */ (read(key))
|
|
122
|
+
|
|
123
|
+
return validatedCompositePrimaryKeyValue(primaryKey, value)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Returns an unambiguous map key while preserving legacy scalar map keys.
|
|
128
|
+
* @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
|
|
129
|
+
* @param {ReturnType<typeof JSON.parse>} value - Candidate identity.
|
|
130
|
+
* @returns {string} - Canonical identity key.
|
|
131
|
+
*/
|
|
132
|
+
export function modelPrimaryKeyCacheKey(primaryKey, value) {
|
|
133
|
+
if (!Array.isArray(primaryKey)) return String(value)
|
|
134
|
+
|
|
135
|
+
const validatedValue = validatedCompositePrimaryKeyValue(primaryKey, value)
|
|
136
|
+
|
|
137
|
+
return JSON.stringify(primaryKey.map((key) => validatedValue[key]))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Restores a model identity from its canonical string representation.
|
|
142
|
+
* @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
|
|
143
|
+
* @param {string} cacheKey - Canonical identity key.
|
|
144
|
+
* @returns {ModelPrimaryKeyValue} - Restored identity.
|
|
145
|
+
*/
|
|
146
|
+
export function modelPrimaryKeyValueFromCacheKey(primaryKey, cacheKey) {
|
|
147
|
+
if (!Array.isArray(primaryKey)) return cacheKey
|
|
148
|
+
|
|
149
|
+
let parsed
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
parsed = JSON.parse(cacheKey)
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (!(error instanceof SyntaxError)) throw error
|
|
155
|
+
|
|
156
|
+
throw new TypeError("Expected composite primary key cache value to be valid JSON.", {cause: error})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!Array.isArray(parsed) || parsed.length !== primaryKey.length) {
|
|
160
|
+
throw new TypeError(`Expected composite primary key cache value with ${primaryKey.length} fields.`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** @type {CompositeModelPrimaryKeyValue} */
|
|
164
|
+
const value = {}
|
|
165
|
+
|
|
166
|
+
for (let index = 0; index < primaryKey.length; index += 1) {
|
|
167
|
+
value[primaryKey[index]] = parsed[index]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return validatedCompositePrimaryKeyValue(primaryKey, value)
|
|
171
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Computes a SHA-256 hex digest for arbitrary bytes without Node-only imports.
|
|
5
|
+
* @param {Uint8Array} bytes - Bytes to digest.
|
|
6
|
+
* @returns {string} Hex digest.
|
|
7
|
+
*/
|
|
8
|
+
export default function sha256BytesHex(bytes) {
|
|
9
|
+
const bitLength = bytes.length * 8
|
|
10
|
+
const blockCount = Math.ceil((bytes.length + 9) / 64)
|
|
11
|
+
const block = new Uint8Array(64)
|
|
12
|
+
const hash = SHA256_INITIAL_HASH.slice()
|
|
13
|
+
/** @type {number[]} */
|
|
14
|
+
const words = new Array(64)
|
|
15
|
+
|
|
16
|
+
const highLength = Math.floor(bitLength / 0x100000000)
|
|
17
|
+
const lowLength = bitLength >>> 0
|
|
18
|
+
|
|
19
|
+
for (let blockIndex = 0; blockIndex < blockCount; blockIndex += 1) {
|
|
20
|
+
const offset = blockIndex * 64
|
|
21
|
+
const chunkLength = Math.min(64, Math.max(0, bytes.length - offset))
|
|
22
|
+
|
|
23
|
+
block.fill(0)
|
|
24
|
+
if (chunkLength > 0) block.set(bytes.subarray(offset, offset + chunkLength))
|
|
25
|
+
if (bytes.length >= offset && bytes.length < offset + 64) block[bytes.length - offset] = 0x80
|
|
26
|
+
|
|
27
|
+
if (blockIndex === blockCount - 1) {
|
|
28
|
+
block[56] = (highLength >>> 24) & 0xff
|
|
29
|
+
block[57] = (highLength >>> 16) & 0xff
|
|
30
|
+
block[58] = (highLength >>> 8) & 0xff
|
|
31
|
+
block[59] = highLength & 0xff
|
|
32
|
+
block[60] = (lowLength >>> 24) & 0xff
|
|
33
|
+
block[61] = (lowLength >>> 16) & 0xff
|
|
34
|
+
block[62] = (lowLength >>> 8) & 0xff
|
|
35
|
+
block[63] = lowLength & 0xff
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let index = 0; index < 16; index++) {
|
|
39
|
+
const byteIndex = index * 4
|
|
40
|
+
|
|
41
|
+
words[index] = ((block[byteIndex] << 24) | (block[byteIndex + 1] << 16) | (block[byteIndex + 2] << 8) | block[byteIndex + 3]) >>> 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (let index = 16; index < 64; index++) {
|
|
45
|
+
const s0 = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ (words[index - 15] >>> 3)
|
|
46
|
+
const s1 = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ (words[index - 2] >>> 10)
|
|
47
|
+
|
|
48
|
+
words[index] = add32(words[index - 16], s0, words[index - 7], s1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let a = hash[0]
|
|
52
|
+
let b = hash[1]
|
|
53
|
+
let c = hash[2]
|
|
54
|
+
let d = hash[3]
|
|
55
|
+
let e = hash[4]
|
|
56
|
+
let f = hash[5]
|
|
57
|
+
let g = hash[6]
|
|
58
|
+
let h = hash[7]
|
|
59
|
+
|
|
60
|
+
for (let index = 0; index < 64; index++) {
|
|
61
|
+
const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25)
|
|
62
|
+
const ch = (e & f) ^ ((~e) & g)
|
|
63
|
+
const temp1 = add32(h, s1, ch, SHA256_K[index], words[index])
|
|
64
|
+
const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)
|
|
65
|
+
const maj = (a & b) ^ (a & c) ^ (b & c)
|
|
66
|
+
const temp2 = add32(s0, maj)
|
|
67
|
+
|
|
68
|
+
h = g
|
|
69
|
+
g = f
|
|
70
|
+
f = e
|
|
71
|
+
e = add32(d, temp1)
|
|
72
|
+
d = c
|
|
73
|
+
c = b
|
|
74
|
+
b = a
|
|
75
|
+
a = add32(temp1, temp2)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
hash[0] = add32(hash[0], a)
|
|
79
|
+
hash[1] = add32(hash[1], b)
|
|
80
|
+
hash[2] = add32(hash[2], c)
|
|
81
|
+
hash[3] = add32(hash[3], d)
|
|
82
|
+
hash[4] = add32(hash[4], e)
|
|
83
|
+
hash[5] = add32(hash[5], f)
|
|
84
|
+
hash[6] = add32(hash[6], g)
|
|
85
|
+
hash[7] = add32(hash[7], h)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return hash.map((value) => value.toString(16).padStart(8, "0")).join("")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const SHA256_INITIAL_HASH = [
|
|
92
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
93
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const SHA256_K = [
|
|
97
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
|
98
|
+
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
99
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
100
|
+
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
101
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
|
102
|
+
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
103
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
|
104
|
+
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
105
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
106
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
107
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
|
108
|
+
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
109
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
|
110
|
+
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
111
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
112
|
+
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Rotates an unsigned value right.
|
|
117
|
+
* @param {number} value Unsigned value.
|
|
118
|
+
* @param {number} bits Rotation count.
|
|
119
|
+
* @returns {number} Rotated value.
|
|
120
|
+
*/
|
|
121
|
+
function rotateRight(value, bits) {
|
|
122
|
+
return (value >>> bits) | (value << (32 - bits))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Adds numbers with unsigned 32-bit overflow.
|
|
127
|
+
* @param {...number} values Values to add.
|
|
128
|
+
* @returns {number} Unsigned sum.
|
|
129
|
+
*/
|
|
130
|
+
function add32(...values) {
|
|
131
|
+
return values.reduce((sum, value) => (sum + value) >>> 0, 0)
|
|
132
|
+
}
|
|
@@ -1,141 +1,13 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import sha256BytesHex from "./sha256-bytes-hex.js"
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Computes SHA-256 without importing Node-only crypto modules, keeping this
|
|
3
|
-
* module safe for Expo/browser bundles.
|
|
4
|
-
* pure-JavaScript SHA-256 derived from FIPS 180-4, using only 32-bit unsigned
|
|
5
|
-
* integer arithmetic.
|
|
7
|
+
* module safe for Expo/browser bundles.
|
|
6
8
|
* @param {string} message - UTF-8 message.
|
|
7
|
-
* @returns {string}
|
|
9
|
+
* @returns {string} Hex digest.
|
|
8
10
|
*/
|
|
9
11
|
export default function sha256Hex(message) {
|
|
10
|
-
|
|
11
|
-
const padded = [...bytes]
|
|
12
|
-
const bitLength = bytes.length * 8
|
|
13
|
-
const hash = [...SHA256_INITIAL_HASH]
|
|
14
|
-
/** @type {number[]} */
|
|
15
|
-
const words = new Array(64)
|
|
16
|
-
|
|
17
|
-
padded.push(0x80)
|
|
18
|
-
while (padded.length % 64 !== 56) padded.push(0)
|
|
19
|
-
|
|
20
|
-
const highLength = Math.floor(bitLength / 0x100000000)
|
|
21
|
-
const lowLength = bitLength >>> 0
|
|
22
|
-
|
|
23
|
-
for (const value of [highLength, lowLength]) {
|
|
24
|
-
padded.push((value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
28
|
-
for (let i = 0; i < 16; i++) {
|
|
29
|
-
const index = offset + (i * 4)
|
|
30
|
-
|
|
31
|
-
words[i] = (((padded[index] || 0) << 24) | ((padded[index + 1] || 0) << 16) | ((padded[index + 2] || 0) << 8) | (padded[index + 3] || 0)) >>> 0
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
for (let i = 16; i < 64; i++) {
|
|
35
|
-
const s0 = rotateRight(words[i - 15], 7) ^ rotateRight(words[i - 15], 18) ^ (words[i - 15] >>> 3)
|
|
36
|
-
const s1 = rotateRight(words[i - 2], 17) ^ rotateRight(words[i - 2], 19) ^ (words[i - 2] >>> 10)
|
|
37
|
-
|
|
38
|
-
words[i] = add32(words[i - 16], s0, words[i - 7], s1)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let [a, b, c, d, e, f, g, h] = hash
|
|
42
|
-
|
|
43
|
-
for (let i = 0; i < 64; i++) {
|
|
44
|
-
const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25)
|
|
45
|
-
const ch = (e & f) ^ ((~e) & g)
|
|
46
|
-
const temp1 = add32(h, s1, ch, SHA256_K[i], words[i])
|
|
47
|
-
const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)
|
|
48
|
-
const maj = (a & b) ^ (a & c) ^ (b & c)
|
|
49
|
-
const temp2 = add32(s0, maj)
|
|
50
|
-
|
|
51
|
-
h = g
|
|
52
|
-
g = f
|
|
53
|
-
f = e
|
|
54
|
-
e = add32(d, temp1)
|
|
55
|
-
d = c
|
|
56
|
-
c = b
|
|
57
|
-
b = a
|
|
58
|
-
a = add32(temp1, temp2)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
hash[0] = add32(hash[0], a)
|
|
62
|
-
hash[1] = add32(hash[1], b)
|
|
63
|
-
hash[2] = add32(hash[2], c)
|
|
64
|
-
hash[3] = add32(hash[3], d)
|
|
65
|
-
hash[4] = add32(hash[4], e)
|
|
66
|
-
hash[5] = add32(hash[5], f)
|
|
67
|
-
hash[6] = add32(hash[6], g)
|
|
68
|
-
hash[7] = add32(hash[7], h)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return hash.map((value) => value.toString(16).padStart(8, "0")).join("")
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const SHA256_INITIAL_HASH = [
|
|
75
|
-
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
76
|
-
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
|
77
|
-
]
|
|
78
|
-
|
|
79
|
-
const SHA256_K = [
|
|
80
|
-
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
|
81
|
-
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
82
|
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
83
|
-
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
84
|
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
|
85
|
-
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
86
|
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
|
87
|
-
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
88
|
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
89
|
-
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
90
|
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
|
91
|
-
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
92
|
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
|
93
|
-
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
94
|
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
95
|
-
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Converts a string to UTF-8 bytes.
|
|
100
|
-
* @param {string} value - String value.
|
|
101
|
-
* @returns {number[]} - UTF-8 bytes.
|
|
102
|
-
*/
|
|
103
|
-
function utf8Bytes(value) {
|
|
104
|
-
/** @type {number[]} */
|
|
105
|
-
const bytes = []
|
|
106
|
-
|
|
107
|
-
for (const character of value) {
|
|
108
|
-
const codePoint = /** @type {number} */ (character.codePointAt(0))
|
|
109
|
-
|
|
110
|
-
if (codePoint <= 0x7f) {
|
|
111
|
-
bytes.push(codePoint)
|
|
112
|
-
} else if (codePoint <= 0x7ff) {
|
|
113
|
-
bytes.push(0xc0 | (codePoint >>> 6), 0x80 | (codePoint & 0x3f))
|
|
114
|
-
} else if (codePoint <= 0xffff) {
|
|
115
|
-
bytes.push(0xe0 | (codePoint >>> 12), 0x80 | ((codePoint >>> 6) & 0x3f), 0x80 | (codePoint & 0x3f))
|
|
116
|
-
} else {
|
|
117
|
-
bytes.push(0xf0 | (codePoint >>> 18), 0x80 | ((codePoint >>> 12) & 0x3f), 0x80 | ((codePoint >>> 6) & 0x3f), 0x80 | (codePoint & 0x3f))
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return bytes
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Adds unsigned 32-bit integers.
|
|
126
|
-
* @param {...number} values - Values to add.
|
|
127
|
-
* @returns {number} - Unsigned 32-bit result.
|
|
128
|
-
*/
|
|
129
|
-
function add32(...values) {
|
|
130
|
-
return values.reduce((sum, value) => (sum + value) >>> 0, 0)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Rotates a 32-bit integer right.
|
|
135
|
-
* @param {number} value - Value to rotate.
|
|
136
|
-
* @param {number} bits - Bit count.
|
|
137
|
-
* @returns {number} - Rotated value.
|
|
138
|
-
*/
|
|
139
|
-
function rotateRight(value, bits) {
|
|
140
|
-
return (value >>> bits) | (value << (32 - bits))
|
|
12
|
+
return sha256BytesHex(new TextEncoder().encode(message))
|
|
141
13
|
}
|
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.
|
|
6
|
+
"version": "1.0.645",
|
|
7
7
|
"main": "build/index.js",
|
|
8
8
|
"types": "build/index.d.ts",
|
|
9
9
|
"files": [
|
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
"description": "",
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@js-temporal/polyfill": "^0.5.1",
|
|
67
|
-
"@velocious/testing": "0.0.1",
|
|
68
67
|
"awaitery": "^1.0.19",
|
|
69
68
|
"bcryptjs": "^3.0.2",
|
|
70
69
|
"better-localstorage": "^1.0.7",
|
|
@@ -92,10 +91,11 @@
|
|
|
92
91
|
"ws": "^8.21.3"
|
|
93
92
|
},
|
|
94
93
|
"peerDependencies": {
|
|
94
|
+
"@velocious/testing": "0.0.9",
|
|
95
95
|
"react": ">=18",
|
|
96
|
+
"smtp-connection": "^4.0.2",
|
|
96
97
|
"sqlite": "^5.1.1",
|
|
97
|
-
"sqlite3": "^6.0.1"
|
|
98
|
-
"smtp-connection": "^4.0.2"
|
|
98
|
+
"sqlite3": "^6.0.1"
|
|
99
99
|
},
|
|
100
100
|
"peerDependenciesMeta": {
|
|
101
101
|
"react": {
|
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
"@types/react-dom": "^19.2.3",
|
|
122
122
|
"@types/strftime": "^0.9.8",
|
|
123
123
|
"@types/ws": "^8.18.1",
|
|
124
|
+
"@velocious/testing": "0.0.9",
|
|
124
125
|
"chokidar-cli": "^3.0.0",
|
|
125
126
|
"cpy-cli": "^7.0.0",
|
|
126
127
|
"esbuild": "^0.28.0",
|
|
@@ -189,9 +189,10 @@ export default class VelociousAuthorizationAbility {
|
|
|
189
189
|
* @param {string} args.action - Requested action.
|
|
190
190
|
* @param {typeof import("../database/record/index.js").default} args.modelClass - Model class.
|
|
191
191
|
* @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Query.
|
|
192
|
+
* @param {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [args.ruleQueryFactory] - Optional factory for the queries that evaluate individual conditional rules.
|
|
192
193
|
* @returns {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} - Authorized query.
|
|
193
194
|
*/
|
|
194
|
-
applyToQuery({action, modelClass, query}) {
|
|
195
|
+
applyToQuery({action, modelClass, query, ruleQueryFactory}) {
|
|
195
196
|
this.loadAbilitiesForModelClass(modelClass)
|
|
196
197
|
|
|
197
198
|
const applicableRules = this.rulesFor({action, modelClass})
|
|
@@ -203,18 +204,18 @@ export default class VelociousAuthorizationAbility {
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
if (allowRules.some((rule) => !rule.conditions)) {
|
|
206
|
-
this.applyDenyRules({action, denyRules, modelClass, query})
|
|
207
|
+
this.applyDenyRules({action, denyRules, modelClass, query, ruleQueryFactory})
|
|
207
208
|
return query
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
const allowSqlParts = this.conditionSqlParts({action, modelClass, query, rules: allowRules})
|
|
211
|
+
const allowSqlParts = this.conditionSqlParts({action, modelClass, query, ruleQueryFactory, rules: allowRules})
|
|
211
212
|
|
|
212
213
|
if (allowSqlParts.length === 0) {
|
|
213
214
|
return query.where("1=0")
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
query.where(`(${allowSqlParts.join(" OR ")})`)
|
|
217
|
-
this.applyDenyRules({action, denyRules, modelClass, query})
|
|
218
|
+
this.applyDenyRules({action, denyRules, modelClass, query, ruleQueryFactory})
|
|
218
219
|
|
|
219
220
|
return query
|
|
220
221
|
}
|
|
@@ -240,19 +241,20 @@ export default class VelociousAuthorizationAbility {
|
|
|
240
241
|
* @param {string} args.action - Action.
|
|
241
242
|
* @param {typeof import("../database/record/index.js").default} args.modelClass - Model class.
|
|
242
243
|
* @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Base query.
|
|
244
|
+
* @param {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [args.ruleQueryFactory] - Optional conditional-rule query factory.
|
|
243
245
|
* @param {AbilityRuleType[]} args.rules - Rules.
|
|
244
246
|
* @returns {string[]} - SQL condition parts.
|
|
245
247
|
*/
|
|
246
|
-
conditionSqlParts({action, modelClass, query, rules}) {
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const
|
|
248
|
+
conditionSqlParts({action, modelClass, query, ruleQueryFactory, rules}) {
|
|
249
|
+
const primaryKey = modelClass.primaryKey()
|
|
250
|
+
const primaryKeyAttributes = Array.isArray(primaryKey) ? primaryKey : [primaryKey]
|
|
251
|
+
const quotedBaseTable = query.driver.quoteTable(query.getTableReferenceForJoin())
|
|
250
252
|
const sqlParts = []
|
|
251
253
|
|
|
252
254
|
for (const rule of rules) {
|
|
253
255
|
if (!rule.conditions) continue
|
|
254
256
|
|
|
255
|
-
const scopedQuery = modelClass._newQuery()
|
|
257
|
+
const scopedQuery = ruleQueryFactory ? ruleQueryFactory() : modelClass._newQuery()
|
|
256
258
|
const resultQuery = this.applyRuleCondition({
|
|
257
259
|
action,
|
|
258
260
|
conditions: rule.conditions,
|
|
@@ -260,7 +262,16 @@ export default class VelociousAuthorizationAbility {
|
|
|
260
262
|
query: scopedQuery
|
|
261
263
|
})
|
|
262
264
|
const finalQuery = resultQuery || scopedQuery
|
|
263
|
-
|
|
265
|
+
|
|
266
|
+
if (ruleQueryFactory && finalQuery !== scopedQuery) {
|
|
267
|
+
const finalFroms = finalQuery.getFroms()
|
|
268
|
+
|
|
269
|
+
finalFroms.splice(0, finalFroms.length, ...scopedQuery.getFroms())
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const quotedScopedTable = query.driver.quoteTable(finalQuery.getTableReferenceForJoin())
|
|
273
|
+
const primaryKeyColumns = primaryKeyAttributes.map((attributeName) => modelClass.getColumnNameForAttributeName(attributeName))
|
|
274
|
+
const selectedPkSql = primaryKeyColumns.map((columnName) => `${quotedScopedTable}.${query.driver.quoteColumn(columnName)}`)
|
|
264
275
|
|
|
265
276
|
if (finalQuery._distinct) {
|
|
266
277
|
query.distinct(true)
|
|
@@ -268,7 +279,18 @@ export default class VelociousAuthorizationAbility {
|
|
|
268
279
|
|
|
269
280
|
finalQuery.select(selectedPkSql)
|
|
270
281
|
|
|
271
|
-
|
|
282
|
+
if (Array.isArray(primaryKey)) {
|
|
283
|
+
const authorizedRowsAlias = query.driver.quoteTable("velocious_authorized_rows")
|
|
284
|
+
const identitySql = primaryKeyColumns.map((columnName) => {
|
|
285
|
+
const quotedColumn = query.driver.quoteColumn(columnName)
|
|
286
|
+
|
|
287
|
+
return `${authorizedRowsAlias}.${quotedColumn} = ${quotedBaseTable}.${quotedColumn}`
|
|
288
|
+
}).join(" AND ")
|
|
289
|
+
|
|
290
|
+
sqlParts.push(`EXISTS (SELECT 1 FROM (${finalQuery.toSql()}) AS ${authorizedRowsAlias} WHERE ${identitySql})`)
|
|
291
|
+
} else {
|
|
292
|
+
sqlParts.push(`${quotedBaseTable}.${query.driver.quoteColumn(primaryKeyColumns[0])} IN (${finalQuery.toSql()})`)
|
|
293
|
+
}
|
|
272
294
|
}
|
|
273
295
|
|
|
274
296
|
return sqlParts
|
|
@@ -281,9 +303,10 @@ export default class VelociousAuthorizationAbility {
|
|
|
281
303
|
* @param {AbilityRuleType[]} args.denyRules - Deny rules.
|
|
282
304
|
* @param {typeof import("../database/record/index.js").default} args.modelClass - Model class.
|
|
283
305
|
* @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Query.
|
|
306
|
+
* @param {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [args.ruleQueryFactory] - Optional conditional-rule query factory.
|
|
284
307
|
* @returns {void} - No return value.
|
|
285
308
|
*/
|
|
286
|
-
applyDenyRules({action, denyRules, modelClass, query}) {
|
|
309
|
+
applyDenyRules({action, denyRules, modelClass, query, ruleQueryFactory}) {
|
|
287
310
|
if (denyRules.length === 0) return
|
|
288
311
|
|
|
289
312
|
if (denyRules.some((rule) => !rule.conditions)) {
|
|
@@ -291,7 +314,7 @@ export default class VelociousAuthorizationAbility {
|
|
|
291
314
|
return
|
|
292
315
|
}
|
|
293
316
|
|
|
294
|
-
const denySqlParts = this.conditionSqlParts({action, modelClass, query, rules: denyRules})
|
|
317
|
+
const denySqlParts = this.conditionSqlParts({action, modelClass, query, ruleQueryFactory, rules: denyRules})
|
|
295
318
|
|
|
296
319
|
if (denySqlParts.length > 0) {
|
|
297
320
|
query.where(`NOT (${denySqlParts.join(" OR ")})`)
|
|
@@ -48,6 +48,15 @@ export default class BackgroundJobsAdapter {
|
|
|
48
48
|
*/
|
|
49
49
|
async reconcileQueueConcurrency() { throw new Error("BackgroundJobsAdapter#reconcileQueueConcurrency is not implemented") }
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Repairs drift in adapter-owned durable active concurrency counts. Adapters
|
|
53
|
+
* without duplicate active-count persistence can keep this no-op result.
|
|
54
|
+
* @returns {Promise<import("./types.js").BackgroundJobConcurrencyReconciliation>} - Repair summary.
|
|
55
|
+
*/
|
|
56
|
+
async reconcileActiveConcurrency() {
|
|
57
|
+
return {candidateCount: 0, checkedCount: 0, repairedCount: 0, repairs: [], repairsTruncatedCount: 0}
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
/**
|
|
52
61
|
* Enqueues a job.
|
|
53
62
|
* @param {{jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} _args - Job request.
|