velocious 1.0.516 → 1.0.518

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.
Files changed (72) hide show
  1. package/README.md +2 -0
  2. package/build/background-jobs/job.js +27 -2
  3. package/build/background-jobs/store.js +356 -39
  4. package/build/background-jobs/types.js +6 -0
  5. package/build/configuration-types.js +12 -1
  6. package/build/configuration.js +2 -1
  7. package/build/database/drivers/base.js +26 -0
  8. package/build/database/drivers/mssql/index.js +13 -0
  9. package/build/database/drivers/mysql/index.js +18 -0
  10. package/build/database/drivers/pgsql/index.js +12 -0
  11. package/build/database/drivers/sqlite/connection-sql-js.js +11 -0
  12. package/build/database/drivers/sqlite/index.js +14 -4
  13. package/build/database/drivers/sqlite/index.native.js +13 -0
  14. package/build/database/drivers/sqlite/index.web.js +11 -1
  15. package/build/database/drivers/sqlite/query.js +1 -2
  16. package/build/http-server/client/request-buffer/index.js +151 -89
  17. package/build/src/background-jobs/job.d.ts +15 -0
  18. package/build/src/background-jobs/job.d.ts.map +1 -1
  19. package/build/src/background-jobs/job.js +24 -3
  20. package/build/src/background-jobs/store.d.ts +111 -9
  21. package/build/src/background-jobs/store.d.ts.map +1 -1
  22. package/build/src/background-jobs/store.js +339 -40
  23. package/build/src/background-jobs/types.d.ts +30 -0
  24. package/build/src/background-jobs/types.d.ts.map +1 -1
  25. package/build/src/background-jobs/types.js +7 -1
  26. package/build/src/configuration-types.d.ts +29 -2
  27. package/build/src/configuration-types.d.ts.map +1 -1
  28. package/build/src/configuration-types.js +13 -2
  29. package/build/src/configuration.d.ts.map +1 -1
  30. package/build/src/configuration.js +3 -2
  31. package/build/src/database/drivers/base.d.ts +13 -0
  32. package/build/src/database/drivers/base.d.ts.map +1 -1
  33. package/build/src/database/drivers/base.js +25 -1
  34. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  35. package/build/src/database/drivers/mssql/index.js +13 -1
  36. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  37. package/build/src/database/drivers/mysql/index.js +21 -1
  38. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  39. package/build/src/database/drivers/pgsql/index.js +14 -1
  40. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +6 -0
  41. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
  42. package/build/src/database/drivers/sqlite/connection-sql-js.js +11 -1
  43. package/build/src/database/drivers/sqlite/index.d.ts +2 -2
  44. package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
  45. package/build/src/database/drivers/sqlite/index.js +15 -5
  46. package/build/src/database/drivers/sqlite/index.native.d.ts.map +1 -1
  47. package/build/src/database/drivers/sqlite/index.native.js +14 -1
  48. package/build/src/database/drivers/sqlite/index.web.d.ts +2 -1
  49. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  50. package/build/src/database/drivers/sqlite/index.web.js +11 -2
  51. package/build/src/database/drivers/sqlite/query.d.ts +2 -2
  52. package/build/src/database/drivers/sqlite/query.d.ts.map +1 -1
  53. package/build/src/database/drivers/sqlite/query.js +2 -3
  54. package/build/src/http-server/client/request-buffer/index.d.ts +23 -2
  55. package/build/src/http-server/client/request-buffer/index.d.ts.map +1 -1
  56. package/build/src/http-server/client/request-buffer/index.js +141 -86
  57. package/package.json +2 -2
  58. package/src/background-jobs/job.js +27 -2
  59. package/src/background-jobs/store.js +356 -39
  60. package/src/background-jobs/types.js +6 -0
  61. package/src/configuration-types.js +12 -1
  62. package/src/configuration.js +2 -1
  63. package/src/database/drivers/base.js +26 -0
  64. package/src/database/drivers/mssql/index.js +13 -0
  65. package/src/database/drivers/mysql/index.js +18 -0
  66. package/src/database/drivers/pgsql/index.js +12 -0
  67. package/src/database/drivers/sqlite/connection-sql-js.js +11 -0
  68. package/src/database/drivers/sqlite/index.js +14 -4
  69. package/src/database/drivers/sqlite/index.native.js +13 -0
  70. package/src/database/drivers/sqlite/index.web.js +11 -1
  71. package/src/database/drivers/sqlite/query.js +1 -2
  72. package/src/http-server/client/request-buffer/index.js +151 -89
