velocious 1.0.474 → 1.0.476

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 (141) hide show
  1. package/README.md +10 -2
  2. package/build/configuration-types.js +39 -1
  3. package/build/configuration.js +46 -1
  4. package/build/database/drivers/base.js +67 -3
  5. package/build/database/drivers/mssql/index.js +13 -0
  6. package/build/database/drivers/mssql/sql/remove-index.js +6 -0
  7. package/build/database/drivers/mysql/index.js +87 -7
  8. package/build/database/drivers/mysql/sql/remove-index.js +6 -0
  9. package/build/database/drivers/pgsql/index.js +13 -0
  10. package/build/database/drivers/pgsql/sql/remove-index.js +6 -0
  11. package/build/database/drivers/sqlite/base.js +13 -0
  12. package/build/database/drivers/sqlite/connection-sql-js.js +16 -5
  13. package/build/database/drivers/sqlite/index.web.js +29 -15
  14. package/build/database/drivers/sqlite/sql/remove-index.js +6 -0
  15. package/build/database/drivers/sqlite/web-persistence.js +521 -0
  16. package/build/database/migration/index.js +44 -0
  17. package/build/database/pool/base-methods-forward.js +1 -0
  18. package/build/database/query/remove-index-base.js +39 -0
  19. package/build/database/record/index.js +25 -9
  20. package/build/environment-handlers/node/cli/commands/generate/base-models.js +36 -0
  21. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +63 -0
  22. package/build/frontend-model-controller.js +401 -4
  23. package/build/frontend-model-resource/base-resource.js +5 -1
  24. package/build/frontend-models/base.js +146 -2
  25. package/build/frontend-models/query.js +3 -3
  26. package/build/frontend-models/resource-definition.js +304 -2
  27. package/build/routes/hooks/frontend-model-command-route-hook.js +16 -0
  28. package/build/src/configuration-types.d.ts +118 -2
  29. package/build/src/configuration-types.d.ts.map +1 -1
  30. package/build/src/configuration-types.js +36 -2
  31. package/build/src/configuration.d.ts +18 -1
  32. package/build/src/configuration.d.ts.map +1 -1
  33. package/build/src/configuration.js +42 -2
  34. package/build/src/database/drivers/base.d.ts +50 -0
  35. package/build/src/database/drivers/base.d.ts.map +1 -1
  36. package/build/src/database/drivers/base.js +62 -4
  37. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  38. package/build/src/database/drivers/mssql/index.js +12 -1
  39. package/build/src/database/drivers/mssql/sql/remove-index.d.ts +4 -0
  40. package/build/src/database/drivers/mssql/sql/remove-index.d.ts.map +1 -0
  41. package/build/src/database/drivers/mssql/sql/remove-index.js +5 -0
  42. package/build/src/database/drivers/mysql/index.d.ts +32 -3
  43. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  44. package/build/src/database/drivers/mysql/index.js +76 -7
  45. package/build/src/database/drivers/mysql/sql/remove-index.d.ts +4 -0
  46. package/build/src/database/drivers/mysql/sql/remove-index.d.ts.map +1 -0
  47. package/build/src/database/drivers/mysql/sql/remove-index.js +5 -0
  48. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  49. package/build/src/database/drivers/pgsql/index.js +12 -1
  50. package/build/src/database/drivers/pgsql/sql/remove-index.d.ts +4 -0
  51. package/build/src/database/drivers/pgsql/sql/remove-index.d.ts.map +1 -0
  52. package/build/src/database/drivers/pgsql/sql/remove-index.js +5 -0
  53. package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
  54. package/build/src/database/drivers/sqlite/base.js +12 -1
  55. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +8 -1
  56. package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
  57. package/build/src/database/drivers/sqlite/connection-sql-js.js +15 -6
  58. package/build/src/database/drivers/sqlite/index.web.d.ts +5 -5
  59. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  60. package/build/src/database/drivers/sqlite/index.web.js +27 -13
  61. package/build/src/database/drivers/sqlite/sql/remove-index.d.ts +4 -0
  62. package/build/src/database/drivers/sqlite/sql/remove-index.d.ts.map +1 -0
  63. package/build/src/database/drivers/sqlite/sql/remove-index.js +5 -0
  64. package/build/src/database/drivers/sqlite/web-persistence.d.ts +77 -0
  65. package/build/src/database/drivers/sqlite/web-persistence.d.ts.map +1 -0
  66. package/build/src/database/drivers/sqlite/web-persistence.js +464 -0
  67. package/build/src/database/migration/index.d.ts +25 -0
  68. package/build/src/database/migration/index.d.ts.map +1 -1
  69. package/build/src/database/migration/index.js +39 -1
  70. package/build/src/database/pool/base-methods-forward.d.ts.map +1 -1
  71. package/build/src/database/pool/base-methods-forward.js +2 -1
  72. package/build/src/database/query/remove-index-base.d.ts +35 -0
  73. package/build/src/database/query/remove-index-base.d.ts.map +1 -0
  74. package/build/src/database/query/remove-index-base.js +34 -0
  75. package/build/src/database/record/index.d.ts +11 -0
  76. package/build/src/database/record/index.d.ts.map +1 -1
  77. package/build/src/database/record/index.js +26 -11
  78. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +15 -0
  79. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  80. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +33 -1
  81. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +30 -0
  82. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  83. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +53 -1
  84. package/build/src/frontend-model-controller.d.ts +96 -0
  85. package/build/src/frontend-model-controller.d.ts.map +1 -1
  86. package/build/src/frontend-model-controller.js +359 -5
  87. package/build/src/frontend-model-resource/base-resource.d.ts +4 -2
  88. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  89. package/build/src/frontend-model-resource/base-resource.js +7 -2
  90. package/build/src/frontend-models/base.d.ts +29 -0
  91. package/build/src/frontend-models/base.d.ts.map +1 -1
  92. package/build/src/frontend-models/base.js +131 -3
  93. package/build/src/frontend-models/query.js +4 -4
  94. package/build/src/frontend-models/resource-definition.d.ts +6 -0
  95. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  96. package/build/src/frontend-models/resource-definition.js +272 -3
  97. package/build/src/routes/hooks/frontend-model-command-route-hook.d.ts.map +1 -1
  98. package/build/src/routes/hooks/frontend-model-command-route-hook.js +15 -1
  99. package/build/src/sync/device-identity.d.ts +240 -0
  100. package/build/src/sync/device-identity.d.ts.map +1 -0
  101. package/build/src/sync/device-identity.js +454 -0
  102. package/build/src/sync/local-mutation-log.d.ts +212 -0
  103. package/build/src/sync/local-mutation-log.d.ts.map +1 -0
  104. package/build/src/sync/local-mutation-log.js +404 -0
  105. package/build/src/sync/offline-grant.d.ts +157 -0
  106. package/build/src/sync/offline-grant.d.ts.map +1 -0
  107. package/build/src/sync/offline-grant.js +292 -0
  108. package/build/sync/device-identity.js +503 -0
  109. package/build/sync/local-mutation-log.js +448 -0
  110. package/build/sync/offline-grant.js +327 -0
  111. package/build/tsconfig.tsbuildinfo +1 -1
  112. package/package.json +3 -3
  113. package/src/configuration-types.js +39 -1
  114. package/src/configuration.js +46 -1
  115. package/src/database/drivers/base.js +67 -3
  116. package/src/database/drivers/mssql/index.js +13 -0
  117. package/src/database/drivers/mssql/sql/remove-index.js +6 -0
  118. package/src/database/drivers/mysql/index.js +87 -7
  119. package/src/database/drivers/mysql/sql/remove-index.js +6 -0
  120. package/src/database/drivers/pgsql/index.js +13 -0
  121. package/src/database/drivers/pgsql/sql/remove-index.js +6 -0
  122. package/src/database/drivers/sqlite/base.js +13 -0
  123. package/src/database/drivers/sqlite/connection-sql-js.js +16 -5
  124. package/src/database/drivers/sqlite/index.web.js +29 -15
  125. package/src/database/drivers/sqlite/sql/remove-index.js +6 -0
  126. package/src/database/drivers/sqlite/web-persistence.js +521 -0
  127. package/src/database/migration/index.js +44 -0
  128. package/src/database/pool/base-methods-forward.js +1 -0
  129. package/src/database/query/remove-index-base.js +39 -0
  130. package/src/database/record/index.js +25 -9
  131. package/src/environment-handlers/node/cli/commands/generate/base-models.js +36 -0
  132. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +63 -0
  133. package/src/frontend-model-controller.js +401 -4
  134. package/src/frontend-model-resource/base-resource.js +5 -1
  135. package/src/frontend-models/base.js +146 -2
  136. package/src/frontend-models/query.js +3 -3
  137. package/src/frontend-models/resource-definition.js +304 -2
  138. package/src/routes/hooks/frontend-model-command-route-hook.js +16 -0
  139. package/src/sync/device-identity.js +503 -0
  140. package/src/sync/local-mutation-log.js +448 -0
  141. package/src/sync/offline-grant.js +327 -0
