velocious 1.0.466 → 1.0.467

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 (29) hide show
  1. package/README.md +2 -1
  2. package/build/configuration.js +2 -2
  3. package/build/database/pool/async-tracked-multi-connection.js +7 -2
  4. package/build/database/pool/single-multi-use.js +38 -0
  5. package/build/frontend-model-controller.js +162 -56
  6. package/build/frontend-models/query.js +55 -33
  7. package/build/src/configuration.js +3 -3
  8. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  9. package/build/src/database/pool/async-tracked-multi-connection.js +9 -3
  10. package/build/src/database/pool/single-multi-use.d.ts +1 -0
  11. package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
  12. package/build/src/database/pool/single-multi-use.js +34 -1
  13. package/build/src/frontend-model-controller.d.ts +45 -5
  14. package/build/src/frontend-model-controller.d.ts.map +1 -1
  15. package/build/src/frontend-model-controller.js +155 -57
  16. package/build/src/frontend-models/query.d.ts +8 -0
  17. package/build/src/frontend-models/query.d.ts.map +1 -1
  18. package/build/src/frontend-models/query.js +53 -34
  19. package/build/src/utils/ransack.d.ts +11 -3
  20. package/build/src/utils/ransack.d.ts.map +1 -1
  21. package/build/src/utils/ransack.js +42 -23
  22. package/build/utils/ransack.js +44 -22
  23. package/package.json +1 -1
  24. package/src/configuration.js +2 -2
  25. package/src/database/pool/async-tracked-multi-connection.js +7 -2
  26. package/src/database/pool/single-multi-use.js +38 -0
  27. package/src/frontend-model-controller.js +162 -56
  28. package/src/frontend-models/query.js +55 -33
  29. package/src/utils/ransack.js +44 -22
@@ -4,6 +4,28 @@ import * as inflection from "inflection"
4
4
  import {isPlainObject} from "is-plain-object"
5
5
  import {resolveFrontendModelClass} from "../frontend-models/model-registry.js"
6
6
 
