velocious 1.0.564 → 1.0.566
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 +4 -2
- package/build/configuration-types.js +1 -0
- package/build/configuration.js +20 -0
- package/build/database/record/acts-as-list.js +37 -10
- package/build/environment-handlers/node/cli/commands/generate/base-models.js +4 -12
- package/build/http-server/client/websocket-session.js +455 -69
- package/build/src/configuration-types.d.ts +8 -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 +12 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +19 -1
- package/build/src/database/record/acts-as-list.d.ts.map +1 -1
- package/build/src/database/record/acts-as-list.js +34 -12
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +5 -13
- package/build/src/http-server/client/websocket-session.d.ts +102 -4
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +404 -67
- package/package.json +1 -1
- package/src/configuration-types.js +1 -0
- package/src/configuration.js +20 -0
- package/src/database/record/acts-as-list.js +37 -10
- package/src/environment-handlers/node/cli/commands/generate/base-models.js +4 -12
- package/src/http-server/client/websocket-session.js +455 -69
package/package.json
CHANGED
|
@@ -255,6 +255,7 @@
|
|
|
255
255
|
* @property {boolean} [inProcess] - Run HTTP handlers in the main thread instead of worker threads.
|
|
256
256
|
* @property {number} [maxWorkers] - Backward-compatible alias for workers.
|
|
257
257
|
* @property {number} [port] - Port to bind the HTTP server to.
|
|
258
|
+
* @property {{maxPendingBytes?: number, maxPendingMessages?: number}} [websocketInboundQueue] - Per-session retained inbound WebSocket message limits.
|
|
258
259
|
* @property {{maxPendingBytes?: number, maxPendingFrames?: number}} [websocketOutboundQueue] - Per-client retained outbound WebSocket frame limits.
|
|
259
260
|
* @property {number} [workers] - Worker handlers to start for the HTTP server.
|
|
260
261
|
*/
|
package/src/configuration.js
CHANGED
|
@@ -114,6 +114,8 @@ function resolveBeaconUnreachableReportMs(value) {
|
|
|
114
114
|
return 30_000
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
const DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_BYTES = 16 * 1024 * 1024
|
|
118
|
+
const DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_MESSAGES = 256
|
|
117
119
|
const DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_BYTES = 16 * 1024 * 1024
|
|
118
120
|
const DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_FRAMES = 256
|
|
119
121
|
|
|
@@ -228,10 +230,15 @@ export default class VelociousConfiguration {
|
|
|
228
230
|
* @type {Promise<void> | undefined}
|
|
229
231
|
*/
|
|
230
232
|
this._initializePromise = undefined
|
|
233
|
+
const websocketInboundQueue = httpServer?.websocketInboundQueue
|
|
231
234
|
const websocketOutboundQueue = httpServer?.websocketOutboundQueue
|
|
232
235
|
|
|
233
236
|
this.httpServer = {
|
|
234
237
|
...(httpServer || {}),
|
|
238
|
+
websocketInboundQueue: {
|
|
239
|
+
maxPendingBytes: positiveSafeInteger(websocketInboundQueue?.maxPendingBytes, "httpServer.websocketInboundQueue.maxPendingBytes", DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_BYTES),
|
|
240
|
+
maxPendingMessages: positiveSafeInteger(websocketInboundQueue?.maxPendingMessages, "httpServer.websocketInboundQueue.maxPendingMessages", DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_MESSAGES)
|
|
241
|
+
},
|
|
235
242
|
websocketOutboundQueue: {
|
|
236
243
|
maxPendingBytes: positiveSafeInteger(websocketOutboundQueue?.maxPendingBytes, "httpServer.websocketOutboundQueue.maxPendingBytes", DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_BYTES),
|
|
237
244
|
maxPendingFrames: positiveSafeInteger(websocketOutboundQueue?.maxPendingFrames, "httpServer.websocketOutboundQueue.maxPendingFrames", DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_FRAMES)
|
|
@@ -2370,6 +2377,19 @@ export default class VelociousConfiguration {
|
|
|
2370
2377
|
*/
|
|
2371
2378
|
getWebsocketSessionHeartbeatSeconds() { return this._websocketSessionHeartbeatSeconds }
|
|
2372
2379
|
|
|
2380
|
+
/**
|
|
2381
|
+
* Gets per-session WebSocket inbound message queue limits.
|
|
2382
|
+
* @returns {{maxBytes: number, maxMessages: number}} - Per-session inbound queue high-water marks.
|
|
2383
|
+
*/
|
|
2384
|
+
getWebsocketInboundQueueLimits() {
|
|
2385
|
+
const queue = this.httpServer.websocketInboundQueue
|
|
2386
|
+
|
|
2387
|
+
return {
|
|
2388
|
+
maxBytes: queue.maxPendingBytes,
|
|
2389
|
+
maxMessages: queue.maxPendingMessages
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2373
2393
|
/**
|
|
2374
2394
|
* Gets per-client WebSocket outbound queue limits.
|
|
2375
2395
|
* @returns {{maxBytes: number, maxFrames: number}} - Per-client outbound queue high-water marks.
|
|
@@ -50,6 +50,7 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
50
50
|
const position = record.readAttribute(positionColumn)
|
|
51
51
|
|
|
52
52
|
if (position != null) {
|
|
53
|
+
assertPositivePosition({position, positionColumn})
|
|
53
54
|
await shiftPositionsUp({record, positionColumn, scope, fromPosition: position})
|
|
54
55
|
} else {
|
|
55
56
|
const nextPosition = await highestPositionInScope({record, positionColumn, scope})
|
|
@@ -77,12 +78,19 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
77
78
|
|
|
78
79
|
if (!posChanged && !scopeChanged) return
|
|
79
80
|
|
|
81
|
+
assertPositivePosition({
|
|
82
|
+
position: rawAttributes[posColumn],
|
|
83
|
+
positionColumn,
|
|
84
|
+
persisted: true
|
|
85
|
+
})
|
|
86
|
+
|
|
80
87
|
const oldPosition = posChanged ? /** @type {number} */ (rawAttributes[posColumn]) : /** @type {number} */ (record.readAttribute(positionColumn))
|
|
81
88
|
const newPosition = posChanged ? /** @type {number} */ (changes[posColumn]) : /** @type {number} */ (record.readAttribute(positionColumn))
|
|
82
89
|
const oldScopeValue = scopeChanged ? /** @type {number} */ (rawAttributes[scopeCol]) : /** @type {number} */ (record.readAttribute(scope))
|
|
83
90
|
const newScopeValue = scopeChanged ? /** @type {number} */ (changes[scopeCol]) : /** @type {number} */ (record.readAttribute(scope))
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
assertPositivePosition({position: newPosition, positionColumn})
|
|
93
|
+
if (oldPosition == null) return
|
|
86
94
|
if (newPosition === oldPosition && newScopeValue === oldScopeValue) return
|
|
87
95
|
|
|
88
96
|
if (scopeChanged && oldScopeValue !== newScopeValue) {
|
|
@@ -100,7 +108,7 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
100
108
|
return
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue
|
|
111
|
+
await moveOutOfWay({record, positionColumn, scope, scopeValue: oldScopeValue})
|
|
104
112
|
setShiftingFlag(record, false)
|
|
105
113
|
await shiftPositionsDown({record, positionColumn, scope, scopeValue: oldScopeValue, fromPosition: oldPosition + 1})
|
|
106
114
|
await shiftPositionsUp({record, positionColumn, scope, scopeValue: newScopeValue, fromPosition: newPosition, excludeRecordId: record.id()})
|
|
@@ -121,7 +129,14 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
121
129
|
modelClass.beforeDestroy(async (record) => {
|
|
122
130
|
const position = record.readAttribute(positionColumn)
|
|
123
131
|
|
|
124
|
-
if (position == null)
|
|
132
|
+
if (position == null) {
|
|
133
|
+
const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
|
|
134
|
+
const posColumn = modelClass.getColumnNameForAttributeName(positionColumn)
|
|
135
|
+
|
|
136
|
+
if (posColumn in record._attributes) assertPositivePosition({position, positionColumn, persisted: true})
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
assertPositivePosition({position, positionColumn, persisted: true})
|
|
125
140
|
|
|
126
141
|
await moveOutOfWay({record, positionColumn, scope})
|
|
127
142
|
setShiftingFlag(record, false)
|
|
@@ -130,6 +145,22 @@ export default function registerActsAsListCallbacks(modelClass, positionColumn,
|
|
|
130
145
|
})
|
|
131
146
|
}
|
|
132
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Enforces the public gap-less list position invariant before any shifting.
|
|
150
|
+
* @param {object} args - Arguments.
|
|
151
|
+
* @param {number | null | undefined} args.position - Position to validate.
|
|
152
|
+
* @param {string} args.positionColumn - Position attribute name.
|
|
153
|
+
* @param {boolean} [args.persisted] - Whether the invalid value came from persisted state.
|
|
154
|
+
* @returns {void}
|
|
155
|
+
*/
|
|
156
|
+
function assertPositivePosition({position, positionColumn, persisted = false}) {
|
|
157
|
+
if (typeof position === "number" && Number.isInteger(position) && position > 0) return
|
|
158
|
+
|
|
159
|
+
const source = persisted ? "Persisted" : "Requested"
|
|
160
|
+
|
|
161
|
+
throw new Error(`${source} actsAsList ${positionColumn} must be a positive integer`)
|
|
162
|
+
}
|
|
163
|
+
|
|
133
164
|
/**
|
|
134
165
|
* Places a moved row after surrounding rows have shifted.
|
|
135
166
|
* @param {object} args - Arguments.
|
|
@@ -380,30 +411,26 @@ function resolveScopeValue(record, scope) {
|
|
|
380
411
|
* @param {string} args.positionColumn - camelCase position attribute.
|
|
381
412
|
* @param {string} args.scope - camelCase scope attribute.
|
|
382
413
|
* @param {string | number | null} [args.scopeValue] - Scope containing the record before move-out.
|
|
383
|
-
* @param {string | number | null} [args.targetScopeValue] - Temporary scope value to assign.
|
|
384
414
|
* @returns {Promise<void>}
|
|
385
415
|
*/
|
|
386
|
-
async function moveOutOfWay({record, positionColumn, scope, scopeValue
|
|
416
|
+
async function moveOutOfWay({record, positionColumn, scope, scopeValue}) {
|
|
387
417
|
const modelClass = /** @type {typeof import("./index.js").default} */ (record.constructor)
|
|
388
418
|
const connection = modelClass.connection()
|
|
389
419
|
const tableName = modelClass._getTable().getName()
|
|
390
420
|
const resolvedScopeValue = scopeValue != null ? scopeValue : resolveScopeValue(record, scope)
|
|
391
|
-
const resolvedTargetScopeValue = targetScopeValue != null ? targetScopeValue : resolvedScopeValue
|
|
392
421
|
|
|
393
422
|
if (resolvedScopeValue == null) return
|
|
394
|
-
if (resolvedTargetScopeValue == null) return
|
|
395
423
|
|
|
396
|
-
const tempPosition = -record.id()
|
|
397
424
|
const positionColumnSql = connection.quoteColumn(modelClass.getColumnNameForAttributeName(positionColumn))
|
|
398
425
|
const scopeColumnSql = connection.quoteColumn(modelClass.getColumnNameForAttributeName(scope))
|
|
399
426
|
const tableSql = connection.quoteTable(tableName)
|
|
400
|
-
const pkSql = connection.quoteColumn(
|
|
427
|
+
const pkSql = connection.quoteColumn(modelClass.primaryKey())
|
|
401
428
|
|
|
402
429
|
setShiftingFlag(record, true)
|
|
403
430
|
|
|
404
431
|
try {
|
|
405
432
|
await connection.query(
|
|
406
|
-
`UPDATE ${tableSql} SET ${
|
|
433
|
+
`UPDATE ${tableSql} SET ${positionColumnSql} = -${positionColumnSql} WHERE ${scopeColumnSql} = ${connection.quote(resolvedScopeValue)} AND ${pkSql} = ${connection.quote(record.id())}`
|
|
407
434
|
)
|
|
408
435
|
} finally {
|
|
409
436
|
// Don't clear the flag here — the caller will do that after shifts
|
|
@@ -154,16 +154,12 @@ export default class DbGenerateModel extends BaseCommand {
|
|
|
154
154
|
// --- getModelClass() override (fixes polymorphic typing in JS/JSDoc) ---
|
|
155
155
|
if (await fileExists(sourceModelFullFilePath)) {
|
|
156
156
|
// Model file exists (e.g. src/models/ticket.js) → return typeof Ticket
|
|
157
|
-
fileContent += "
|
|
158
|
-
fileContent += ` * @returns {typeof import("${sourceModelFilePath}").default}\n`
|
|
159
|
-
fileContent += " */\n"
|
|
157
|
+
fileContent += ` /** @returns {typeof import("${sourceModelFilePath}").default} */\n`
|
|
160
158
|
fileContent += " // @ts-ignore - override narrows return type for better IntelliSense in generated model bases\n"
|
|
161
159
|
fileContent += ` getModelClass() { return /** @type {typeof import("${sourceModelFilePath}").default} */ (this.constructor) }\n\n`
|
|
162
160
|
} else {
|
|
163
161
|
// No model file yet → fall back to typeof TicketBase
|
|
164
|
-
fileContent +=
|
|
165
|
-
fileContent += ` * @returns {typeof ${modelNameCamelized}Base}\n`
|
|
166
|
-
fileContent += " */\n"
|
|
162
|
+
fileContent += ` /** @returns {typeof ${modelNameCamelized}Base} */\n`
|
|
167
163
|
fileContent += " // @ts-ignore - override narrows return type for better IntelliSense in generated model bases\n"
|
|
168
164
|
fileContent += ` getModelClass() { return /** @type {typeof ${modelNameCamelized}Base} */ (this.constructor) }\n\n`
|
|
169
165
|
}
|
|
@@ -181,9 +177,7 @@ export default class DbGenerateModel extends BaseCommand {
|
|
|
181
177
|
}
|
|
182
178
|
|
|
183
179
|
if (jsdocType) {
|
|
184
|
-
fileContent += "
|
|
185
|
-
fileContent += ` * @returns {${jsdocType}${column.getNull() ? " | null" : ""}}\n`
|
|
186
|
-
fileContent += " */\n"
|
|
180
|
+
fileContent += ` /** @returns {${jsdocType}${column.getNull() ? " | null" : ""}} */\n`
|
|
187
181
|
}
|
|
188
182
|
|
|
189
183
|
fileContent += ` ${camelizedColumnName}() { return this.readAttribute("${camelizedColumnName}") }\n\n`
|
|
@@ -199,9 +193,7 @@ export default class DbGenerateModel extends BaseCommand {
|
|
|
199
193
|
|
|
200
194
|
fileContent += ` set${camelizedColumnNameBigFirst}(newValue) { return this._setColumnAttribute("${camelizedColumnName}", newValue) }\n\n`
|
|
201
195
|
|
|
202
|
-
fileContent += "
|
|
203
|
-
fileContent += " * @returns {boolean}\n"
|
|
204
|
-
fileContent += " */\n"
|
|
196
|
+
fileContent += " /** @returns {boolean} */\n"
|
|
205
197
|
fileContent += ` has${camelizedColumnNameBigFirst}() { return this._hasAttribute(this.${camelizedColumnName}()) }\n`
|
|
206
198
|
|
|
207
199
|
methodsCount++
|