@@ -0,0 +1,327 @@
1
+ // @ts-check
2
+
3
+ const OFFLINE_GRANT_SIGNATURE_ALGORITHM = "HS256"
4
+ const OFFLINE_GRANT_SIGNATURE_PREFIX = "hmac-sha256-"
5
+
6
+ /**
7
+ * Signed offline grant envelope.
8
+ * @typedef {object} SignedOfflineGrant
9
+ * @property {"HS256"} algorithm - Signature algorithm.
10
+ * @property {OfflineGrant} grant - Signed grant payload.
11
+ * @property {string} keyId - Signing key id.
12
+ * @property {string} signature - Hex HMAC signature with a hmac-sha256 prefix.
13
+ */
14
+
15
+ /**
16
+ * Backend-issued offline grant payload.
17
+ * @typedef {object} OfflineGrant
18
+ * @property {string} deviceId - Device id allowed to use the grant.
19
+ * @property {string} expiresAt - ISO timestamp after which replay must reject the grant.
20
+ * @property {string} grantId - Stable grant id.
21
+ * @property {string} issuedAt - ISO timestamp when the backend issued the grant.
22
+ * @property {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} resources - Sync manifest/materialized resource metadata.
23
+ * @property {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} scopes - Materialized grant scopes.
24
+ * @property {string} userId - Actor user id allowed to use the grant.
25
+ */
26
+
27
+ /**
28
+ * Offline grant signing key.
29
+ * @typedef {object} OfflineGrantSigningKey
30
+ * @property {boolean} [current] - Whether this is the active key for new grants.
31
+ * @property {string} id - Public key id included in signed grant envelopes.
32
+ * @property {string} secret - Private HMAC secret. Never expose this to clients.
33
+ */
34
+
35
+ /**
36
+ * Creates a signed offline grant envelope.
37
+ * @param {object} args - Arguments.
38
+ * @param {OfflineGrant} args.grant - Grant payload.
39
+ * @param {OfflineGrantSigningKey} args.signingKey - Current signing key.
40
+ * @returns {Promise<SignedOfflineGrant>} - Signed grant envelope.
41
+ */
42
+ export async function createOfflineGrant({grant, signingKey}) {
43
+ const normalizedGrant = normalizeOfflineGrant(grant)
44
+ const normalizedSigningKey = normalizeOfflineGrantSigningKey(signingKey)
45
+ const signatureInput = offlineGrantSignatureInput({
46
+ algorithm: OFFLINE_GRANT_SIGNATURE_ALGORITHM,
47
+ grant: normalizedGrant,
48
+ keyId: normalizedSigningKey.id
49
+ })
50
+
51
+ return {
52
+ algorithm: OFFLINE_GRANT_SIGNATURE_ALGORITHM,
53
+ grant: normalizedGrant,
54
+ keyId: normalizedSigningKey.id,
55
+ signature: await hmacSha256Hex({message: signatureInput, secret: normalizedSigningKey.secret})
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Verifies a signed offline grant and returns the trusted grant payload.
61
+ * @param {object} args - Arguments.
62
+ * @param {Date} [args.now] - Verification time. Defaults to current time.
63
+ * @param {SignedOfflineGrant} args.signedGrant - Signed grant envelope.
64
+ * @param {OfflineGrantSigningKey[]} args.signingKeys - Candidate verification keys.
65
+ * @returns {Promise<OfflineGrant>} - Verified grant payload.
66
+ */
67
+ export async function verifyOfflineGrant({now = new Date(), signedGrant, signingKeys}) {
68
+ const normalizedSignedGrant = normalizeSignedOfflineGrant(signedGrant)
69
+
70
+ if (normalizedSignedGrant.algorithm !== OFFLINE_GRANT_SIGNATURE_ALGORITHM) {
71
+ throw new Error(`Unsupported offline grant signature algorithm '${normalizedSignedGrant.algorithm}'`)
72
+ }
73
+
74
+ const signingKey = signingKeys
75
+ .map((key) => normalizeOfflineGrantSigningKey(key))
76
+ .find((key) => key.id === normalizedSignedGrant.keyId)
77
+
78
+ if (!signingKey) throw new Error(`No offline grant signing key for key id '${normalizedSignedGrant.keyId}'`)
79
+
80
+ const expectedSignature = await hmacSha256Hex({
81
+ message: offlineGrantSignatureInput(normalizedSignedGrant),
82
+ secret: signingKey.secret
83
+ })
84
+
85
+ if (expectedSignature !== normalizedSignedGrant.signature) {
86
+ throw new Error("Offline grant signature did not match")
87
+ }
88
+
89
+ if (Number.isNaN(now.getTime())) throw new Error("Invalid offline grant verification time")
90
+ if (new Date(normalizedSignedGrant.grant.expiresAt).getTime() <= now.getTime()) throw new Error("Offline grant expired")
91
+
92
+ return normalizedSignedGrant.grant
93
+ }
94
+
95
+ /**
96
+ * Builds a signed grant from bootstrap request inputs.
97
+ * @param {object} args - Arguments.
98
+ * @param {string} args.deviceId - Device id.
99
+ * @param {string | undefined} [args.grantId] - Optional deterministic grant id for tests/custom callers.
100
+ * @param {number | undefined} [args.grantTtlMs] - Grant TTL in milliseconds.
101
+ * @param {Date} args.now - Issue time.
102
+ * @param {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} args.resources - Resource sync manifest.
103
+ * @param {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} args.scopes - Grant scopes.
104
+ * @param {OfflineGrantSigningKey} args.signingKey - Current signing key.
105
+ * @param {string} args.userId - Actor user id.
106
+ * @returns {Promise<SignedOfflineGrant>} - Signed grant.
107
+ */
108
+ export async function createOfflineGrantFromBootstrap({deviceId, grantId, grantTtlMs, now, resources, scopes, signingKey, userId}) {
109
+ const issuedAt = isoDateString(now, "issuedAt")
110
+ const ttlMs = grantTtlMs === undefined ? 24 * 60 * 60 * 1000 : grantTtlMs
111
+
112
+ if (!Number.isInteger(ttlMs) || ttlMs <= 0) throw new Error("Offline grant TTL must be a positive integer number of milliseconds")
113
+
114
+ return await createOfflineGrant({
115
+ grant: {
116
+ deviceId,
117
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
118
+ grantId: grantId || randomGrantId(),
119
+ issuedAt,
120
+ resources,
121
+ scopes,
122
+ userId
123
+ },
124
+ signingKey
125
+ })
126
+ }
127
+
128
+ /**
129
+ * Normalizes signed grant signature input.
130
+ * @param {{algorithm: "HS256", grant: OfflineGrant, keyId: string}} signedGrant - Grant signature fields.
131
+ * @returns {string} - Stable signature input.
132
+ */
133
+ function offlineGrantSignatureInput(signedGrant) {
134
+ return stableJsonStringify({
135
+ algorithm: signedGrant.algorithm,
136
+ grant: signedGrant.grant,
137
+ keyId: signedGrant.keyId
138
+ })
139
+ }
140
+
141
+ /**
142
+ * Normalizes a signed offline grant envelope.
143
+ * @param {SignedOfflineGrant} signedGrant - Signed grant.
144
+ * @returns {SignedOfflineGrant} - Normalized signed grant.
145
+ */
146
+ function normalizeSignedOfflineGrant(signedGrant) {
147
+ if (!signedGrant || typeof signedGrant !== "object" || Array.isArray(signedGrant)) throw new Error("Expected signed offline grant object")
148
+ if (signedGrant.algorithm !== OFFLINE_GRANT_SIGNATURE_ALGORITHM) throw new Error(`Unsupported offline grant signature algorithm '${String(signedGrant.algorithm)}'`)
149
+ if (typeof signedGrant.keyId !== "string" || signedGrant.keyId.length < 1) throw new Error("Expected offline grant key id")
150
+ if (typeof signedGrant.signature !== "string" || !signedGrant.signature.match(/^hmac-sha256-[a-f0-9]{64}$/)) throw new Error("Expected offline grant signature")
151
+
152
+ return {
153
+ algorithm: signedGrant.algorithm,
154
+ grant: normalizeOfflineGrant(signedGrant.grant),
155
+ keyId: signedGrant.keyId,
156
+ signature: signedGrant.signature
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Normalizes an offline grant payload.
162
+ * @param {OfflineGrant} grant - Grant payload.
163
+ * @returns {OfflineGrant} - Normalized grant payload.
164
+ */
165
+ function normalizeOfflineGrant(grant) {
166
+ if (!grant || typeof grant !== "object" || Array.isArray(grant)) throw new Error("Expected offline grant object")
167
+
168
+ return {
169
+ deviceId: requiredString(grant.deviceId, "deviceId"),
170
+ expiresAt: isoDateString(new Date(requiredString(grant.expiresAt, "expiresAt")), "expiresAt"),
171
+ grantId: requiredString(grant.grantId, "grantId"),
172
+ issuedAt: isoDateString(new Date(requiredString(grant.issuedAt, "issuedAt")), "issuedAt"),
173
+ resources: deterministicJsonObject({label: "resources", value: grant.resources}),
174
+ scopes: deterministicJsonObject({label: "scopes", value: grant.scopes}),
175
+ userId: requiredString(grant.userId, "userId")
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Normalizes an offline grant signing key.
181
+ * @param {OfflineGrantSigningKey} signingKey - Signing key.
182
+ * @returns {OfflineGrantSigningKey} - Normalized signing key.
183
+ */
184
+ export function normalizeOfflineGrantSigningKey(signingKey) {
185
+ if (!signingKey || typeof signingKey !== "object" || Array.isArray(signingKey)) throw new Error("Expected offline grant signing key object")
186
+
187
+ return {
188
+ current: signingKey.current === true,
189
+ id: requiredString(signingKey.id, "signingKey.id"),
190
+ secret: requiredString(signingKey.secret, "signingKey.secret")
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Returns the current key used to sign new grants.
196
+ * @param {OfflineGrantSigningKey[]} signingKeys - Configured signing keys.
197
+ * @returns {OfflineGrantSigningKey} - Current signing key.
198
+ */
199
+ export function currentOfflineGrantSigningKey(signingKeys) {
200
+ const normalizedKeys = signingKeys.map((key) => normalizeOfflineGrantSigningKey(key))
201
+ const currentKeys = normalizedKeys.filter((key) => key.current === true)
202
+
203
+ if (normalizedKeys.length < 1) throw new Error("At least one offline grant signing key is required")
204
+ if (currentKeys.length > 1) throw new Error("Only one offline grant signing key can be current")
205
+
206
+ return currentKeys[0] || normalizedKeys[0]
207
+ }
208
+
209
+ /**
210
+ * Signs a message using HMAC-SHA256 and returns a prefixed hex signature.
211
+ * @param {object} args - Arguments.
212
+ * @param {string} args.message - Message to sign.
213
+ * @param {string} args.secret - HMAC secret.
214
+ * @returns {Promise<string>} - Prefixed hex signature.
215
+ */
216
+ async function hmacSha256Hex({message, secret}) {
217
+ const cryptoProvider = globalThis.crypto
218
+
219
+ if (!cryptoProvider?.subtle) throw new Error("WebCrypto subtle API is required for offline grant signing")
220
+
221
+ const key = await cryptoProvider.subtle.importKey(
222
+ "raw",
223
+ new TextEncoder().encode(secret),
224
+ {hash: "SHA-256", name: "HMAC"},
225
+ false,
226
+ ["sign"]
227
+ )
228
+ const signature = await cryptoProvider.subtle.sign("HMAC", key, new TextEncoder().encode(message))
229
+
230
+ return `${OFFLINE_GRANT_SIGNATURE_PREFIX}${hex(new Uint8Array(signature))}`
231
+ }
232
+
233
+ /**
234
+ * Converts bytes to lower-case hex.
235
+ * @param {Uint8Array} bytes - Bytes.
236
+ * @returns {string} - Hex string.
237
+ */
238
+ function hex(bytes) {
239
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("")
240
+ }
241
+
242
+ /**
243
+ * Requires a non-empty string field.
244
+ * @param {unknown} value - Value.
245
+ * @param {string} name - Field name.
246
+ * @returns {string} - String value.
247
+ */
248
+ function requiredString(value, name) {
249
+ if (typeof value !== "string" || value.length < 1) throw new Error(`Expected offline grant ${name}`)
250
+
251
+ return value
252
+ }
253
+
254
+ /**
255
+ * Normalizes a Date to an ISO timestamp.
256
+ * @param {Date} date - Date value.
257
+ * @param {string} label - Field label.
258
+ * @returns {string} - ISO timestamp.
259
+ */
260
+ function isoDateString(date, label) {
261
+ if (Number.isNaN(date.getTime())) throw new Error(`Invalid offline grant ${label}`)
262
+
263
+ return date.toISOString()
264
+ }
265
+
266
+ /**
267
+ * Generates a fallback random grant id.
268
+ * @returns {string} - Grant id.
269
+ */
270
+ function randomGrantId() {
271
+ return globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : `grant-${Date.now()}-${Math.random().toString(16).slice(2)}`
272
+ }
273
+
274
+ /**
275
+ * Stable JSON stringifier with sorted object keys.
276
+ * @param {unknown} value - Value.
277
+ * @returns {string} - JSON string.
278
+ */
279
+ function stableJsonStringify(value) {
280
+ return JSON.stringify(deterministicJson({label: "root", value}))
281
+ }
282
+
283
+ /**
284
+ * Normalizes a value as a JSON object.
285
+ * @param {object} args - Arguments.
286
+ * @param {string} args.label - Diagnostic label.
287
+ * @param {unknown} args.value - Value.
288
+ * @returns {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} - JSON object.
289
+ */
290
+ function deterministicJsonObject({label, value}) {
291
+ const normalized = deterministicJson({label, value})
292
+
293
+ if (!normalized || typeof normalized !== "object" || Array.isArray(normalized)) throw new Error(`Expected offline grant ${label} object`)
294
+
295
+ return /** @type {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} */ (normalized)
296
+ }
297
+
298
+ /**
299
+ * Normalizes deterministic JSON with sorted object keys.
300
+ * @param {object} args - Arguments.
301
+ * @param {string} args.label - Diagnostic label.
302
+ * @param {unknown} args.value - Value.
303
+ * @returns {import("../configuration-types.js").FrontendModelSyncJsonValue} - Normalized JSON value.
304
+ */
305
+ function deterministicJson({label, value}) {
306
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value
307
+
308
+ if (Array.isArray(value)) {
309
+ return value.map((entry, index) => deterministicJson({label: `${label}/${index}`, value: entry}))
310
+ }
311
+
312
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
313
+ /** @type {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} */
314
+ const normalized = {}
315
+
316
+ for (const key of Object.keys(value).sort()) {
317
+ const childValue = /** @type {Record<string, unknown>} */ (value)[key]
318
+
319
+ if (childValue === undefined) continue
320
+ normalized[key] = deterministicJson({label: `${label}/${key}`, value: childValue})
321
+ }
322
+
323
+ return normalized
324
+ }
325
+
326
+ throw new Error(`Offline grant ${label} must be deterministic JSON`)
327
+ }
@@ -1 +1 @@
1
- {"root":["../index.js","../bin/velocious.js","../src/application.js","../src/configuration-resolver.js","../src/configuration-types.js","../src/configuration.js","../src/controller.js","../src/current-configuration.js","../src/current.js","../src/error-logger.js","../src/frontend-model-controller.js","../src/initializer.js","../src/logger.js","../src/mailer.js","../src/record-payload-values.js","../src/time-zone.js","../src/velocious-error.js","../src/authorization/ability.js","../src/authorization/base-resource.js","../src/background-jobs/client.js","../src/background-jobs/cron-expression.js","../src/background-jobs/forked-runner-child.js","../src/background-jobs/job-record.js","../src/background-jobs/job-registry.js","../src/background-jobs/job-runner.js","../src/background-jobs/job.js","../src/background-jobs/json-socket.js","../src/background-jobs/main.js","../src/background-jobs/normalize-error.js","../src/background-jobs/scheduler.js","../src/background-jobs/socket-request.js","../src/background-jobs/status-reporter.js","../src/background-jobs/store.js","../src/background-jobs/types.js","../src/background-jobs/worker.js","../src/background-jobs/web/authorization.js","../src/background-jobs/web/controller.js","../src/background-jobs/web/index.js","../src/background-jobs/web/path-matcher.js","../src/background-jobs/web/registry.js","../src/beacon/client.js","../src/beacon/in-process-broker.js","../src/beacon/in-process-client.js","../src/beacon/server.js","../src/beacon/types.js","../src/cli/base-command.js","../src/cli/browser-cli.js","../src/cli/index.js","../src/cli/tenant-database-command-helper.js","../src/cli/use-browser-cli.js","../src/cli/commands/background-jobs-main.js","../src/cli/commands/background-jobs-runner.js","../src/cli/commands/background-jobs-worker.js","../src/cli/commands/beacon.js","../src/cli/commands/console.js","../src/cli/commands/init.js","../src/cli/commands/routes.js","../src/cli/commands/run-script.js","../src/cli/commands/runner.js","../src/cli/commands/server.js","../src/cli/commands/test.js","../src/cli/commands/db/base-command.js","../src/cli/commands/db/create.js","../src/cli/commands/db/drop.js","../src/cli/commands/db/migrate.js","../src/cli/commands/db/reset.js","../src/cli/commands/db/rollback.js","../src/cli/commands/db/seed.js","../src/cli/commands/db/schema/dump.js","../src/cli/commands/db/schema/load.js","../src/cli/commands/db/tenants/check.js","../src/cli/commands/db/tenants/create.js","../src/cli/commands/db/tenants/migrate.js","../src/cli/commands/destroy/migration.js","../src/cli/commands/generate/base-models.js","../src/cli/commands/generate/frontend-models.js","../src/cli/commands/generate/migration.js","../src/cli/commands/generate/model.js","../src/cli/commands/lint/relationships.js","../src/database/annotations-async-hooks.js","../src/database/annotations.js","../src/database/datetime-storage.js","../src/database/handler.js","../src/database/initializer-from-require-context.js","../src/database/migrator.js","../src/database/use-database.js","../src/database/drivers/base-column.js","../src/database/drivers/base-columns-index.js","../src/database/drivers/base-foreign-key.js","../src/database/drivers/base-table.js","../src/database/drivers/base.js","../src/database/drivers/mssql/column.js","../src/database/drivers/mssql/columns-index.js","../src/database/drivers/mssql/connect-connection.js","../src/database/drivers/mssql/foreign-key.js","../src/database/drivers/mssql/index.js","../src/database/drivers/mssql/options.js","../src/database/drivers/mssql/query-parser.js","../src/database/drivers/mssql/structure-sql.js","../src/database/drivers/mssql/table.js","../src/database/drivers/mssql/sql/alter-table.js","../src/database/drivers/mssql/sql/create-database.js","../src/database/drivers/mssql/sql/create-index.js","../src/database/drivers/mssql/sql/create-table.js","../src/database/drivers/mssql/sql/delete.js","../src/database/drivers/mssql/sql/drop-database.js","../src/database/drivers/mssql/sql/drop-table.js","../src/database/drivers/mssql/sql/insert.js","../src/database/drivers/mssql/sql/update.js","../src/database/drivers/mssql/sql/upsert.js","../src/database/drivers/mysql/column.js","../src/database/drivers/mysql/columns-index.js","../src/database/drivers/mysql/foreign-key.js","../src/database/drivers/mysql/index.js","../src/database/drivers/mysql/options.js","../src/database/drivers/mysql/query-parser.js","../src/database/drivers/mysql/query.js","../src/database/drivers/mysql/structure-sql.js","../src/database/drivers/mysql/table.js","../src/database/drivers/mysql/sql/alter-table.js","../src/database/drivers/mysql/sql/create-database.js","../src/database/drivers/mysql/sql/create-index.js","../src/database/drivers/mysql/sql/create-table.js","../src/database/drivers/mysql/sql/delete.js","../src/database/drivers/mysql/sql/drop-database.js","../src/database/drivers/mysql/sql/drop-table.js","../src/database/drivers/mysql/sql/insert.js","../src/database/drivers/mysql/sql/update.js","../src/database/drivers/mysql/sql/upsert.js","../src/database/drivers/pgsql/column.js","../src/database/drivers/pgsql/columns-index.js","../src/database/drivers/pgsql/foreign-key.js","../src/database/drivers/pgsql/index.js","../src/database/drivers/pgsql/options.js","../src/database/drivers/pgsql/query-parser.js","../src/database/drivers/pgsql/structure-sql.js","../src/database/drivers/pgsql/table.js","../src/database/drivers/pgsql/sql/alter-table.js","../src/database/drivers/pgsql/sql/create-database.js","../src/database/drivers/pgsql/sql/create-index.js","../src/database/drivers/pgsql/sql/create-table.js","../src/database/drivers/pgsql/sql/delete.js","../src/database/drivers/pgsql/sql/drop-database.js","../src/database/drivers/pgsql/sql/drop-table.js","../src/database/drivers/pgsql/sql/insert.js","../src/database/drivers/pgsql/sql/update.js","../src/database/drivers/pgsql/sql/upsert.js","../src/database/drivers/sqlite/base.js","../src/database/drivers/sqlite/column.js","../src/database/drivers/sqlite/columns-index.js","../src/database/drivers/sqlite/connection-sql-js.js","../src/database/drivers/sqlite/foreign-key.js","../src/database/drivers/sqlite/index.js","../src/database/drivers/sqlite/index.native.js","../src/database/drivers/sqlite/index.web.js","../src/database/drivers/sqlite/options.js","../src/database/drivers/sqlite/query-parser.js","../src/database/drivers/sqlite/query.js","../src/database/drivers/sqlite/query.native.js","../src/database/drivers/sqlite/query.web.js","../src/database/drivers/sqlite/structure-sql.js","../src/database/drivers/sqlite/table-rebuilder.js","../src/database/drivers/sqlite/table.js","../src/database/drivers/sqlite/sql/alter-table.js","../src/database/drivers/sqlite/sql/create-index.js","../src/database/drivers/sqlite/sql/create-table.js","../src/database/drivers/sqlite/sql/delete.js","../src/database/drivers/sqlite/sql/drop-table.js","../src/database/drivers/sqlite/sql/insert.js","../src/database/drivers/sqlite/sql/update.js","../src/database/drivers/sqlite/sql/upsert.js","../src/database/drivers/structure-sql/utils.js","../src/database/migration/index.js","../src/database/migrator/files-finder.js","../src/database/migrator/types.js","../src/database/pool/async-tracked-multi-connection.js","../src/database/pool/base-methods-forward.js","../src/database/pool/base.js","../src/database/pool/single-multi-use.js","../src/database/query/alter-table-base.js","../src/database/query/base.js","../src/database/query/create-database-base.js","../src/database/query/create-index-base.js","../src/database/query/create-table-base.js","../src/database/query/delete-base.js","../src/database/query/drop-database-base.js","../src/database/query/drop-table-base.js","../src/database/query/from-base.js","../src/database/query/from-plain.js","../src/database/query/from-table.js","../src/database/query/index.js","../src/database/query/insert-base.js","../src/database/query/join-base.js","../src/database/query/join-object.js","../src/database/query/join-plain.js","../src/database/query/join-tracker.js","../src/database/query/model-class-query.js","../src/database/query/order-base.js","../src/database/query/order-column.js","../src/database/query/order-plain.js","../src/database/query/preloader.js","../src/database/query/query-data.js","../src/database/query/select-base.js","../src/database/query/select-plain.js","../src/database/query/select-table-and-column.js","../src/database/query/update-base.js","../src/database/query/upsert-base.js","../src/database/query/where-base.js","../src/database/query/where-combinator.js","../src/database/query/where-hash.js","../src/database/query/where-model-class-hash.js","../src/database/query/where-not.js","../src/database/query/where-plain.js","../src/database/query/with-count.js","../src/database/query/preloader/belongs-to.js","../src/database/query/preloader/ensure-model-class-initialized.js","../src/database/query/preloader/has-many.js","../src/database/query/preloader/has-one.js","../src/database/query/preloader/selection.js","../src/database/query-parser/base-query-parser.js","../src/database/query-parser/from-parser.js","../src/database/query-parser/group-parser.js","../src/database/query-parser/joins-parser.js","../src/database/query-parser/limit-parser.js","../src/database/query-parser/options.js","../src/database/query-parser/order-parser.js","../src/database/query-parser/select-parser.js","../src/database/query-parser/where-parser.js","../src/database/record/acts-as-list.js","../src/database/record/index.js","../src/database/record/record-not-found-error.js","../src/database/record/state-machine.js","../src/database/record/user-module.js","../src/database/record/attachments/attachment-record.js","../src/database/record/attachments/download.js","../src/database/record/attachments/handle.js","../src/database/record/attachments/normalize-input.js","../src/database/record/attachments/store.js","../src/database/record/attachments/storage-drivers/filesystem.js","../src/database/record/attachments/storage-drivers/native.js","../src/database/record/attachments/storage-drivers/s3.js","../src/database/record/instance-relationships/base.js","../src/database/record/instance-relationships/belongs-to.js","../src/database/record/instance-relationships/has-many.js","../src/database/record/instance-relationships/has-one.js","../src/database/record/relationships/base.js","../src/database/record/relationships/belongs-to.js","../src/database/record/relationships/has-many.js","../src/database/record/relationships/has-one.js","../src/database/record/validators/base.js","../src/database/record/validators/format.js","../src/database/record/validators/presence.js","../src/database/record/validators/uniqueness.js","../src/database/table-data/index.js","../src/database/table-data/table-column.js","../src/database/table-data/table-foreign-key.js","../src/database/table-data/table-index.js","../src/database/table-data/table-reference.js","../src/environment-handlers/base.js","../src/environment-handlers/browser.js","../src/environment-handlers/node.js","../src/environment-handlers/node/cli/commands/background-jobs-main.js","../src/environment-handlers/node/cli/commands/background-jobs-runner.js","../src/environment-handlers/node/cli/commands/background-jobs-worker.js","../src/environment-handlers/node/cli/commands/beacon.js","../src/environment-handlers/node/cli/commands/cli-command-context.js","../src/environment-handlers/node/cli/commands/console.js","../src/environment-handlers/node/cli/commands/init.js","../src/environment-handlers/node/cli/commands/routes.js","../src/environment-handlers/node/cli/commands/run-script.js","../src/environment-handlers/node/cli/commands/runner.js","../src/environment-handlers/node/cli/commands/server.js","../src/environment-handlers/node/cli/commands/test.js","../src/environment-handlers/node/cli/commands/db/seed.js","../src/environment-handlers/node/cli/commands/db/schema/dump.js","../src/environment-handlers/node/cli/commands/db/schema/load.js","../src/environment-handlers/node/cli/commands/destroy/migration.js","../src/environment-handlers/node/cli/commands/generate/base-models.js","../src/environment-handlers/node/cli/commands/generate/frontend-models.js","../src/environment-handlers/node/cli/commands/generate/generated-file-banner.js","../src/environment-handlers/node/cli/commands/generate/migration.js","../src/environment-handlers/node/cli/commands/generate/model.js","../src/environment-handlers/node/cli/commands/lint/relationships.js","../src/error-reporting/request-details.js","../src/frontend-model-resource/base-resource.js","../src/frontend-model-resource/velocious-attachment-resource.js","../src/frontend-models/base.js","../src/frontend-models/built-in-resources.js","../src/frontend-models/clear-pending-debounced-callback.js","../src/frontend-models/event-hook-models.js","../src/frontend-models/model-registry.js","../src/frontend-models/outgoing-event-buffer.js","../src/frontend-models/preloader.js","../src/frontend-models/query.js","../src/frontend-models/resource-config-validation.js","../src/frontend-models/resource-definition.js","../src/frontend-models/transport-serialization.js","../src/frontend-models/use-created-event.js","../src/frontend-models/use-destroyed-event.js","../src/frontend-models/use-model-class-event.js","../src/frontend-models/use-updated-event.js","../src/frontend-models/websocket-channel.js","../src/frontend-models/websocket-publishers.js","../src/http-client/header.js","../src/http-client/index.js","../src/http-client/request.js","../src/http-client/response.js","../src/http-client/websocket-client.js","../src/http-server/cookie.js","../src/http-server/development-reloader.js","../src/http-server/index.js","../src/http-server/remote-address.js","../src/http-server/server-client.js","../src/http-server/server-lock.js","../src/http-server/websocket-channel-subscribers.js","../src/http-server/websocket-channel.js","../src/http-server/websocket-connection.js","../src/http-server/websocket-event-log-store.js","../src/http-server/websocket-events-host.js","../src/http-server/websocket-events.js","../src/http-server/client/index.js","../src/http-server/client/params-to-object.js","../src/http-server/client/request-parser.js","../src/http-server/client/request-runner.js","../src/http-server/client/request-timing.js","../src/http-server/client/request.js","../src/http-server/client/response.js","../src/http-server/client/websocket-request.js","../src/http-server/client/websocket-session.js","../src/http-server/client/request-buffer/form-data-part.js","../src/http-server/client/request-buffer/header.js","../src/http-server/client/request-buffer/index.js","../src/http-server/client/uploaded-file/memory-uploaded-file.js","../src/http-server/client/uploaded-file/temporary-uploaded-file.js","../src/http-server/client/uploaded-file/uploaded-file.js","../src/http-server/worker-handler/channel-subscriber-dispatch.js","../src/http-server/worker-handler/in-process.js","../src/http-server/worker-handler/index.js","../src/http-server/worker-handler/worker-script.js","../src/http-server/worker-handler/worker-thread.js","../src/jobs/mail-delivery.js","../src/logger/base-logger.js","../src/logger/console-logger.js","../src/logger/file-logger.js","../src/logger/outputs/array-output.js","../src/logger/outputs/console-output.js","../src/logger/outputs/file-output.js","../src/logger/outputs/stdout-output.js","../src/mailer/base.js","../src/mailer/delivery.js","../src/mailer/index.js","../src/mailer/backends/smtp.js","../src/plugins/sqljs-wasm-route-controller.js","../src/plugins/sqljs-wasm-route.js","../src/routes/app-routes.js","../src/routes/base-route.js","../src/routes/basic-route.js","../src/routes/get-route.js","../src/routes/index.js","../src/routes/namespace-route.js","../src/routes/plugin-routes.js","../src/routes/post-route.js","../src/routes/resolver.js","../src/routes/resource-route.js","../src/routes/root-route.js","../src/routes/built-in/debug/controller.js","../src/routes/built-in/errors/controller.js","../src/routes/hooks/frontend-model-command-route-hook.js","../src/testing/base-expect.js","../src/testing/browser-frontend-model-event-hook-scenarios.js","../src/testing/browser-test-app.js","../src/testing/expect-to-change.js","../src/testing/expect-utils.js","../src/testing/expect.js","../src/testing/request-client.js","../src/testing/test-files-finder.js","../src/testing/test-filter-parser.js","../src/testing/test-runner.js","../src/testing/test-suite-splitter.js","../src/testing/test.js","../src/types/external-modules.d.ts","../src/utils/backtrace-cleaner-node.js","../src/utils/backtrace-cleaner.js","../src/utils/deburr-column-name.js","../src/utils/event-emitter.js","../src/utils/file-exists.js","../src/utils/format-value.js","../src/utils/is-date.js","../src/utils/model-scope.js","../src/utils/nest-callbacks.js","../src/utils/plain-object.js","../src/utils/ransack.js","../src/utils/rest-args-error.js","../src/utils/singularize-model-name.js","../src/utils/split-sql-statements.js","../src/utils/to-import-specifier.js","../src/utils/with-tracked-stack-async-hooks.js","../src/utils/with-tracked-stack.js"],"version":"6.0.3"}
1
+ {"root":["../index.js","../bin/velocious.js","../src/application.js","../src/configuration-resolver.js","../src/configuration-types.js","../src/configuration.js","../src/controller.js","../src/current-configuration.js","../src/current.js","../src/error-logger.js","../src/frontend-model-controller.js","../src/initializer.js","../src/logger.js","../src/mailer.js","../src/record-payload-values.js","../src/time-zone.js","../src/velocious-error.js","../src/authorization/ability.js","../src/authorization/base-resource.js","../src/background-jobs/client.js","../src/background-jobs/cron-expression.js","../src/background-jobs/forked-runner-child.js","../src/background-jobs/job-record.js","../src/background-jobs/job-registry.js","../src/background-jobs/job-runner.js","../src/background-jobs/job.js","../src/background-jobs/json-socket.js","../src/background-jobs/main.js","../src/background-jobs/normalize-error.js","../src/background-jobs/scheduler.js","../src/background-jobs/socket-request.js","../src/background-jobs/status-reporter.js","../src/background-jobs/store.js","../src/background-jobs/types.js","../src/background-jobs/worker.js","../src/background-jobs/web/authorization.js","../src/background-jobs/web/controller.js","../src/background-jobs/web/index.js","../src/background-jobs/web/path-matcher.js","../src/background-jobs/web/registry.js","../src/beacon/client.js","../src/beacon/in-process-broker.js","../src/beacon/in-process-client.js","../src/beacon/server.js","../src/beacon/types.js","../src/cli/base-command.js","../src/cli/browser-cli.js","../src/cli/index.js","../src/cli/tenant-database-command-helper.js","../src/cli/use-browser-cli.js","../src/cli/commands/background-jobs-main.js","../src/cli/commands/background-jobs-runner.js","../src/cli/commands/background-jobs-worker.js","../src/cli/commands/beacon.js","../src/cli/commands/console.js","../src/cli/commands/init.js","../src/cli/commands/routes.js","../src/cli/commands/run-script.js","../src/cli/commands/runner.js","../src/cli/commands/server.js","../src/cli/commands/test.js","../src/cli/commands/db/base-command.js","../src/cli/commands/db/create.js","../src/cli/commands/db/drop.js","../src/cli/commands/db/migrate.js","../src/cli/commands/db/reset.js","../src/cli/commands/db/rollback.js","../src/cli/commands/db/seed.js","../src/cli/commands/db/schema/dump.js","../src/cli/commands/db/schema/load.js","../src/cli/commands/db/tenants/check.js","../src/cli/commands/db/tenants/create.js","../src/cli/commands/db/tenants/migrate.js","../src/cli/commands/destroy/migration.js","../src/cli/commands/generate/base-models.js","../src/cli/commands/generate/frontend-models.js","../src/cli/commands/generate/migration.js","../src/cli/commands/generate/model.js","../src/cli/commands/lint/relationships.js","../src/database/annotations-async-hooks.js","../src/database/annotations.js","../src/database/datetime-storage.js","../src/database/handler.js","../src/database/initializer-from-require-context.js","../src/database/migrator.js","../src/database/use-database.js","../src/database/drivers/base-column.js","../src/database/drivers/base-columns-index.js","../src/database/drivers/base-foreign-key.js","../src/database/drivers/base-table.js","../src/database/drivers/base.js","../src/database/drivers/mssql/column.js","../src/database/drivers/mssql/columns-index.js","../src/database/drivers/mssql/connect-connection.js","../src/database/drivers/mssql/foreign-key.js","../src/database/drivers/mssql/index.js","../src/database/drivers/mssql/options.js","../src/database/drivers/mssql/query-parser.js","../src/database/drivers/mssql/structure-sql.js","../src/database/drivers/mssql/table.js","../src/database/drivers/mssql/sql/alter-table.js","../src/database/drivers/mssql/sql/create-database.js","../src/database/drivers/mssql/sql/create-index.js","../src/database/drivers/mssql/sql/create-table.js","../src/database/drivers/mssql/sql/delete.js","../src/database/drivers/mssql/sql/drop-database.js","../src/database/drivers/mssql/sql/drop-table.js","../src/database/drivers/mssql/sql/insert.js","../src/database/drivers/mssql/sql/remove-index.js","../src/database/drivers/mssql/sql/update.js","../src/database/drivers/mssql/sql/upsert.js","../src/database/drivers/mysql/column.js","../src/database/drivers/mysql/columns-index.js","../src/database/drivers/mysql/foreign-key.js","../src/database/drivers/mysql/index.js","../src/database/drivers/mysql/options.js","../src/database/drivers/mysql/query-parser.js","../src/database/drivers/mysql/query.js","../src/database/drivers/mysql/structure-sql.js","../src/database/drivers/mysql/table.js","../src/database/drivers/mysql/sql/alter-table.js","../src/database/drivers/mysql/sql/create-database.js","../src/database/drivers/mysql/sql/create-index.js","../src/database/drivers/mysql/sql/create-table.js","../src/database/drivers/mysql/sql/delete.js","../src/database/drivers/mysql/sql/drop-database.js","../src/database/drivers/mysql/sql/drop-table.js","../src/database/drivers/mysql/sql/insert.js","../src/database/drivers/mysql/sql/remove-index.js","../src/database/drivers/mysql/sql/update.js","../src/database/drivers/mysql/sql/upsert.js","../src/database/drivers/pgsql/column.js","../src/database/drivers/pgsql/columns-index.js","../src/database/drivers/pgsql/foreign-key.js","../src/database/drivers/pgsql/index.js","../src/database/drivers/pgsql/options.js","../src/database/drivers/pgsql/query-parser.js","../src/database/drivers/pgsql/structure-sql.js","../src/database/drivers/pgsql/table.js","../src/database/drivers/pgsql/sql/alter-table.js","../src/database/drivers/pgsql/sql/create-database.js","../src/database/drivers/pgsql/sql/create-index.js","../src/database/drivers/pgsql/sql/create-table.js","../src/database/drivers/pgsql/sql/delete.js","../src/database/drivers/pgsql/sql/drop-database.js","../src/database/drivers/pgsql/sql/drop-table.js","../src/database/drivers/pgsql/sql/insert.js","../src/database/drivers/pgsql/sql/remove-index.js","../src/database/drivers/pgsql/sql/update.js","../src/database/drivers/pgsql/sql/upsert.js","../src/database/drivers/sqlite/base.js","../src/database/drivers/sqlite/column.js","../src/database/drivers/sqlite/columns-index.js","../src/database/drivers/sqlite/connection-sql-js.js","../src/database/drivers/sqlite/foreign-key.js","../src/database/drivers/sqlite/index.js","../src/database/drivers/sqlite/index.native.js","../src/database/drivers/sqlite/index.web.js","../src/database/drivers/sqlite/options.js","../src/database/drivers/sqlite/query-parser.js","../src/database/drivers/sqlite/query.js","../src/database/drivers/sqlite/query.native.js","../src/database/drivers/sqlite/query.web.js","../src/database/drivers/sqlite/structure-sql.js","../src/database/drivers/sqlite/table-rebuilder.js","../src/database/drivers/sqlite/table.js","../src/database/drivers/sqlite/web-persistence.js","../src/database/drivers/sqlite/sql/alter-table.js","../src/database/drivers/sqlite/sql/create-index.js","../src/database/drivers/sqlite/sql/create-table.js","../src/database/drivers/sqlite/sql/delete.js","../src/database/drivers/sqlite/sql/drop-table.js","../src/database/drivers/sqlite/sql/insert.js","../src/database/drivers/sqlite/sql/remove-index.js","../src/database/drivers/sqlite/sql/update.js","../src/database/drivers/sqlite/sql/upsert.js","../src/database/drivers/structure-sql/utils.js","../src/database/migration/index.js","../src/database/migrator/files-finder.js","../src/database/migrator/types.js","../src/database/pool/async-tracked-multi-connection.js","../src/database/pool/base-methods-forward.js","../src/database/pool/base.js","../src/database/pool/single-multi-use.js","../src/database/query/alter-table-base.js","../src/database/query/base.js","../src/database/query/create-database-base.js","../src/database/query/create-index-base.js","../src/database/query/create-table-base.js","../src/database/query/delete-base.js","../src/database/query/drop-database-base.js","../src/database/query/drop-table-base.js","../src/database/query/from-base.js","../src/database/query/from-plain.js","../src/database/query/from-table.js","../src/database/query/index.js","../src/database/query/insert-base.js","../src/database/query/join-base.js","../src/database/query/join-object.js","../src/database/query/join-plain.js","../src/database/query/join-tracker.js","../src/database/query/model-class-query.js","../src/database/query/order-base.js","../src/database/query/order-column.js","../src/database/query/order-plain.js","../src/database/query/preloader.js","../src/database/query/query-data.js","../src/database/query/remove-index-base.js","../src/database/query/select-base.js","../src/database/query/select-plain.js","../src/database/query/select-table-and-column.js","../src/database/query/update-base.js","../src/database/query/upsert-base.js","../src/database/query/where-base.js","../src/database/query/where-combinator.js","../src/database/query/where-hash.js","../src/database/query/where-model-class-hash.js","../src/database/query/where-not.js","../src/database/query/where-plain.js","../src/database/query/with-count.js","../src/database/query/preloader/belongs-to.js","../src/database/query/preloader/ensure-model-class-initialized.js","../src/database/query/preloader/has-many.js","../src/database/query/preloader/has-one.js","../src/database/query/preloader/selection.js","../src/database/query-parser/base-query-parser.js","../src/database/query-parser/from-parser.js","../src/database/query-parser/group-parser.js","../src/database/query-parser/joins-parser.js","../src/database/query-parser/limit-parser.js","../src/database/query-parser/options.js","../src/database/query-parser/order-parser.js","../src/database/query-parser/select-parser.js","../src/database/query-parser/where-parser.js","../src/database/record/acts-as-list.js","../src/database/record/index.js","../src/database/record/record-not-found-error.js","../src/database/record/state-machine.js","../src/database/record/user-module.js","../src/database/record/attachments/attachment-record.js","../src/database/record/attachments/download.js","../src/database/record/attachments/handle.js","../src/database/record/attachments/normalize-input.js","../src/database/record/attachments/store.js","../src/database/record/attachments/storage-drivers/filesystem.js","../src/database/record/attachments/storage-drivers/native.js","../src/database/record/attachments/storage-drivers/s3.js","../src/database/record/instance-relationships/base.js","../src/database/record/instance-relationships/belongs-to.js","../src/database/record/instance-relationships/has-many.js","../src/database/record/instance-relationships/has-one.js","../src/database/record/relationships/base.js","../src/database/record/relationships/belongs-to.js","../src/database/record/relationships/has-many.js","../src/database/record/relationships/has-one.js","../src/database/record/validators/base.js","../src/database/record/validators/format.js","../src/database/record/validators/presence.js","../src/database/record/validators/uniqueness.js","../src/database/table-data/index.js","../src/database/table-data/table-column.js","../src/database/table-data/table-foreign-key.js","../src/database/table-data/table-index.js","../src/database/table-data/table-reference.js","../src/environment-handlers/base.js","../src/environment-handlers/browser.js","../src/environment-handlers/node.js","../src/environment-handlers/node/cli/commands/background-jobs-main.js","../src/environment-handlers/node/cli/commands/background-jobs-runner.js","../src/environment-handlers/node/cli/commands/background-jobs-worker.js","../src/environment-handlers/node/cli/commands/beacon.js","../src/environment-handlers/node/cli/commands/cli-command-context.js","../src/environment-handlers/node/cli/commands/console.js","../src/environment-handlers/node/cli/commands/init.js","../src/environment-handlers/node/cli/commands/routes.js","../src/environment-handlers/node/cli/commands/run-script.js","../src/environment-handlers/node/cli/commands/runner.js","../src/environment-handlers/node/cli/commands/server.js","../src/environment-handlers/node/cli/commands/test.js","../src/environment-handlers/node/cli/commands/db/seed.js","../src/environment-handlers/node/cli/commands/db/schema/dump.js","../src/environment-handlers/node/cli/commands/db/schema/load.js","../src/environment-handlers/node/cli/commands/destroy/migration.js","../src/environment-handlers/node/cli/commands/generate/base-models.js","../src/environment-handlers/node/cli/commands/generate/frontend-models.js","../src/environment-handlers/node/cli/commands/generate/generated-file-banner.js","../src/environment-handlers/node/cli/commands/generate/migration.js","../src/environment-handlers/node/cli/commands/generate/model.js","../src/environment-handlers/node/cli/commands/lint/relationships.js","../src/error-reporting/request-details.js","../src/frontend-model-resource/base-resource.js","../src/frontend-model-resource/velocious-attachment-resource.js","../src/frontend-models/base.js","../src/frontend-models/built-in-resources.js","../src/frontend-models/clear-pending-debounced-callback.js","../src/frontend-models/event-hook-models.js","../src/frontend-models/model-registry.js","../src/frontend-models/outgoing-event-buffer.js","../src/frontend-models/preloader.js","../src/frontend-models/query.js","../src/frontend-models/resource-config-validation.js","../src/frontend-models/resource-definition.js","../src/frontend-models/transport-serialization.js","../src/frontend-models/use-created-event.js","../src/frontend-models/use-destroyed-event.js","../src/frontend-models/use-model-class-event.js","../src/frontend-models/use-updated-event.js","../src/frontend-models/websocket-channel.js","../src/frontend-models/websocket-publishers.js","../src/http-client/header.js","../src/http-client/index.js","../src/http-client/request.js","../src/http-client/response.js","../src/http-client/websocket-client.js","../src/http-server/cookie.js","../src/http-server/development-reloader.js","../src/http-server/index.js","../src/http-server/remote-address.js","../src/http-server/server-client.js","../src/http-server/server-lock.js","../src/http-server/websocket-channel-subscribers.js","../src/http-server/websocket-channel.js","../src/http-server/websocket-connection.js","../src/http-server/websocket-event-log-store.js","../src/http-server/websocket-events-host.js","../src/http-server/websocket-events.js","../src/http-server/client/index.js","../src/http-server/client/params-to-object.js","../src/http-server/client/request-parser.js","../src/http-server/client/request-runner.js","../src/http-server/client/request-timing.js","../src/http-server/client/request.js","../src/http-server/client/response.js","../src/http-server/client/websocket-request.js","../src/http-server/client/websocket-session.js","../src/http-server/client/request-buffer/form-data-part.js","../src/http-server/client/request-buffer/header.js","../src/http-server/client/request-buffer/index.js","../src/http-server/client/uploaded-file/memory-uploaded-file.js","../src/http-server/client/uploaded-file/temporary-uploaded-file.js","../src/http-server/client/uploaded-file/uploaded-file.js","../src/http-server/worker-handler/channel-subscriber-dispatch.js","../src/http-server/worker-handler/in-process.js","../src/http-server/worker-handler/index.js","../src/http-server/worker-handler/worker-script.js","../src/http-server/worker-handler/worker-thread.js","../src/jobs/mail-delivery.js","../src/logger/base-logger.js","../src/logger/console-logger.js","../src/logger/file-logger.js","../src/logger/outputs/array-output.js","../src/logger/outputs/console-output.js","../src/logger/outputs/file-output.js","../src/logger/outputs/stdout-output.js","../src/mailer/base.js","../src/mailer/delivery.js","../src/mailer/index.js","../src/mailer/backends/smtp.js","../src/plugins/sqljs-wasm-route-controller.js","../src/plugins/sqljs-wasm-route.js","../src/routes/app-routes.js","../src/routes/base-route.js","../src/routes/basic-route.js","../src/routes/get-route.js","../src/routes/index.js","../src/routes/namespace-route.js","../src/routes/plugin-routes.js","../src/routes/post-route.js","../src/routes/resolver.js","../src/routes/resource-route.js","../src/routes/root-route.js","../src/routes/built-in/debug/controller.js","../src/routes/built-in/errors/controller.js","../src/routes/hooks/frontend-model-command-route-hook.js","../src/sync/device-identity.js","../src/sync/local-mutation-log.js","../src/sync/offline-grant.js","../src/testing/base-expect.js","../src/testing/browser-frontend-model-event-hook-scenarios.js","../src/testing/browser-test-app.js","../src/testing/expect-to-change.js","../src/testing/expect-utils.js","../src/testing/expect.js","../src/testing/request-client.js","../src/testing/test-files-finder.js","../src/testing/test-filter-parser.js","../src/testing/test-runner.js","../src/testing/test-suite-splitter.js","../src/testing/test.js","../src/types/external-modules.d.ts","../src/utils/backtrace-cleaner-node.js","../src/utils/backtrace-cleaner.js","../src/utils/deburr-column-name.js","../src/utils/event-emitter.js","../src/utils/file-exists.js","../src/utils/format-value.js","../src/utils/is-date.js","../src/utils/model-scope.js","../src/utils/nest-callbacks.js","../src/utils/plain-object.js","../src/utils/ransack.js","../src/utils/rest-args-error.js","../src/utils/singularize-model-name.js","../src/utils/split-sql-statements.js","../src/utils/to-import-specifier.js","../src/utils/with-tracked-stack-async-hooks.js","../src/utils/with-tracked-stack.js"],"version":"6.0.3"}
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.474",
6
+ "version": "1.0.476",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -90,7 +90,7 @@
90
90
  "@types/escape-string-regexp": "^2.0.3",
91
91
  "@types/mssql": "^12.3.0",
92
92
  "@types/mysql": "^2.15.27",
93
- "@types/node": "^25.5.0",
93
+ "@types/node": "^26.0.1",
94
94
  "@types/pg": "^8.20.0",
95
95
  "@types/proxy-addr": "^2.0.3",
96
96
  "@types/react": "^19.2.14",
@@ -103,7 +103,7 @@
103
103
  "eslint-plugin-jsdoc": "^63.0.0",
104
104
  "eslint-plugin-jsdoc-inline-type-casts": "https://github.com/kaspernj/eslint-plugin-jsdoc-inline-type-casts/archive/refs/heads/master.tar.gz",
105
105
  "eslint-plugin-jsdoc-tag-lines": "^1.0.1",
106
- "fallow": "2.98.0",
106
+ "fallow": "2.102.0",
107
107
  "globals": "^17.0.0",
108
108
  "mssql": "^12.0.0",
109
109
  "mysql": "^2.18.1",
@@ -320,6 +320,7 @@
320
320
  * @property {string[]} [relationships] - Relationship names to expose in frontend models. Type and target model are inferred from the backend model's registered relationships.
321
321
  * @property {string} [primaryKey] - Primary key attribute name.
322
322
  * @property {FrontendModelResourceServerConfiguration} [server] - Optional legacy backend behavior overrides for built-in frontend actions.
323
+ * @property {FrontendModelResourceSyncConfiguration | boolean} [sync] - Optional safe local/offline sync policy metadata. `policy` participates in the hash but is not exposed to generated frontend config/manifest.
323
324
  */
324
325
 
325
326
  /**
@@ -338,13 +339,49 @@
338
339
  */
339
340
 
340
341
  /**
341
- * @typedef {Omit<FrontendModelResourceConfiguration, "abilities" | "builtInCollectionCommands" | "builtInMemberCommands" | "collectionCommands" | "commands" | "memberCommands"> & {
342
+ * JSON value accepted by sync policy metadata/hash inputs.
343
+ * @typedef {null | string | number | boolean | unknown[] | Record<string, unknown>} FrontendModelSyncJsonValue
344
+ */
345
+
346
+ /**
347
+ * Frontend-model local/offline sync policy config. `metadata` is exposed to
348
+ * frontends and peers; `policy` is hashed but intentionally omitted from
349
+ * frontend-safe manifests.
350
+ * @typedef {object} FrontendModelResourceSyncConfiguration
351
+ * @property {boolean} [enabled] - Whether the resource is sync-enabled. Defaults to true when `sync` is configured.
352
+ * @property {string[]} [operations] - Sync operation names such as `index`, `find`, `create`, `update`, custom domain commands, etc.
353
+ * @property {string | number} [policyVersion] - App-controlled policy version used as a stable change signal.
354
+ * @property {Record<string, FrontendModelSyncJsonValue>} [metadata] - Safe frontend-visible metadata.
355
+ * @property {Record<string, FrontendModelSyncJsonValue>} [policy] - Deterministic non-secret policy inputs included in the policy hash only.
356
+ */
357
+
358
+ /**
359
+ * Velocious sync configuration.
360
+ * @typedef {object} VelociousSyncConfiguration
361
+ * @property {import("./sync/device-identity.js").SyncJsonWebKey | null} [deviceCertificateBackendPublicKey] - Public backend key used to verify offline device certificates for sync replay.
362
+ * @property {Array<import("./sync/offline-grant.js").OfflineGrantSigningKey>} offlineGrantSigningKeys - Signing keys used to issue and verify offline grants. Secrets must never be exposed to clients.
363
+ * @property {number} [offlineGrantTtlMs] - Default offline grant TTL in milliseconds. Defaults to 24 hours.
364
+ */
365
+
366
+ /**
367
+ * Frontend-safe normalized sync metadata.
368
+ * @typedef {object} NormalizedFrontendModelResourceSyncConfiguration
369
+ * @property {boolean} enabled - Whether the resource is sync-enabled.
370
+ * @property {string[]} operations - Sorted, duplicate-free sync operation names.
371
+ * @property {string | null} policyVersion - App-controlled policy version, or null.
372
+ * @property {string} policyHash - Deterministic sha256 hash of safe policy inputs.
373
+ * @property {Record<string, FrontendModelSyncJsonValue>} [metadata] - Safe frontend-visible metadata.
374
+ */
375
+
376
+ /**
377
+ * @typedef {Omit<FrontendModelResourceConfiguration, "abilities" | "builtInCollectionCommands" | "builtInMemberCommands" | "collectionCommands" | "commands" | "memberCommands" | "sync"> & {
342
378
  * abilities: FrontendModelResourceAbilitiesConfiguration
343
379
  * builtInCollectionCommands: Record<string, string>
344
380
  * builtInMemberCommands: Record<string, string>
345
381
  * collectionCommands: Record<string, string>
346
382
  * commandMetadata: Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>
347
383
  * memberCommands: Record<string, string>
384
+ * sync?: NormalizedFrontendModelResourceSyncConfiguration
348
385
  * }} NormalizedFrontendModelResourceConfiguration
349
386
  */
350
387
 
@@ -471,6 +508,7 @@
471
508
  * @property {string[]} locales - Supported locales.
472
509
  * @property {LocaleFallbacksType} localeFallbacks - Locale fallback map.
473
510
  * @property {StructureSqlConfiguration} [structureSql] - Structure SQL generation configuration.
511
+ * @property {VelociousSyncConfiguration} [sync] - Local/offline sync framework configuration.
474
512
  * @property {TenantResolverType} [tenantResolver] - Resolver for creating request-scoped tenant context objects.
475
513
  * @property {TenantDatabaseResolverType} [tenantDatabaseResolver] - Resolver for deriving tenant-specific database config overrides.
476
514
  * @property {Record<string, TenantDatabaseProviderType>} [tenantDatabaseProviders] - Tenant database lifecycle providers keyed by database identifier.
@@ -20,6 +20,7 @@ import VelociousWebsocketChannelSubscribers from "./http-server/websocket-channe
20
20
  import {CurrentConfigurationNotSetError, currentConfiguration, setCurrentConfiguration} from "./current-configuration.js"
21
21
  import {requestDetails} from "./error-reporting/request-details.js"
22
22
  import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
23
+ import {currentOfflineGrantSigningKey, normalizeOfflineGrantSigningKey} from "./sync/offline-grant.js"
23
24
  import PluginRoutes from "./routes/plugin-routes.js"
24
25
  import restArgsError from "./utils/rest-args-error.js"
25
26
  import {validateTimeZone} from "./time-zone.js"
@@ -126,7 +127,7 @@ export default class VelociousConfiguration {
126
127
  * Runs constructor.
127
128
  * @param {import("./configuration-types.js").ConfigurationArgsType} args - Configuration arguments.
128
129
  */
129
- constructor({abilityResolver, abilityResources, attachments, autoload = true, backgroundJobs, backendProjects, beacon, cookieSecret, cors, database, debug = false, debugEndpoint = false, directory, enforceTenantDatabaseScopes = true, environment, environmentHandler, exposeInternalErrorsToClients = false, httpServer, initializeModels, initializers, locale, localeFallbacks, locales, logging, mailerBackend, requestTimeoutMs, routeResolverHooks, scheduledBackgroundJobs, structureSql, tenantDatabaseProviders, tenantDatabaseResolver, tenantResolver, testing, timeZone, timezoneOffsetMinutes, trustedProxies, websocketChannelResolver, websocketMessageHandlerResolver, ...restArgs}) {
130
+ constructor({abilityResolver, abilityResources, attachments, autoload = true, backgroundJobs, backendProjects, beacon, cookieSecret, cors, database, debug = false, debugEndpoint = false, directory, enforceTenantDatabaseScopes = true, environment, environmentHandler, exposeInternalErrorsToClients = false, httpServer, initializeModels, initializers, locale, localeFallbacks, locales, logging, mailerBackend, requestTimeoutMs, routeResolverHooks, scheduledBackgroundJobs, structureSql, sync, tenantDatabaseProviders, tenantDatabaseResolver, tenantResolver, testing, timeZone, timezoneOffsetMinutes, trustedProxies, websocketChannelResolver, websocketMessageHandlerResolver, ...restArgs}) {
130
131
  restArgsError(restArgs)
131
132
 
132
133
  this._abilityResolver = abilityResolver
@@ -189,6 +190,7 @@ export default class VelociousConfiguration {
189
190
  this._trustedProxies = trustedProxies
190
191
  this._requestTimeoutMs = requestTimeoutMs
191
192
  this._structureSql = structureSql
193
+ this._sync = this._normalizeSyncConfiguration(sync)
192
194
  this._tenantDatabaseProviders = tenantDatabaseProviders || {}
193
195
  this._tenantDatabaseResolver = tenantDatabaseResolver
194
196
  this._tenantResolver = tenantResolver
@@ -380,6 +382,49 @@ export default class VelociousConfiguration {
380
382
  return this._cookieSecret
381
383
  }
382
384
 
385
+ /**
386
+ * Runs get sync configuration.
387
+ * @returns {import("./configuration-types.js").VelociousSyncConfiguration} - Sync configuration.
388
+ */
389
+ getSyncConfiguration() {
390
+ return this._sync
391
+ }
392
+
393
+ /**
394
+ * Runs current offline grant signing key.
395
+ * @returns {import("./sync/offline-grant.js").OfflineGrantSigningKey} - Current signing key.
396
+ */
397
+ currentOfflineGrantSigningKey() {
398
+ const signingKeys = this.getSyncConfiguration().offlineGrantSigningKeys
399
+
400
+ return currentOfflineGrantSigningKey(signingKeys)
401
+ }
402
+
403
+ /**
404
+ * Normalizes sync configuration.
405
+ * @param {import("./configuration-types.js").VelociousSyncConfiguration | undefined} sync - Sync configuration.
406
+ * @returns {import("./configuration-types.js").VelociousSyncConfiguration} - Normalized sync configuration.
407
+ */
408
+ _normalizeSyncConfiguration(sync) {
409
+ const deviceCertificateBackendPublicKey = sync?.deviceCertificateBackendPublicKey || null
410
+ const offlineGrantSigningKeys = sync?.offlineGrantSigningKeys || []
411
+ const offlineGrantTtlMs = sync?.offlineGrantTtlMs
412
+
413
+ if (deviceCertificateBackendPublicKey !== null && (typeof deviceCertificateBackendPublicKey !== "object" || Array.isArray(deviceCertificateBackendPublicKey))) {
414
+ throw new Error("sync.deviceCertificateBackendPublicKey must be a public JSON Web Key object")
415
+ }
416
+ if (!Array.isArray(offlineGrantSigningKeys)) throw new Error("sync.offlineGrantSigningKeys must be an array")
417
+ if (offlineGrantTtlMs !== undefined && (!Number.isInteger(offlineGrantTtlMs) || offlineGrantTtlMs <= 0)) {
418
+ throw new Error("sync.offlineGrantTtlMs must be a positive integer number of milliseconds")
419
+ }
420
+
421
+ return {
422
+ deviceCertificateBackendPublicKey,
423
+ offlineGrantSigningKeys: offlineGrantSigningKeys.map((key) => normalizeOfflineGrantSigningKey(key)),
424
+ offlineGrantTtlMs: offlineGrantTtlMs || 24 * 60 * 60 * 1000
425
+ }
426
+ }
427
+
383
428
  /**
384
429
  * Runs get database configuration.
385
430
  * @returns {Record<string, import("./configuration-types.js").DatabaseConfigurationType>} - The database configuration.