7
+ /** Error raised when a Ransack descriptor is malformed. */
8
+ export class RansackQueryError extends Error {
9
+ /**
10
+ * Creates a Ransack query error.
11
+ * @param {string} message - Error message.
12
+ */
13
+ constructor(message) {
14
+ super(message)
15
+
16
+ this.name = "RansackQueryError"
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Builds a Ransack query error.
22
+ * @param {string} message - Error message.
23
+ * @returns {RansackQueryError} - Ransack query error.
24
+ */
25
+ function ransackQueryError(message) {
26
+ return new RansackQueryError(message)
27
+ }
28
+
7
29
  /**
8
30
  * RansackPredicate type.
9
31
  * @typedef {"cont" | "end" | "eq" | "gt" | "gteq" | "in" | "lt" | "lteq" | "not_eq" | "not_in" | "null" | "start"} RansackPredicate
@@ -76,7 +98,7 @@ export function normalizeRansackParams(modelClass, params) {
76
98
  */
77
99
  export function normalizeRansackGroup(modelClass, params) {
78
100
  if (!isPlainObject(params)) {
79
- throw new Error(`ransack params must be a plain object, got: ${typeof params}`)
101
+ throw ransackQueryError(`ransack params must be a plain object, got: ${typeof params}`)
80
102
  }
81
103
 
82
104
  /**
@@ -124,7 +146,7 @@ function normalizeSimpleRansackCondition({key, modelClass, rawValue}) {
124
146
  const parsedKey = parseRansackKey(key)
125
147
 
126
148
  if (!parsedKey) {
127
- throw new Error(`Unsupported ransack predicate in key: ${key}`)
149
+ throw ransackQueryError(`Unsupported ransack predicate in key: ${key}`)
128
150
  }
129
151
 
130
152
  const value = normalizeRansackValue({
@@ -159,17 +181,17 @@ function normalizeAdvancedRansackConditions({modelClass, value}) {
159
181
 
160
182
  for (const entry of normalizeRansackCollection(value, "conditions")) {
161
183
  if (!isPlainObject(entry)) {
162
- throw new Error(`Ransack condition entries must be plain objects, got: ${typeof entry}`)
184
+ throw ransackQueryError(`Ransack condition entries must be plain objects, got: ${typeof entry}`)
163
185
  }
164
186
 
165
187
  const predicateValue = entry.p
166
188
 
167
189
  if (typeof predicateValue !== "string") {
168
- throw new Error("Ransack condition predicate must be a string")
190
+ throw ransackQueryError("Ransack condition predicate must be a string")
169
191
  }
170
192
 
171
193
  if (!supportedPredicates.includes(predicateValue)) {
172
- throw new Error(`Unsupported ransack predicate in condition: ${predicateValue}`)
194
+ throw ransackQueryError(`Unsupported ransack predicate in condition: ${predicateValue}`)
173
195
  }
174
196
 
175
197
  const predicate = /** @type {RansackPredicate} */ (predicateValue)
@@ -206,7 +228,7 @@ function normalizeAdvancedRansackGroups({modelClass, value}) {
206
228
 
207
229
  for (const entry of normalizeRansackCollection(value, "groupings")) {
208
230
  if (!isPlainObject(entry)) {
209
- throw new Error(`Ransack grouping entries must be plain objects, got: ${typeof entry}`)
231
+ throw ransackQueryError(`Ransack grouping entries must be plain objects, got: ${typeof entry}`)
210
232
  }
211
233
 
212
234
  groupings.push(normalizeRansackGroup(modelClass, entry))
@@ -225,7 +247,7 @@ function normalizeRansackCombinator(value, defaultValue) {
225
247
  if (value === undefined || value === null || value === "") return defaultValue
226
248
  if (value === "and" || value === "or") return value
227
249
 
228
- throw new Error(`Invalid ransack combinator: ${String(value)}`)
250
+ throw ransackQueryError(`Invalid ransack combinator: ${String(value)}`)
229
251
  }
230
252
 
231
253
  /**
@@ -253,7 +275,7 @@ function normalizeRansackCollection(value, name) {
253
275
  .map((key) => value[key])
254
276
  }
255
277
 
256
- throw new Error(`Ransack ${name} must be an array or object, got: ${typeof value}`)
278
+ throw ransackQueryError(`Ransack ${name} must be an array or object, got: ${typeof value}`)
257
279
  }
258
280
 
259
281
  /**
@@ -292,7 +314,7 @@ function resolveRansackAttributesFromAdvancedValue({modelClass, value}) {
292
314
  }
293
315
 
294
316
  if (attributes.length < 1) {
295
- throw new Error("Ransack condition must include at least one attribute")
317
+ throw ransackQueryError("Ransack condition must include at least one attribute")
296
318
  }
297
319
 
298
320
  return attributes
@@ -314,7 +336,7 @@ function normalizeAdvancedAttributeValues(value) {
314
336
  return normalizeRansackCollection(value, "attributes").map((entry) => normalizeAdvancedAttributeValue(entry))
315
337
  }
316
338
 
317
- throw new Error(`Ransack condition attributes must be strings, arrays, or objects, got: ${typeof value}`)
339
+ throw ransackQueryError(`Ransack condition attributes must be strings, arrays, or objects, got: ${typeof value}`)
318
340
  }
319
341
 
320
342
  /**
@@ -329,7 +351,7 @@ function normalizeAdvancedAttributeValue(value) {
329
351
  return value.name
330
352
  }
331
353
 
332
- throw new Error(`Ransack condition attribute entries must be strings or {name}, got: ${typeof value}`)
354
+ throw ransackQueryError(`Ransack condition attribute entries must be strings or {name}, got: ${typeof value}`)
333
355
  }
334
356
 
335
357
  /**
@@ -346,7 +368,7 @@ function resolveRansackAttributes({modelClass, value}) {
346
368
  const attributeName = resolveAttributeName({modelClass: targetModelClass, value: resolvedPath.attributeValue})
347
369
 
348
370
  if (!attributeName) {
349
- throw new Error(`Unknown ransack attribute "${resolvedPath.attributeValue}" for ${targetModelClass.name}`)
371
+ throw ransackQueryError(`Unknown ransack attribute "${resolvedPath.attributeValue}" for ${targetModelClass.name}`)
350
372
  }
351
373
 
352
374
  return {
@@ -370,7 +392,7 @@ function modelClassAtPath({modelClass, path}) {
370
392
  const relationship = relationshipEntries(currentModelClass)[relationshipName]
371
393
 
372
394
  if (!relationship) {
373
- throw new Error(`Unknown ransack relationship "${relationshipName}" for ${currentModelClass.name}`)
395
+ throw ransackQueryError(`Unknown ransack relationship "${relationshipName}" for ${currentModelClass.name}`)
374
396
  }
375
397
 
376
398
  currentModelClass = relationship.targetModelClass
@@ -412,7 +434,7 @@ function resolveRansackPath({modelClass, value}) {
412
434
  }
413
435
 
414
436
  if (remainingValue.length < 1) {
415
- throw new Error(`Invalid ransack key path: ${value}`)
437
+ throw ransackQueryError(`Invalid ransack key path: ${value}`)
416
438
  }
417
439
 
418
440
  return {
@@ -648,7 +670,7 @@ function parseRansackKey(key) {
648
670
  const pathValue = key.slice(0, key.length - suffix.length)
649
671
 
650
672
  if (pathValue.length < 1) {
651
- throw new Error(`Invalid ransack key: ${key}`)
673
+ throw ransackQueryError(`Invalid ransack key: ${key}`)
652
674
  }
653
675
 
654
676
  return {
@@ -665,7 +687,7 @@ function parseRansackKey(key) {
665
687
  const pathValue = key.slice(0, key.length - camelSuffix.length)
666
688
 
667
689
  if (pathValue.length < 1) {
668
- throw new Error(`Invalid ransack key: ${key}`)
690
+ throw ransackQueryError(`Invalid ransack key: ${key}`)
669
691
  }
670
692
 
671
693
  return {
@@ -750,7 +772,7 @@ function normalizeRansackBoolean(value) {
750
772
  if (ransackFalseValues.has(value)) return false
751
773
  if (ransackValueIsBlank(value)) return null
752
774
 
753
- throw new Error(`Invalid ransack boolean value: ${String(value)}`)
775
+ throw ransackQueryError(`Invalid ransack boolean value: ${String(value)}`)
754
776
  }
755
777
 
756
778
  /**
@@ -789,10 +811,10 @@ function normalizeRansackArray(value) {
789
811
  */
790
812
 
791
813
  /**
792
- * Parses and validates a ransack `s` sort string against model attributes.
793
- * @param {RansackModelClass} modelClass - Model class for attribute validation.
814
+ * Parses a ransack `s` sort string against model attributes.
815
+ * @param {RansackModelClass} modelClass - Model class for attribute lookup.
794
816
  * @param {string} sortString - Ransack sort string (e.g., "name asc" or "name asc, createdAt desc").
795
- * @returns {RansackSort[]} - Validated sort definitions.
817
+ * @returns {RansackSort[]} - Parsed sort definitions.
796
818
  */
797
819
  export function parseRansackSort(modelClass, sortString) {
798
820
  const segments = sortString.split(",").map((segment) => segment.trim()).filter((segment) => segment.length > 0)
@@ -808,13 +830,13 @@ export function parseRansackSort(modelClass, sortString) {
808
830
  const directionCandidate = parts.length > 1 ? parts[1].toLowerCase() : "asc"
809
831
 
810
832
  if (directionCandidate !== "asc" && directionCandidate !== "desc") {
811
- throw new Error(`Invalid ransack sort direction "${directionCandidate}" in: ${segment}`)
833
+ throw ransackQueryError(`Invalid ransack sort direction "${directionCandidate}" in: ${segment}`)
812
834
  }
813
835
 
814
836
  const resolvedAttribute = resolveAttributeName({modelClass, value: columnCandidate})
815
837
 
816
838
  if (!resolvedAttribute) {
817
- throw new Error(`Unknown ransack sort attribute "${columnCandidate}" for ${modelClass.name}`)
839
+ throw ransackQueryError(`Unknown ransack sort attribute "${columnCandidate}" for ${modelClass.name}`)
818
840
  }
819
841
 
820
842
  sorts.push({attribute: resolvedAttribute, direction: directionCandidate})
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.466",
6
+ "version": "1.0.467",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -2230,8 +2230,8 @@ export default class VelociousConfiguration {
2230
2230
 
2231
2231
  if (!matches) continue
2232
2232
 
2233
- this.withoutCurrentConnectionContexts(() => {
2234
- void Promise
2233
+ void this.withoutCurrentConnectionContexts(() => {
2234
+ return Promise
2235
2235
  .resolve()
2236
2236
  .then(() => this._deliverWebsocketChannelBroadcast(subscription, body, {eventId: meta?.eventId}))
2237
2237
  .catch((error) => {
@@ -6,6 +6,7 @@ import BasePool, {POOL_CONFIGURATION_KEY} from "./base.js"
6
6
  export const CLOSED_CONNECTION = Symbol("velociousClosedConnection")
7
7
  const IDLE_CONNECTION_CHECKED_IN_AT = Symbol("velociousIdleConnectionCheckedInAt")
8
8
  const CONNECTION_CHECKED_OUT_AT = Symbol("velociousConnectionCheckedOutAt")
9
+ const SUPPRESSED_CONNECTION_CONTEXT = Symbol("velociousSuppressedConnectionContext")
9
10
  const DEFAULT_MAX_CONNECTIONS = 10
10
11
  const DEFAULT_IDLE_TIMEOUT_MILLIS = 5000
11
12
  const DEFAULT_CHECKOUT_TIMEOUT_MILLIS = 10000
@@ -103,7 +104,7 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
103
104
  * Runs a callback without the inherited current connection context.
104
105
  * @type {(callback: () => ?) => ?}
105
106
  */
106
- const withoutCurrentConnectionContext = (callback) => this.asyncLocalStorage.run(undefined, callback)
107
+ const withoutCurrentConnectionContext = (callback) => this.asyncLocalStorage.run(SUPPRESSED_CONNECTION_CONTEXT, callback)
107
108
  this._withoutCurrentConnectionContext = withoutCurrentConnectionContext
108
109
  }
109
110
 
@@ -720,6 +721,7 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
720
721
  const id = this.asyncLocalStorage.getStore()
721
722
 
722
723
  if (id === undefined) return this.currentFallbackConnectionOrFail()
724
+ if (id === SUPPRESSED_CONNECTION_CONTEXT) return this.currentFallbackConnectionOrFail()
723
725
 
724
726
  this.ensureConnectionIsInUse(id)
725
727
 
@@ -815,6 +817,7 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
815
817
  getCurrentContextConnection() {
816
818
  const id = this.asyncLocalStorage.getStore()
817
819
 
820
+ if (id === SUPPRESSED_CONNECTION_CONTEXT) return undefined
818
821
  if (id === undefined) return this._testSharedConnection
819
822
 
820
823
  return this.getCurrentConnection()
@@ -825,7 +828,9 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
825
828
  * @returns {boolean} - Whether nested code can reuse the current connection context.
826
829
  */
827
830
  hasCurrentConnectionContext() {
828
- return this.asyncLocalStorage.getStore() !== undefined
831
+ const id = this.asyncLocalStorage.getStore()
832
+
833
+ return id !== undefined && id !== SUPPRESSED_CONNECTION_CONTEXT
829
834
  }
830
835
 
831
836
  /**
@@ -3,6 +3,8 @@
3
3
  import BasePool from "./base.js"
4
4
 
5
5
  export default class VelociousDatabasePoolSingleMultiUser extends BasePool {
6
+ suppressedConnectionContextCount = 0
7
+
6
8
  /**
7
9
  * Runs checkin.
8
10
  * @param {import("../drivers/base.js").default} connection - Connection.
@@ -58,6 +60,32 @@ export default class VelociousDatabasePoolSingleMultiUser extends BasePool {
58
60
  }
59
61
  }
60
62
 
63
+ /**
64
+ * Runs without current connection context.
65
+ * @template T
66
+ * @param {() => T} callback - Callback to run without the shared current connection.
67
+ * @returns {T} - Callback result.
68
+ */
69
+ withoutCurrentConnectionContext(callback) {
70
+ this.suppressedConnectionContextCount += 1
71
+
72
+ try {
73
+ const result = callback()
74
+
75
+ if (result instanceof Promise) {
76
+ return /** @type {T} */ (result.finally(() => {
77
+ this.suppressedConnectionContextCount -= 1
78
+ }))
79
+ }
80
+
81
+ this.suppressedConnectionContextCount -= 1
82
+ return result
83
+ } catch (error) {
84
+ this.suppressedConnectionContextCount -= 1
85
+ throw error
86
+ }
87
+ }
88
+
61
89
  /**
62
90
  * Clears schema metadata cached by the reusable connection if it exists.
63
91
  * @returns {void} - No return value.
@@ -97,9 +125,19 @@ export default class VelociousDatabasePoolSingleMultiUser extends BasePool {
97
125
  * @returns {import("../drivers/base.js").default | undefined} - The current context connection.
98
126
  */
99
127
  getCurrentContextConnection() {
128
+ if (this.suppressedConnectionContextCount > 0) return undefined
129
+
100
130
  return this.connection
101
131
  }
102
132
 
133
+ /**
134
+ * Returns whether the shared connection is available to the current execution context.
135
+ * @returns {boolean} - Whether nested code can reuse the shared connection.
136
+ */
137
+ hasCurrentConnectionContext() {
138
+ return this.suppressedConnectionContextCount === 0
139
+ }
140
+
103
141
  /**
104
142
  * Runs get debug snapshot.
105
143
  * @returns {import("./base.js").DatabasePoolDebugSnapshot} - Diagnostic snapshot for this pool.