velocious 1.0.675 → 1.0.677
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 +4 -4
- package/build/background-jobs/pooled-runner-child.js +87 -5
- package/build/background-jobs/pooled-runner-shutdown.js +55 -0
- package/build/background-jobs/types.js +24 -2
- package/build/background-jobs/worker.js +182 -36
- package/build/src/background-jobs/pooled-runner-child.js +87 -7
- package/build/src/background-jobs/pooled-runner-shutdown.d.ts +26 -0
- package/build/src/background-jobs/pooled-runner-shutdown.d.ts.map +1 -0
- package/build/src/background-jobs/pooled-runner-shutdown.js +49 -0
- package/build/src/background-jobs/types.d.ts +84 -3
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +25 -3
- package/build/src/background-jobs/worker.d.ts +54 -4
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +191 -39
- 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/background-jobs/pooled-runner-child.js +87 -5
- package/src/background-jobs/pooled-runner-shutdown.js +55 -0
- package/src/background-jobs/types.js +24 -2
- package/src/background-jobs/worker.js +182 -36
- package/src/testing/browser-frontend-tenant-database-scenarios.js +158 -0
- package/src/testing/browser-test-app.js +2 -0
|
@@ -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>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import { randomUUID } from "node:crypto"
|
|
4
|
+
import timeout from "awaitery/build/timeout.js"
|
|
4
5
|
import runJobPayload, { BackgroundJobPerformedFailure } from "./job-runner.js"
|
|
6
|
+
import { boundedPooledRunnerInflightJobIds, isPooledChildShutdownReason, isPooledChildShutdownSignal } from "./pooled-runner-shutdown.js"
|
|
5
7
|
import { closeRunnerConnections, closeRunnerFrameworkConnections, currentConfigurationOrNull } from "./runner-graceful-shutdown.js"
|
|
6
8
|
import setRunnerProcessTitle from "./runner-process-title.js"
|
|
7
9
|
import PooledRunnerBrokerIdentity from "./pooled-runner-broker-identity.js"
|
|
8
10
|
import { runWithSharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js"
|
|
9
11
|
|
|
10
12
|
const BASE_PROCESS_TITLE = "velocious background-jobs-runner"
|
|
13
|
+
/** A shutdown observation may delay resource teardown only for this bounded IPC send. */
|
|
14
|
+
const SHUTDOWN_OBSERVATION_SEND_TIMEOUT_MS = 100
|
|
11
15
|
/** Stable identity of this pooled child process for the life of the process. */
|
|
12
16
|
const childInstanceId = randomUUID()
|
|
13
17
|
|
|
@@ -20,13 +24,18 @@ let shutdownPromise
|
|
|
20
24
|
* Closes the runner's connections — releasing any advisory lock a killed-mid-pass
|
|
21
25
|
* job still holds — before exiting, instead of leaving a half-open session that
|
|
22
26
|
* keeps the lock until the DB server's `wait_timeout`.
|
|
23
|
-
* @param {
|
|
27
|
+
* @param {object} args - Shutdown observation.
|
|
28
|
+
* @param {number} args.exitCode - Process exit code.
|
|
29
|
+
* @param {import("./types.js").PooledChildShutdownReason} args.reason - Exact shutdown reason observed by the child.
|
|
30
|
+
* @param {number | null} [args.shutdownRequestedAtMs] - Parent request timestamp when supplied over IPC.
|
|
31
|
+
* @param {import("node:child_process").ChildProcess["signalCode"]} [args.signal] - Requested or observed signal.
|
|
24
32
|
* @returns {Promise<void>}
|
|
25
33
|
*/
|
|
26
|
-
function shutdownRunner(exitCode) {
|
|
34
|
+
function shutdownRunner({exitCode, reason, shutdownRequestedAtMs = null, signal = null}) {
|
|
27
35
|
if (shutdownPromise) return shutdownPromise
|
|
28
36
|
|
|
29
37
|
shutdownPromise = (async () => {
|
|
38
|
+
await sendShutdownObservation({reason, shutdownRequestedAtMs, signal})
|
|
30
39
|
await closeRunnerConnections(currentConfigurationOrNull())
|
|
31
40
|
process.exit(exitCode)
|
|
32
41
|
})()
|
|
@@ -60,6 +69,52 @@ function updateProcessTitle() {
|
|
|
60
69
|
process.title = count > 0 ? `${BASE_PROCESS_TITLE}: ${count} ${count === 1 ? "job" : "jobs"}` : BASE_PROCESS_TITLE
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Sends one bounded lifecycle observation before application/framework teardown.
|
|
74
|
+
* A closed IPC channel is expected for `ipc_disconnect`; other send failures are
|
|
75
|
+
* surfaced on stderr but never hold connection cleanup past the fixed bound.
|
|
76
|
+
* @param {object} args - Shutdown observation.
|
|
77
|
+
* @param {import("./types.js").PooledChildShutdownReason} args.reason - Shutdown reason.
|
|
78
|
+
* @param {number | null} args.shutdownRequestedAtMs - Parent request timestamp.
|
|
79
|
+
* @param {import("node:child_process").ChildProcess["signalCode"]} args.signal - Requested or observed signal.
|
|
80
|
+
* @returns {Promise<void>} - Resolves after IPC accepts the observation or the bound expires.
|
|
81
|
+
*/
|
|
82
|
+
async function sendShutdownObservation({reason, shutdownRequestedAtMs, signal}) {
|
|
83
|
+
if (!process.send || !process.connected) return
|
|
84
|
+
|
|
85
|
+
const boundedInflight = boundedPooledRunnerInflightJobIds(runningJobIds)
|
|
86
|
+
/** @type {import("./types.js").PooledChildShutdownObservation & {type: "shutdown-observation"}} */
|
|
87
|
+
const message = {
|
|
88
|
+
childInstanceId,
|
|
89
|
+
...boundedInflight,
|
|
90
|
+
reason,
|
|
91
|
+
shutdownObservedAtMs: Date.now(),
|
|
92
|
+
shutdownRequestedAtMs,
|
|
93
|
+
signal,
|
|
94
|
+
type: "shutdown-observation"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await timeout({timeout: SHUTDOWN_OBSERVATION_SEND_TIMEOUT_MS}, async () => {
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
try {
|
|
101
|
+
process.send?.(message, (error) => {
|
|
102
|
+
if (error) {
|
|
103
|
+
reject(error)
|
|
104
|
+
} else {
|
|
105
|
+
resolve(undefined)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
} catch (error) {
|
|
109
|
+
reject(error)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error("Pooled background job runner could not send its shutdown observation:", error)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
63
118
|
/**
|
|
64
119
|
* Reports one acceptance observation (job received / perform started) to the
|
|
65
120
|
* worker over IPC. The message carries the job's exact handoff lease so the
|
|
@@ -105,6 +160,22 @@ function isJobMessage(message) {
|
|
|
105
160
|
return record.type === "job" && !!record.payload && typeof record.payload === "object" && typeof record.payload.id === "string"
|
|
106
161
|
}
|
|
107
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Checks whether an IPC value requests a typed pooled-child shutdown.
|
|
165
|
+
* @param {ReturnType<typeof JSON.parse>} message - IPC message.
|
|
166
|
+
* @returns {message is {type: "shutdown-request", reason: import("./types.js").PooledChildShutdownReason, shutdownRequestedAtMs: number, signal: import("node:child_process").ChildProcess["signalCode"]}} - Whether this is a valid parent shutdown request.
|
|
167
|
+
*/
|
|
168
|
+
function isShutdownRequestMessage(message) {
|
|
169
|
+
if (!message || typeof message !== "object") return false
|
|
170
|
+
const record = /** @type {{type?: ReturnType<typeof JSON.parse>, reason?: ReturnType<typeof JSON.parse>, shutdownRequestedAtMs?: ReturnType<typeof JSON.parse>, signal?: ReturnType<typeof JSON.parse>}} */ (message)
|
|
171
|
+
|
|
172
|
+
return record.type === "shutdown-request"
|
|
173
|
+
&& isPooledChildShutdownReason(record.reason)
|
|
174
|
+
&& typeof record.shutdownRequestedAtMs === "number"
|
|
175
|
+
&& Number.isFinite(record.shutdownRequestedAtMs)
|
|
176
|
+
&& isPooledChildShutdownSignal(record.signal)
|
|
177
|
+
}
|
|
178
|
+
|
|
108
179
|
/**
|
|
109
180
|
* Sends the terminal outcome after the main/DB report has been acknowledged or rejected.
|
|
110
181
|
* @param {object} args - Outcome.
|
|
@@ -176,6 +247,16 @@ async function runJob(payload, sharedTransactionBroker) {
|
|
|
176
247
|
* @returns {void}
|
|
177
248
|
*/
|
|
178
249
|
function handleMessage(message) {
|
|
250
|
+
if (isShutdownRequestMessage(message)) {
|
|
251
|
+
void shutdownRunner({
|
|
252
|
+
exitCode: message.reason === "parent_retire_drained" ? 0 : 1,
|
|
253
|
+
reason: message.reason,
|
|
254
|
+
shutdownRequestedAtMs: message.shutdownRequestedAtMs,
|
|
255
|
+
signal: message.signal
|
|
256
|
+
})
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
179
260
|
if (!isJobMessage(message) || runningJobIds.has(message.payload.id)) return
|
|
180
261
|
|
|
181
262
|
runningJobIds.add(message.payload.id)
|
|
@@ -185,6 +266,7 @@ function handleMessage(message) {
|
|
|
185
266
|
}
|
|
186
267
|
|
|
187
268
|
process.on("message", (message) => handleMessage(message))
|
|
188
|
-
process.once("disconnect", () => void shutdownRunner(0))
|
|
189
|
-
|
|
190
|
-
|
|
269
|
+
process.once("disconnect", () => void shutdownRunner({exitCode: 0, reason: "ipc_disconnect"}))
|
|
270
|
+
process.once("SIGTERM", () => void shutdownRunner({exitCode: 1, reason: "signal_sigterm", signal: "SIGTERM"}))
|
|
271
|
+
process.once("SIGINT", () => void shutdownRunner({exitCode: 1, reason: "signal_sigint", signal: "SIGINT"}))
|
|
272
|
+
if (process.send) process.send({childInstanceId, childPid: process.pid, type: "ready"})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { constants } from "node:os"
|
|
4
|
+
|
|
5
|
+
/** Maximum job ids copied into a pooled-child shutdown observation or failure snapshot. */
|
|
6
|
+
export const POOLED_RUNNER_INFLIGHT_JOB_ID_LIMIT = 100
|
|
7
|
+
|
|
8
|
+
/** @type {readonly import("./types.js").PooledChildShutdownReason[]} */
|
|
9
|
+
export const POOLED_CHILD_SHUTDOWN_REASONS = Object.freeze([
|
|
10
|
+
"parent_retire_drained",
|
|
11
|
+
"job_timeout",
|
|
12
|
+
"worker_stop",
|
|
13
|
+
"signal_sigterm",
|
|
14
|
+
"signal_sigint",
|
|
15
|
+
"signal_sigkill",
|
|
16
|
+
"signal_other",
|
|
17
|
+
"ipc_disconnect",
|
|
18
|
+
"process_error",
|
|
19
|
+
"unexpected_exit"
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks one dynamic IPC reason against the complete shutdown contract.
|
|
24
|
+
* @param {ReturnType<typeof JSON.parse>} value - Candidate shutdown reason.
|
|
25
|
+
* @returns {value is import("./types.js").PooledChildShutdownReason} - Whether the value is a known reason.
|
|
26
|
+
*/
|
|
27
|
+
export function isPooledChildShutdownReason(value) {
|
|
28
|
+
if (typeof value !== "string") return false
|
|
29
|
+
|
|
30
|
+
return POOLED_CHILD_SHUTDOWN_REASONS.some((reason) => reason === value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks one dynamic IPC value against Node's named process signals.
|
|
35
|
+
* @param {ReturnType<typeof JSON.parse>} value - Candidate signal.
|
|
36
|
+
* @returns {value is import("node:child_process").ChildProcess["signalCode"]} - Whether the value is null or a named signal.
|
|
37
|
+
*/
|
|
38
|
+
export function isPooledChildShutdownSignal(value) {
|
|
39
|
+
return value === null || (typeof value === "string" && Object.hasOwn(constants.signals, value))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sorts, deduplicates, and bounds job ids before they cross IPC/report boundaries.
|
|
44
|
+
* @param {Iterable<string>} jobIds - In-flight durable job ids.
|
|
45
|
+
* @returns {{inflightJobIds: string[], inflightJobIdsTruncatedCount: number}} - Bounded snapshot.
|
|
46
|
+
*/
|
|
47
|
+
export function boundedPooledRunnerInflightJobIds(jobIds) {
|
|
48
|
+
const sortedJobIds = [...new Set(jobIds)].sort((left, right) => left.localeCompare(right))
|
|
49
|
+
const inflightJobIds = sortedJobIds.slice(0, POOLED_RUNNER_INFLIGHT_JOB_ID_LIMIT)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
inflightJobIds,
|
|
53
|
+
inflightJobIdsTruncatedCount: sortedJobIds.length - inflightJobIds.length
|
|
54
|
+
}
|
|
55
|
+
}
|