velocious 1.0.675 → 1.0.676
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 +2 -2
- package/build/src/testing/browser-frontend-tenant-database-scenarios.d.ts +11 -0
- package/build/src/testing/browser-frontend-tenant-database-scenarios.d.ts.map +1 -0
- package/build/src/testing/browser-frontend-tenant-database-scenarios.js +147 -0
- package/build/src/testing/browser-test-app.js +3 -1
- package/build/testing/browser-frontend-tenant-database-scenarios.js +158 -0
- package/build/testing/browser-test-app.js +2 -0
- package/package.json +1 -1
- package/scripts/test-browser.js +5 -0
- package/src/testing/browser-frontend-tenant-database-scenarios.js +158 -0
- package/src/testing/browser-test-app.js +2 -0
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* Self-sustaining sync feeds: upstream imports triggered by the changes pull itself, with framework-owned coalescing and throttling (see [docs/sync-upstream-imports.md](docs/sync-upstream-imports.md))
|
|
22
22
|
* AwesomeTasks-shaped offline sync proof using routed resources, domain commands, signed offline grants, and peer-forwarded mutations (see the [developer guide](docs/shared-resource-sync-guide.md) and [proof](docs/awesome-tasks-offline-sync-proof.md))
|
|
23
23
|
* SQLite web persistence that automatically prefers OPFS, then IndexedDB, and migrates legacy persisted bytes when possible (see [docs/sqlite-web-persistence.md](docs/sqlite-web-persistence.md))
|
|
24
|
-
* Bounded frontend tenant SQLite handles with independently deduplicated per-database migrations/model readiness, React lifecycle integration, durable flush/close, backend-complete deletion, clean-only LRU eviction,
|
|
25
|
-
* Expo / Metro compatibility guidance and
|
|
24
|
+
* Bounded frontend tenant SQLite handles with independently deduplicated per-database migrations/model readiness, React lifecycle integration, durable flush/close, backend-complete deletion, clean-only LRU eviction, scoped pins, and real-browser two-project persistence coverage (see [docs/frontend-tenant-sqlite-lifecycle.md](docs/frontend-tenant-sqlite-lifecycle.md))
|
|
25
|
+
* Expo / Metro compatibility guidance and an all-platform export fixture that exercises two real tenant SQLite replicas in exported web and resolves native bundles to `expo-sqlite` (see [docs/expo-metro-compatibility.md](docs/expo-metro-compatibility.md))
|
|
26
26
|
* Gap-less positional lists with automatic reordering via `actsAsList`, including models with numeric, string, or UUID primary keys (see [docs/acts-as-list.md](docs/acts-as-list.md))
|
|
27
27
|
* Rails-style nested-attribute writes on frontend-model `save()` (see [docs/nested-attributes.md](docs/nested-attributes.md))
|
|
28
28
|
* Async-aware test-data factories with inherited traits, graph-first native association autosave, metadata-aware override precedence, callbacks, sequences, linting, and a process-global reload-retention budget that bounds cache-busted re-import memory (see [docs/factories.md](docs/factories.md))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exercises two durable physical tenant databases inside the real browser.
|
|
3
|
+
* @returns {Promise<{alphaNames: ReturnType<typeof JSON.parse>[], betaNames: ReturnType<typeof JSON.parse>[], identitiesAreDistinct: boolean, openCount: number}>} Serializable proof result.
|
|
4
|
+
*/
|
|
5
|
+
export default function runFrontendTenantDatabasePersistenceScenario(): Promise<{
|
|
6
|
+
alphaNames: ReturnType<typeof JSON.parse>[];
|
|
7
|
+
betaNames: ReturnType<typeof JSON.parse>[];
|
|
8
|
+
identitiesAreDistinct: boolean;
|
|
9
|
+
openCount: number;
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=browser-frontend-tenant-database-scenarios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-frontend-tenant-database-scenarios.d.ts","sourceRoot":"","sources":["../../../src/testing/browser-frontend-tenant-database-scenarios.js"],"names":[],"mappings":"AAiEA;;;GAGG;AACH,wBAA8B,4CAA4C,IAF7D,OAAO,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IAAC,qBAAqB,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,CAAC,CA0FjK"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import Configuration, { CurrentConfigurationNotSetError } from "../configuration.js";
|
|
3
|
+
import DatabaseRecord from "../database/record/index.js";
|
|
4
|
+
import BrowserEnvironmentHandler from "../environment-handlers/browser.js";
|
|
5
|
+
import Migration from "../database/migration/index.js";
|
|
6
|
+
import SingleMultiUsePool from "../database/pool/single-multi-use.js";
|
|
7
|
+
import SqliteWebDriver from "../database/drivers/sqlite/index.web.js";
|
|
8
|
+
import Tenant from "../tenants/tenant.js";
|
|
9
|
+
const DATABASE_IDENTIFIER = "projectTenant";
|
|
10
|
+
const SCHEMA_GENERATION = "frontend-tenant-browser-persistence-1";
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current configuration without requiring one to exist.
|
|
13
|
+
* @returns {Configuration | undefined} Current configuration when installed.
|
|
14
|
+
*/
|
|
15
|
+
function currentConfigurationOrUndefined() {
|
|
16
|
+
try {
|
|
17
|
+
return Configuration.current();
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (!(error instanceof CurrentConfigurationNotSetError))
|
|
21
|
+
throw error;
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds the real browser-backed two-project configuration.
|
|
27
|
+
* @returns {Configuration} Browser tenant configuration.
|
|
28
|
+
*/
|
|
29
|
+
function buildConfiguration() {
|
|
30
|
+
return new Configuration({
|
|
31
|
+
database: {
|
|
32
|
+
test: {
|
|
33
|
+
projectTenant: {
|
|
34
|
+
driver: SqliteWebDriver,
|
|
35
|
+
locateFile: () => "/sql-wasm.wasm",
|
|
36
|
+
migrations: true,
|
|
37
|
+
name: "velocious-browser-project-proof-template",
|
|
38
|
+
poolType: SingleMultiUsePool,
|
|
39
|
+
tenantOnly: true,
|
|
40
|
+
type: "sqlite"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
directory: "/frontend-tenant-browser-persistence-scenario",
|
|
45
|
+
environment: "test",
|
|
46
|
+
environmentHandler: new BrowserEnvironmentHandler(),
|
|
47
|
+
frontendTenantSqlite: { maxOpenHandles: 2 },
|
|
48
|
+
initializeModels: async () => { },
|
|
49
|
+
locale: "en",
|
|
50
|
+
localeFallbacks: { en: ["en"] },
|
|
51
|
+
locales: ["en"],
|
|
52
|
+
tenantDatabaseResolver: ({ identifier, tenant }) => {
|
|
53
|
+
if (identifier !== DATABASE_IDENTIFIER || !tenant || typeof tenant !== "object")
|
|
54
|
+
return;
|
|
55
|
+
const projectSlug = /** @type {{projectSlug?: string}} */ (tenant).projectSlug;
|
|
56
|
+
if (projectSlug !== "alpha" && projectSlug !== "beta")
|
|
57
|
+
return;
|
|
58
|
+
return { name: `velocious-browser-project-proof-${projectSlug}` };
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Exercises two durable physical tenant databases inside the real browser.
|
|
64
|
+
* @returns {Promise<{alphaNames: ReturnType<typeof JSON.parse>[], betaNames: ReturnType<typeof JSON.parse>[], identitiesAreDistinct: boolean, openCount: number}>} Serializable proof result.
|
|
65
|
+
*/
|
|
66
|
+
export default async function runFrontendTenantDatabasePersistenceScenario() {
|
|
67
|
+
const previousConfiguration = currentConfigurationOrUndefined();
|
|
68
|
+
const configuration = buildConfiguration();
|
|
69
|
+
class BrowserProjectRecord extends DatabaseRecord {
|
|
70
|
+
}
|
|
71
|
+
class CreateBrowserProjectRecords extends Migration {
|
|
72
|
+
async up() {
|
|
73
|
+
await this.execute("CREATE TABLE browser_project_records(id integer PRIMARY KEY AUTOINCREMENT, name varchar(255) NOT NULL)");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
BrowserProjectRecord.setTableName("browser_project_records");
|
|
77
|
+
BrowserProjectRecord.switchesTenantDatabase(DATABASE_IDENTIFIER);
|
|
78
|
+
BrowserProjectRecord.registerRecordClass({ configuration });
|
|
79
|
+
CreateBrowserProjectRecords.onDatabases([DATABASE_IDENTIFIER]);
|
|
80
|
+
const migrationFile = "20260919090000-create-browser-project-records.js";
|
|
81
|
+
/**
|
|
82
|
+
* Loads the one migration bundled into this browser proof.
|
|
83
|
+
* @type {import("../database/migrator/types.js").RequireMigrationContextType}
|
|
84
|
+
*/
|
|
85
|
+
const migrations = (fileName) => {
|
|
86
|
+
if (fileName !== migrationFile)
|
|
87
|
+
throw new Error(`Unknown browser tenant proof migration: ${fileName}`);
|
|
88
|
+
return { default: CreateBrowserProjectRecords };
|
|
89
|
+
};
|
|
90
|
+
migrations.keys = () => [migrationFile];
|
|
91
|
+
migrations.id = "frontend-tenant-browser-persistence-scenario";
|
|
92
|
+
const alpha = Tenant.handle({ projectSlug: "alpha" }, configuration);
|
|
93
|
+
const beta = Tenant.handle({ projectSlug: "beta" }, configuration);
|
|
94
|
+
configuration.setCurrent();
|
|
95
|
+
try {
|
|
96
|
+
await Promise.all([
|
|
97
|
+
alpha.delete({ databaseIdentifier: DATABASE_IDENTIFIER }),
|
|
98
|
+
beta.delete({ databaseIdentifier: DATABASE_IDENTIFIER })
|
|
99
|
+
]);
|
|
100
|
+
await Promise.all([
|
|
101
|
+
alpha.initialize({ databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION }),
|
|
102
|
+
beta.initialize({ databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION })
|
|
103
|
+
]);
|
|
104
|
+
for (let index = 0; index < 12; index++) {
|
|
105
|
+
await Promise.all([
|
|
106
|
+
alpha.databaseOperation({ databaseIdentifier: DATABASE_IDENTIFIER }, async (operation) => {
|
|
107
|
+
await operation.forModel(BrowserProjectRecord).create({ name: `alpha-${index}` });
|
|
108
|
+
}),
|
|
109
|
+
beta.databaseOperation({ databaseIdentifier: DATABASE_IDENTIFIER }, async (operation) => {
|
|
110
|
+
await operation.forModel(BrowserProjectRecord).create({ name: `beta-${index}` });
|
|
111
|
+
})
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
await Promise.all([
|
|
115
|
+
alpha.close({ databaseIdentifier: DATABASE_IDENTIFIER, flush: true }),
|
|
116
|
+
beta.close({ databaseIdentifier: DATABASE_IDENTIFIER, flush: true })
|
|
117
|
+
]);
|
|
118
|
+
await Promise.all([
|
|
119
|
+
alpha.initialize({ databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION }),
|
|
120
|
+
beta.initialize({ databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION })
|
|
121
|
+
]);
|
|
122
|
+
const [alphaNames, betaNames] = await Promise.all([
|
|
123
|
+
alpha.databaseOperation({ databaseIdentifier: DATABASE_IDENTIFIER }, async (operation) => {
|
|
124
|
+
return await operation.forModel(BrowserProjectRecord).order("id").pluck("name");
|
|
125
|
+
}),
|
|
126
|
+
beta.databaseOperation({ databaseIdentifier: DATABASE_IDENTIFIER }, async (operation) => {
|
|
127
|
+
return await operation.forModel(BrowserProjectRecord).order("id").pluck("name");
|
|
128
|
+
})
|
|
129
|
+
]);
|
|
130
|
+
return {
|
|
131
|
+
alphaNames,
|
|
132
|
+
betaNames,
|
|
133
|
+
identitiesAreDistinct: alpha.databaseIdentity(DATABASE_IDENTIFIER) !== beta.databaseIdentity(DATABASE_IDENTIFIER),
|
|
134
|
+
openCount: configuration.inspectFrontendTenantSqliteHandles().openCount
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await Promise.all([
|
|
139
|
+
alpha.delete({ databaseIdentifier: DATABASE_IDENTIFIER }),
|
|
140
|
+
beta.delete({ databaseIdentifier: DATABASE_IDENTIFIER })
|
|
141
|
+
]);
|
|
142
|
+
if (previousConfiguration)
|
|
143
|
+
previousConfiguration.setCurrent();
|
|
144
|
+
await configuration.closeDatabaseConnections();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci1mcm9udGVuZC10ZW5hbnQtZGF0YWJhc2Utc2NlbmFyaW9zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3Rlc3RpbmcvYnJvd3Nlci1mcm9udGVuZC10ZW5hbnQtZGF0YWJhc2Utc2NlbmFyaW9zLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLGFBQWEsRUFBRSxFQUFFLCtCQUErQixFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFDcEYsT0FBTyxjQUFjLE1BQU0sNkJBQTZCLENBQUE7QUFDeEQsT0FBTyx5QkFBeUIsTUFBTSxvQ0FBb0MsQ0FBQTtBQUMxRSxPQUFPLFNBQVMsTUFBTSxnQ0FBZ0MsQ0FBQTtBQUN0RCxPQUFPLGtCQUFrQixNQUFNLHNDQUFzQyxDQUFBO0FBQ3JFLE9BQU8sZUFBZSxNQUFNLHlDQUF5QyxDQUFBO0FBQ3JFLE9BQU8sTUFBTSxNQUFNLHNCQUFzQixDQUFBO0FBRXpDLE1BQU0sbUJBQW1CLEdBQUcsZUFBZSxDQUFBO0FBQzNDLE1BQU0saUJBQWlCLEdBQUcsdUNBQXVDLENBQUE7QUFFakU7OztHQUdHO0FBQ0gsU0FBUywrQkFBK0I7SUFDdEMsSUFBSSxDQUFDO1FBQ0gsT0FBTyxhQUFhLENBQUMsT0FBTyxFQUFFLENBQUE7SUFDaEMsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixJQUFJLENBQUMsQ0FBQyxLQUFLLFlBQVksK0JBQStCLENBQUM7WUFBRSxNQUFNLEtBQUssQ0FBQTtRQUVwRSxPQUFPLFNBQVMsQ0FBQTtJQUNsQixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7R0FHRztBQUNILFNBQVMsa0JBQWtCO0lBQ3pCLE9BQU8sSUFBSSxhQUFhLENBQUM7UUFDdkIsUUFBUSxFQUFFO1lBQ1IsSUFBSSxFQUFFO2dCQUNKLGFBQWEsRUFBRTtvQkFDYixNQUFNLEVBQUUsZUFBZTtvQkFDdkIsVUFBVSxFQUFFLEdBQUcsRUFBRSxDQUFDLGdC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QUFNLENBQUMsQ0FBQTtZQUNqRixDQUFDLENBQUM7U0FDSCxDQUFDLENBQUE7UUFFRixPQUFPO1lBQ0wsVUFBVTtZQUNWLFNBQVM7WUFDVCxxQkFBcUIsRUFBRSxLQUFLLENBQUMsZ0JBQWdCLENBQUMsbUJBQW1CLENBQUMsS0FBSyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsbUJBQW1CLENBQUM7WUFDakgsU0FBUyxFQUFFLGFBQWEsQ0FBQyxrQ0FBa0MsRUFBRSxDQUFDLFNBQVM7U0FDeEUsQ0FBQTtJQUNILENBQUM7WUFBUyxDQUFDO1FBQ1QsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDO1lBQ2hCLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBQyxrQkFBa0IsRUFBRSxtQkFBbUIsRUFBQyxDQUFDO1lBQ3ZELElBQUksQ0FBQyxNQUFNLENBQUMsRUFBQyxrQkFBa0IsRUFBRSxtQkFBbUIsRUFBQyxDQUFDO1NBQ3ZELENBQUMsQ0FBQTtRQUNGLElBQUkscUJBQXFCO1lBQUUscUJBQXFCLENBQUMsVUFBVSxFQUFFLENBQUE7UUFDN0QsTUFBTSxhQUFhLENBQUMsd0JBQXdCLEVBQUUsQ0FBQTtJQUNoRCxDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgQ29uZmlndXJhdGlvbiwgeyBDdXJyZW50Q29uZmlndXJhdGlvbk5vdFNldEVycm9yIH0gZnJvbSBcIi4uL2NvbmZpZ3VyYXRpb24uanNcIlxuaW1wb3J0IERhdGFiYXNlUmVjb3JkIGZyb20gXCIuLi9kYXRhYmFzZS9yZWNvcmQvaW5kZXguanNcIlxuaW1wb3J0IEJyb3dzZXJFbnZpcm9ubWVudEhhbmRsZXIgZnJvbSBcIi4uL2Vudmlyb25tZW50LWhhbmRsZXJzL2Jyb3dzZXIuanNcIlxuaW1wb3J0IE1pZ3JhdGlvbiBmcm9tIFwiLi4vZGF0YWJhc2UvbWlncmF0aW9uL2luZGV4LmpzXCJcbmltcG9ydCBTaW5nbGVNdWx0aVVzZVBvb2wgZnJvbSBcIi4uL2RhdGFiYXNlL3Bvb2wvc2luZ2xlLW11bHRpLXVzZS5qc1wiXG5pbXBvcnQgU3FsaXRlV2ViRHJpdmVyIGZyb20gXCIuLi9kYXRhYmFzZS9kcml2ZXJzL3NxbGl0ZS9pbmRleC53ZWIuanNcIlxuaW1wb3J0IFRlbmFudCBmcm9tIFwiLi4vdGVuYW50cy90ZW5hbnQuanNcIlxuXG5jb25zdCBEQVRBQkFTRV9JREVOVElGSUVSID0gXCJwcm9qZWN0VGVuYW50XCJcbmNvbnN0IFNDSEVNQV9HRU5FUkFUSU9OID0gXCJmcm9udGVuZC10ZW5hbnQtYnJvd3Nlci1wZXJzaXN0ZW5jZS0xXCJcblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBjdXJyZW50IGNvbmZpZ3VyYXRpb24gd2l0aG91dCByZXF1aXJpbmcgb25lIHRvIGV4aXN0LlxuICogQHJldHVybnMge0NvbmZpZ3VyYXRpb24gfCB1bmRlZmluZWR9IEN1cnJlbnQgY29uZmlndXJhdGlvbiB3aGVuIGluc3RhbGxlZC5cbiAqL1xuZnVuY3Rpb24gY3VycmVudENvbmZpZ3VyYXRpb25PclVuZGVmaW5lZCgpIHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gQ29uZmlndXJhdGlvbi5jdXJyZW50KClcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBpZiAoIShlcnJvciBpbnN0YW5jZW9mIEN1cnJlbnRDb25maWd1cmF0aW9uTm90U2V0RXJyb3IpKSB0aHJvdyBlcnJvclxuXG4gICAgcmV0dXJuIHVuZGVmaW5lZFxuICB9XG59XG5cbi8qKlxuICogQnVpbGRzIHRoZSByZWFsIGJyb3dzZXItYmFja2VkIHR3by1wcm9qZWN0IGNvbmZpZ3VyYXRpb24uXG4gKiBAcmV0dXJucyB7Q29uZmlndXJhdGlvbn0gQnJvd3NlciB0ZW5hbnQgY29uZmlndXJhdGlvbi5cbiAqL1xuZnVuY3Rpb24gYnVpbGRDb25maWd1cmF0aW9uKCkge1xuICByZXR1cm4gbmV3IENvbmZpZ3VyYXRpb24oe1xuICAgIGRhdGFiYXNlOiB7XG4gICAgICB0ZXN0OiB7XG4gICAgICAgIHByb2plY3RUZW5hbnQ6IHtcbiAgICAgICAgICBkcml2ZXI6IFNxbGl0ZVdlYkRyaXZlcixcbiAgICAgICAgICBsb2NhdGVGaWxlOiAoKSA9PiBcIi9zcWwtd2FzbS53YXNtXCIsXG4gICAgICAgICAgbWlncmF0aW9uczogdHJ1ZSxcbiAgICAgICAgICBuYW1lOiBcInZlbG9jaW91cy1icm93c2VyLXByb2plY3QtcHJvb2YtdGVtcGxhdGVcIixcbiAgICAgICAgICBwb29sVHlwZTogU2luZ2xlTXVsdGlVc2VQb29sLFxuICAgICAgICAgIHRlbmFudE9ubHk6IHRydWUsXG4gICAgICAgICAgdHlwZTogXCJzcWxpdGVcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBkaXJlY3Rvcnk6IFwiL2Zyb250ZW5kLXRlbmFudC1icm93c2VyLXBlcnNpc3RlbmNlLXNjZW5hcmlvXCIsXG4gICAgZW52aXJvbm1lbnQ6IFwidGVzdFwiLFxuICAgIGVudmlyb25tZW50SGFuZGxlcjogbmV3IEJyb3dzZXJFbnZpcm9ubWVudEhhbmRsZXIoKSxcbiAgICBmcm9udGVuZFRlbmFudFNxbGl0ZToge21heE9wZW5IYW5kbGVzOiAyfSxcbiAgICBpbml0aWFsaXplTW9kZWxzOiBhc3luYyAoKSA9PiB7fSxcbiAgICBsb2NhbGU6IFwiZW5cIixcbiAgICBsb2NhbGVGYWxsYmFja3M6IHtlbjogW1wiZW5cIl19LFxuICAgIGxvY2FsZXM6IFtcImVuXCJdLFxuICAgIHRlbmFudERhdGFiYXNlUmVzb2x2ZXI6ICh7aWRlbnRpZmllciwgdGVuYW50fSkgPT4ge1xuICAgICAgaWYgKGlkZW50aWZpZXIgIT09IERBVEFCQVNFX0lERU5USUZJRVIgfHwgIXRlbmFudCB8fCB0eXBlb2YgdGVuYW50ICE9PSBcIm9iamVjdFwiKSByZXR1cm5cbiAgICAgIGNvbnN0IHByb2plY3RTbHVnID0gLyoqIEB0eXBlIHt7cHJvamVjdFNsdWc/OiBzdHJpbmd9fSAqLyAodGVuYW50KS5wcm9qZWN0U2x1Z1xuXG4gICAgICBpZiAocHJvamVjdFNsdWcgIT09IFwiYWxwaGFcIiAmJiBwcm9qZWN0U2x1ZyAhPT0gXCJiZXRhXCIpIHJldHVyblxuXG4gICAgICByZXR1cm4ge25hbWU6IGB2ZWxvY2lvdXMtYnJvd3Nlci1wcm9qZWN0LXByb29mLSR7cHJvamVjdFNsdWd9YH1cbiAgICB9XG4gIH0pXG59XG5cbi8qKlxuICogRXhlcmNpc2VzIHR3byBkdXJhYmxlIHBoeXNpY2FsIHRlbmFudCBkYXRhYmFzZXMgaW5zaWRlIHRoZSByZWFsIGJyb3dzZXIuXG4gKiBAcmV0dXJucyB7UHJvbWlzZTx7YWxwaGFOYW1lczogUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT5bXSwgYmV0YU5hbWVzOiBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPltdLCBpZGVudGl0aWVzQXJlRGlzdGluY3Q6IGJvb2xlYW4sIG9wZW5Db3VudDogbnVtYmVyfT59IFNlcmlhbGl6YWJsZSBwcm9vZiByZXN1bHQuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGFzeW5jIGZ1bmN0aW9uIHJ1bkZyb250ZW5kVGVuYW50RGF0YWJhc2VQZXJzaXN0ZW5jZVNjZW5hcmlvKCkge1xuICBjb25zdCBwcmV2aW91c0NvbmZpZ3VyYXRpb24gPSBjdXJyZW50Q29uZmlndXJhdGlvbk9yVW5kZWZpbmVkKClcbiAgY29uc3QgY29uZmlndXJhdGlvbiA9IGJ1aWxkQ29uZmlndXJhdGlvbigpXG5cbiAgY2xhc3MgQnJvd3NlclByb2plY3RSZWNvcmQgZXh0ZW5kcyBEYXRhYmFzZVJlY29yZCB7fVxuICBjbGFzcyBDcmVhdGVCcm93c2VyUHJvamVjdFJlY29yZHMgZXh0ZW5kcyBNaWdyYXRpb24ge1xuICAgIGFzeW5jIHVwKCkge1xuICAgICAgYXdhaXQgdGhpcy5leGVjdXRlKFwiQ1JFQVRFIFRBQkxFIGJyb3dzZXJfcHJvamVjdF9yZWNvcmRzKGlkIGludGVnZXIgUFJJTUFSWSBLRVkgQVVUT0lOQ1JFTUVOVCwgbmFtZSB2YXJjaGFyKDI1NSkgTk9UIE5VTEwpXCIpXG4gICAgfVxuICB9XG5cbiAgQnJvd3NlclByb2plY3RSZWNvcmQuc2V0VGFibGVOYW1lKFwiYnJvd3Nlcl9wcm9qZWN0X3JlY29yZHNcIilcbiAgQnJvd3NlclByb2plY3RSZWNvcmQuc3dpdGNoZXNUZW5hbnREYXRhYmFzZShEQVRBQkFTRV9JREVOVElGSUVSKVxuICBCcm93c2VyUHJvamVjdFJlY29yZC5yZWdpc3RlclJlY29yZENsYXNzKHtjb25maWd1cmF0aW9ufSlcbiAgQ3JlYXRlQnJvd3NlclByb2plY3RSZWNvcmRzLm9uRGF0YWJhc2VzKFtEQVRBQkFTRV9JREVOVElGSUVSXSlcblxuICBjb25zdCBtaWdyYXRpb25GaWxlID0gXCIyMDI2MDkxOTA5MDAwMC1jcmVhdGUtYnJvd3Nlci1wcm9qZWN0LXJlY29yZHMuanNcIlxuICAvKipcbiAgICogTG9hZHMgdGhlIG9uZSBtaWdyYXRpb24gYnVuZGxlZCBpbnRvIHRoaXMgYnJvd3NlciBwcm9vZi5cbiAgICogQHR5cGUge2ltcG9ydChcIi4uL2RhdGFiYXNlL21pZ3JhdG9yL3R5cGVzLmpzXCIpLlJlcXVpcmVNaWdyYXRpb25Db250ZXh0VHlwZX1cbiAgICovXG4gIGNvbnN0IG1pZ3JhdGlvbnMgPSAoZmlsZU5hbWUpID0+IHtcbiAgICBpZiAoZmlsZU5hbWUgIT09IG1pZ3JhdGlvbkZpbGUpIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBicm93c2VyIHRlbmFudCBwcm9vZiBtaWdyYXRpb246ICR7ZmlsZU5hbWV9YClcblxuICAgIHJldHVybiB7ZGVmYXVsdDogQ3JlYXRlQnJvd3NlclByb2plY3RSZWNvcmRzfVxuICB9XG5cbiAgbWlncmF0aW9ucy5rZXlzID0gKCkgPT4gW21pZ3JhdGlvbkZpbGVdXG4gIG1pZ3JhdGlvbnMuaWQgPSBcImZyb250ZW5kLXRlbmFudC1icm93c2VyLXBlcnNpc3RlbmNlLXNjZW5hcmlvXCJcblxuICBjb25zdCBhbHBoYSA9IFRlbmFudC5oYW5kbGUoe3Byb2plY3RTbHVnOiBcImFscGhhXCJ9LCBjb25maWd1cmF0aW9uKVxuICBjb25zdCBiZXRhID0gVGVuYW50LmhhbmRsZSh7cHJvamVjdFNsdWc6IFwiYmV0YVwifSwgY29uZmlndXJhdGlvbilcblxuICBjb25maWd1cmF0aW9uLnNldEN1cnJlbnQoKVxuXG4gIHRyeSB7XG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoW1xuICAgICAgYWxwaGEuZGVsZXRlKHtkYXRhYmFzZUlkZW50aWZpZXI6IERBVEFCQVNFX0lERU5USUZJRVJ9KSxcbiAgICAgIGJldGEuZGVsZXRlKHtkYXRhYmFzZUlkZW50aWZpZXI6IERBVEFCQVNFX0lERU5USUZJRVJ9KVxuICAgIF0pXG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoW1xuICAgICAgYWxwaGEuaW5pdGlhbGl6ZSh7ZGF0YWJhc2VJZGVudGlmaWVyOiBEQVRBQkFTRV9JREVOVElGSUVSLCBtaWdyYXRpb25zLCBzY2hlbWFHZW5lcmF0aW9uOiBTQ0hFTUFfR0VORVJBVElPTn0pLFxuICAgICAgYmV0YS5pbml0aWFsaXplKHtkYXRhYmFzZUlkZW50aWZpZXI6IERBVEFCQVNFX0lERU5USUZJRVIsIG1pZ3JhdGlvbnMsIHNjaGVtYUdlbmVyYXRpb246IFNDSEVNQV9HRU5FUkFUSU9OfSlcbiAgICBdKVxuXG4gICAgZm9yIChsZXQgaW5kZXggPSAwOyBpbmRleCA8IDEyOyBpbmRleCsrKSB7XG4gICAgICBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgICAgIGFscGhhLmRhdGFiYXNlT3BlcmF0aW9uKHtkYXRhYmFzZUlkZW50aWZpZXI6IERBVEFCQVNFX0lERU5USUZJRVJ9LCBhc3luYyAob3BlcmF0aW9uKSA9PiB7XG4gICAgICAgICAgYXdhaXQgb3BlcmF0aW9uLmZvck1vZGVsKEJyb3dzZXJQcm9qZWN0UmVjb3JkKS5jcmVhdGUoe25hbWU6IGBhbHBoYS0ke2luZGV4fWB9KVxuICAgICAgICB9KSxcbiAgICAgICAgYmV0YS5kYXRhYmFzZU9wZXJhdGlvbih7ZGF0YWJhc2VJZGVudGlmaWVyOiBEQVRBQkFTRV9JREVOVElGSUVSfSwgYXN5bmMgKG9wZXJhdGlvbikgPT4ge1xuICAgICAgICAgIGF3YWl0IG9wZXJhdGlvbi5mb3JNb2RlbChCcm93c2VyUHJvamVjdFJlY29yZCkuY3JlYXRlKHtuYW1lOiBgYmV0YS0ke2luZGV4fWB9KVxuICAgICAgICB9KVxuICAgICAgXSlcbiAgICB9XG5cbiAgICBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgICBhbHBoYS5jbG9zZSh7ZGF0YWJhc2VJZGVudGlmaWVyOiBEQVRBQkFTRV9JREVOVElGSUVSLCBmbHVzaDogdHJ1ZX0pLFxuICAgICAgYmV0YS5jbG9zZSh7ZGF0YWJhc2VJZGVudGlmaWVyOiBEQVRBQkFTRV9JREVOVElGSUVSLCBmbHVzaDogdHJ1ZX0pXG4gICAgXSlcbiAgICBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgICBhbHBoYS5pbml0aWFsaXplKHtkYXRhYmFzZUlkZW50aWZpZXI6IERBVEFCQVNFX0lERU5USUZJRVIsIG1pZ3JhdGlvbnMsIHNjaGVtYUdlbmVyYXRpb246IFNDSEVNQV9HRU5FUkFUSU9OfSksXG4gICAgICBiZXRhLmluaXRpYWxpemUoe2RhdGFiYXNlSWRlbnRpZmllcjogREFUQUJBU0VfSURFTlRJRklFUiwgbWlncmF0aW9ucywgc2NoZW1hR2VuZXJhdGlvbjogU0NIRU1BX0dFTkVSQVRJT059KVxuICAgIF0pXG5cbiAgICBjb25zdCBbYWxwaGFOYW1lcywgYmV0YU5hbWVzXSA9IGF3YWl0IFByb21pc2UuYWxsKFtcbiAgICAgIGFscGhhLmRhdGFiYXNlT3BlcmF0aW9uKHtkYXRhYmFzZUlkZW50aWZpZXI6IERBVEFCQVNFX0lERU5USUZJRVJ9LCBhc3luYyAob3BlcmF0aW9uKSA9PiB7XG4gICAgICAgIHJldHVybiBhd2FpdCBvcGVyYXRpb24uZm9yTW9kZWwoQnJvd3NlclByb2plY3RSZWNvcmQpLm9yZGVyKFwiaWRcIikucGx1Y2soXCJuYW1lXCIpXG4gICAgICB9KSxcbiAgICAgIGJldGEuZGF0YWJhc2VPcGVyYXRpb24oe2RhdGFiYXNlSWRlbnRpZmllcjogREFUQUJBU0VfSURFTlRJRklFUn0sIGFzeW5jIChvcGVyYXRpb24pID0+IHtcbiAgICAgICAgcmV0dXJuIGF3YWl0IG9wZXJhdGlvbi5mb3JNb2RlbChCcm93c2VyUHJvamVjdFJlY29yZCkub3JkZXIoXCJpZFwiKS5wbHVjayhcIm5hbWVcIilcbiAgICAgIH0pXG4gICAgXSlcblxuICAgIHJldHVybiB7XG4gICAgICBhbHBoYU5hbWVzLFxuICAgICAgYmV0YU5hbWVzLFxuICAgICAgaWRlbnRpdGllc0FyZURpc3RpbmN0OiBhbHBoYS5kYXRhYmFzZUlkZW50aXR5KERBVEFCQVNFX0lERU5USUZJRVIpICE9PSBiZXRhLmRhdGFiYXNlSWRlbnRpdHkoREFUQUJBU0VfSURFTlRJRklFUiksXG4gICAgICBvcGVuQ291bnQ6IGNvbmZpZ3VyYXRpb24uaW5zcGVjdEZyb250ZW5kVGVuYW50U3FsaXRlSGFuZGxlcygpLm9wZW5Db3VudFxuICAgIH1cbiAgfSBmaW5hbGx5IHtcbiAgICBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgICBhbHBoYS5kZWxldGUoe2RhdGFiYXNlSWRlbnRpZmllcjogREFUQUJBU0VfSURFTlRJRklFUn0pLFxuICAgICAgYmV0YS5kZWxldGUoe2RhdGFiYXNlSWRlbnRpZmllcjogREFUQUJBU0VfSURFTlRJRklFUn0pXG4gICAgXSlcbiAgICBpZiAocHJldmlvdXNDb25maWd1cmF0aW9uKSBwcmV2aW91c0NvbmZpZ3VyYXRpb24uc2V0Q3VycmVudCgpXG4gICAgYXdhaXQgY29uZmlndXJhdGlvbi5jbG9zZURhdGFiYXNlQ29ubmVjdGlvbnMoKVxuICB9XG59XG4iXX0=
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import SystemTestBrowserHelper from "system-testing/build/system-test-browser-helper.js";
|
|
3
3
|
import BrowserEnvironmentHandler from "../environment-handlers/browser.js";
|
|
4
4
|
import runFrontendModelEventHookScenario from "./browser-frontend-model-event-hook-scenarios.js";
|
|
5
|
+
import runFrontendTenantDatabasePersistenceScenario from "./browser-frontend-tenant-database-scenarios.js";
|
|
5
6
|
import runUseDatabaseSelectionTransitionScenario from "./browser-use-database-hook-scenarios.js";
|
|
6
7
|
const root = document.getElementById("root") || (() => {
|
|
7
8
|
const element = document.createElement("div");
|
|
@@ -22,7 +23,8 @@ systemTestBrowserHelper.enableOnBrowser();
|
|
|
22
23
|
Object.assign(globalThis, { velociousBrowserTest: {
|
|
23
24
|
BrowserEnvironmentHandler,
|
|
24
25
|
runFrontendModelEventHookScenario,
|
|
26
|
+
runFrontendTenantDatabasePersistenceScenario,
|
|
25
27
|
runUseDatabaseSelectionTransitionScenario,
|
|
26
28
|
systemTestBrowserHelper
|
|
27
29
|
} });
|
|
28
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJvd3Nlci10ZXN0LWFwcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy90ZXN0aW5nL2Jyb3dzZXItdGVzdC1hcHAuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sdUJBQXVCLE1BQU0sb0RBQW9ELENBQUE7QUFDeEYsT0FBTyx5QkFBeUIsTUFBTSxvQ0FBb0MsQ0FBQTtBQUMxRSxPQUFPLGlDQUFpQyxNQUFNLGtEQUFrRCxDQUFBO0FBQ2hHLE9BQU8sNENBQTRDLE1BQU0saURBQWlELENBQUE7QUFDMUcsT0FBTyx5Q0FBeUMsTUFBTSwwQ0FBMEMsQ0FBQTtBQUVoRyxNQUFNLElBQUksR0FBRyxRQUFRLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO0lBQ3BELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDN0MsT0FBTyxDQUFDLEVBQUUsR0FBRyxNQUFNLENBQUE7SUFDbkIsUUFBUSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDbEMsT0FBTyxPQUFPLENBQUE7QUFDaEIsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtBQUVKLE1BQU0sc0JBQXNCLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQUM1RCxzQkFBc0IsQ0FBQyxZQUFZLENBQUMsYUFBYSxFQUFFLHdCQUF3QixDQUFDLENBQUE7QUFDNUUsc0JBQXNCLENBQUMsWUFBWSxDQUFDLGVBQWUsRUFBRSxNQUFNLENBQUMsQ0FBQTtBQUU1RCxNQUFNLFNBQVMsR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFBO0FBQy9DLFNBQVMsQ0FBQyxZQUFZLENBQUMsYUFBYSxFQUFFLFdBQVcsQ0FBQyxDQUFBO0FBQ2xELFNBQVMsQ0FBQyxXQUFXLEdBQUcsT0FBTyxDQUFBO0FBRS9CLHNCQUFzQixDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtBQUM3QyxJQUFJLENBQUMsV0FBVyxDQUFDLHNCQUFzQixDQUFDLENBQUE7QUFFeEMsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLHVCQUF1QixFQUFFLENBQUE7QUFDN0QsdUJBQXVCLENBQUMsZUFBZSxFQUFFLENBQUE7QUFFekMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsRUFBQyxvQkFBb0IsRUFBRTtRQUMvQyx5QkFBeUI7UUFDekIsaUNBQWlDO1FBQ2pDLDRDQUE0QztRQUM1Qyx5Q0FBeUM7UUFDekMsdUJBQXVCO0tBQ3hCLEVBQUMsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmltcG9ydCBTeXN0ZW1UZXN0QnJvd3NlckhlbHBlciBmcm9tIFwic3lzdGVtLXRlc3RpbmcvYnVpbGQvc3lzdGVtLXRlc3QtYnJvd3Nlci1oZWxwZXIuanNcIlxuaW1wb3J0IEJyb3dzZXJFbnZpcm9ubWVudEhhbmRsZXIgZnJvbSBcIi4uL2Vudmlyb25tZW50LWhhbmRsZXJzL2Jyb3dzZXIuanNcIlxuaW1wb3J0IHJ1bkZyb250ZW5kTW9kZWxFdmVudEhvb2tTY2VuYXJpbyBmcm9tIFwiLi9icm93c2VyLWZyb250ZW5kLW1vZGVsLWV2ZW50LWhvb2stc2NlbmFyaW9zLmpzXCJcbmltcG9ydCBydW5Gcm9udGVuZFRlbmFudERhdGFiYXNlUGVyc2lzdGVuY2VTY2VuYXJpbyBmcm9tIFwiLi9icm93c2VyLWZyb250ZW5kLXRlbmFudC1kYXRhYmFzZS1zY2VuYXJpb3MuanNcIlxuaW1wb3J0IHJ1blVzZURhdGFiYXNlU2VsZWN0aW9uVHJhbnNpdGlvblNjZW5hcmlvIGZyb20gXCIuL2Jyb3dzZXItdXNlLWRhdGFiYXNlLWhvb2stc2NlbmFyaW9zLmpzXCJcblxuY29uc3Qgcm9vdCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFwicm9vdFwiKSB8fCAoKCkgPT4ge1xuICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKVxuICBlbGVtZW50LmlkID0gXCJyb290XCJcbiAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChlbGVtZW50KVxuICByZXR1cm4gZWxlbWVudFxufSkoKVxuXG5jb25zdCBzeXN0ZW1UZXN0aW5nQ29tcG9uZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKVxuc3lzdGVtVGVzdGluZ0NvbXBvbmVudC5zZXRBdHRyaWJ1dGUoXCJkYXRhLXRlc3RpZFwiLCBcInN5c3RlbVRlc3RpbmdDb21wb25lbnRcIilcbnN5c3RlbVRlc3RpbmdDb21wb25lbnQuc2V0QXR0cmlidXRlKFwiZGF0YS1mb2N1c3NlZFwiLCBcInRydWVcIilcblxuY29uc3QgYmxhbmtUZXh0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKVxuYmxhbmtUZXh0LnNldEF0dHJpYnV0ZShcImRhdGEtdGVzdGlkXCIsIFwiYmxhbmtUZXh0XCIpXG5ibGFua1RleHQudGV4dENvbnRlbnQgPSBcImJsYW5rXCJcblxuc3lzdGVtVGVzdGluZ0NvbXBvbmVudC5hcHBlbmRDaGlsZChibGFua1RleHQpXG5yb290LmFwcGVuZENoaWxkKHN5c3RlbVRlc3RpbmdDb21wb25lbnQpXG5cbmNvbnN0IHN5c3RlbVRlc3RCcm93c2VySGVscGVyID0gbmV3IFN5c3RlbVRlc3RCcm93c2VySGVscGVyKClcbnN5c3RlbVRlc3RCcm93c2VySGVscGVyLmVuYWJsZU9uQnJvd3NlcigpXG5cbk9iamVjdC5hc3NpZ24oZ2xvYmFsVGhpcywge3ZlbG9jaW91c0Jyb3dzZXJUZXN0OiB7XG4gIEJyb3dzZXJFbnZpcm9ubWVudEhhbmRsZXIsXG4gIHJ1bkZyb250ZW5kTW9kZWxFdmVudEhvb2tTY2VuYXJpbyxcbiAgcnVuRnJvbnRlbmRUZW5hbnREYXRhYmFzZVBlcnNpc3RlbmNlU2NlbmFyaW8sXG4gIHJ1blVzZURhdGFiYXNlU2VsZWN0aW9uVHJhbnNpdGlvblNjZW5hcmlvLFxuICBzeXN0ZW1UZXN0QnJvd3NlckhlbHBlclxufX0pXG4iXX0=
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import Configuration, { CurrentConfigurationNotSetError } from "../configuration.js"
|
|
4
|
+
import DatabaseRecord from "../database/record/index.js"
|
|
5
|
+
import BrowserEnvironmentHandler from "../environment-handlers/browser.js"
|
|
6
|
+
import Migration from "../database/migration/index.js"
|
|
7
|
+
import SingleMultiUsePool from "../database/pool/single-multi-use.js"
|
|
8
|
+
import SqliteWebDriver from "../database/drivers/sqlite/index.web.js"
|
|
9
|
+
import Tenant from "../tenants/tenant.js"
|
|
10
|
+
|
|
11
|
+
const DATABASE_IDENTIFIER = "projectTenant"
|
|
12
|
+
const SCHEMA_GENERATION = "frontend-tenant-browser-persistence-1"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns the current configuration without requiring one to exist.
|
|
16
|
+
* @returns {Configuration | undefined} Current configuration when installed.
|
|
17
|
+
*/
|
|
18
|
+
function currentConfigurationOrUndefined() {
|
|
19
|
+
try {
|
|
20
|
+
return Configuration.current()
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (!(error instanceof CurrentConfigurationNotSetError)) throw error
|
|
23
|
+
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds the real browser-backed two-project configuration.
|
|
30
|
+
* @returns {Configuration} Browser tenant configuration.
|
|
31
|
+
*/
|
|
32
|
+
function buildConfiguration() {
|
|
33
|
+
return new Configuration({
|
|
34
|
+
database: {
|
|
35
|
+
test: {
|
|
36
|
+
projectTenant: {
|
|
37
|
+
driver: SqliteWebDriver,
|
|
38
|
+
locateFile: () => "/sql-wasm.wasm",
|
|
39
|
+
migrations: true,
|
|
40
|
+
name: "velocious-browser-project-proof-template",
|
|
41
|
+
poolType: SingleMultiUsePool,
|
|
42
|
+
tenantOnly: true,
|
|
43
|
+
type: "sqlite"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
directory: "/frontend-tenant-browser-persistence-scenario",
|
|
48
|
+
environment: "test",
|
|
49
|
+
environmentHandler: new BrowserEnvironmentHandler(),
|
|
50
|
+
frontendTenantSqlite: {maxOpenHandles: 2},
|
|
51
|
+
initializeModels: async () => {},
|
|
52
|
+
locale: "en",
|
|
53
|
+
localeFallbacks: {en: ["en"]},
|
|
54
|
+
locales: ["en"],
|
|
55
|
+
tenantDatabaseResolver: ({identifier, tenant}) => {
|
|
56
|
+
if (identifier !== DATABASE_IDENTIFIER || !tenant || typeof tenant !== "object") return
|
|
57
|
+
const projectSlug = /** @type {{projectSlug?: string}} */ (tenant).projectSlug
|
|
58
|
+
|
|
59
|
+
if (projectSlug !== "alpha" && projectSlug !== "beta") return
|
|
60
|
+
|
|
61
|
+
return {name: `velocious-browser-project-proof-${projectSlug}`}
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Exercises two durable physical tenant databases inside the real browser.
|
|
68
|
+
* @returns {Promise<{alphaNames: ReturnType<typeof JSON.parse>[], betaNames: ReturnType<typeof JSON.parse>[], identitiesAreDistinct: boolean, openCount: number}>} Serializable proof result.
|
|
69
|
+
*/
|
|
70
|
+
export default async function runFrontendTenantDatabasePersistenceScenario() {
|
|
71
|
+
const previousConfiguration = currentConfigurationOrUndefined()
|
|
72
|
+
const configuration = buildConfiguration()
|
|
73
|
+
|
|
74
|
+
class BrowserProjectRecord extends DatabaseRecord {}
|
|
75
|
+
class CreateBrowserProjectRecords extends Migration {
|
|
76
|
+
async up() {
|
|
77
|
+
await this.execute("CREATE TABLE browser_project_records(id integer PRIMARY KEY AUTOINCREMENT, name varchar(255) NOT NULL)")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
BrowserProjectRecord.setTableName("browser_project_records")
|
|
82
|
+
BrowserProjectRecord.switchesTenantDatabase(DATABASE_IDENTIFIER)
|
|
83
|
+
BrowserProjectRecord.registerRecordClass({configuration})
|
|
84
|
+
CreateBrowserProjectRecords.onDatabases([DATABASE_IDENTIFIER])
|
|
85
|
+
|
|
86
|
+
const migrationFile = "20260919090000-create-browser-project-records.js"
|
|
87
|
+
/**
|
|
88
|
+
* Loads the one migration bundled into this browser proof.
|
|
89
|
+
* @type {import("../database/migrator/types.js").RequireMigrationContextType}
|
|
90
|
+
*/
|
|
91
|
+
const migrations = (fileName) => {
|
|
92
|
+
if (fileName !== migrationFile) throw new Error(`Unknown browser tenant proof migration: ${fileName}`)
|
|
93
|
+
|
|
94
|
+
return {default: CreateBrowserProjectRecords}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
migrations.keys = () => [migrationFile]
|
|
98
|
+
migrations.id = "frontend-tenant-browser-persistence-scenario"
|
|
99
|
+
|
|
100
|
+
const alpha = Tenant.handle({projectSlug: "alpha"}, configuration)
|
|
101
|
+
const beta = Tenant.handle({projectSlug: "beta"}, configuration)
|
|
102
|
+
|
|
103
|
+
configuration.setCurrent()
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await Promise.all([
|
|
107
|
+
alpha.delete({databaseIdentifier: DATABASE_IDENTIFIER}),
|
|
108
|
+
beta.delete({databaseIdentifier: DATABASE_IDENTIFIER})
|
|
109
|
+
])
|
|
110
|
+
await Promise.all([
|
|
111
|
+
alpha.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION}),
|
|
112
|
+
beta.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION})
|
|
113
|
+
])
|
|
114
|
+
|
|
115
|
+
for (let index = 0; index < 12; index++) {
|
|
116
|
+
await Promise.all([
|
|
117
|
+
alpha.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
118
|
+
await operation.forModel(BrowserProjectRecord).create({name: `alpha-${index}`})
|
|
119
|
+
}),
|
|
120
|
+
beta.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
121
|
+
await operation.forModel(BrowserProjectRecord).create({name: `beta-${index}`})
|
|
122
|
+
})
|
|
123
|
+
])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await Promise.all([
|
|
127
|
+
alpha.close({databaseIdentifier: DATABASE_IDENTIFIER, flush: true}),
|
|
128
|
+
beta.close({databaseIdentifier: DATABASE_IDENTIFIER, flush: true})
|
|
129
|
+
])
|
|
130
|
+
await Promise.all([
|
|
131
|
+
alpha.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION}),
|
|
132
|
+
beta.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION})
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
const [alphaNames, betaNames] = await Promise.all([
|
|
136
|
+
alpha.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
137
|
+
return await operation.forModel(BrowserProjectRecord).order("id").pluck("name")
|
|
138
|
+
}),
|
|
139
|
+
beta.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
140
|
+
return await operation.forModel(BrowserProjectRecord).order("id").pluck("name")
|
|
141
|
+
})
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
alphaNames,
|
|
146
|
+
betaNames,
|
|
147
|
+
identitiesAreDistinct: alpha.databaseIdentity(DATABASE_IDENTIFIER) !== beta.databaseIdentity(DATABASE_IDENTIFIER),
|
|
148
|
+
openCount: configuration.inspectFrontendTenantSqliteHandles().openCount
|
|
149
|
+
}
|
|
150
|
+
} finally {
|
|
151
|
+
await Promise.all([
|
|
152
|
+
alpha.delete({databaseIdentifier: DATABASE_IDENTIFIER}),
|
|
153
|
+
beta.delete({databaseIdentifier: DATABASE_IDENTIFIER})
|
|
154
|
+
])
|
|
155
|
+
if (previousConfiguration) previousConfiguration.setCurrent()
|
|
156
|
+
await configuration.closeDatabaseConnections()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import SystemTestBrowserHelper from "system-testing/build/system-test-browser-helper.js"
|
|
4
4
|
import BrowserEnvironmentHandler from "../environment-handlers/browser.js"
|
|
5
5
|
import runFrontendModelEventHookScenario from "./browser-frontend-model-event-hook-scenarios.js"
|
|
6
|
+
import runFrontendTenantDatabasePersistenceScenario from "./browser-frontend-tenant-database-scenarios.js"
|
|
6
7
|
import runUseDatabaseSelectionTransitionScenario from "./browser-use-database-hook-scenarios.js"
|
|
7
8
|
|
|
8
9
|
const root = document.getElementById("root") || (() => {
|
|
@@ -29,6 +30,7 @@ systemTestBrowserHelper.enableOnBrowser()
|
|
|
29
30
|
Object.assign(globalThis, {velociousBrowserTest: {
|
|
30
31
|
BrowserEnvironmentHandler,
|
|
31
32
|
runFrontendModelEventHookScenario,
|
|
33
|
+
runFrontendTenantDatabasePersistenceScenario,
|
|
32
34
|
runUseDatabaseSelectionTransitionScenario,
|
|
33
35
|
systemTestBrowserHelper
|
|
34
36
|
}})
|
package/package.json
CHANGED
package/scripts/test-browser.js
CHANGED
|
@@ -95,6 +95,11 @@ async function buildBrowserTestApp() {
|
|
|
95
95
|
sourcemap: true
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
await fs.copyFile(
|
|
99
|
+
path.join(rootDir, "node_modules", "sql.js", "dist", "sql-wasm.wasm"),
|
|
100
|
+
path.join(distDir, "sql-wasm.wasm")
|
|
101
|
+
)
|
|
102
|
+
|
|
98
103
|
const html = `<!doctype html>
|
|
99
104
|
<html lang="en">
|
|
100
105
|
<head>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import Configuration, { CurrentConfigurationNotSetError } from "../configuration.js"
|
|
4
|
+
import DatabaseRecord from "../database/record/index.js"
|
|
5
|
+
import BrowserEnvironmentHandler from "../environment-handlers/browser.js"
|
|
6
|
+
import Migration from "../database/migration/index.js"
|
|
7
|
+
import SingleMultiUsePool from "../database/pool/single-multi-use.js"
|
|
8
|
+
import SqliteWebDriver from "../database/drivers/sqlite/index.web.js"
|
|
9
|
+
import Tenant from "../tenants/tenant.js"
|
|
10
|
+
|
|
11
|
+
const DATABASE_IDENTIFIER = "projectTenant"
|
|
12
|
+
const SCHEMA_GENERATION = "frontend-tenant-browser-persistence-1"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns the current configuration without requiring one to exist.
|
|
16
|
+
* @returns {Configuration | undefined} Current configuration when installed.
|
|
17
|
+
*/
|
|
18
|
+
function currentConfigurationOrUndefined() {
|
|
19
|
+
try {
|
|
20
|
+
return Configuration.current()
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (!(error instanceof CurrentConfigurationNotSetError)) throw error
|
|
23
|
+
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds the real browser-backed two-project configuration.
|
|
30
|
+
* @returns {Configuration} Browser tenant configuration.
|
|
31
|
+
*/
|
|
32
|
+
function buildConfiguration() {
|
|
33
|
+
return new Configuration({
|
|
34
|
+
database: {
|
|
35
|
+
test: {
|
|
36
|
+
projectTenant: {
|
|
37
|
+
driver: SqliteWebDriver,
|
|
38
|
+
locateFile: () => "/sql-wasm.wasm",
|
|
39
|
+
migrations: true,
|
|
40
|
+
name: "velocious-browser-project-proof-template",
|
|
41
|
+
poolType: SingleMultiUsePool,
|
|
42
|
+
tenantOnly: true,
|
|
43
|
+
type: "sqlite"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
directory: "/frontend-tenant-browser-persistence-scenario",
|
|
48
|
+
environment: "test",
|
|
49
|
+
environmentHandler: new BrowserEnvironmentHandler(),
|
|
50
|
+
frontendTenantSqlite: {maxOpenHandles: 2},
|
|
51
|
+
initializeModels: async () => {},
|
|
52
|
+
locale: "en",
|
|
53
|
+
localeFallbacks: {en: ["en"]},
|
|
54
|
+
locales: ["en"],
|
|
55
|
+
tenantDatabaseResolver: ({identifier, tenant}) => {
|
|
56
|
+
if (identifier !== DATABASE_IDENTIFIER || !tenant || typeof tenant !== "object") return
|
|
57
|
+
const projectSlug = /** @type {{projectSlug?: string}} */ (tenant).projectSlug
|
|
58
|
+
|
|
59
|
+
if (projectSlug !== "alpha" && projectSlug !== "beta") return
|
|
60
|
+
|
|
61
|
+
return {name: `velocious-browser-project-proof-${projectSlug}`}
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Exercises two durable physical tenant databases inside the real browser.
|
|
68
|
+
* @returns {Promise<{alphaNames: ReturnType<typeof JSON.parse>[], betaNames: ReturnType<typeof JSON.parse>[], identitiesAreDistinct: boolean, openCount: number}>} Serializable proof result.
|
|
69
|
+
*/
|
|
70
|
+
export default async function runFrontendTenantDatabasePersistenceScenario() {
|
|
71
|
+
const previousConfiguration = currentConfigurationOrUndefined()
|
|
72
|
+
const configuration = buildConfiguration()
|
|
73
|
+
|
|
74
|
+
class BrowserProjectRecord extends DatabaseRecord {}
|
|
75
|
+
class CreateBrowserProjectRecords extends Migration {
|
|
76
|
+
async up() {
|
|
77
|
+
await this.execute("CREATE TABLE browser_project_records(id integer PRIMARY KEY AUTOINCREMENT, name varchar(255) NOT NULL)")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
BrowserProjectRecord.setTableName("browser_project_records")
|
|
82
|
+
BrowserProjectRecord.switchesTenantDatabase(DATABASE_IDENTIFIER)
|
|
83
|
+
BrowserProjectRecord.registerRecordClass({configuration})
|
|
84
|
+
CreateBrowserProjectRecords.onDatabases([DATABASE_IDENTIFIER])
|
|
85
|
+
|
|
86
|
+
const migrationFile = "20260919090000-create-browser-project-records.js"
|
|
87
|
+
/**
|
|
88
|
+
* Loads the one migration bundled into this browser proof.
|
|
89
|
+
* @type {import("../database/migrator/types.js").RequireMigrationContextType}
|
|
90
|
+
*/
|
|
91
|
+
const migrations = (fileName) => {
|
|
92
|
+
if (fileName !== migrationFile) throw new Error(`Unknown browser tenant proof migration: ${fileName}`)
|
|
93
|
+
|
|
94
|
+
return {default: CreateBrowserProjectRecords}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
migrations.keys = () => [migrationFile]
|
|
98
|
+
migrations.id = "frontend-tenant-browser-persistence-scenario"
|
|
99
|
+
|
|
100
|
+
const alpha = Tenant.handle({projectSlug: "alpha"}, configuration)
|
|
101
|
+
const beta = Tenant.handle({projectSlug: "beta"}, configuration)
|
|
102
|
+
|
|
103
|
+
configuration.setCurrent()
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await Promise.all([
|
|
107
|
+
alpha.delete({databaseIdentifier: DATABASE_IDENTIFIER}),
|
|
108
|
+
beta.delete({databaseIdentifier: DATABASE_IDENTIFIER})
|
|
109
|
+
])
|
|
110
|
+
await Promise.all([
|
|
111
|
+
alpha.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION}),
|
|
112
|
+
beta.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION})
|
|
113
|
+
])
|
|
114
|
+
|
|
115
|
+
for (let index = 0; index < 12; index++) {
|
|
116
|
+
await Promise.all([
|
|
117
|
+
alpha.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
118
|
+
await operation.forModel(BrowserProjectRecord).create({name: `alpha-${index}`})
|
|
119
|
+
}),
|
|
120
|
+
beta.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
121
|
+
await operation.forModel(BrowserProjectRecord).create({name: `beta-${index}`})
|
|
122
|
+
})
|
|
123
|
+
])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await Promise.all([
|
|
127
|
+
alpha.close({databaseIdentifier: DATABASE_IDENTIFIER, flush: true}),
|
|
128
|
+
beta.close({databaseIdentifier: DATABASE_IDENTIFIER, flush: true})
|
|
129
|
+
])
|
|
130
|
+
await Promise.all([
|
|
131
|
+
alpha.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION}),
|
|
132
|
+
beta.initialize({databaseIdentifier: DATABASE_IDENTIFIER, migrations, schemaGeneration: SCHEMA_GENERATION})
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
const [alphaNames, betaNames] = await Promise.all([
|
|
136
|
+
alpha.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
137
|
+
return await operation.forModel(BrowserProjectRecord).order("id").pluck("name")
|
|
138
|
+
}),
|
|
139
|
+
beta.databaseOperation({databaseIdentifier: DATABASE_IDENTIFIER}, async (operation) => {
|
|
140
|
+
return await operation.forModel(BrowserProjectRecord).order("id").pluck("name")
|
|
141
|
+
})
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
alphaNames,
|
|
146
|
+
betaNames,
|
|
147
|
+
identitiesAreDistinct: alpha.databaseIdentity(DATABASE_IDENTIFIER) !== beta.databaseIdentity(DATABASE_IDENTIFIER),
|
|
148
|
+
openCount: configuration.inspectFrontendTenantSqliteHandles().openCount
|
|
149
|
+
}
|
|
150
|
+
} finally {
|
|
151
|
+
await Promise.all([
|
|
152
|
+
alpha.delete({databaseIdentifier: DATABASE_IDENTIFIER}),
|
|
153
|
+
beta.delete({databaseIdentifier: DATABASE_IDENTIFIER})
|
|
154
|
+
])
|
|
155
|
+
if (previousConfiguration) previousConfiguration.setCurrent()
|
|
156
|
+
await configuration.closeDatabaseConnections()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import SystemTestBrowserHelper from "system-testing/build/system-test-browser-helper.js"
|
|
4
4
|
import BrowserEnvironmentHandler from "../environment-handlers/browser.js"
|
|
5
5
|
import runFrontendModelEventHookScenario from "./browser-frontend-model-event-hook-scenarios.js"
|
|
6
|
+
import runFrontendTenantDatabasePersistenceScenario from "./browser-frontend-tenant-database-scenarios.js"
|
|
6
7
|
import runUseDatabaseSelectionTransitionScenario from "./browser-use-database-hook-scenarios.js"
|
|
7
8
|
|
|
8
9
|
const root = document.getElementById("root") || (() => {
|
|
@@ -29,6 +30,7 @@ systemTestBrowserHelper.enableOnBrowser()
|
|
|
29
30
|
Object.assign(globalThis, {velociousBrowserTest: {
|
|
30
31
|
BrowserEnvironmentHandler,
|
|
31
32
|
runFrontendModelEventHookScenario,
|
|
33
|
+
runFrontendTenantDatabasePersistenceScenario,
|
|
32
34
|
runUseDatabaseSelectionTransitionScenario,
|
|
33
35
|
systemTestBrowserHelper
|
|
34
36
|
}})
|