velocious 1.0.567 → 1.0.569
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 +49 -1
- package/build/configuration.js +36 -0
- package/build/database/drivers/base-table.js +4 -3
- package/build/database/drivers/base.js +195 -54
- package/build/database/drivers/index-metadata.js +28 -0
- package/build/database/drivers/mssql/column.js +2 -34
- package/build/database/drivers/mssql/columns-index.js +37 -0
- package/build/database/drivers/mssql/index.js +10 -7
- package/build/database/drivers/mssql/table.js +55 -11
- package/build/database/drivers/mysql/index.js +10 -5
- package/build/database/drivers/mysql/sql/alter-table.js +13 -0
- package/build/database/drivers/pgsql/column.js +2 -27
- package/build/database/drivers/pgsql/columns-index.js +37 -0
- package/build/database/drivers/pgsql/index.js +4 -4
- package/build/database/drivers/pgsql/table.js +61 -10
- package/build/database/drivers/sqlite/base.js +2 -2
- package/build/database/drivers/sqlite/connection-sql-js.js +62 -2
- package/build/database/drivers/sqlite/index.web.js +74 -0
- package/build/database/drivers/sqlite/sql/alter-table.js +18 -0
- package/build/database/migration/index.js +92 -3
- package/build/database/operation-connection.js +110 -0
- package/build/database/operation-lease.js +47 -0
- package/build/database/operation.js +140 -0
- package/build/database/pool/base.js +26 -1
- package/build/database/pool/single-multi-use.js +49 -0
- package/build/database/query/alter-table-base.js +20 -1
- package/build/database/query/model-class-query.js +38 -7
- package/build/database/query/preloader/belongs-to.js +3 -2
- package/build/database/query/preloader/has-many.js +4 -3
- package/build/database/query/preloader/has-one.js +2 -1
- package/build/database/query/preloader/query-for-model.js +17 -0
- package/build/database/query/query-data.js +12 -3
- package/build/database/query/with-count.js +5 -3
- package/build/database/record/acts-as-list.js +11 -8
- package/build/database/record/attachments/normalize-input.js +38 -10
- package/build/database/record/attachments/storage-drivers/filesystem.js +33 -2
- package/build/database/record/attachments/storage-drivers/native.js +17 -2
- package/build/database/record/attachments/storage-drivers/s3.js +27 -7
- package/build/database/record/attachments/store.js +173 -53
- package/build/database/record/auditing.js +12 -9
- package/build/database/record/counter-cache-magnitude.js +5 -3
- package/build/database/record/index.js +95 -14
- package/build/database/record/instance-relationships/belongs-to.js +4 -2
- package/build/database/record/instance-relationships/has-many.js +7 -5
- package/build/database/record/instance-relationships/has-one.js +4 -2
- package/build/database/record/validators/uniqueness.js +3 -2
- package/build/database/table-data/table-foreign-key.js +9 -1
- package/build/environment-handlers/base.js +1 -1
- package/build/environment-handlers/node/attachment-path-source.js +144 -0
- package/build/environment-handlers/node.js +28 -3
- package/build/frontend-models/websocket-publishers.js +2 -2
- package/build/src/configuration.d.ts +12 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +35 -1
- package/build/src/database/drivers/base-table.d.ts.map +1 -1
- package/build/src/database/drivers/base-table.js +4 -3
- package/build/src/database/drivers/base.d.ts +73 -20
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +181 -54
- package/build/src/database/drivers/index-metadata.d.ts +41 -0
- package/build/src/database/drivers/index-metadata.d.ts.map +1 -0
- package/build/src/database/drivers/index-metadata.js +26 -0
- package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/column.js +3 -31
- package/build/src/database/drivers/mssql/columns-index.d.ts +41 -0
- package/build/src/database/drivers/mssql/columns-index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/columns-index.js +35 -1
- package/build/src/database/drivers/mssql/index.d.ts +4 -1
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +11 -8
- package/build/src/database/drivers/mssql/table.d.ts +42 -0
- package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/table.js +49 -12
- package/build/src/database/drivers/mysql/index.d.ts +2 -1
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +10 -6
- package/build/src/database/drivers/mysql/sql/alter-table.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/sql/alter-table.js +12 -1
- package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/column.js +3 -24
- package/build/src/database/drivers/pgsql/columns-index.d.ts +41 -0
- package/build/src/database/drivers/pgsql/columns-index.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/columns-index.js +35 -1
- package/build/src/database/drivers/pgsql/index.d.ts +2 -1
- package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/index.js +5 -5
- package/build/src/database/drivers/pgsql/table.d.ts +42 -0
- package/build/src/database/drivers/pgsql/table.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/table.js +54 -11
- package/build/src/database/drivers/sqlite/base.d.ts +1 -1
- package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/base.js +3 -3
- package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +20 -0
- package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/connection-sql-js.js +56 -3
- package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.web.js +64 -1
- package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/sql/alter-table.js +15 -1
- package/build/src/database/migration/index.d.ts +35 -1
- package/build/src/database/migration/index.d.ts.map +1 -1
- package/build/src/database/migration/index.js +78 -4
- package/build/src/database/operation-connection.d.ts +61 -0
- package/build/src/database/operation-connection.d.ts.map +1 -0
- package/build/src/database/operation-connection.js +93 -0
- package/build/src/database/operation-lease.d.ts +27 -0
- package/build/src/database/operation-lease.d.ts.map +1 -0
- package/build/src/database/operation-lease.js +44 -0
- package/build/src/database/operation.d.ts +77 -0
- package/build/src/database/operation.d.ts.map +1 -0
- package/build/src/database/operation.js +123 -0
- package/build/src/database/pool/base.d.ts +14 -0
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +24 -2
- package/build/src/database/pool/single-multi-use.d.ts +1 -0
- package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
- package/build/src/database/pool/single-multi-use.js +49 -1
- package/build/src/database/query/alter-table-base.d.ts +6 -0
- package/build/src/database/query/alter-table-base.d.ts.map +1 -1
- package/build/src/database/query/alter-table-base.js +18 -2
- package/build/src/database/query/model-class-query.d.ts +14 -0
- package/build/src/database/query/model-class-query.d.ts.map +1 -1
- package/build/src/database/query/model-class-query.js +32 -8
- package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
- package/build/src/database/query/preloader/belongs-to.js +4 -3
- package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-many.js +5 -4
- package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-one.js +3 -2
- package/build/src/database/query/preloader/query-for-model.d.ts +10 -0
- package/build/src/database/query/preloader/query-for-model.d.ts.map +1 -0
- package/build/src/database/query/preloader/query-for-model.js +16 -0
- package/build/src/database/query/query-data.d.ts.map +1 -1
- package/build/src/database/query/query-data.js +13 -4
- package/build/src/database/query/with-count.d.ts.map +1 -1
- package/build/src/database/query/with-count.js +6 -4
- package/build/src/database/record/acts-as-list.js +12 -9
- package/build/src/database/record/attachments/attachment-record.d.ts +9 -0
- package/build/src/database/record/attachments/attachment-record.d.ts.map +1 -1
- package/build/src/database/record/attachments/normalize-input.d.ts +33 -4
- package/build/src/database/record/attachments/normalize-input.d.ts.map +1 -1
- package/build/src/database/record/attachments/normalize-input.js +39 -11
- package/build/src/database/record/attachments/storage-drivers/filesystem.d.ts +2 -5
- package/build/src/database/record/attachments/storage-drivers/filesystem.d.ts.map +1 -1
- package/build/src/database/record/attachments/storage-drivers/filesystem.js +28 -3
- package/build/src/database/record/attachments/storage-drivers/native.d.ts +2 -6
- package/build/src/database/record/attachments/storage-drivers/native.d.ts.map +1 -1
- package/build/src/database/record/attachments/storage-drivers/native.js +14 -3
- package/build/src/database/record/attachments/storage-drivers/s3.d.ts +2 -6
- package/build/src/database/record/attachments/storage-drivers/s3.d.ts.map +1 -1
- package/build/src/database/record/attachments/storage-drivers/s3.js +29 -8
- package/build/src/database/record/attachments/store.d.ts +31 -2
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +158 -54
- package/build/src/database/record/auditing.d.ts.map +1 -1
- package/build/src/database/record/auditing.js +13 -10
- package/build/src/database/record/counter-cache-magnitude.js +6 -4
- package/build/src/database/record/index.d.ts +42 -2
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +87 -15
- package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/belongs-to.js +4 -3
- package/build/src/database/record/instance-relationships/has-many.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/has-many.js +7 -5
- package/build/src/database/record/instance-relationships/has-one.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/has-one.js +4 -3
- package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
- package/build/src/database/record/validators/uniqueness.js +4 -3
- package/build/src/database/table-data/table-foreign-key.d.ts +9 -1
- package/build/src/database/table-data/table-foreign-key.d.ts.map +1 -1
- package/build/src/database/table-data/table-foreign-key.js +9 -2
- package/build/src/environment-handlers/base.d.ts +2 -5
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +2 -2
- package/build/src/environment-handlers/node/attachment-path-source.d.ts +64 -0
- package/build/src/environment-handlers/node/attachment-path-source.d.ts.map +1 -0
- package/build/src/environment-handlers/node/attachment-path-source.js +116 -0
- package/build/src/environment-handlers/node.d.ts +12 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +24 -4
- package/build/src/frontend-models/websocket-publishers.js +3 -3
- package/build/src/sync/server-sequence-allocator.d.ts +16 -4
- package/build/src/sync/server-sequence-allocator.d.ts.map +1 -1
- package/build/src/sync/server-sequence-allocator.js +37 -11
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +7 -3
- package/build/src/sync/sync-publisher.d.ts +2 -1
- package/build/src/sync/sync-publisher.d.ts.map +1 -1
- package/build/src/sync/sync-publisher.js +11 -6
- package/build/sync/server-sequence-allocator.js +43 -10
- package/build/sync/sync-client.js +6 -2
- package/build/sync/sync-publisher.js +10 -5
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/configuration.js +36 -0
- package/src/database/drivers/base-table.js +4 -3
- package/src/database/drivers/base.js +195 -54
- package/src/database/drivers/index-metadata.js +28 -0
- package/src/database/drivers/mssql/column.js +2 -34
- package/src/database/drivers/mssql/columns-index.js +37 -0
- package/src/database/drivers/mssql/index.js +10 -7
- package/src/database/drivers/mssql/table.js +55 -11
- package/src/database/drivers/mysql/index.js +10 -5
- package/src/database/drivers/mysql/sql/alter-table.js +13 -0
- package/src/database/drivers/pgsql/column.js +2 -27
- package/src/database/drivers/pgsql/columns-index.js +37 -0
- package/src/database/drivers/pgsql/index.js +4 -4
- package/src/database/drivers/pgsql/table.js +61 -10
- package/src/database/drivers/sqlite/base.js +2 -2
- package/src/database/drivers/sqlite/connection-sql-js.js +62 -2
- package/src/database/drivers/sqlite/index.web.js +74 -0
- package/src/database/drivers/sqlite/sql/alter-table.js +18 -0
- package/src/database/migration/index.js +92 -3
- package/src/database/operation-connection.js +110 -0
- package/src/database/operation-lease.js +47 -0
- package/src/database/operation.js +140 -0
- package/src/database/pool/base.js +26 -1
- package/src/database/pool/single-multi-use.js +49 -0
- package/src/database/query/alter-table-base.js +20 -1
- package/src/database/query/model-class-query.js +38 -7
- package/src/database/query/preloader/belongs-to.js +3 -2
- package/src/database/query/preloader/has-many.js +4 -3
- package/src/database/query/preloader/has-one.js +2 -1
- package/src/database/query/preloader/query-for-model.js +17 -0
- package/src/database/query/query-data.js +12 -3
- package/src/database/query/with-count.js +5 -3
- package/src/database/record/acts-as-list.js +11 -8
- package/src/database/record/attachments/normalize-input.js +38 -10
- package/src/database/record/attachments/storage-drivers/filesystem.js +33 -2
- package/src/database/record/attachments/storage-drivers/native.js +17 -2
- package/src/database/record/attachments/storage-drivers/s3.js +27 -7
- package/src/database/record/attachments/store.js +173 -53
- package/src/database/record/auditing.js +12 -9
- package/src/database/record/counter-cache-magnitude.js +5 -3
- package/src/database/record/index.js +95 -14
- package/src/database/record/instance-relationships/belongs-to.js +4 -2
- package/src/database/record/instance-relationships/has-many.js +7 -5
- package/src/database/record/instance-relationships/has-one.js +4 -2
- package/src/database/record/validators/uniqueness.js +3 -2
- package/src/database/table-data/table-foreign-key.js +9 -1
- package/src/environment-handlers/base.js +1 -1
- package/src/environment-handlers/node/attachment-path-source.js +144 -0
- package/src/environment-handlers/node.js +28 -3
- package/src/frontend-models/websocket-publishers.js +2 -2
- package/src/sync/server-sequence-allocator.js +43 -10
- package/src/sync/sync-client.js +6 -2
- package/src/sync/sync-publisher.js +10 -5
|
@@ -29,8 +29,9 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
29
29
|
|
|
30
30
|
if (!targetModelClass) throw new Error("Can't build a new record without a taget model class")
|
|
31
31
|
|
|
32
|
-
const newInstance =
|
|
33
|
-
|
|
32
|
+
const newInstance = this.getModel().bindRelatedRecord(
|
|
33
|
+
/** @type {InstanceType<TMC>} */ (new targetModelClass(data))
|
|
34
|
+
)
|
|
34
35
|
|
|
35
36
|
// Add it to the loaded models of this relationship
|
|
36
37
|
if (this._loaded === undefined) {
|
|
@@ -196,13 +197,14 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
196
197
|
)
|
|
197
198
|
const targetTable = TargetModelClass.tableName()
|
|
198
199
|
const throughTable = throughModelClass.tableName()
|
|
199
|
-
const
|
|
200
|
+
const baseQuery = this.getModel().queryForModel(TargetModelClass)
|
|
201
|
+
const driver = baseQuery.driver
|
|
200
202
|
const parentPrimaryKey = this.getPrimaryKey()
|
|
201
203
|
const parentId = /** @type {string | number} */ (this.getModel().readColumn(parentPrimaryKey))
|
|
202
204
|
const joinSql = `LEFT JOIN ${driver.quoteTable(throughTable)} ON ${driver.quoteTable(throughTable)}.${driver.quoteColumn(throughPrimaryKey)} = ${driver.quoteTable(targetTable)}.${driver.quoteColumn(targetForeignKey)}`
|
|
203
205
|
const whereSql = `${driver.quoteTable(throughTable)}.${driver.quoteColumn(throughForeignKey)} = ${driver.options().quote(parentId)}`
|
|
204
206
|
|
|
205
|
-
const query =
|
|
207
|
+
const query = baseQuery.joins(joinSql).where(whereSql)
|
|
206
208
|
|
|
207
209
|
return this.applyScope(query)
|
|
208
210
|
}
|
|
@@ -224,7 +226,7 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
224
226
|
whereArgs[typeColumn] = this.getModel().getModelClass().getModelName()
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
const query = TargetModelClass.where(whereArgs)
|
|
229
|
+
const query = this.getModel().queryForModel(TargetModelClass).where(whereArgs)
|
|
228
230
|
|
|
229
231
|
return this.applyScope(query)
|
|
230
232
|
}
|
|
@@ -32,7 +32,9 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
32
32
|
|
|
33
33
|
if (!TargetModelClass) throw new Error("Can't build a new record without a target model class")
|
|
34
34
|
|
|
35
|
-
const newInstance =
|
|
35
|
+
const newInstance = this.getModel().bindRelatedRecord(
|
|
36
|
+
/** @type {InstanceType<TMC>} */ (new TargetModelClass(data))
|
|
37
|
+
)
|
|
36
38
|
|
|
37
39
|
this._loaded = newInstance
|
|
38
40
|
|
|
@@ -64,7 +66,7 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
64
66
|
|
|
65
67
|
whereArgs[foreignKey] = primaryModelID
|
|
66
68
|
|
|
67
|
-
let query = TargetModelClass.where(whereArgs)
|
|
69
|
+
let query = this.getModel().queryForModel(TargetModelClass).where(whereArgs)
|
|
68
70
|
|
|
69
71
|
query = this.applyScope(query)
|
|
70
72
|
|
|
@@ -15,7 +15,7 @@ export default class VelociousDatabaseRecordValidatorsUniqueness extends Base {
|
|
|
15
15
|
async validate({model, attributeName}) {
|
|
16
16
|
const modelClass = /** @type {typeof import("../index.js").default} */ (model.constructor)
|
|
17
17
|
|
|
18
|
-
const connection =
|
|
18
|
+
const connection = model.connection()
|
|
19
19
|
const tableName = modelClass._getTable().getName()
|
|
20
20
|
const attributeValue = /** @type {string | number} */ (model.readAttribute(attributeName))
|
|
21
21
|
const attributeNameUnderscore = inflection.underscore(attributeName)
|
|
@@ -49,7 +49,8 @@ export default class VelociousDatabaseRecordValidatorsUniqueness extends Base {
|
|
|
49
49
|
whereArgs[scopeUnderscore] = /** @type {string | number} */ (scopeValue)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
let existingRecordQuery =
|
|
52
|
+
let existingRecordQuery = model
|
|
53
|
+
.queryForModel(modelClass)
|
|
53
54
|
.select(modelClass.primaryKey())
|
|
54
55
|
.where(whereArgs)
|
|
55
56
|
|
|
@@ -7,16 +7,18 @@ export default class TableForeignKey {
|
|
|
7
7
|
* Runs constructor.
|
|
8
8
|
* @param {object} args - Options object.
|
|
9
9
|
* @param {string} args.columnName - Column name.
|
|
10
|
+
* @param {boolean} [args.dropForeignKey] - Whether to drop this foreign key.
|
|
10
11
|
* @param {boolean} [args.isNewForeignKey] - Whether is new foreign key.
|
|
11
12
|
* @param {string} [args.name] - Name.
|
|
12
13
|
* @param {string} args.tableName - Table name.
|
|
13
14
|
* @param {string} args.referencedColumnName - Referenced column name.
|
|
14
15
|
* @param {string} args.referencedTableName - Referenced table name.
|
|
15
16
|
*/
|
|
16
|
-
constructor({columnName, isNewForeignKey, name, tableName, referencedColumnName, referencedTableName, ...restArgs}) {
|
|
17
|
+
constructor({columnName, dropForeignKey, isNewForeignKey, name, tableName, referencedColumnName, referencedTableName, ...restArgs}) {
|
|
17
18
|
restArgsError(restArgs)
|
|
18
19
|
|
|
19
20
|
this._columnName = columnName
|
|
21
|
+
this._dropForeignKey = dropForeignKey
|
|
20
22
|
this._isNewForeignKey = isNewForeignKey
|
|
21
23
|
this._name = name
|
|
22
24
|
this._tableName = tableName
|
|
@@ -30,6 +32,12 @@ export default class TableForeignKey {
|
|
|
30
32
|
*/
|
|
31
33
|
getColumnName() { return this._columnName }
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Runs get drop foreign key.
|
|
37
|
+
* @returns {boolean} - Whether this foreign key should be dropped.
|
|
38
|
+
*/
|
|
39
|
+
getDropForeignKey() { return this._dropForeignKey || false }
|
|
40
|
+
|
|
33
41
|
/**
|
|
34
42
|
* Runs get is new foreign key.
|
|
35
43
|
* @returns {boolean} - Whether is new foreign key.
|
|
@@ -559,7 +559,7 @@ export default class VelociousEnvironmentHandlerBase {
|
|
|
559
559
|
* @param {object} _args - Args.
|
|
560
560
|
* @param {string[]} _args.allowedPathPrefixes - Allowed path prefixes.
|
|
561
561
|
* @param {string} _args.inputPath - Input path.
|
|
562
|
-
* @returns {Promise<
|
|
562
|
+
* @returns {Promise<import("../database/record/attachments/normalize-input.js").AttachmentPathSource>} - Opened path source.
|
|
563
563
|
*/
|
|
564
564
|
async resolveAttachmentInputPath(_args) {
|
|
565
565
|
throw new Error("Attachment path input is not supported in this environment")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { Readable } from "node:stream"
|
|
4
|
+
|
|
5
|
+
const READ_CHUNK_SIZE = 64 * 1024
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opened Node attachment path source.
|
|
9
|
+
*
|
|
10
|
+
* The opened file handle fixes the source identity at normalization time. Reads
|
|
11
|
+
* are limited to the stat snapshot and fail if the file becomes shorter.
|
|
12
|
+
*/
|
|
13
|
+
export default class AttachmentPathSource {
|
|
14
|
+
/**
|
|
15
|
+
* Creates an opened attachment path source.
|
|
16
|
+
* @param {object} args - Source args.
|
|
17
|
+
* @param {number} args.byteSize - Opened-handle stat size.
|
|
18
|
+
* @param {import("node:fs/promises").FileHandle} args.fileHandle - Open file handle.
|
|
19
|
+
* @param {string} args.filePath - Validated path used to open the handle.
|
|
20
|
+
*/
|
|
21
|
+
constructor({byteSize, fileHandle, filePath}) {
|
|
22
|
+
this.byteSize = byteSize
|
|
23
|
+
this.fileHandle = fileHandle
|
|
24
|
+
this.filePath = filePath
|
|
25
|
+
/** @type {Set<Readable>} */
|
|
26
|
+
this.activeStreams = new Set()
|
|
27
|
+
/** @type {Promise<void> | null} */
|
|
28
|
+
this.closePromise = null
|
|
29
|
+
this.closed = false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a bounded, backpressured stream over the opened file snapshot.
|
|
34
|
+
* @returns {Promise<Readable>} - Snapshot read stream.
|
|
35
|
+
*/
|
|
36
|
+
async createReadStream() {
|
|
37
|
+
this.assertOpen()
|
|
38
|
+
|
|
39
|
+
const stream = Readable.from(this.readChunks())
|
|
40
|
+
|
|
41
|
+
this.activeStreams.add(stream)
|
|
42
|
+
stream.once("close", () => {
|
|
43
|
+
this.activeStreams.delete(stream)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return stream
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reads the opened file snapshot into memory for compatibility-only callers.
|
|
51
|
+
* @returns {Promise<Buffer>} - Exact snapshot bytes.
|
|
52
|
+
*/
|
|
53
|
+
async readBuffer() {
|
|
54
|
+
this.assertOpen()
|
|
55
|
+
|
|
56
|
+
const contentBuffer = Buffer.allocUnsafe(this.byteSize)
|
|
57
|
+
let offset = 0
|
|
58
|
+
|
|
59
|
+
while (offset < this.byteSize) {
|
|
60
|
+
this.assertOpen()
|
|
61
|
+
|
|
62
|
+
const {bytesRead} = await this.fileHandle.read(
|
|
63
|
+
contentBuffer,
|
|
64
|
+
offset,
|
|
65
|
+
Math.min(READ_CHUNK_SIZE, this.byteSize - offset),
|
|
66
|
+
offset
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if (bytesRead === 0) this.throwTruncated({bytesRead: offset})
|
|
70
|
+
|
|
71
|
+
offset += bytesRead
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return contentBuffer
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Closes all active streams and the owned file handle.
|
|
79
|
+
* @returns {Promise<void>} - Resolves after close.
|
|
80
|
+
*/
|
|
81
|
+
async close() {
|
|
82
|
+
if (!this.closePromise) {
|
|
83
|
+
this.closed = true
|
|
84
|
+
this.closePromise = (async () => {
|
|
85
|
+
const streams = Array.from(this.activeStreams)
|
|
86
|
+
const streamClosePromises = streams.map(async (stream) => {
|
|
87
|
+
if (stream.closed) return
|
|
88
|
+
|
|
89
|
+
await new Promise((resolve) => {
|
|
90
|
+
stream.once("close", resolve)
|
|
91
|
+
stream.destroy()
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await Promise.all(streamClosePromises)
|
|
96
|
+
await this.fileHandle.close()
|
|
97
|
+
})()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await this.closePromise
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Produces bounded chunks from the opened file handle.
|
|
105
|
+
* @yields {Buffer} - Snapshot chunks.
|
|
106
|
+
*/
|
|
107
|
+
async *readChunks() {
|
|
108
|
+
let offset = 0
|
|
109
|
+
|
|
110
|
+
while (offset < this.byteSize) {
|
|
111
|
+
this.assertOpen()
|
|
112
|
+
|
|
113
|
+
const chunkBuffer = Buffer.allocUnsafe(Math.min(READ_CHUNK_SIZE, this.byteSize - offset))
|
|
114
|
+
const {bytesRead} = await this.fileHandle.read(chunkBuffer, 0, chunkBuffer.length, offset)
|
|
115
|
+
|
|
116
|
+
if (bytesRead === 0) this.throwTruncated({bytesRead: offset})
|
|
117
|
+
|
|
118
|
+
offset += bytesRead
|
|
119
|
+
yield bytesRead === chunkBuffer.length ? chunkBuffer : chunkBuffer.subarray(0, bytesRead)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Asserts that the source remains open.
|
|
125
|
+
* @returns {void} - Throws if closed.
|
|
126
|
+
*/
|
|
127
|
+
assertOpen() {
|
|
128
|
+
if (this.closed) {
|
|
129
|
+
throw new Error(`Attachment path source is closed: ${this.filePath}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Throws a truncation error.
|
|
135
|
+
* @param {object} args - Args.
|
|
136
|
+
* @param {number} args.bytesRead - Bytes read before EOF.
|
|
137
|
+
* @returns {never} - Always throws.
|
|
138
|
+
*/
|
|
139
|
+
throwTruncated({bytesRead}) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Attachment path source was truncated: expected ${this.byteSize} bytes but read ${bytesRead} from ${this.filePath}`
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -35,6 +35,7 @@ import requireContext from "require-context"
|
|
|
35
35
|
import InitializerFromRequireContext from "../database/initializer-from-require-context.js"
|
|
36
36
|
import toImportSpecifier from "../utils/to-import-specifier.js"
|
|
37
37
|
import {validateTimeZone} from "../time-zone.js"
|
|
38
|
+
import AttachmentPathSource from "./node/attachment-path-source.js"
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Defines this typedef.
|
|
@@ -227,7 +228,7 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
227
228
|
* @param {object} args - Args.
|
|
228
229
|
* @param {string[]} args.allowedPathPrefixes - Allowed path prefixes.
|
|
229
230
|
* @param {string} args.inputPath - Input path.
|
|
230
|
-
* @returns {Promise<
|
|
231
|
+
* @returns {Promise<AttachmentPathSource>} - Opened regular-file path source.
|
|
231
232
|
*/
|
|
232
233
|
async resolveAttachmentInputPath({allowedPathPrefixes, inputPath}) {
|
|
233
234
|
const filePath = path.resolve(inputPath)
|
|
@@ -239,9 +240,33 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
239
240
|
throw new Error("Attachment path is outside allowed directories")
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
const
|
|
243
|
+
const fileHandle = await fs.open(filePath, "r")
|
|
243
244
|
|
|
244
|
-
|
|
245
|
+
try {
|
|
246
|
+
const fileStats = await fileHandle.stat()
|
|
247
|
+
|
|
248
|
+
if (!fileStats.isFile()) {
|
|
249
|
+
throw new Error("Attachment path must reference a regular file")
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return new AttachmentPathSource({
|
|
253
|
+
byteSize: fileStats.size,
|
|
254
|
+
fileHandle,
|
|
255
|
+
filePath
|
|
256
|
+
})
|
|
257
|
+
} catch (error) {
|
|
258
|
+
try {
|
|
259
|
+
await fileHandle.close()
|
|
260
|
+
} catch (closeError) {
|
|
261
|
+
throw new AggregateError(
|
|
262
|
+
[error, closeError],
|
|
263
|
+
`Attachment path validation and source close both failed for ${filePath}`,
|
|
264
|
+
{cause: closeError}
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
throw error
|
|
269
|
+
}
|
|
245
270
|
}
|
|
246
271
|
|
|
247
272
|
/**
|
|
@@ -145,7 +145,7 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
|
|
|
145
145
|
|
|
146
146
|
if (action !== "create" && action !== "update") return
|
|
147
147
|
|
|
148
|
-
void model.
|
|
148
|
+
void model.connection().afterCommit(async () => {
|
|
149
149
|
broadcastFrontendModelEvent(model._getConfiguration(), modelName, {
|
|
150
150
|
action,
|
|
151
151
|
id: model.id(),
|
|
@@ -156,7 +156,7 @@ export async function ensureFrontendModelWebsocketPublishersRegistered(configura
|
|
|
156
156
|
})
|
|
157
157
|
|
|
158
158
|
modelClass.afterDestroy((model) => {
|
|
159
|
-
void model.
|
|
159
|
+
void model.connection().afterCommit(async () => {
|
|
160
160
|
broadcastFrontendModelEvent(model._getConfiguration(), modelName, {
|
|
161
161
|
action: "destroy",
|
|
162
162
|
id: model.id()
|
|
@@ -49,23 +49,52 @@ export default class ServerSequenceAllocator {
|
|
|
49
49
|
* parallel `next()` calls - including calls from other allocator instances
|
|
50
50
|
* sharing the same table and connection - cannot interleave their insert
|
|
51
51
|
* and last-insert-id reads and hand out duplicate sequences.
|
|
52
|
+
* @param {{connection?: import("../database/drivers/base.js").default}} [options] - Explicit record-owned connection.
|
|
52
53
|
* @returns {Promise<number>} Next sequence value.
|
|
53
54
|
*/
|
|
54
|
-
async next() {
|
|
55
|
+
async next({connection} = {}) {
|
|
55
56
|
const queueKey = `${this.databaseIdentifier}::${this.tableName}`
|
|
56
57
|
const previousAllocation = allocationQueues.get(queueKey) ?? Promise.resolve()
|
|
57
|
-
const allocation = previousAllocation.then(() => this._allocateNext())
|
|
58
|
+
const allocation = previousAllocation.then(() => this._allocateNext(connection))
|
|
58
59
|
|
|
59
60
|
allocationQueues.set(queueKey, allocation.then(() => undefined, () => undefined))
|
|
60
61
|
|
|
61
62
|
return await allocation
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Allocates for a record while preserving allocator routing and operation ownership.
|
|
67
|
+
* @param {import("../database/record/index.js").default} record - Record receiving the sequence.
|
|
68
|
+
* @returns {Promise<number>} Next sequence value.
|
|
69
|
+
*/
|
|
70
|
+
async _nextForRecord(record) {
|
|
71
|
+
const operation = record.databaseOperation()
|
|
72
|
+
|
|
73
|
+
if (!operation) return await this.next()
|
|
74
|
+
|
|
75
|
+
const ModelClass = record.getModelClass()
|
|
76
|
+
|
|
77
|
+
if (this._getConfiguration() !== ModelClass._getConfiguration()) {
|
|
78
|
+
throw new Error("Server sequence allocator belongs to another Velocious configuration")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const modelDatabaseIdentifier = ModelClass.getDatabaseIdentifier()
|
|
82
|
+
|
|
83
|
+
if (this.databaseIdentifier !== modelDatabaseIdentifier) {
|
|
84
|
+
throw new Error(`Server sequence allocator uses database ${JSON.stringify(this.databaseIdentifier)}, not operation model database ${JSON.stringify(modelDatabaseIdentifier)}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const operationScope = operation.forModel(ModelClass)
|
|
88
|
+
|
|
89
|
+
return await this.next({connection: operationScope.driver})
|
|
90
|
+
}
|
|
91
|
+
|
|
64
92
|
/**
|
|
65
93
|
* Ensures the backing table exists.
|
|
94
|
+
* @param {import("../database/drivers/base.js").default} [connection] - Explicit record-owned connection.
|
|
66
95
|
* @returns {Promise<void>} Resolves when ready.
|
|
67
96
|
*/
|
|
68
|
-
async ensureReady() {
|
|
97
|
+
async ensureReady(connection) {
|
|
69
98
|
if (this._isReady) return
|
|
70
99
|
|
|
71
100
|
if (this._usesMemoryStorage()) {
|
|
@@ -85,7 +114,7 @@ export default class ServerSequenceAllocator {
|
|
|
85
114
|
// cache readiness when the table was not created inside an active
|
|
86
115
|
// transaction; otherwise the next allocation re-verifies the table.
|
|
87
116
|
if (!created || !db.insideTransaction()) this._isReady = true
|
|
88
|
-
})
|
|
117
|
+
}, connection)
|
|
89
118
|
|
|
90
119
|
try {
|
|
91
120
|
await this._readyPromise
|
|
@@ -96,10 +125,11 @@ export default class ServerSequenceAllocator {
|
|
|
96
125
|
|
|
97
126
|
/**
|
|
98
127
|
* Allocates one sequence value after queueing.
|
|
128
|
+
* @param {import("../database/drivers/base.js").default} [connection] - Explicit record-owned connection.
|
|
99
129
|
* @returns {Promise<number>} Allocated sequence value.
|
|
100
130
|
*/
|
|
101
|
-
async _allocateNext() {
|
|
102
|
-
await this.ensureReady()
|
|
131
|
+
async _allocateNext(connection) {
|
|
132
|
+
await this.ensureReady(connection)
|
|
103
133
|
|
|
104
134
|
if (this._usesMemoryStorage()) {
|
|
105
135
|
return ++this._memorySequence
|
|
@@ -123,7 +153,7 @@ export default class ServerSequenceAllocator {
|
|
|
123
153
|
if (insertedId !== undefined && insertedId !== null) return Number(insertedId)
|
|
124
154
|
|
|
125
155
|
return Number(await db.lastInsertID())
|
|
126
|
-
})
|
|
156
|
+
}, connection)
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
/**
|
|
@@ -158,9 +188,12 @@ export default class ServerSequenceAllocator {
|
|
|
158
188
|
* Runs a callback with a database connection.
|
|
159
189
|
* @template Result
|
|
160
190
|
* @param {(db: import("../database/drivers/base.js").default) => Promise<Result>} callback - Database callback.
|
|
191
|
+
* @param {import("../database/drivers/base.js").default} [connection] - Explicit record-owned connection.
|
|
161
192
|
* @returns {Promise<Result>} Callback result.
|
|
162
193
|
*/
|
|
163
|
-
async _withDb(callback) {
|
|
194
|
+
async _withDb(callback, connection) {
|
|
195
|
+
if (connection) return await callback(connection)
|
|
196
|
+
|
|
164
197
|
return await this._getConfiguration().ensureConnections({databaseIdentifiers: [this.databaseIdentifier], name: "Server sequence allocator"}, async (dbs) => {
|
|
165
198
|
const db = dbs[this.databaseIdentifier]
|
|
166
199
|
|
|
@@ -225,11 +258,11 @@ export function withServerSequence(ModelClass, {allocator, column = "serverSeque
|
|
|
225
258
|
if (typeof prototype[advanceMethodName] != "function") {
|
|
226
259
|
/**
|
|
227
260
|
* Assigns the next server-side sequence.
|
|
228
|
-
* @this {Record<string, ?>}
|
|
261
|
+
* @this {import("../database/record/index.js").default & Record<string, ?>}
|
|
229
262
|
* @returns {Promise<void>}
|
|
230
263
|
*/
|
|
231
264
|
prototype[advanceMethodName] = async function advanceServerSequenceThroughAllocator() {
|
|
232
|
-
this[setterMethodName](await allocator.
|
|
265
|
+
this[setterMethodName](await allocator._nextForRecord(this))
|
|
233
266
|
}
|
|
234
267
|
}
|
|
235
268
|
|
package/src/sync/sync-client.js
CHANGED
|
@@ -220,13 +220,17 @@ export default class SyncClient {
|
|
|
220
220
|
resource: record
|
|
221
221
|
})
|
|
222
222
|
const syncType = this.defaultSyncType({operation, record, resourceConfig})
|
|
223
|
+
const databaseOperation = record.databaseOperation()
|
|
224
|
+
const operationScope = databaseOperation
|
|
225
|
+
? databaseOperation.forModel(this.config.syncModel)
|
|
226
|
+
: this.config.syncModel
|
|
223
227
|
|
|
224
|
-
await
|
|
228
|
+
await record.connection().afterCommit(async () => {
|
|
225
229
|
try {
|
|
226
230
|
await SyncApiClient.queueLocalSync({
|
|
227
231
|
data,
|
|
228
232
|
resource: record,
|
|
229
|
-
syncModel:
|
|
233
|
+
syncModel: operationScope,
|
|
230
234
|
syncType
|
|
231
235
|
})
|
|
232
236
|
} catch (error) {
|
|
@@ -203,10 +203,14 @@ export default class SyncPublisher {
|
|
|
203
203
|
sync_type: syncType,
|
|
204
204
|
...scopeValues.columns
|
|
205
205
|
}
|
|
206
|
+
const databaseOperation = record.databaseOperation()
|
|
207
|
+
const operationScope = databaseOperation
|
|
208
|
+
? databaseOperation.forModel(this.config.syncModel)
|
|
209
|
+
: this.config.syncModel
|
|
206
210
|
|
|
207
|
-
await
|
|
211
|
+
await record.connection().afterCommit(async () => {
|
|
208
212
|
try {
|
|
209
|
-
const syncRow = await this.upsertPublishedSyncRow(attributes)
|
|
213
|
+
const syncRow = await this.upsertPublishedSyncRow(attributes, operationScope)
|
|
210
214
|
|
|
211
215
|
await this.broadcaster()({
|
|
212
216
|
body: {
|
|
@@ -273,10 +277,11 @@ export default class SyncPublisher {
|
|
|
273
277
|
* change back to), so repeated server changes to one resource reuse and
|
|
274
278
|
* re-sequence one feed row.
|
|
275
279
|
* @param {Record<string, ?>} attributes - Snapshotted sync row attributes.
|
|
280
|
+
* @param {?} syncModel - Operation-bound or static Sync model interface.
|
|
276
281
|
* @returns {Promise<?>} Upserted sync row.
|
|
277
282
|
*/
|
|
278
|
-
async upsertPublishedSyncRow(attributes) {
|
|
279
|
-
const existingSync = await
|
|
283
|
+
async upsertPublishedSyncRow(attributes, syncModel = this.config.syncModel) {
|
|
284
|
+
const existingSync = await syncModel
|
|
280
285
|
.where({
|
|
281
286
|
[this.config.actorForeignKeyColumn]: null,
|
|
282
287
|
resource_id: attributes.resource_id,
|
|
@@ -284,7 +289,7 @@ export default class SyncPublisher {
|
|
|
284
289
|
})
|
|
285
290
|
.first()
|
|
286
291
|
|
|
287
|
-
return await upsertSyncRow({attributes, existingSync, syncModel
|
|
292
|
+
return await upsertSyncRow({attributes, existingSync, syncModel})
|
|
288
293
|
}
|
|
289
294
|
|
|
290
295
|
/**
|