velocious 1.0.473 → 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/authorization/base-resource.js +99 -0
- package/build/configuration-types.js +41 -3
- 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 +64 -1
- package/build/frontend-model-controller.js +139 -16
- package/build/frontend-model-resource/base-resource.js +258 -46
- package/build/frontend-model-resource/velocious-attachment-resource.js +0 -3
- package/build/frontend-models/base.js +146 -2
- package/build/frontend-models/query.js +3 -3
- package/build/frontend-models/resource-definition.js +400 -23
- package/build/routes/hooks/frontend-model-command-route-hook.js +8 -0
- package/build/src/authorization/base-resource.d.ts +41 -0
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +88 -1
- package/build/src/configuration-types.d.ts +128 -5
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +38 -4
- 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 +54 -2
- 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 +125 -17
- package/build/src/frontend-model-resource/base-resource.d.ts +69 -0
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +247 -54
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +0 -2
- package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/velocious-attachment-resource.js +1 -3
- 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 +355 -28
- 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/scripts/tensorbuzz-retry +21 -0
- package/src/authorization/base-resource.js +99 -0
- package/src/configuration-types.js +41 -3
- 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 +64 -1
- package/src/frontend-model-controller.js +139 -16
- package/src/frontend-model-resource/base-resource.js +258 -46
- package/src/frontend-model-resource/velocious-attachment-resource.js +0 -3
- package/src/frontend-models/base.js +146 -2
- package/src/frontend-models/query.js +3 -3
- package/src/frontend-models/resource-definition.js +400 -23
- 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
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
*/
|
|
241
241
|
|
|
242
242
|
/**
|
|
243
|
-
* @typedef {Record<string, unknown> & {configuration?: import("./configuration.js").default, currentUser?: unknown, params?: VelociousParams, request?: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default}} VelociousLooseObject
|
|
243
|
+
* @typedef {Record<string, unknown> & {configuration?: import("./configuration.js").default, currentDevice?: unknown, currentUser?: unknown, modelRegistry?: Record<string, unknown> | {model: (name: string) => unknown}, now?: Date | (() => Date), offlineGrant?: unknown, params?: VelociousParams, request?: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, resourceRuntime?: "backend" | "frontend" | "offline"}} VelociousLooseObject
|
|
244
244
|
*/
|
|
245
245
|
|
|
246
246
|
/**
|
|
@@ -309,16 +309,18 @@
|
|
|
309
309
|
/**
|
|
310
310
|
* @typedef {object} FrontendModelResourceConfiguration
|
|
311
311
|
* @property {Array<string | FrontendModelAttributeConfiguration> | Record<string, FrontendModelAttributeConfiguration | import("./database/drivers/base-column.js").default | boolean>} attributes - Attributes to expose on the frontend model.
|
|
312
|
-
* @property {string[]} [abilities] -
|
|
312
|
+
* @property {string[]} [abilities] - Additional camelCase ability action names to expose for per-record `record.can(action)` reads. Base CRUD actions (`read`, `create`, `update`, `destroy`) are always included and must not be listed here.
|
|
313
313
|
* @property {Record<string, FrontendModelAttachmentConfiguration>} [attachments] - Attachment helpers keyed by attachment name.
|
|
314
314
|
* @property {string[]} [commands] - Legacy built-in command names (`index`, `find`, `create`, `update`, `destroy`, `attach`, `download`, `url`).
|
|
315
315
|
* @property {Array<FrontendModelResourceCustomCommand>} [collectionCommands] - Custom collection commands. Each entry is a camelCase method name, or a `{name, args?, returnType?}` object declaring typed arguments and/or a response type. The runtime derives the kebab-case command slug from the name.
|
|
316
316
|
* @property {Array<FrontendModelResourceCustomCommand>} [memberCommands] - Custom member commands. Each entry is a camelCase method name, or a `{name, args?, returnType?}` object declaring typed arguments and/or a response type. The runtime derives the kebab-case command slug from the name.
|
|
317
317
|
* @property {string[]} [builtInCollectionCommands] - Built-in collection command names (`index`, `create`).
|
|
318
318
|
* @property {string[]} [builtInMemberCommands] - Built-in member command names (`find`, `update`, `destroy`, `attach`, `download`, `url`).
|
|
319
|
+
* @property {string} [modelName] - Frontend model name override.
|
|
319
320
|
* @property {string[]} [relationships] - Relationship names to expose in frontend models. Type and target model are inferred from the backend model's registered relationships.
|
|
320
321
|
* @property {string} [primaryKey] - Primary key attribute name.
|
|
321
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.
|
|
322
324
|
*/
|
|
323
325
|
|
|
324
326
|
/**
|
|
@@ -337,13 +339,48 @@
|
|
|
337
339
|
*/
|
|
338
340
|
|
|
339
341
|
/**
|
|
340
|
-
*
|
|
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"> & {
|
|
341
377
|
* abilities: FrontendModelResourceAbilitiesConfiguration
|
|
342
378
|
* builtInCollectionCommands: Record<string, string>
|
|
343
379
|
* builtInMemberCommands: Record<string, string>
|
|
344
380
|
* collectionCommands: Record<string, string>
|
|
345
381
|
* commandMetadata: Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>
|
|
346
382
|
* memberCommands: Record<string, string>
|
|
383
|
+
* sync?: NormalizedFrontendModelResourceSyncConfiguration
|
|
347
384
|
* }} NormalizedFrontendModelResourceConfiguration
|
|
348
385
|
*/
|
|
349
386
|
|
|
@@ -470,6 +507,7 @@
|
|
|
470
507
|
* @property {string[]} locales - Supported locales.
|
|
471
508
|
* @property {LocaleFallbacksType} localeFallbacks - Locale fallback map.
|
|
472
509
|
* @property {StructureSqlConfiguration} [structureSql] - Structure SQL generation configuration.
|
|
510
|
+
* @property {VelociousSyncConfiguration} [sync] - Local/offline sync framework configuration.
|
|
473
511
|
* @property {TenantResolverType} [tenantResolver] - Resolver for creating request-scoped tenant context objects.
|
|
474
512
|
* @property {TenantDatabaseResolverType} [tenantDatabaseResolver] - Resolver for deriving tenant-specific database config overrides.
|
|
475
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.
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* @property {string} [logName] - Query log subject.
|
|
51
51
|
* @property {boolean} [logQuery] - Whether to log the query.
|
|
52
52
|
* @property {boolean} [processListComment] - Whether to add process-list comments to the query.
|
|
53
|
+
* @property {boolean} [sessionTimeZone] - Whether to ensure the configured database session time zone before the query.
|
|
53
54
|
* @property {string} [sourceStack] - Stack captured at the caller boundary.
|
|
54
55
|
*/
|
|
55
56
|
|
|
@@ -237,6 +238,14 @@ export default class VelociousDatabaseDriversBase {
|
|
|
237
238
|
// No-op by default
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Flushes pending writes that the driver delayed for persistence.
|
|
243
|
+
* @returns {Promise<void>} - Resolves when pending writes are durable.
|
|
244
|
+
*/
|
|
245
|
+
async flushPendingWrites() {
|
|
246
|
+
// No-op by default
|
|
247
|
+
}
|
|
248
|
+
|
|
240
249
|
/**
|
|
241
250
|
* Runs set connection checkout name.
|
|
242
251
|
* @param {string | undefined} name - Human-readable name for this active checkout.
|
|
@@ -1041,12 +1050,14 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1041
1050
|
let result
|
|
1042
1051
|
|
|
1043
1052
|
try {
|
|
1053
|
+
const runQueryActualWithHooks = async () => await this._queryActualWithHooks(querySql, options)
|
|
1054
|
+
|
|
1044
1055
|
if (requestTiming && tries === 1) {
|
|
1045
|
-
result = await requestTiming.measureDbQuery(
|
|
1056
|
+
result = await requestTiming.measureDbQuery(runQueryActualWithHooks)
|
|
1046
1057
|
} else if (requestTiming) {
|
|
1047
|
-
result = await requestTiming.measure("db",
|
|
1058
|
+
result = await requestTiming.measure("db", runQueryActualWithHooks)
|
|
1048
1059
|
} else {
|
|
1049
|
-
result = await
|
|
1060
|
+
result = await runQueryActualWithHooks()
|
|
1050
1061
|
}
|
|
1051
1062
|
} finally {
|
|
1052
1063
|
this._activeQuery = previousActiveQuery
|
|
@@ -1070,6 +1081,42 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1070
1081
|
return result
|
|
1071
1082
|
}
|
|
1072
1083
|
|
|
1084
|
+
/**
|
|
1085
|
+
* Runs query actual with before/after hooks.
|
|
1086
|
+
* @param {string} sql - SQL string.
|
|
1087
|
+
* @param {QueryOptions} options - Query options.
|
|
1088
|
+
* @returns {Promise<QueryResultType>} - Resolves with the query.
|
|
1089
|
+
*/
|
|
1090
|
+
async _queryActualWithHooks(sql, options) {
|
|
1091
|
+
await this.beforeQuery(sql, options)
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
return await this._queryActual(sql)
|
|
1095
|
+
} finally {
|
|
1096
|
+
await this.afterQuery(sql, options)
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Hook that runs immediately before a SQL query is sent to the driver.
|
|
1102
|
+
* @param {string} _sql - SQL string.
|
|
1103
|
+
* @param {QueryOptions} _options - Query options.
|
|
1104
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
1105
|
+
*/
|
|
1106
|
+
async beforeQuery(_sql, _options) {
|
|
1107
|
+
// No-op by default
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Hook that runs immediately after a SQL query has completed or failed.
|
|
1112
|
+
* @param {string} _sql - SQL string.
|
|
1113
|
+
* @param {QueryOptions} _options - Query options.
|
|
1114
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
1115
|
+
*/
|
|
1116
|
+
async afterQuery(_sql, _options) {
|
|
1117
|
+
// No-op by default
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1073
1120
|
/**
|
|
1074
1121
|
* Runs get debug snapshot.
|
|
1075
1122
|
* @returns {DatabaseConnectionDebugSnapshot} - Diagnostic snapshot for this connection.
|
|
@@ -1504,6 +1551,7 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1504
1551
|
}
|
|
1505
1552
|
}
|
|
1506
1553
|
})
|
|
1554
|
+
await this.flushPendingWrites()
|
|
1507
1555
|
}
|
|
1508
1556
|
|
|
1509
1557
|
/**
|
|
@@ -30,15 +30,20 @@ import Update from "./sql/update.js"
|
|
|
30
30
|
const MYSQL_INDEFINITE_LOCK_TIMEOUT_SECONDS = 60 * 60 * 24 * 365
|
|
31
31
|
|
|
32
32
|
export default class VelociousDatabaseDriversMysql extends Base{
|
|
33
|
+
/** @type {string | null} */
|
|
34
|
+
_desiredSessionTimeZone = "+00:00"
|
|
35
|
+
|
|
36
|
+
/** @type {string | null} */
|
|
37
|
+
_currentSessionTimeZone = null
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* Runs connect.
|
|
35
41
|
* @returns {Promise<void>} - Resolves when complete.
|
|
36
42
|
*/
|
|
37
43
|
async connect() {
|
|
44
|
+
this.resetCurrentSessionTimeZone()
|
|
38
45
|
this.pool = mysql.createPool(Object.assign({connectionLimit: 1}, this.connectArgs()))
|
|
39
46
|
this.pool.on("error", this.onPoolError)
|
|
40
|
-
|
|
41
|
-
await this.setSessionTimezoneToUtc()
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/**
|
|
@@ -56,6 +61,7 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
56
61
|
async close() {
|
|
57
62
|
await this.pool?.end()
|
|
58
63
|
this.pool = undefined
|
|
64
|
+
this.resetCurrentSessionTimeZone()
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/**
|
|
@@ -64,7 +70,7 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
64
70
|
* @returns {Promise<void>} - Resolves when complete.
|
|
65
71
|
*/
|
|
66
72
|
async setConnectionCheckoutName(name) {
|
|
67
|
-
await this.query(`SET @velocious_connection_checkout_name = ${name === undefined ? "NULL" : this.quote(name)}`, {logName: "Set Connection Checkout Name", processListComment: false})
|
|
73
|
+
await this.query(`SET @velocious_connection_checkout_name = ${name === undefined ? "NULL" : this.quote(name)}`, {logName: "Set Connection Checkout Name", processListComment: false, sessionTimeZone: false})
|
|
68
74
|
await super.setConnectionCheckoutName(name)
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -73,16 +79,77 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
73
79
|
* @returns {Promise<void>} - Resolves when complete.
|
|
74
80
|
*/
|
|
75
81
|
async clearConnectionCheckoutName() {
|
|
76
|
-
await this.query("SET @velocious_connection_checkout_name = NULL", {logName: "Clear Connection Checkout Name", processListComment: false})
|
|
82
|
+
await this.query("SET @velocious_connection_checkout_name = NULL", {logName: "Clear Connection Checkout Name", processListComment: false, sessionTimeZone: false})
|
|
77
83
|
await super.clearConnectionCheckoutName()
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|
|
81
|
-
*
|
|
87
|
+
* Hook before every query.
|
|
88
|
+
* @param {string} _sql - SQL string.
|
|
89
|
+
* @param {import("../base.js").QueryOptions} options - Query options.
|
|
82
90
|
* @returns {Promise<void>} - Resolves when complete.
|
|
83
91
|
*/
|
|
84
|
-
async
|
|
85
|
-
|
|
92
|
+
async beforeQuery(_sql, options) {
|
|
93
|
+
if (options.sessionTimeZone !== false) await this.ensureSessionTimeZone()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets the desired database session time zone for this connection context.
|
|
98
|
+
* @returns {string | null} - Desired session time zone.
|
|
99
|
+
*/
|
|
100
|
+
getDesiredSessionTimeZone() {
|
|
101
|
+
return this._desiredSessionTimeZone
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets the desired database session time zone without querying MySQL immediately.
|
|
106
|
+
* @param {string | null} timeZone - Desired session time zone.
|
|
107
|
+
*/
|
|
108
|
+
setDesiredSessionTimeZone(timeZone) {
|
|
109
|
+
this._desiredSessionTimeZone = timeZone
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gets the database session time zone last confirmed through SET time_zone.
|
|
114
|
+
* @returns {string | null} - Current known session time zone.
|
|
115
|
+
*/
|
|
116
|
+
getCurrentSessionTimeZone() {
|
|
117
|
+
return this._currentSessionTimeZone
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clears the current known database session time zone when the physical connection changes.
|
|
122
|
+
*/
|
|
123
|
+
resetCurrentSessionTimeZone() {
|
|
124
|
+
this._currentSessionTimeZone = null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Ensures MySQL has the desired session time zone before user SQL runs.
|
|
129
|
+
* @returns {Promise<boolean>} - True when SET time_zone was executed.
|
|
130
|
+
*/
|
|
131
|
+
async ensureSessionTimeZone() {
|
|
132
|
+
const desiredSessionTimeZone = this.getDesiredSessionTimeZone()
|
|
133
|
+
|
|
134
|
+
if (desiredSessionTimeZone === null || this.getCurrentSessionTimeZone() === desiredSessionTimeZone) return false
|
|
135
|
+
|
|
136
|
+
await this.setSessionTimeZone(desiredSessionTimeZone)
|
|
137
|
+
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Sets the database session time zone if it changed from the last confirmed value.
|
|
143
|
+
* @param {string} timeZone - Session time zone value accepted by MySQL.
|
|
144
|
+
* @returns {Promise<boolean>} - True when SET time_zone was executed.
|
|
145
|
+
*/
|
|
146
|
+
async setSessionTimeZone(timeZone) {
|
|
147
|
+
if (this.getCurrentSessionTimeZone() === timeZone) return false
|
|
148
|
+
|
|
149
|
+
await this._queryActual(`SET time_zone = ${this.quote(timeZone)}`)
|
|
150
|
+
this._currentSessionTimeZone = timeZone
|
|
151
|
+
|
|
152
|
+
return true
|
|
86
153
|
}
|
|
87
154
|
|
|
88
155
|
/**
|
|
@@ -8,16 +8,28 @@ export default class VelociousDatabaseDriversSqliteConnectionSqlJs {
|
|
|
8
8
|
* Runs constructor.
|
|
9
9
|
* @param {import("../base.js").default} driver - Database driver instance.
|
|
10
10
|
* @param {import("sql.js").Database} connection - Connection.
|
|
11
|
+
* @param {import("./web-persistence.js").SqliteWebPersistence} persistence - Database persistence adapter.
|
|
11
12
|
*/
|
|
12
|
-
constructor(driver, connection) {
|
|
13
|
+
constructor(driver, connection, persistence) {
|
|
13
14
|
this.connection = connection
|
|
14
15
|
this.driver = driver
|
|
16
|
+
this.persistence = persistence
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
async close() {
|
|
18
|
-
await this.
|
|
20
|
+
await this.flushDatabaseSave()
|
|
19
21
|
await this.connection.close()
|
|
20
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Flushes any debounced database save and waits until persistence is complete.
|
|
26
|
+
* @returns {Promise<void>} - Resolves when the current database bytes are stored.
|
|
27
|
+
*/
|
|
28
|
+
async flushDatabaseSave() {
|
|
29
|
+
this.saveDatabaseDebounce.clear()
|
|
30
|
+
await this.saveDatabase()
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
/**
|
|
22
34
|
* Runs query.
|
|
23
35
|
* @param {string} sql - SQL string.
|
|
@@ -36,10 +48,9 @@ export default class VelociousDatabaseDriversSqliteConnectionSqlJs {
|
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
saveDatabase = async () => {
|
|
39
|
-
const
|
|
51
|
+
const databaseContent = this.connection.export()
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
await this.driver.betterLocalStorage.set(this.driver.localStorageName(), localStorageContent)
|
|
53
|
+
await this.persistence.save(databaseContent)
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
saveDatabaseDebounce = debounce(this.saveDatabase, 500)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import BetterLocalStorage from "better-localstorage"
|
|
4
3
|
import ConnectionSqlJs from "./connection-sql-js.js"
|
|
5
4
|
import initSqlJs from "sql.js"
|
|
5
|
+
import {createSqliteWebPersistence, deleteSqliteWebPersistences, sqliteWebPersistenceKey} from "./web-persistence.js"
|
|
6
6
|
|
|
7
7
|
import Base from "./base.js"
|
|
8
8
|
|
|
@@ -12,10 +12,6 @@ import Base from "./base.js"
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export default class VelociousDatabaseDriversSqliteWeb extends Base {
|
|
15
|
-
/**
|
|
16
|
-
* Better local storage.
|
|
17
|
-
* @type {BetterLocalStorage | undefined} */
|
|
18
|
-
betterLocalStorage = undefined
|
|
19
15
|
/**
|
|
20
16
|
* Connection.
|
|
21
17
|
* @type {ConnectionSqlJs | undefined} */
|
|
@@ -39,16 +35,14 @@ export default class VelociousDatabaseDriversSqliteWeb extends Base {
|
|
|
39
35
|
this.args = this.getArgs()
|
|
40
36
|
|
|
41
37
|
if (!this.args.getConnection) {
|
|
42
|
-
this.betterLocalStorage ||= new BetterLocalStorage()
|
|
43
|
-
|
|
44
38
|
if (this.args.reset) {
|
|
45
|
-
await
|
|
39
|
+
await deleteSqliteWebPersistences({databaseName: this.databaseName()})
|
|
46
40
|
}
|
|
47
41
|
|
|
42
|
+
const persistence = await createSqliteWebPersistence({databaseName: this.databaseName()})
|
|
48
43
|
const SQL = await initSqlJs({locateFile: this.sqlJsLocateFile()})
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const connectionSqlJs = new ConnectionSqlJs(this, new SQL.Database(databaseContent))
|
|
44
|
+
const databaseContent = await persistence.load()
|
|
45
|
+
const connectionSqlJs = new ConnectionSqlJs(this, new SQL.Database(databaseContent), persistence)
|
|
52
46
|
|
|
53
47
|
this._connection = connectionSqlJs
|
|
54
48
|
}
|
|
@@ -58,6 +52,18 @@ export default class VelociousDatabaseDriversSqliteWeb extends Base {
|
|
|
58
52
|
await this.getConnection().close()
|
|
59
53
|
}
|
|
60
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Flushes pending SQL.js local persistence writes.
|
|
57
|
+
* @returns {Promise<void>} - Resolves when pending writes are durable.
|
|
58
|
+
*/
|
|
59
|
+
async flushPendingWrites() {
|
|
60
|
+
if (!this.args?.getConnection) {
|
|
61
|
+
if (!this._connection) throw new Error("SQLite web connection has not been initialized")
|
|
62
|
+
|
|
63
|
+
await this._connection.flushDatabaseSave()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
/**
|
|
62
68
|
* Runs get connection.
|
|
63
69
|
* @returns {ConnectionSqlJs | SqliteWebConnection} - The connection.
|
|
@@ -72,11 +78,19 @@ export default class VelociousDatabaseDriversSqliteWeb extends Base {
|
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
localStorageName() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
return sqliteWebPersistenceKey(this.databaseName())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns the configured database name.
|
|
86
|
+
* @returns {string} - Database name.
|
|
87
|
+
*/
|
|
88
|
+
databaseName() {
|
|
89
|
+
const name = this.args?.name
|
|
90
|
+
|
|
91
|
+
if (typeof name !== "string" || name.length < 1) throw new Error("No name given in arguments for SQLite Web database")
|
|
78
92
|
|
|
79
|
-
return
|
|
93
|
+
return name
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
/**
|