velocious 1.0.474 → 1.0.475
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/build/configuration-types.js +38 -1
- package/build/configuration.js +41 -1
- package/build/database/drivers/base.js +51 -3
- package/build/database/drivers/mysql/index.js +74 -7
- package/build/database/drivers/sqlite/connection-sql-js.js +16 -5
- package/build/database/drivers/sqlite/index.web.js +29 -15
- package/build/database/drivers/sqlite/web-persistence.js +521 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +63 -0
- package/build/frontend-model-controller.js +127 -4
- package/build/frontend-model-resource/base-resource.js +5 -1
- package/build/frontend-models/base.js +146 -2
- package/build/frontend-models/query.js +3 -3
- package/build/frontend-models/resource-definition.js +304 -2
- package/build/routes/hooks/frontend-model-command-route-hook.js +8 -0
- package/build/src/configuration-types.d.ts +113 -2
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +35 -2
- package/build/src/configuration.d.ts +18 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +37 -2
- package/build/src/database/drivers/base.d.ts +30 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +47 -4
- package/build/src/database/drivers/mysql/index.d.ts +32 -3
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +65 -7
- package/build/src/database/drivers/sqlite/connection-sql-js.d.ts +8 -1
- package/build/src/database/drivers/sqlite/connection-sql-js.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/connection-sql-js.js +15 -6
- package/build/src/database/drivers/sqlite/index.web.d.ts +5 -5
- package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.web.js +27 -13
- package/build/src/database/drivers/sqlite/web-persistence.d.ts +77 -0
- package/build/src/database/drivers/sqlite/web-persistence.d.ts.map +1 -0
- package/build/src/database/drivers/sqlite/web-persistence.js +464 -0
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +30 -0
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +53 -1
- package/build/src/frontend-model-controller.d.ts +34 -0
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +113 -5
- package/build/src/frontend-model-resource/base-resource.d.ts +4 -2
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +7 -2
- package/build/src/frontend-models/base.d.ts +29 -0
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +131 -3
- package/build/src/frontend-models/query.js +4 -4
- package/build/src/frontend-models/resource-definition.d.ts +6 -0
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +272 -3
- package/build/src/routes/hooks/frontend-model-command-route-hook.d.ts.map +1 -1
- package/build/src/routes/hooks/frontend-model-command-route-hook.js +8 -1
- package/build/src/sync/device-identity.d.ts +240 -0
- package/build/src/sync/device-identity.d.ts.map +1 -0
- package/build/src/sync/device-identity.js +454 -0
- package/build/src/sync/local-mutation-log.d.ts +212 -0
- package/build/src/sync/local-mutation-log.d.ts.map +1 -0
- package/build/src/sync/local-mutation-log.js +404 -0
- package/build/src/sync/offline-grant.d.ts +157 -0
- package/build/src/sync/offline-grant.d.ts.map +1 -0
- package/build/src/sync/offline-grant.js +292 -0
- package/build/sync/device-identity.js +503 -0
- package/build/sync/local-mutation-log.js +448 -0
- package/build/sync/offline-grant.js +327 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/configuration-types.js +38 -1
- package/src/configuration.js +41 -1
- package/src/database/drivers/base.js +51 -3
- package/src/database/drivers/mysql/index.js +74 -7
- package/src/database/drivers/sqlite/connection-sql-js.js +16 -5
- package/src/database/drivers/sqlite/index.web.js +29 -15
- package/src/database/drivers/sqlite/web-persistence.js +521 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +63 -0
- package/src/frontend-model-controller.js +127 -4
- package/src/frontend-model-resource/base-resource.js +5 -1
- package/src/frontend-models/base.js +146 -2
- package/src/frontend-models/query.js +3 -3
- package/src/frontend-models/resource-definition.js +304 -2
- package/src/routes/hooks/frontend-model-command-route-hook.js +8 -0
- package/src/sync/device-identity.js +503 -0
- package/src/sync/local-mutation-log.js +448 -0
- 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/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/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/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/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.
|
|
6
|
+
"version": "1.0.475",
|
|
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": "^
|
|
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.
|
|
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,48 @@
|
|
|
338
339
|
*/
|
|
339
340
|
|
|
340
341
|
/**
|
|
341
|
-
*
|
|
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 {Array<import("./sync/offline-grant.js").OfflineGrantSigningKey>} offlineGrantSigningKeys - Signing keys used to issue and verify offline grants. Secrets must never be exposed to clients.
|
|
362
|
+
* @property {number} [offlineGrantTtlMs] - Default offline grant TTL in milliseconds. Defaults to 24 hours.
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Frontend-safe normalized sync metadata.
|
|
367
|
+
* @typedef {object} NormalizedFrontendModelResourceSyncConfiguration
|
|
368
|
+
* @property {boolean} enabled - Whether the resource is sync-enabled.
|
|
369
|
+
* @property {string[]} operations - Sorted, duplicate-free sync operation names.
|
|
370
|
+
* @property {string | null} policyVersion - App-controlled policy version, or null.
|
|
371
|
+
* @property {string} policyHash - Deterministic sha256 hash of safe policy inputs.
|
|
372
|
+
* @property {Record<string, FrontendModelSyncJsonValue>} [metadata] - Safe frontend-visible metadata.
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @typedef {Omit<FrontendModelResourceConfiguration, "abilities" | "builtInCollectionCommands" | "builtInMemberCommands" | "collectionCommands" | "commands" | "memberCommands" | "sync"> & {
|
|
342
377
|
* abilities: FrontendModelResourceAbilitiesConfiguration
|
|
343
378
|
* builtInCollectionCommands: Record<string, string>
|
|
344
379
|
* builtInMemberCommands: Record<string, string>
|
|
345
380
|
* collectionCommands: Record<string, string>
|
|
346
381
|
* commandMetadata: Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>
|
|
347
382
|
* memberCommands: Record<string, string>
|
|
383
|
+
* sync?: NormalizedFrontendModelResourceSyncConfiguration
|
|
348
384
|
* }} NormalizedFrontendModelResourceConfiguration
|
|
349
385
|
*/
|
|
350
386
|
|
|
@@ -471,6 +507,7 @@
|
|
|
471
507
|
* @property {string[]} locales - Supported locales.
|
|
472
508
|
* @property {LocaleFallbacksType} localeFallbacks - Locale fallback map.
|
|
473
509
|
* @property {StructureSqlConfiguration} [structureSql] - Structure SQL generation configuration.
|
|
510
|
+
* @property {VelociousSyncConfiguration} [sync] - Local/offline sync framework configuration.
|
|
474
511
|
* @property {TenantResolverType} [tenantResolver] - Resolver for creating request-scoped tenant context objects.
|
|
475
512
|
* @property {TenantDatabaseResolverType} [tenantDatabaseResolver] - Resolver for deriving tenant-specific database config overrides.
|
|
476
513
|
* @property {Record<string, TenantDatabaseProviderType>} [tenantDatabaseProviders] - Tenant database lifecycle providers keyed by database identifier.
|
package/src/configuration.js
CHANGED
|
@@ -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,44 @@ 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 offlineGrantSigningKeys = sync?.offlineGrantSigningKeys || []
|
|
410
|
+
const offlineGrantTtlMs = sync?.offlineGrantTtlMs
|
|
411
|
+
|
|
412
|
+
if (!Array.isArray(offlineGrantSigningKeys)) throw new Error("sync.offlineGrantSigningKeys must be an array")
|
|
413
|
+
if (offlineGrantTtlMs !== undefined && (!Number.isInteger(offlineGrantTtlMs) || offlineGrantTtlMs <= 0)) {
|
|
414
|
+
throw new Error("sync.offlineGrantTtlMs must be a positive integer number of milliseconds")
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
offlineGrantSigningKeys: offlineGrantSigningKeys.map((key) => normalizeOfflineGrantSigningKey(key)),
|
|
419
|
+
offlineGrantTtlMs: offlineGrantTtlMs || 24 * 60 * 60 * 1000
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
383
423
|
/**
|
|
384
424
|
* Runs get database configuration.
|
|
385
425
|
* @returns {Record<string, import("./configuration-types.js").DatabaseConfigurationType>} - The database configuration.
|