Skip to content

Commit acd47f5

Browse files
committed
refactor(@angular/build): replace lmdb with node:sqlite for persistent caching
The persistent cache implementation for the esbuild-based build system is now using the Node.js built-in `node:sqlite` module, removing the third-party `lmdb` dependency. The database uses WAL (Write-Ahead Logging) mode and normal synchronous settings for optimal performance during parallel builds.
1 parent 597e6c3 commit acd47f5

File tree

7 files changed

+97
-258
lines changed

7 files changed

+97
-258
lines changed

packages/angular/build/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ ts_project(
9393
":node_modules/jsonc-parser",
9494
":node_modules/less",
9595
":node_modules/listr2",
96-
":node_modules/lmdb",
9796
":node_modules/magic-string",
9897
":node_modules/mrmime",
9998
":node_modules/ng-packagr",

packages/angular/build/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@
4545
"vite": "7.3.1",
4646
"watchpack": "2.5.1"
4747
},
48-
"optionalDependencies": {
49-
"lmdb": "3.5.1"
50-
},
5148
"devDependencies": {
5249
"@angular-devkit/core": "workspace:*",
5350
"@angular/ssr": "workspace:*",

packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,19 @@ export function createCompilerPlugin(
7373

7474
// Initialize a worker pool for JavaScript transformations.
7575
// Webcontainers currently do not support this persistent cache store.
76-
let cacheStore: import('../lmdb-cache-store').LmdbCacheStore | undefined;
76+
let cacheStore: import('../sqlite-cache-store').SqliteCacheStore | undefined;
7777
if (pluginOptions.sourceFileCache?.persistentCachePath && !process.versions.webcontainer) {
7878
try {
79-
const { LmdbCacheStore } = await import('../lmdb-cache-store');
80-
cacheStore = new LmdbCacheStore(
79+
const { SqliteCacheStore } = await import('../sqlite-cache-store');
80+
cacheStore = new SqliteCacheStore(
8181
path.join(pluginOptions.sourceFileCache.persistentCachePath, 'angular-compiler.db'),
8282
);
8383
} catch (e) {
8484
setupWarnings.push({
8585
text: 'Unable to initialize JavaScript cache storage.',
8686
location: null,
8787
notes: [
88-
// Only show first line of lmdb load error which has platform support listed
88+
// Only show first line of sqlite load error
8989
{ text: (e as Error)?.message.split('\n')[0] ?? `${e}` },
9090
{
9191
text: 'This will not affect the build output content but may result in slower builds.',

packages/angular/build/src/tools/esbuild/i18n-inliner.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto';
1111
import { extname, join } from 'node:path';
1212
import { WorkerPool } from '../../utils/worker-pool';
1313
import { BuildOutputFile, BuildOutputFileType } from './bundler-context';
14-
import type { LmdbCacheStore } from './lmdb-cache-store';
14+
import type { SqliteCacheStore } from './sqlite-cache-store';
1515
import { createOutputFile } from './utils';
1616

1717
/**
@@ -39,7 +39,7 @@ export interface I18nInlinerOptions {
3939
export class I18nInliner {
4040
#cacheInitFailed = false;
4141
#workerPool: WorkerPool;
42-
#cache: LmdbCacheStore | undefined;
42+
#cache: SqliteCacheStore | undefined;
4343
readonly #localizeFiles: ReadonlyMap<string, BuildOutputFile>;
4444
readonly #unmodifiedFiles: Array<BuildOutputFile>;
4545

@@ -274,9 +274,9 @@ export class I18nInliner {
274274

275275
// Initialize a persistent cache for i18n transformations.
276276
try {
277-
const { LmdbCacheStore } = await import('./lmdb-cache-store');
277+
const { SqliteCacheStore } = await import('./sqlite-cache-store');
278278

279-
this.#cache = new LmdbCacheStore(join(persistentCachePath, 'angular-i18n.db'));
279+
this.#cache = new SqliteCacheStore(join(persistentCachePath, 'angular-i18n.db'));
280280
} catch {
281281
this.#cacheInitFailed = true;
282282

packages/angular/build/src/tools/esbuild/lmdb-cache-store.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import assert from 'node:assert';
10+
import { DatabaseSync, StatementSync } from 'node:sqlite';
11+
import { deserialize, serialize } from 'node:v8';
12+
import { Cache, type CacheStore } from './cache';
13+
14+
export class SqliteCacheStore implements CacheStore<unknown> {
15+
#db: DatabaseSync | undefined;
16+
#getStatement: StatementSync | undefined;
17+
#hasStatement: StatementSync | undefined;
18+
#setStatement: StatementSync | undefined;
19+
20+
constructor(readonly cachePath: string) {}
21+
22+
#ensureDb(): void {
23+
if (this.#db) {
24+
return;
25+
}
26+
27+
const db = new DatabaseSync(this.cachePath);
28+
db.exec(`
29+
PRAGMA journal_mode = WAL;
30+
PRAGMA synchronous = NORMAL;
31+
CREATE TABLE IF NOT EXISTS cache (
32+
key TEXT PRIMARY KEY,
33+
value BLOB
34+
);
35+
`);
36+
37+
this.#getStatement = db.prepare('SELECT value FROM cache WHERE key = ?');
38+
this.#hasStatement = db.prepare('SELECT 1 FROM cache WHERE key = ?');
39+
this.#setStatement = db.prepare('INSERT OR REPLACE INTO cache (key, value) VALUES (?, ?)');
40+
41+
this.#db = db;
42+
}
43+
44+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
45+
async get(key: string): Promise<any> {
46+
this.#ensureDb();
47+
48+
assert(this.#getStatement, 'getStatement should be initialized by ensureDb');
49+
50+
const result = this.#getStatement.get(key) as { value: Uint8Array } | undefined;
51+
if (result) {
52+
return deserialize(result.value);
53+
}
54+
55+
return undefined;
56+
}
57+
58+
has(key: string): boolean {
59+
this.#ensureDb();
60+
61+
assert(this.#hasStatement, 'hasStatement should be initialized by ensureDb');
62+
63+
const result = this.#hasStatement.get(key);
64+
65+
return result !== undefined;
66+
}
67+
68+
async set(key: string, value: unknown): Promise<this> {
69+
this.#ensureDb();
70+
71+
assert(this.#setStatement, 'setStatement should be initialized by ensureDb');
72+
73+
this.#setStatement.run(key, serialize(value));
74+
75+
return this;
76+
}
77+
78+
createCache<V = unknown>(namespace: string): Cache<V> {
79+
return new Cache(this, namespace);
80+
}
81+
82+
async close() {
83+
try {
84+
this.#db?.close();
85+
} catch {
86+
// Failure to close should not be fatal
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)