@proteinjs/db 1.58.0 → 1.58.1
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/CHANGELOG.md +11 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/generated/source/index.d.ts +9 -0
- package/dist/generated/source/index.d.ts.map +1 -0
- package/dist/generated/source/index.js +63 -0
- package/dist/generated/source/index.js.map +1 -0
- package/dist/generated/test/index.js +1 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/{src/source → source}/SourceRecordSyncRunner.d.ts +17 -28
- package/dist/source/SourceRecordSyncRunner.d.ts.map +1 -0
- package/dist/{src/source → source}/SourceRecordSyncRunner.js +62 -36
- package/dist/source/SourceRecordSyncRunner.js.map +1 -0
- package/dist/source/index.d.ts +10 -0
- package/dist/source/index.d.ts.map +1 -0
- package/dist/source/index.js +26 -0
- package/dist/source/index.js.map +1 -0
- package/dist/src/Db.d.ts +3 -1
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +5 -3
- package/dist/src/Db.js.map +1 -1
- package/dist/src/MigrationRunner.js +2 -2
- package/dist/src/MigrationRunner.js.map +1 -1
- package/dist/src/source/SourceRecordSync.d.ts +44 -0
- package/dist/src/source/SourceRecordSync.d.ts.map +1 -0
- package/dist/src/source/SourceRecordSync.js +18 -0
- package/dist/src/source/SourceRecordSync.js.map +1 -0
- package/dist/test/BrowserIndexNoNodeBuiltins.test.d.ts +2 -0
- package/dist/test/BrowserIndexNoNodeBuiltins.test.d.ts.map +1 -0
- package/dist/test/BrowserIndexNoNodeBuiltins.test.js +185 -0
- package/dist/test/BrowserIndexNoNodeBuiltins.test.js.map +1 -0
- package/dist/test/SourceRecordLoaderQualifiedName.test.js +4 -3
- package/dist/test/SourceRecordLoaderQualifiedName.test.js.map +1 -1
- package/dist/test/SourceRecordSyncRunner.test.js +1 -1
- package/dist/test/SourceRecordSyncRunner.test.js.map +1 -1
- package/dist/test/SourceRecordSyncSeam.test.d.ts +2 -0
- package/dist/test/SourceRecordSyncSeam.test.d.ts.map +1 -0
- package/dist/test/SourceRecordSyncSeam.test.js +245 -0
- package/dist/test/SourceRecordSyncSeam.test.js.map +1 -0
- package/dist/test/reusable/SourceRecordSyncTests.js +1 -1
- package/dist/test/reusable/SourceRecordSyncTests.js.map +1 -1
- package/generated/index.ts +1 -1
- package/generated/source/index.ts +59 -0
- package/generated/test/index.ts +1 -1
- package/index.ts +5 -3
- package/package.json +12 -4
- package/{src/source → source}/SourceRecordSyncRunner.ts +42 -61
- package/source/index.ts +9 -0
- package/source.d.ts +2 -0
- package/source.js +4 -0
- package/src/Db.ts +5 -3
- package/src/MigrationRunner.ts +2 -2
- package/src/source/SourceRecordSync.ts +54 -0
- package/test/BrowserIndexNoNodeBuiltins.test.ts +123 -0
- package/test/SourceRecordLoaderQualifiedName.test.ts +4 -3
- package/test/SourceRecordSyncRunner.test.ts +1 -1
- package/test/SourceRecordSyncSeam.test.ts +140 -0
- package/test/reusable/SourceRecordSyncTests.ts +1 -1
- package/tsconfig.json +3 -2
- package/dist/src/source/SourceRecordSyncRunner.d.ts.map +0 -1
- package/dist/src/source/SourceRecordSyncRunner.js.map +0 -1
package/index.ts
CHANGED
|
@@ -17,9 +17,11 @@ export * from './src/RecordIterator';
|
|
|
17
17
|
export * from './src/CursorWindowPager';
|
|
18
18
|
export * from './src/source/SourceRecord';
|
|
19
19
|
export * from './src/source/SourceRecordRepo';
|
|
20
|
-
// The sync
|
|
21
|
-
//
|
|
22
|
-
|
|
20
|
+
// The source-record sync's seam: its summary types and `getSourceRecordSync`. The runner itself is
|
|
21
|
+
// server-only code (it reads package.json files off the booting process) behind the
|
|
22
|
+
// `@proteinjs/db/source` subpath, which a server's boot imports once; a consumer test harness that
|
|
23
|
+
// runs one boot of the sync directly imports it from there too.
|
|
24
|
+
export * from './src/source/SourceRecordSync';
|
|
23
25
|
export * from './src/MigrationRunner';
|
|
24
26
|
export * from './src/QueryBuilderFactory';
|
|
25
27
|
export * from './src/TableWatcher';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db",
|
|
3
|
-
"version": "1.58.
|
|
3
|
+
"version": "1.58.1",
|
|
4
4
|
"main": "./dist/generated/index.js",
|
|
5
5
|
"types": "./dist/generated/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -11,12 +11,19 @@
|
|
|
11
11
|
"./test": {
|
|
12
12
|
"types": "./dist/generated/test/index.d.ts",
|
|
13
13
|
"default": "./dist/generated/test/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./source": {
|
|
16
|
+
"types": "./dist/generated/source/index.d.ts",
|
|
17
|
+
"default": "./dist/generated/source/index.js"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"typesVersions": {
|
|
17
21
|
"*": {
|
|
18
22
|
"test": [
|
|
19
23
|
"dist/generated/test/index.d.ts"
|
|
24
|
+
],
|
|
25
|
+
"source": [
|
|
26
|
+
"dist/generated/source/index.d.ts"
|
|
20
27
|
]
|
|
21
28
|
}
|
|
22
29
|
},
|
|
@@ -36,7 +43,7 @@
|
|
|
36
43
|
"license": "MIT",
|
|
37
44
|
"scripts": {
|
|
38
45
|
"clean": "rm -rf dist/ node_modules/ generated/",
|
|
39
|
-
"build": "reflection-build && REFLECTION_SOURCE_DIRS=test,src REFLECTION_EXPORT_FROM=test/index.ts reflection-build && tsc",
|
|
46
|
+
"build": "reflection-build && REFLECTION_SOURCE_DIRS=test,src REFLECTION_EXPORT_FROM=test/index.ts reflection-build && REFLECTION_SOURCE_DIRS=source,src REFLECTION_EXPORT_FROM=source/index.ts reflection-build && tsc",
|
|
40
47
|
"watch": "reflection-watch",
|
|
41
48
|
"test": "jest --passWithNoTests"
|
|
42
49
|
},
|
|
@@ -64,7 +71,8 @@
|
|
|
64
71
|
"eslint-plugin-prettier": "5.1.3",
|
|
65
72
|
"jest": "29.7.0",
|
|
66
73
|
"ts-jest": "29.1.1",
|
|
67
|
-
"typescript": "5.2.2"
|
|
74
|
+
"typescript": "5.2.2",
|
|
75
|
+
"webpack": "5.88.2"
|
|
68
76
|
},
|
|
69
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "0c9fe556af1052933bb3e6fef08f9e33c129c2a6"
|
|
70
78
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
1
3
|
import { Logger } from '@proteinjs/logger';
|
|
2
4
|
import { QueryBuilder } from '@proteinjs/db-query';
|
|
3
|
-
import { getSourceRecordLoaders, SourceRecord, getSourceRecordTables } from '
|
|
4
|
-
import { Table } from '../Table';
|
|
5
|
-
import { Db, getDbAsSystem } from '../Db';
|
|
6
|
-
import { SourceRecordRepo } from '
|
|
7
|
-
import { RecordSerializer } from '../Record';
|
|
5
|
+
import { getSourceRecordLoaders, SourceRecord, getSourceRecordTables } from '../src/source/SourceRecord';
|
|
6
|
+
import { Table } from '../src/Table';
|
|
7
|
+
import { Db, getDbAsSystem } from '../src/Db';
|
|
8
|
+
import { SourceRecordRepo } from '../src/source/SourceRecordRepo';
|
|
9
|
+
import { RecordSerializer } from '../src/Record';
|
|
10
|
+
import { SourceRecordLoadSummary, SourceRecordSync } from '../src/source/SourceRecordSync';
|
|
8
11
|
|
|
9
12
|
type DeclaredRecord = {
|
|
10
13
|
/** The owning package (the declaring loader's package) — the grain the sync prunes within. */
|
|
@@ -19,29 +22,12 @@ type SourceRecordsMap = {
|
|
|
19
22
|
[tableName: string]: { table: Table<any>; records: DeclaredRecord[] };
|
|
20
23
|
};
|
|
21
24
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Declarations whose natural key is held by a row the sync does not own (written at runtime —
|
|
30
|
-
* a person's own row, say): refused for this boot, that row left exactly as it is (see
|
|
31
|
-
* {@link SourceRecordOptions.naturalKey}).
|
|
32
|
-
*/
|
|
33
|
-
refused: number;
|
|
34
|
-
deletes: number;
|
|
35
|
-
removedUpdates: number;
|
|
36
|
-
/** Rows stamped by a newer version of their declaring package — left exactly as they are. */
|
|
37
|
-
skippedNewer: number;
|
|
38
|
-
/** Source-loaded rows with no owner stamp that no declaration in this build claims. */
|
|
39
|
-
unowned: number;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export type SourceRecordLoadSummary = { [tableName: string]: SourceRecordTableLoadSummary };
|
|
43
|
-
|
|
44
|
-
export class SourceRecordSyncRunner {
|
|
25
|
+
/**
|
|
26
|
+
* The one {@link SourceRecordSync}: server-only code (it reads package.json files off the booting
|
|
27
|
+
* process) behind the `@proteinjs/db/source` subpath — a server's boot imports that subpath once,
|
|
28
|
+
* and reflection hands this runner to `Db.init` through `getSourceRecordSync`.
|
|
29
|
+
*/
|
|
30
|
+
export class SourceRecordSyncRunner implements SourceRecordSync {
|
|
45
31
|
private logger = new Logger({ name: this.constructor.name });
|
|
46
32
|
|
|
47
33
|
/** Memo of resolved package versions — one resolution (and at most one warning) per source. */
|
|
@@ -627,44 +613,39 @@ export class SourceRecordSyncRunner {
|
|
|
627
613
|
* `<pkg>/package.json` is usually not requireable), then the nearest package.json whose
|
|
628
614
|
* `name` matches is read walking up from it.
|
|
629
615
|
*
|
|
630
|
-
* Returns undefined where neither road resolves —
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
616
|
+
* Returns undefined where neither road resolves — a package not resolvable from cwd. The sync
|
|
617
|
+
* then has no place in the version order for that package: it never touches its
|
|
618
|
+
* version-stamped rows, and what it writes is unversioned (see {@link isNewerStamp}). Logged
|
|
619
|
+
* once per boot so a misconfigured cwd is visible.
|
|
620
|
+
*
|
|
621
|
+
* Plain Node here — `fs`, `path` and the CJS `require.resolve` — because this module is
|
|
622
|
+
* server-only code behind the `@proteinjs/db/source` subpath: the package's browser index never
|
|
623
|
+
* imports it (test/BrowserIndexNoNodeBuiltins.test.ts holds that line), so nothing here hides
|
|
624
|
+
* from a bundler.
|
|
634
625
|
*/
|
|
635
626
|
private resolveSourceVersion(source: string): string | undefined {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const cwd = typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : undefined;
|
|
643
|
-
if (nodeRequire && cwd) {
|
|
644
|
-
try {
|
|
645
|
-
const path = nodeRequire('path');
|
|
646
|
-
const fs = nodeRequire('fs');
|
|
647
|
-
const ownVersion = this.packageVersionAt(fs, path.join(cwd, 'package.json'), source);
|
|
648
|
-
if (ownVersion) {
|
|
649
|
-
return ownVersion;
|
|
650
|
-
}
|
|
627
|
+
const cwd = process.cwd();
|
|
628
|
+
try {
|
|
629
|
+
const ownVersion = this.packageVersionAt(path.join(cwd, 'package.json'), source);
|
|
630
|
+
if (ownVersion) {
|
|
631
|
+
return ownVersion;
|
|
632
|
+
}
|
|
651
633
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
634
|
+
const entryPath = require.resolve(source, { paths: [cwd] });
|
|
635
|
+
// Walk up from the entry file to the package's own package.json (the name check skips
|
|
636
|
+
// nested stubs like a dist/package.json); stop at the filesystem root.
|
|
637
|
+
for (let directory = path.dirname(entryPath); ; directory = path.dirname(directory)) {
|
|
638
|
+
const version = this.packageVersionAt(path.join(directory, 'package.json'), source);
|
|
639
|
+
if (version) {
|
|
640
|
+
return version;
|
|
641
|
+
}
|
|
660
642
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
643
|
+
if (path.dirname(directory) === directory) {
|
|
644
|
+
break;
|
|
664
645
|
}
|
|
665
|
-
} catch {
|
|
666
|
-
// Unresolvable from cwd — reported below.
|
|
667
646
|
}
|
|
647
|
+
} catch {
|
|
648
|
+
// Unresolvable from cwd — reported below.
|
|
668
649
|
}
|
|
669
650
|
|
|
670
651
|
this.logger.warn({
|
|
@@ -674,7 +655,7 @@ export class SourceRecordSyncRunner {
|
|
|
674
655
|
}
|
|
675
656
|
|
|
676
657
|
/** The `version` of the package.json at `packageJsonPath` when it exists and names `source`. */
|
|
677
|
-
private packageVersionAt(
|
|
658
|
+
private packageVersionAt(packageJsonPath: string, source: string): string | undefined {
|
|
678
659
|
if (!fs.existsSync(packageJsonPath)) {
|
|
679
660
|
return undefined;
|
|
680
661
|
}
|
package/source/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@proteinjs/db/source` — the server-only half of the source-record sync: the runner behind
|
|
3
|
+
* `Db.init`'s source-record leg. It reads package.json files off the booting process (the
|
|
4
|
+
* version stamp on every row it writes), so it never rides the package's browser index; a
|
|
5
|
+
* server's boot imports this subpath once, which registers the runner as the package's
|
|
6
|
+
* `SourceRecordSync` — the seam `Db.init` and the pre-schema-sync migration phase resolve through
|
|
7
|
+
* reflection. A test harness that runs one boot of the sync directly imports the runner from here.
|
|
8
|
+
*/
|
|
9
|
+
export * from './SourceRecordSyncRunner';
|
package/source.d.ts
ADDED
package/source.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Node10/classic-resolution stub for the `./source` subpath, owned by reflection-build.
|
|
2
|
+
// Consumers that bypass the exports map (node10 resolution, tsconfig paths mappings) resolve
|
|
3
|
+
// this file path first, so every install shape loads the built dist — never sources.
|
|
4
|
+
module.exports = require('./dist/generated/source/index.js');
|
package/src/Db.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { ReverseCascadeEdgeIndex } from './ReverseCascadeEdgeIndex';
|
|
13
13
|
import { Record, RecordSerializer, SerializedRecord } from './Record';
|
|
14
14
|
import { Logger } from '@proteinjs/logger';
|
|
15
|
-
import {
|
|
15
|
+
import { getSourceRecordSync } from './source/SourceRecordSync';
|
|
16
16
|
import { ParameterizationConfig, ParamType, QueryBuilder, Statement, StatementFactory } from '@proteinjs/db-query';
|
|
17
17
|
import { QueryBuilderFactory } from './QueryBuilderFactory';
|
|
18
18
|
import { StatementConfigFactory } from './StatementConfigFactory';
|
|
@@ -211,7 +211,9 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
211
211
|
* backfill, which fails loudly over violating data); an immediate no-op unless a migration
|
|
212
212
|
* declares {@link Migration.preSchemaSync};
|
|
213
213
|
* 3. schema sync — create/alter every registered table (the DDL);
|
|
214
|
-
* 4. source-record sync
|
|
214
|
+
* 4. source-record sync — through {@link getSourceRecordSync}: the runner is server-only code
|
|
215
|
+
* behind the `@proteinjs/db/source` subpath (the browser index never carries it), registered
|
|
216
|
+
* by the server's one import of that subpath; a boot without it fails here, loudly.
|
|
215
217
|
*/
|
|
216
218
|
async init(): Promise<void> {
|
|
217
219
|
await this.dbDriver.createDbIfNotExists();
|
|
@@ -225,7 +227,7 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
225
227
|
const { MigrationRunner } = await import('./MigrationRunner');
|
|
226
228
|
await new MigrationRunner().runPreSchemaSyncMigrations(this.dbDriver.getTableManager());
|
|
227
229
|
await this.dbDriver.getTableManager().loadTables();
|
|
228
|
-
await
|
|
230
|
+
await getSourceRecordSync().load();
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
async tableExists<T extends R>(table: Table<T>): Promise<boolean> {
|
package/src/MigrationRunner.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Moment, moment } from './opt/moment';
|
|
|
2
2
|
import { Db, getDb, getDbAsSystem } from './Db';
|
|
3
3
|
import { Table } from './Table';
|
|
4
4
|
import { SourceRecordRepo } from './source/SourceRecordRepo';
|
|
5
|
-
import {
|
|
5
|
+
import { getSourceRecordSync } from './source/SourceRecordSync';
|
|
6
6
|
import { getSourceRecordLoaders } from './source/SourceRecord';
|
|
7
7
|
import { MigrationRunnerService, getMigrationRunnerService } from './services/MigrationRunnerService';
|
|
8
8
|
import { Migration, MigrationEstimate, MigrationTable } from './tables/MigrationTable';
|
|
@@ -195,7 +195,7 @@ export class MigrationRunner implements MigrationRunnerService {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
await tableManager.loadTable(migrationTable);
|
|
198
|
-
await
|
|
198
|
+
await getSourceRecordSync().load(migrationTable);
|
|
199
199
|
this.logger.info({
|
|
200
200
|
message: `Running ${flagged.length} pre-schema-sync migration${flagged.length === 1 ? '' : 's'} before schema sync`,
|
|
201
201
|
obj: { ids: flagged.map((migration) => migration.id) },
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Loadable, SourceRepository } from '@proteinjs/reflection';
|
|
2
|
+
import type { Table } from '../Table';
|
|
3
|
+
|
|
4
|
+
/** What one boot of the sync did to one source-record table. */
|
|
5
|
+
export type SourceRecordTableLoadSummary = {
|
|
6
|
+
inserts: number;
|
|
7
|
+
updates: number;
|
|
8
|
+
unchanged: number;
|
|
9
|
+
adopted: number;
|
|
10
|
+
/**
|
|
11
|
+
* Declarations whose natural key is held by a row the sync does not own (written at runtime —
|
|
12
|
+
* a person's own row, say): refused for this boot, that row left exactly as it is (see
|
|
13
|
+
* {@link SourceRecordOptions.naturalKey}).
|
|
14
|
+
*/
|
|
15
|
+
refused: number;
|
|
16
|
+
deletes: number;
|
|
17
|
+
removedUpdates: number;
|
|
18
|
+
/** Rows stamped by a newer version of their declaring package — left exactly as they are. */
|
|
19
|
+
skippedNewer: number;
|
|
20
|
+
/** Source-loaded rows with no owner stamp that no declaration in this build claims. */
|
|
21
|
+
unowned: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SourceRecordLoadSummary = { [tableName: string]: SourceRecordTableLoadSummary };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The source-record sync a boot runs — the seam `Db.init` (and the pre-schema-sync migration
|
|
28
|
+
* phase) reach it through. Its one implementation, `SourceRecordSyncRunner`, is server-only code:
|
|
29
|
+
* it reads package.json files off the booting process to version-stamp every row, so it lives
|
|
30
|
+
* behind the `@proteinjs/db/source` subpath and this package's browser index never carries it
|
|
31
|
+
* (test/BrowserIndexNoNodeBuiltins.test.ts holds that line). A server imports
|
|
32
|
+
* `@proteinjs/db/source` once at boot, which registers the runner here through reflection; the
|
|
33
|
+
* browser index declares only this interface.
|
|
34
|
+
*/
|
|
35
|
+
export interface SourceRecordSync extends Loadable {
|
|
36
|
+
/** One boot of the sync, for every source-record table in the build — or for `onlyTable`. */
|
|
37
|
+
load(onlyTable?: Table<any>): Promise<SourceRecordLoadSummary>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The registered sync — or a loud refusal naming the subpath that registers it. A boot never
|
|
42
|
+
* silently skips its source records: a server that has not imported `@proteinjs/db/source` fails
|
|
43
|
+
* here, at `Db.init`, with the fix in the message.
|
|
44
|
+
*/
|
|
45
|
+
export const getSourceRecordSync = (): SourceRecordSync => {
|
|
46
|
+
const sync = SourceRepository.get().object<SourceRecordSync>('@proteinjs/db/SourceRecordSync');
|
|
47
|
+
if (!sync) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Unable to find a @proteinjs/db/SourceRecordSync implementation. The source-record sync is server-only code behind the '@proteinjs/db/source' subpath: import '@proteinjs/db/source' once in the server's boot (it registers SourceRecordSyncRunner), and the boot's source records load.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return sync;
|
|
54
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import webpack from 'webpack';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* THE LAW: this package's browser-facing index never reaches a Node builtin. `@proteinjs/db` is
|
|
8
|
+
* one package for both halves — the browser's Db talks through DbService, the server's through a
|
|
9
|
+
* driver — and its public entry (`dist/generated/index.js`, what a consumer's webpack bundles)
|
|
10
|
+
* must bundle for the browser as it stands; a consumer app has no business patching the
|
|
11
|
+
* library's reach with a `resolve.fallback`. The source-record sync (it reads package.json files
|
|
12
|
+
* off the booting process) is the server-only code that broke this line in db 1.55–1.58 — a
|
|
13
|
+
* webpack 5 client bundle met its `fs` and booted to nothing — and now lives behind the
|
|
14
|
+
* `@proteinjs/db/source` subpath. This guard holds the line for every module to come.
|
|
15
|
+
*
|
|
16
|
+
* Mechanics: webpack 5.88.2 — the consumer's bundler, at app-ui's version — bundles the built
|
|
17
|
+
* browser index for `target: 'web'` with this package's own dependencies external, so the only
|
|
18
|
+
* resolution left to fail is a Node builtin's, which webpack 5 refuses by name ("Module not
|
|
19
|
+
* found: Error: Can't resolve 'fs'"). It refuses as a WARNING when the reach sits in a try/catch
|
|
20
|
+
* (an optional dependency) — exactly how 1.58.0's runner reached `fs`, through a renamed
|
|
21
|
+
* `require` that esbuild never sees but webpack's parser tracks — so errors AND warnings must be
|
|
22
|
+
* empty. The module list then pins the subpath out of the graph by path.
|
|
23
|
+
*
|
|
24
|
+
* The one named exception: four encryption modules import `crypto` (since before 1.54.1), and
|
|
25
|
+
* consumers substitute a browser build for it (app-ui's webpack: `fallback.crypto:
|
|
26
|
+
* crypto-browserify`). Pinned to exactly those four files — a fifth reader of `crypto`, or any
|
|
27
|
+
* other builtin anywhere, is red.
|
|
28
|
+
*/
|
|
29
|
+
const packageDir = path.join(__dirname, '..');
|
|
30
|
+
const BROWSER_INDEX = 'dist/generated/index.js';
|
|
31
|
+
const SERVER_ONLY_SUBPATH = /(^|\/)dist\/source\//;
|
|
32
|
+
const CRYPTO_READERS = [
|
|
33
|
+
'dist/src/encryption/DataKeyStore.js',
|
|
34
|
+
'dist/src/encryption/EncryptionEnvelope.js',
|
|
35
|
+
'dist/src/encryption/InMemoryMasterKeyProvider.js',
|
|
36
|
+
'dist/src/encryption/SearchTokenizer.js',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/** What one browser bundle of the index came to. */
|
|
40
|
+
type Bundle = { errors: string[]; warnings: string[]; modules: string[]; cryptoReaders: string[] };
|
|
41
|
+
|
|
42
|
+
/** `fs.rmSync` (Node 14.14+; this package's @types/node predates it). */
|
|
43
|
+
const removeDirectory = (directory: string) =>
|
|
44
|
+
(fs as unknown as { rmSync(target: string, options: { recursive: true; force: true }): void }).rmSync(directory, {
|
|
45
|
+
recursive: true,
|
|
46
|
+
force: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/** This package's dependencies: a consumer resolves those itself; never this bundle's concern. */
|
|
50
|
+
const dependencies = (): string[] =>
|
|
51
|
+
Object.keys(JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')).dependencies);
|
|
52
|
+
|
|
53
|
+
const bundleBrowserIndex = (): Promise<Bundle> =>
|
|
54
|
+
new Promise((resolve, reject) => {
|
|
55
|
+
const names = dependencies();
|
|
56
|
+
const cryptoReaders = new Set<string>();
|
|
57
|
+
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'db-browser-index-'));
|
|
58
|
+
webpack(
|
|
59
|
+
{
|
|
60
|
+
mode: 'none',
|
|
61
|
+
target: 'web',
|
|
62
|
+
context: packageDir,
|
|
63
|
+
entry: path.join(packageDir, BROWSER_INDEX),
|
|
64
|
+
output: { path: outDir, filename: 'browser.js' },
|
|
65
|
+
externals: [
|
|
66
|
+
({ request, contextInfo }, callback) => {
|
|
67
|
+
if (names.some((name) => request === name || !!request?.startsWith(`${name}/`))) {
|
|
68
|
+
return callback(undefined, `commonjs ${request}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The crypto exception, pinned to its readers; every other builtin stays webpack's own refusal.
|
|
72
|
+
if (request && /^(node:)?crypto$/.test(request)) {
|
|
73
|
+
const issuer = path.relative(packageDir, contextInfo?.issuer ?? '');
|
|
74
|
+
cryptoReaders.add(issuer);
|
|
75
|
+
if (CRYPTO_READERS.includes(issuer)) {
|
|
76
|
+
return callback(undefined, `commonjs ${request}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
callback();
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
(error, stats) => {
|
|
85
|
+
removeDirectory(outDir);
|
|
86
|
+
if (error || !stats) {
|
|
87
|
+
return reject(error ?? new Error('webpack produced no stats'));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const json = stats.toJson({ all: false, errors: true, warnings: true, modules: true });
|
|
91
|
+
const line = ({ message, moduleName }: { message: string; moduleName?: string }) =>
|
|
92
|
+
`${message.split('\n')[0]} (${moduleName})`;
|
|
93
|
+
resolve({
|
|
94
|
+
errors: (json.errors ?? []).map(line),
|
|
95
|
+
warnings: (json.warnings ?? []).map(line),
|
|
96
|
+
modules: (json.modules ?? []).map((module) => module.name ?? ''),
|
|
97
|
+
cryptoReaders: Array.from(cryptoReaders).sort(),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('the browser index (dist/generated/index.js) bundles for the browser', () => {
|
|
104
|
+
let bundle: Bundle;
|
|
105
|
+
|
|
106
|
+
beforeAll(async () => {
|
|
107
|
+
bundle = await bundleBrowserIndex();
|
|
108
|
+
}, 60000);
|
|
109
|
+
|
|
110
|
+
it('reaches no Node builtin: webpack resolved every module with only the dependencies external', () => {
|
|
111
|
+
expect(bundle.errors).toEqual([]);
|
|
112
|
+
expect(bundle.warnings).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('never imports the server-only source-record sync (the @proteinjs/db/source subpath)', () => {
|
|
116
|
+
expect(bundle.modules.length).toBeGreaterThan(0);
|
|
117
|
+
expect(bundle.modules.filter((module) => SERVER_ONLY_SUBPATH.test(module))).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('pins the crypto exception to exactly its four readers', () => {
|
|
121
|
+
expect(bundle.cryptoReaders).toEqual(CRYPTO_READERS.slice().sort());
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -10,11 +10,12 @@ import { graphSerializer } from '@proteinjs/util';
|
|
|
10
10
|
* name, so a second declaration of the name anywhere in the package's reflected sources leaves
|
|
11
11
|
* which one the artifact records to the build's file order — and the loser disappears without
|
|
12
12
|
* a trace. These tests read the artifacts the build wrote (`generated/index.ts` for the package
|
|
13
|
-
* entry, `generated/test/index.ts` for the `./test` subpath,
|
|
14
|
-
*
|
|
13
|
+
* entry, `generated/test/index.ts` for the `./test` subpath, `generated/source/index.ts` for the
|
|
14
|
+
* `./source` subpath — each built over `src/`) and assert the name holds the declaration
|
|
15
|
+
* interface, not some other declaration that shares it.
|
|
15
16
|
*/
|
|
16
17
|
const QUALIFIED_NAME = '@proteinjs/db/SourceRecordLoader';
|
|
17
|
-
const ARTIFACTS = ['generated/index.ts', 'generated/test/index.ts'];
|
|
18
|
+
const ARTIFACTS = ['generated/index.ts', 'generated/test/index.ts', 'generated/source/index.ts'];
|
|
18
19
|
|
|
19
20
|
/** The fields of an emitted node value these tests read. */
|
|
20
21
|
type EmittedDeclaration = { packageName: string; name: string; qualifiedName: string; sourceType: SourceType };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { SourceRepository } from '@proteinjs/reflection';
|
|
2
|
+
import { Db, DbDriver } from '../src/Db';
|
|
3
|
+
import { Table } from '../src/Table';
|
|
4
|
+
import { TableManager } from '../src/schema/TableManager';
|
|
5
|
+
import { DefaultTransactionContextFactory, TransactionContextData } from '../src/transaction/TransactionContextFactory';
|
|
6
|
+
import { SourceRecordLoadSummary, SourceRecordSync, getSourceRecordSync } from '../src/source/SourceRecordSync';
|
|
7
|
+
import { SourceRecordSyncRunner } from '../source/SourceRecordSyncRunner';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The seam between a boot and the source-record sync. `Db.init` (and the pre-schema-sync
|
|
11
|
+
* migration phase) run the sync through `getSourceRecordSync()` — reflection's one
|
|
12
|
+
* `@proteinjs/db/SourceRecordSync` — instead of importing the runner: the runner is server-only
|
|
13
|
+
* code behind `@proteinjs/db/source`, kept out of the browser index by
|
|
14
|
+
* test/BrowserIndexNoNodeBuiltins.test.ts. Three pins: a boot with no sync registered refuses,
|
|
15
|
+
* loudly, naming the subpath (never a silent boot without its source records); the subpath's
|
|
16
|
+
* built artifact registers the runner as that one implementation (the server road — exactly what
|
|
17
|
+
* a server's `import '@proteinjs/db/source'` runs); and `Db.init` reaches the registered sync
|
|
18
|
+
* after schema sync, as the whole-build boot (no table named).
|
|
19
|
+
*/
|
|
20
|
+
type SourceRepositoryInternals = {
|
|
21
|
+
objectCache: { [qualifiedName: string]: unknown[] };
|
|
22
|
+
namedObjectCache: { [qualifiedName: string]: { qualifiedName: string; packageName: string; object: unknown }[] };
|
|
23
|
+
};
|
|
24
|
+
const QUALIFIED_NAME = '@proteinjs/db/SourceRecordSync';
|
|
25
|
+
const internals = () => SourceRepository.get() as unknown as SourceRepositoryInternals;
|
|
26
|
+
/** Reflection memoizes each lookup (a refusal caches an empty answer): forget this seam's. */
|
|
27
|
+
const forgetLookups = () => {
|
|
28
|
+
delete internals().objectCache[QUALIFIED_NAME];
|
|
29
|
+
delete internals().namedObjectCache[QUALIFIED_NAME];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('getSourceRecordSync — a boot without the server-only subpath refuses', () => {
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
forgetLookups();
|
|
35
|
+
// The package's own entry, as every consumer's `import '@proteinjs/db'` runs it: the seam's
|
|
36
|
+
// interface is in the graph, and nothing implements it until the subpath is imported.
|
|
37
|
+
await import('../generated/index');
|
|
38
|
+
});
|
|
39
|
+
afterAll(forgetLookups);
|
|
40
|
+
|
|
41
|
+
it('throws, naming @proteinjs/db/source, when no SourceRecordSync is registered', () => {
|
|
42
|
+
expect(() => getSourceRecordSync()).toThrow(/Unable to find a @proteinjs\/db\/SourceRecordSync implementation/);
|
|
43
|
+
expect(() => getSourceRecordSync()).toThrow(/import '@proteinjs\/db\/source'/);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('the ./source subpath registers the runner (the server road)', () => {
|
|
48
|
+
beforeAll(async () => {
|
|
49
|
+
forgetLookups();
|
|
50
|
+
// The artifact the build wrote for the subpath — what `import '@proteinjs/db/source'` runs on a server.
|
|
51
|
+
await import('../generated/source');
|
|
52
|
+
});
|
|
53
|
+
afterAll(forgetLookups);
|
|
54
|
+
|
|
55
|
+
it('resolves SourceRecordSyncRunner as the one @proteinjs/db/SourceRecordSync', () => {
|
|
56
|
+
expect(getSourceRecordSync()).toBeInstanceOf(SourceRecordSyncRunner);
|
|
57
|
+
expect(SourceRepository.get().objects<SourceRecordSync>(QUALIFIED_NAME)).toHaveLength(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Db.init runs the registered sync after schema sync (the whole-build boot)', () => {
|
|
62
|
+
/** The boot's legs in the order they ran: schema sync, then the sync with the table it was given. */
|
|
63
|
+
const legs: { leg: 'schema' | 'sync'; onlyTable?: string }[] = [];
|
|
64
|
+
|
|
65
|
+
class RecordingSync implements SourceRecordSync {
|
|
66
|
+
async load(onlyTable?: Table<any>): Promise<SourceRecordLoadSummary> {
|
|
67
|
+
legs.push({ leg: 'sync', onlyTable: onlyTable?.name });
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Accepts the boot's schema leg and records it; no database behind it. */
|
|
73
|
+
class BootDriver implements DbDriver {
|
|
74
|
+
getDbName(): string {
|
|
75
|
+
return 'test';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async createDbIfNotExists(): Promise<void> {}
|
|
79
|
+
|
|
80
|
+
async createDb(): Promise<void> {}
|
|
81
|
+
|
|
82
|
+
async dropDb(): Promise<void> {}
|
|
83
|
+
|
|
84
|
+
getTableManager(): TableManager {
|
|
85
|
+
return {
|
|
86
|
+
loadTables: async () => {
|
|
87
|
+
legs.push({ leg: 'schema' });
|
|
88
|
+
},
|
|
89
|
+
} as unknown as TableManager;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async runQuery(): Promise<never[]> {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async runDml(): Promise<number> {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async runTransaction<T>(fn: (transaction: unknown) => Promise<T>): Promise<T> {
|
|
101
|
+
return await fn({ id: 'transaction' });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getOperationDeadlineMs(): number {
|
|
105
|
+
return 1000;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class BootTransactionContextFactory implements DefaultTransactionContextFactory {
|
|
110
|
+
private context: TransactionContextData = {};
|
|
111
|
+
|
|
112
|
+
getTransactionContext(): TransactionContextData {
|
|
113
|
+
return this.context;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async runInContext<T>(context: TransactionContextData, fn: () => Promise<T>): Promise<T> {
|
|
117
|
+
const previous = this.context;
|
|
118
|
+
this.context = context;
|
|
119
|
+
try {
|
|
120
|
+
return await fn();
|
|
121
|
+
} finally {
|
|
122
|
+
this.context = previous;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
beforeAll(() => {
|
|
128
|
+
forgetLookups();
|
|
129
|
+
// Registered the way the subpath's artifact registers the runner: one named SourceRecordSync.
|
|
130
|
+
internals().namedObjectCache[QUALIFIED_NAME] = [
|
|
131
|
+
{ qualifiedName: '@test/RecordingSync', packageName: '@test', object: new RecordingSync() },
|
|
132
|
+
];
|
|
133
|
+
});
|
|
134
|
+
afterAll(forgetLookups);
|
|
135
|
+
|
|
136
|
+
it('reaches the sync through the seam, after the schema sync, for the whole build', async () => {
|
|
137
|
+
await new Db(new BootDriver(), undefined, new BootTransactionContextFactory()).init();
|
|
138
|
+
expect(legs).toEqual([{ leg: 'schema' }, { leg: 'sync', onlyTable: undefined }]);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
withSourceRecordColumns,
|
|
14
14
|
} from '@proteinjs/db';
|
|
15
15
|
import type { DefaultTransactionContextFactory } from '@proteinjs/db';
|
|
16
|
-
import { SourceRecordSyncRunner } from '../../
|
|
16
|
+
import { SourceRecordSyncRunner } from '../../source/SourceRecordSyncRunner';
|
|
17
17
|
import { TableWatcherRunner } from '../../src/TableWatcherRunner';
|
|
18
18
|
import { DbTestEnvironment } from '../util/DbTestEnvironment';
|
|
19
19
|
import {
|
package/tsconfig.json
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
"paths": {
|
|
21
21
|
"@proteinjs/db": ["index.ts"],
|
|
22
22
|
"@proteinjs/db/*": ["src/*"],
|
|
23
|
-
"@proteinjs/db/test": ["test/index.ts"]
|
|
23
|
+
"@proteinjs/db/test": ["test/index.ts"],
|
|
24
|
+
"@proteinjs/db/source": ["source/index.ts"]
|
|
24
25
|
}
|
|
25
26
|
},
|
|
26
|
-
"exclude": ["node_modules", "dist", "test.js", "test.d.ts"]
|
|
27
|
+
"exclude": ["node_modules", "dist", "test.js", "test.d.ts", "source.js", "source.d.ts"]
|
|
27
28
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SourceRecordSyncRunner.d.ts","sourceRoot":"","sources":["../../../src/source/SourceRecordSyncRunner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAkBjC,gEAAgE;AAChE,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IAAE,CAAC,SAAS,EAAE,MAAM,GAAG,4BAA4B,CAAA;CAAE,CAAC;AAE5F,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAA+C;IAE7D,+FAA+F;IAC/F,OAAO,CAAC,cAAc,CAAyC;IAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA0JpE;;;;;;;;;OASG;YACW,gBAAgB;IA2D9B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;YACW,kCAAkC;IA0BhD;;;;;;OAMG;YACW,YAAY;IAkB1B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA+CvB;;;;;;;;;;;;;;;;;;;OAmBG;YACW,UAAU;IAwBxB;;;OAGG;YACW,eAAe;IAmB7B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;IAkDxB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IAmB1B,mGAAmG;IACnG,OAAO,CAAC,aAAa;IAQrB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,oBAAoB;IAyC5B,gGAAgG;IAChG,OAAO,CAAC,gBAAgB;IASxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,YAAY;IAapB;;;;OAIG;IACH,OAAO,CAAC,eAAe;CA4DxB"}
|