@@ -161,7 +161,18 @@
161
161
  * long-running jobs and for using more cores. Default: `4`.
162
162
  * @property {number} [maxConcurrentForkedJobs] - How many out-of-process
163
163
  * `"forked"` or `"spawned"` jobs a single `background-jobs-worker` is
164
- * allowed to keep in flight. Default: `4`.
164
+ * allowed to keep in flight. Default: `4`. This is a per-worker safety cap;
165
+ * for workload-shaped limits use per-queue caps (`queues`) instead, which are
166
+ * enforced cluster-wide and are immune to duplicate worker processes.
167
+ * @property {Record<string, {maxConcurrent?: number}>} [queues] - Per-queue
168
+ * concurrency caps, Sidekiq-style. A job declares its queue (static `queue` on
169
+ * the job class, or the `queue` enqueue option; defaults to `"default"`), and
170
+ * `queues[name].maxConcurrent` bounds how many jobs from that queue may be
171
+ * in flight across the whole cluster (enforced via durable per-key
172
+ * concurrency, so it holds regardless of how many worker processes run).
173
+ * Size each queue to its workload: I/O-bound queues (e.g. build runners
174
+ * waiting on remote Docker servers) can run far above the core count, while
175
+ * CPU-bound queues should stay near it. Default: `{}` (no queue caps).
165
176
  * @property {BackgroundJobsDispatchStrategy} [dispatchStrategy] - How the main process
166
177
  * detects new work. Defaults to `"beacon"` (event-driven). Set to `"polling"`
167
178
  * to restore the legacy fixed-interval poll.
@@ -1291,8 +1291,9 @@ export default class VelociousConfiguration {
1291
1291
  const pollIntervalMs = typeof configured.pollIntervalMs === "number" && configured.pollIntervalMs >= 1
1292
1292
  ? configured.pollIntervalMs
1293
1293
  : (typeof envPollInterval === "number" && Number.isFinite(envPollInterval) && envPollInterval >= 1 ? envPollInterval : 1000)
1294
+ const queues = configured.queues && typeof configured.queues === "object" ? configured.queues : {}
1294
1295
 
1295
- return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, dispatchStrategy, pollIntervalMs}
1296
+ return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, dispatchStrategy, pollIntervalMs, queues}
1296
1297
  }
1297
1298
 
1298
1299
  /**
@@ -1050,6 +1050,22 @@ export default class VelociousDatabaseDriversBase {
1050
1050
  throw new Error("'query' unexpected came here")
1051
1051
  }
1052
1052
 
1053
+ /**
1054
+ * Executes a mutation and returns the number of rows changed by that statement.
1055
+ * @param {string} sql - Mutation SQL string.
1056
+ * @returns {Promise<number>} - Affected row count.
1057
+ */
1058
+ async affectedRows(sql) {
1059
+ this._assertWritableQuery(sql)
1060
+ await this.beforeQuery(sql, {})
1061
+
1062
+ try {
1063
+ return await this._affectedRowsActual(sql)
1064
+ } finally {
1065
+ await this.afterQuery(sql, {})
1066
+ }
1067
+ }
1068
+
1053
1069
  /**
1054
1070
  * Runs query actual with logging.
1055
1071
  * @param {object} args - Options object.
@@ -1308,6 +1324,16 @@ export default class VelociousDatabaseDriversBase {
1308
1324
  throw new Error(`queryActual not implemented`)
1309
1325
  }
1310
1326
 
1327
+ /**
1328
+ * Executes a mutation and returns its affected row count.
1329
+ * @abstract
1330
+ * @param {string} sql - Mutation SQL string.
1331
+ * @returns {Promise<number>} - Affected row count.
1332
+ */
1333
+ _affectedRowsActual(sql) { // eslint-disable-line no-unused-vars
1334
+ throw new Error(`affectedRowsActual not implemented`)
1335
+ }
1336
+
1311
1337
  /**
1312
1338
  * Runs query to sql.
1313
1339
  * @abstract
@@ -328,6 +328,19 @@ export default class VelociousDatabaseDriversMssql extends Base{
328
328
  return Array.isArray(result.recordsets) ? result.recordsets[0] || [] : []
329
329
  }
330
330
 
331
+ /**
332
+ * Executes a mutation with affected-row metadata.
333
+ * @param {string} sql - Mutation SQL.
334
+ * @returns {Promise<number>} - Affected row count.
335
+ */
336
+ async _affectedRowsActual(sql) {
337
+ const request = this._currentTransaction
338
+ ? new mssql.Request(this._currentTransaction)
339
+ : new mssql.Request(this.connection)
340
+ const result = await request.query(sql)
341
+ return result.rowsAffected.reduce((total, count) => total + count, 0)
342
+ }
343
+
331
344
  /**
332
345
  * Runs query to sql.
333
346
  * @param {import("../../query/index.js").default} query - Query instance.
@@ -344,6 +344,24 @@ export default class VelociousDatabaseDriversMysql extends Base{
344
344
  }
345
345
  }
346
346
 
347
+ /**
348
+ * Executes a mutation with affected-row metadata.
349
+ * @param {string} sql - Mutation SQL.
350
+ * @returns {Promise<number>} - Affected row count.
351
+ */
352
+ async _affectedRowsActual(sql) {
353
+ if (!this.pool) await this.connect()
354
+ if (!this.pool) throw new Error("MySQL pool failed to initialize")
355
+ const pool = this.pool
356
+
357
+ return await new Promise((resolve, reject) => {
358
+ pool.query(sql, (error, result) => {
359
+ if (error) reject(error)
360
+ else resolve("affectedRows" in result ? result.affectedRows : 0)
361
+ })
362
+ })
363
+ }
364
+
347
365
  /**
348
366
  * Runs query to sql.
349
367
  * @param {import("../../query/index.js").default} query - Query instance.
@@ -227,6 +227,18 @@ export default class VelociousDatabaseDriversPgsql extends Base{
227
227
  return response.rows
228
228
  }
229
229
 
230
+ /**
231
+ * Executes a mutation with affected-row metadata.
232
+ * @param {string} sql - Mutation SQL.
233
+ * @returns {Promise<number>} - Affected row count.
234
+ */
235
+ async _affectedRowsActual(sql) {
236
+ if (!this.connection) await this.connect()
237
+ if (!this.connection) throw new Error("PostgreSQL connection failed to initialize")
238
+ const response = await this.connection.query(sql)
239
+ return response.rowCount || 0
240
+ }
241
+
230
242
  /**
231
243
  * Runs query to sql.
232
244
  * @param {import("../../query/index.js").default} query - Query instance.
@@ -47,6 +47,17 @@ export default class VelociousDatabaseDriversSqliteConnectionSqlJs {
47
47
  return result
48
48
  }
49
49
 
50
+ /**
51
+ * Executes a mutation with affected-row metadata.
52
+ * @param {string} sql - Mutation SQL.
53
+ * @returns {Promise<number>} - Affected row count.
54
+ */
55
+ async affectedRows(sql) {
56
+ await this.query(sql)
57
+ const connection = /** @type {import("sql.js").Database & {getRowsModified: () => number}} */ (this.connection)
58
+ return connection.getRowsModified()
59
+ }
60
+
50
61
  saveDatabase = async () => {
51
62
  const databaseContent = this.connection.export()
52
63
 
@@ -15,7 +15,7 @@ import fileExists from "../../../utils/file-exists.js"
15
15
  export default class VelociousDatabaseDriversSqliteNode extends Base {
16
16
  /**
17
17
  * Connection.
18
- * @type {import("sqlite3").Database | undefined} */
18
+ * @type {import("sqlite").Database | undefined} */
19
19
  connection = undefined
20
20
 
21
21
  /**
@@ -39,11 +39,10 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
39
39
  this._advisoryLockDirectory = path.join(databaseDir, `${this.localStorageName()}.velocious-advisory-locks`)
40
40
 
41
41
  try {
42
- // @ts-expect-error
43
- this.connection = /** @type {import("sqlite3").Database} */ (await open({
42
+ this.connection = await open({
44
43
  filename: databasePath,
45
44
  driver: sqlite3.Database
46
- }))
45
+ })
47
46
  } catch (error) {
48
47
  if (error instanceof Error) {
49
48
  throw new Error(`Couldn't open database ${databasePath} because of ${error.constructor.name}: ${error.message}`, {cause: error})
@@ -79,6 +78,17 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
79
78
  return await query(this.connection, sql)
80
79
  }
81
80
 
81
+ /**
82
+ * Executes a mutation with affected-row metadata.
83
+ * @param {string} sql - Mutation SQL.
84
+ * @returns {Promise<number>} - Affected row count.
85
+ */
86
+ async _affectedRowsActual(sql) {
87
+ if (!this.connection) throw new Error("No connection")
88
+ const result = await this.connection.run(sql)
89
+ return result.changes || 0
90
+ }
91
+
82
92
  /**
83
93
  * Layers a filesystem lock directory on top of the in-process waiter
84
94
  * queue so SQLite deployments with multiple Node processes writing to
@@ -81,4 +81,17 @@ export default class VelociousDatabaseDriversSqliteNative extends Base {
81
81
  return query(this.connection, sql)
82
82
  })
83
83
  }
84
+
85
+ /**
86
+ * Executes a mutation with affected-row metadata.
87
+ * @param {string} sql - Mutation SQL.
88
+ * @returns {Promise<number>} - Affected row count.
89
+ */
90
+ async _affectedRowsActual(sql) {
91
+ return await this._queryMutex.sync(async () => {
92
+ if (!this.connection) throw new Error("Not connected yet")
93
+ const result = await this.connection.runAsync(sql)
94
+ return result.changes
95
+ })
96
+ }
84
97
  }
@@ -8,7 +8,7 @@ import Base from "./base.js"
8
8
 
9
9
  /**
10
10
  * VelociousDatabaseDriversSqliteWeb class.
11
- * @typedef {{query: (sql: string) => Promise<Record<string, ?>[]>, close: () => Promise<void>}} SqliteWebConnection
11
+ * @typedef {{query: (sql: string) => Promise<Record<string, ?>[]>, affectedRows: (sql: string) => Promise<number>, close: () => Promise<void>}} SqliteWebConnection
12
12
  */
13
13
 
14
14
  export default class VelociousDatabaseDriversSqliteWeb extends Base {
@@ -110,4 +110,14 @@ export default class VelociousDatabaseDriversSqliteWeb extends Base {
110
110
 
111
111
  return result
112
112
  }
113
+
114
+ /**
115
+ * Executes a mutation with affected-row metadata.
116
+ * @param {string} sql - Mutation SQL.
117
+ * @returns {Promise<number>} - Affected row count.
118
+ */
119
+ async _affectedRowsActual(sql) {
120
+ const connection = this.getConnection()
121
+ return await connection.affectedRows(sql)
122
+ }
113
123
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Runs query.
5
- * @param {import("sqlite3").Database} connection - Connection.
5
+ * @param {import("sqlite").Database} connection - Connection.
6
6
  * @param {string} sql - SQL string.
7
7
  * @returns {Promise<Record<string, ?>[]>} - Resolves with string value.
8
8
  */
@@ -13,7 +13,6 @@ export default async function query(connection, sql) {
13
13
  * @type {Record<string, ?>[]} */
14
14
  let result
15
15
 
16
- // @ts-expect-error
17
16
  result = await connection.all(sql)
18
17
 
19
18
  return result
@@ -67,11 +67,9 @@ export default class RequestBuffer {
67
67
  * @returns {Buffer | undefined} - Remaining data, if any.
68
68
  */
69
69
  feed(data) {
70
- for (let index = 0; index < data.length; index += 1) {
71
- const char = data[index]
72
-
73
- if (this.readingBody) this.bodyLength += 1
70
+ let index = 0
74
71
 
72
+ while (index < data.length) {
75
73
  switch(this.state) {
76
74
  case "status":
77
75
  case "headers":
@@ -79,101 +77,169 @@ export default class RequestBuffer {
79
77
  case "multi-part-form-data-header":
80
78
  case "chunked-size":
81
79
  case "chunked-trailer":
82
- this.data.push(char)
80
+ index = this.feedLine(data, index)
81
+ break
82
+ case "post-body":
83
+ index = this.feedPostBody(data, index)
84
+ break
85
+ default:
86
+ index = this.feedByte(data, index)
87
+ }
83
88
 
84
- if (char == 10) {
85
- const line = String.fromCharCode.apply(null, this.data)
89
+ if (this.completed) {
90
+ return data.subarray(index)
91
+ }
92
+ }
86
93
 
87
- this.data = []
88
- this.parse(line)
89
- }
94
+ return undefined
95
+ }
90
96
 
91
- break
92
- case "chunked-data": {
93
- const chunkedBodyChars = this.chunkedBodyChars
97
+ /**
98
+ * Consumes bytes for the line-based states up to and including the next newline.
99
+ * @param {Buffer} data - Data payload.
100
+ * @param {number} index - Read position.
101
+ * @returns {number} - New read position.
102
+ */
103
+ feedLine(data, index) {
104
+ const newlineIndex = data.indexOf(10, index)
94
105
 
95
- if (this.currentChunkSize === undefined) throw new Error("Chunk size not initialized")
96
- if (!chunkedBodyChars) throw new Error("Chunked body not initialized")
106
+ if (newlineIndex === -1) {
107
+ if (this.readingBody) this.bodyLength += data.length - index
97
108
 
98
- chunkedBodyChars.push(char)
99
- /**
100
- * Current chunk bytes read.
101
- * @type {number} */
102
- const currentChunkBytesRead = (this.currentChunkBytesRead || 0) + 1
109
+ for (let dataIndex = index; dataIndex < data.length; dataIndex += 1) {
110
+ this.data.push(data[dataIndex])
111
+ }
103
112
 
104
- this.currentChunkBytesRead = currentChunkBytesRead
113
+ return data.length
114
+ }
105
115
 
106
- if (currentChunkBytesRead >= this.currentChunkSize) {
107
- this.currentChunkCrlfRead = 0
108
- this.setState("chunked-data-crlf")
109
- }
116
+ if (this.readingBody) this.bodyLength += newlineIndex + 1 - index
110
117
 
111
- break
112
- }
113
- case "chunked-data-crlf":
114
- this.currentChunkCrlfRead = (this.currentChunkCrlfRead || 0) + 1
118
+ let line
115
119
 
116
- if (this.currentChunkCrlfRead >= 2) {
117
- this.currentChunkBytesRead = 0
118
- this.setState("chunked-size")
119
- }
120
+ if (this.data.length == 0) {
121
+ line = data.toString("latin1", index, newlineIndex + 1)
122
+ } else {
123
+ // The rest of a line that started in a previous chunk.
124
+ for (let dataIndex = index; dataIndex <= newlineIndex; dataIndex += 1) {
125
+ this.data.push(data[dataIndex])
126
+ }
120
127
 
121
- break
122
- case "multi-part-form-data-body":
123
- if (!this.formDataPart) throw new Error("FormData part not initialized")
124
- if (!this.boundaryLineEnd) throw new Error("Boundary line end not initialized")
125
- if (!this.boundaryLineNext) throw new Error("Boundary line next not initialized")
126
-
127
- const body = this.formDataPart.body // eslint-disable-line no-case-declarations
128
-
129
- body.push(char)
130
-
131
- const possibleBoundaryEndPosition = body.length - this.boundaryLineEnd.length // eslint-disable-line no-case-declarations
132
- const possibleBoundaryEndChars = body.slice(possibleBoundaryEndPosition, body.length) // eslint-disable-line no-case-declarations
133
- const possibleBoundaryEnd = String.fromCharCode.apply(null, possibleBoundaryEndChars) // eslint-disable-line no-case-declarations
134
-
135
- const possibleBoundaryNextPosition = body.length - this.boundaryLineNext.length // eslint-disable-line no-case-declarations
136
- const possibleBoundaryNextChars = body.slice(possibleBoundaryNextPosition, body.length) // eslint-disable-line no-case-declarations
137
- const possibleBoundaryNext = String.fromCharCode.apply(null, possibleBoundaryNextChars) // eslint-disable-line no-case-declarations
138
-
139
- if (possibleBoundaryEnd == this.boundaryLineEnd) {
140
- this.formDataPart.removeFromBody(possibleBoundaryEnd)
141
- this.formDataPartDone()
142
- this.completeRequest()
143
- } else if (possibleBoundaryNext == this.boundaryLineNext) {
144
- this.formDataPart.removeFromBody(possibleBoundaryNext)
145
- this.formDataPartDone()
146
- this.newFormDataPart()
147
- } else if (this.contentLength && this.bodyLength >= this.contentLength) {
148
- this.formDataPartDone()
149
- this.completeRequest()
150
- } else if (this.formDataPart.contentLength && this.bodyLength >= this.formDataPart.contentLength) {
151
- this.formDataPartDone()
152
-
153
- throw new Error("stub")
154
- }
128
+ line = String.fromCharCode.apply(null, this.data)
129
+ this.data = []
130
+ }
155
131
 
156
- break
157
- case "post-body":
158
- if (!this.postBodyChars) throw new Error("postBodyChars not initialized")
132
+ this.parse(line)
159
133
 
160
- this.postBodyChars[this.bodyLength - 1] = char
134
+ return newlineIndex + 1
135
+ }
161
136
 
162
- if (this.contentLength && this.bodyLength >= this.contentLength) {
163
- this.postRequestDone()
164
- }
137
+ /**
138
+ * Consumes fixed-length request body bytes in bulk.
139
+ * @param {Buffer} data - Data payload.
140
+ * @param {number} index - Read position.
141
+ * @returns {number} - New read position.
142
+ */
143
+ feedPostBody(data, index) {
144
+ if (!this.postBodyBuffers) throw new Error("postBodyBuffers not initialized")
145
+ if (this.contentLength === undefined) throw new Error("Content length not set")
165
146
 
166
- break
167
- default:
168
- this.logger.error(() => [`Unknown state for request buffer`, {state: this.state}])
147
+ const remainingBodyBytes = Math.max(1, this.contentLength - this.bodyLength)
148
+ const endIndex = Math.min(data.length, index + remainingBodyBytes)
149
+
150
+ this.postBodyBuffers.push(data.subarray(index, endIndex))
151
+ this.bodyLength += endIndex - index
152
+
153
+ if (this.contentLength && this.bodyLength >= this.contentLength) {
154
+ this.postRequestDone()
155
+ }
156
+
157
+ return endIndex
158
+ }
159
+
160
+ /**
161
+ * Consumes a single byte for the byte-based parser states.
162
+ * @param {Buffer} data - Data payload.
163
+ * @param {number} index - Read position.
164
+ * @returns {number} - New read position.
165
+ */
166
+ feedByte(data, index) {
167
+ const char = data[index]
168
+
169
+ if (this.readingBody) this.bodyLength += 1
170
+
171
+ switch(this.state) {
172
+ case "chunked-data": {
173
+ const chunkedBodyChars = this.chunkedBodyChars
174
+
175
+ if (this.currentChunkSize === undefined) throw new Error("Chunk size not initialized")
176
+ if (!chunkedBodyChars) throw new Error("Chunked body not initialized")
177
+
178
+ chunkedBodyChars.push(char)
179
+ /**
180
+ * Current chunk bytes read.
181
+ * @type {number} */
182
+ const currentChunkBytesRead = (this.currentChunkBytesRead || 0) + 1
183
+
184
+ this.currentChunkBytesRead = currentChunkBytesRead
185
+
186
+ if (currentChunkBytesRead >= this.currentChunkSize) {
187
+ this.currentChunkCrlfRead = 0
188
+ this.setState("chunked-data-crlf")
189
+ }
190
+
191
+ break
169
192
  }
193
+ case "chunked-data-crlf":
194
+ this.currentChunkCrlfRead = (this.currentChunkCrlfRead || 0) + 1
170
195
 
171
- if (this.completed) {
172
- return data.subarray(index + 1)
196
+ if (this.currentChunkCrlfRead >= 2) {
197
+ this.currentChunkBytesRead = 0
198
+ this.setState("chunked-size")
199
+ }
200
+
201
+ break
202
+ case "multi-part-form-data-body": {
203
+ if (!this.formDataPart) throw new Error("FormData part not initialized")
204
+ if (!this.boundaryLineEnd) throw new Error("Boundary line end not initialized")
205
+ if (!this.boundaryLineNext) throw new Error("Boundary line next not initialized")
206
+
207
+ const body = this.formDataPart.body
208
+
209
+ body.push(char)
210
+
211
+ const possibleBoundaryEndPosition = body.length - this.boundaryLineEnd.length
212
+ const possibleBoundaryEndChars = body.slice(possibleBoundaryEndPosition, body.length)
213
+ const possibleBoundaryEnd = String.fromCharCode.apply(null, possibleBoundaryEndChars)
214
+
215
+ const possibleBoundaryNextPosition = body.length - this.boundaryLineNext.length
216
+ const possibleBoundaryNextChars = body.slice(possibleBoundaryNextPosition, body.length)
217
+ const possibleBoundaryNext = String.fromCharCode.apply(null, possibleBoundaryNextChars)
218
+
219
+ if (possibleBoundaryEnd == this.boundaryLineEnd) {
220
+ this.formDataPart.removeFromBody(possibleBoundaryEnd)
221
+ this.formDataPartDone()
222
+ this.completeRequest()
223
+ } else if (possibleBoundaryNext == this.boundaryLineNext) {
224
+ this.formDataPart.removeFromBody(possibleBoundaryNext)
225
+ this.formDataPartDone()
226
+ this.newFormDataPart()
227
+ } else if (this.contentLength && this.bodyLength >= this.contentLength) {
228
+ this.formDataPartDone()
229
+ this.completeRequest()
230
+ } else if (this.formDataPart.contentLength && this.bodyLength >= this.formDataPart.contentLength) {
231
+ this.formDataPartDone()
232
+
233
+ throw new Error("stub")
234
+ }
235
+
236
+ break
173
237
  }
238
+ default:
239
+ this.logger.error(() => [`Unknown state for request buffer`, {state: this.state}])
174
240
  }
175
241
 
176
- return undefined
242
+ return index + 1
177
243
  }
178
244
 
179
245
  /**
@@ -347,11 +413,8 @@ export default class RequestBuffer {
347
413
  } else {
348
414
  /**
349
415
  * Narrows the runtime value to the documented type.
350
- * @type {number[]} */
351
- this.postBodyChars = []
352
-
353
- // this.postBodyBuffer = new ArrayBuffer(this.contentLength)
354
- // this.postBodyChars = new Uint8Array(this.postBodyBuffer)
416
+ * @type {Buffer[]} */
417
+ this.postBodyBuffers = []
355
418
 
356
419
  this.setState("post-body")
357
420
  }
@@ -379,12 +442,11 @@ export default class RequestBuffer {
379
442
  }
380
443
 
381
444
  postRequestDone() {
382
- if (this.postBodyChars) {
383
- this.postBody = Buffer.from(this.postBodyChars).toString("utf8")
445
+ if (this.postBodyBuffers) {
446
+ this.postBody = Buffer.concat(this.postBodyBuffers).toString("utf8")
384
447
  }
385
448
 
386
- delete this.postBodyChars
387
- // delete this.postBodyBuffer
449
+ delete this.postBodyBuffers
388
450
 
389
451
  this.completeRequest()
390
452
  }
@@ -9,11 +9,26 @@
9
9
  * @template {Array<?>} [TArgs=[]]
10
10
  */
11
11
  export default class VelociousJob<TArgs extends Array<unknown> = []> {
12
+ /**
13
+ * Queue this job class runs on. Subclasses set e.g. `static queue = "builds"`
14
+ * to route onto a queue with its own cluster-wide concurrency cap (configured
15
+ * via `backgroundJobs.queues`). The `{queue}` enqueue option overrides it.
16
+ * Left undefined, jobs run on the `"default"` queue.
17
+ * @type {string | undefined}
18
+ */
19
+ static queue: string | undefined;
12
20
  /**
13
21
  * Runs job name.
14
22
  * @returns {string} - Job name.
15
23
  */
16
24
  static jobName(): string;
25
+ /**
26
+ * Folds this job class's static `queue` into the enqueue options unless the
27
+ * caller already specified one.
28
+ * @param {import("./types.js").BackgroundJobOptions | undefined} options - Job options.
29
+ * @returns {import("./types.js").BackgroundJobOptions} - Options including the resolved queue.
30
+ */
31
+ static _withQueue(options: import("./types.js").BackgroundJobOptions | undefined): import("./types.js").BackgroundJobOptions;
17
32
  /**
18
33
  * Runs perform later.
19
34
  * @param {...?} args - Job args.
@@ -1 +1 @@
1
- {"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,kCAFyB,KAAK,SAAhB,KAAK,CAAC,OAAC,CAAE;IAGrB;;;OAGG;IACH,kBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,6BAHc,OAAC,EAAA,GACF,OAAO,CAAC,MAAM,CAAC,CAW3B;IAED;;;;;;OAMG;IACH,kDAJG;QAAuB,IAAI,EAAnB,KAAK,CAAC,OAAC,CAAC;QACyC,OAAO;KAChE,GAAU,OAAO,CAAC,MAAM,CAAC,CAU3B;IAED;;;;OAIG;IACH,kCAHW,KAAK,CAAC,OAAC,CAAC,GACN;QAAC,OAAO,EAAE,KAAK,CAAC,OAAC,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAA;KAAC,CAgBtF;IAED;;;;OAIG;IACH,kBAHW,KAAK,GACH,OAAO,CAAC,IAAI,CAAC,CAIzB;CACF"}
1
+ {"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,kCAFyB,KAAK,SAAhB,KAAK,CAAC,OAAC,CAAE;IAGrB;;;;;;OAMG;IACH,cAFU,MAAM,GAAG,SAAS,CAEJ;IAExB;;;OAGG;IACH,kBAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,2BAHW,OAAO,YAAY,EAAE,oBAAoB,GAAG,SAAS,GACnD,OAAO,YAAY,EAAE,oBAAoB,CAUrD;IAED;;;;OAIG;IACH,6BAHc,OAAC,EAAA,GACF,OAAO,CAAC,MAAM,CAAC,CAW3B;IAED;;;;;;OAMG;IACH,kDAJG;QAAuB,IAAI,EAAnB,KAAK,CAAC,OAAC,CAAC;QACyC,OAAO;KAChE,GAAU,OAAO,CAAC,MAAM,CAAC,CAU3B;IAED;;;;OAIG;IACH,kCAHW,KAAK,CAAC,OAAC,CAAC,GACN;QAAC,OAAO,EAAE,KAAK,CAAC,OAAC,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAA;KAAC,CAgBtF;IAED;;;;OAIG;IACH,kBAHW,KAAK,GACH,OAAO,CAAC,IAAI,CAAC,CAIzB;CACF"}