Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: allow switching out the DB bindings #7486

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/core/src/bindings/db/jsonl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { JsonlDB } from "@alcalzone/jsonl-db";
import {
type Database,
type DatabaseFactory,
type DatabaseOptions,
} from "@zwave-js/shared/bindings";

/** An implementation of the Database bindings for Node.js based on JsonlDB */
export const db: DatabaseFactory = {
createInstance<V>(
filename: string,
options?: DatabaseOptions<V>,
): Database<V> {
return new JsonlDB(filename, options);
},
};
4 changes: 2 additions & 2 deletions packages/core/src/values/CacheBackedMap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JsonlDB } from "@alcalzone/jsonl-db";
import { type Database } from "@zwave-js/shared/bindings";

export interface CacheBackedMapKeys<K extends string | number> {
/** The common prefix all keys start with */
Expand All @@ -12,7 +12,7 @@ export interface CacheBackedMapKeys<K extends string | number> {
/** Wrapper class which allows storing a Map as a subset of a JsonlDB */
export class CacheBackedMap<K extends string | number, V> implements Map<K, V> {
constructor(
private readonly cache: JsonlDB<any>,
private readonly cache: Database<any>,
private readonly cacheKeys: CacheBackedMapKeys<K>,
) {
this.map = new Map();
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/values/ValueDB.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JsonlDB } from "@alcalzone/jsonl-db";
import { TypedEventTarget } from "@zwave-js/shared";
import { type Database } from "@zwave-js/shared/bindings";
import type { CommandClasses } from "../definitions/CommandClasses.js";
import {
ZWaveError,
Expand Down Expand Up @@ -106,8 +106,8 @@ export class ValueDB extends TypedEventTarget<ValueDBEventCallbacks> {
*/
public constructor(
nodeId: number,
valueDB: JsonlDB,
metadataDB: JsonlDB<ValueMetadata>,
valueDB: Database<unknown>,
metadataDB: Database<ValueMetadata>,
ownKeys?: Set<string>,
) {
super();
Expand All @@ -119,8 +119,8 @@ export class ValueDB extends TypedEventTarget<ValueDBEventCallbacks> {
}

private nodeId: number;
private _db: JsonlDB<unknown>;
private _metadata: JsonlDB<ValueMetadata>;
private _db: Database<unknown>;
private _metadata: Database<ValueMetadata>;
private _index: Set<string>;

private buildIndex(): Set<string> {
Expand Down Expand Up @@ -603,7 +603,9 @@ function compareDBKeyFast(
}

/** Extracts an index for each node from one or more JSONL DBs */
export function indexDBsByNode(databases: JsonlDB[]): Map<number, Set<string>> {
export function indexDBsByNode(
databases: Database<unknown>[],
): Map<number, Set<string>> {
const indexes = new Map<number, Set<string>>();
for (const db of databases) {
for (const key of db.keys()) {
Expand Down
39 changes: 39 additions & 0 deletions packages/shared/src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,42 @@ export interface FileSystem
{}

export type Platform = "linux" | "darwin" | "win32" | "browser" | "other";

export type DatabaseOptions<V> = {
/**
* An optional reviver function (similar to JSON.parse) to transform parsed values before they are accessible in the database.
* If this function is defined, it must always return a value.
*/
reviver?: (key: string, value: any) => V;
/**
* An optional serializer function (similar to JSON.serialize) to transform values before they are written to the database file.
* If this function is defined, it must always return a value.
*/
serializer?: (key: string, value: V) => any;
/** Whether timestamps should be recorded when setting values. Default: false */
enableTimestamps?: boolean;
};

export interface DatabaseFactory {
createInstance<V>(
filename: string,
options?: DatabaseOptions<V>,
): Database<V>;
}

export interface Database<V> {
open(): Promise<void>;
close(): Promise<void>;

has: Map<string, V>["has"];
get: Map<string, V>["get"];
set(key: string, value: V, updateTimestamp?: boolean): this;
delete(key: string): boolean;
clear(): void;

getTimestamp(key: string): number | undefined;
get size(): number;

keys: Map<string, V>["keys"];
entries: Map<string, V>["entries"];
}
26 changes: 16 additions & 10 deletions packages/zwave-js/src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JsonlDB, type JsonlDBOptions } from "@alcalzone/jsonl-db";
import { type JsonlDBOptions } from "@alcalzone/jsonl-db";
import {
type CCAPIHost,
type CCEncodingContext,
Expand Down Expand Up @@ -193,6 +193,7 @@ import {
pick,
} from "@zwave-js/shared";
import {
type Database,
type ReadFile,
type ReadFileSystemInfo,
} from "@zwave-js/shared/bindings";
Expand Down Expand Up @@ -894,19 +895,19 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>

public readonly cacheDir: string;

private _valueDB: JsonlDB | undefined;
private _valueDB: Database<unknown> | undefined;
/** @internal */
public get valueDB(): JsonlDB | undefined {
public get valueDB(): Database<unknown> | undefined {
return this._valueDB;
}
private _metadataDB: JsonlDB<ValueMetadata> | undefined;
private _metadataDB: Database<ValueMetadata> | undefined;
/** @internal */
public get metadataDB(): JsonlDB<ValueMetadata> | undefined {
public get metadataDB(): Database<ValueMetadata> | undefined {
return this._metadataDB;
}
private _networkCache: JsonlDB<any> | undefined;
private _networkCache: Database<any> | undefined;
/** @internal */
public get networkCache(): JsonlDB<any> {
public get networkCache(): Database<any> {
if (this._networkCache == undefined) {
throw new ZWaveError(
"The network cache was not yet initialized!",
Expand Down Expand Up @@ -1332,6 +1333,8 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>
?? (await import("@zwave-js/core/bindings/fs/node")).fs,
serial: this._options.host?.serial
?? (await import("@zwave-js/serial/bindings/node")).serial,
db: this._options.host?.db
?? (await import("@zwave-js/core/bindings/db/jsonl")).db,
};

const spOpenPromise = createDeferredPromise();
Expand Down Expand Up @@ -1593,7 +1596,7 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>
this.cacheDir,
`${homeId.toString(16)}.jsonl`,
);
this._networkCache = new JsonlDB(networkCacheFile, {
this._networkCache = this.bindings.db.createInstance(networkCacheFile, {
...options,
serializer: serializeNetworkCacheValue,
reviver: deserializeNetworkCacheValue,
Expand All @@ -1614,7 +1617,7 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>
this.cacheDir,
`${homeId.toString(16)}.values.jsonl`,
);
this._valueDB = new JsonlDB(valueDBFile, {
this._valueDB = this.bindings.db.createInstance(valueDBFile, {
...options,
enableTimestamps: true,
reviver: (_key, value) => deserializeCacheValue(value),
Expand All @@ -1626,7 +1629,10 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>
this.cacheDir,
`${homeId.toString(16)}.metadata.jsonl`,
);
this._metadataDB = new JsonlDB(metadataDBFile, options);
this._metadataDB = this.bindings.db.createInstance(
metadataDBFile,
options,
);
await this._metadataDB.open();

if (process.env.NO_CACHE === "true") {
Expand Down
7 changes: 6 additions & 1 deletion packages/zwave-js/src/lib/driver/Host.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
// FIXME: This should eventually live in @zwave-js/host

import { type Serial } from "@zwave-js/serial";
import { type FileSystem, type Platform } from "@zwave-js/shared/bindings";
import {
type DatabaseFactory,
type FileSystem,
type Platform,
} from "@zwave-js/shared/bindings";

/** Abstractions for a host system Z-Wave JS is running on */
export interface Host {
fs: FileSystem;
platform: Platform;
serial: Serial;
db: DatabaseFactory;
}
11 changes: 7 additions & 4 deletions packages/zwave-js/src/lib/driver/NetworkCache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { JsonlDB } from "@alcalzone/jsonl-db";
import { type AssociationAddress } from "@zwave-js/cc";
import {
type CommandClasses,
Expand All @@ -12,7 +11,11 @@ import {
securityClassOrder,
} from "@zwave-js/core";
import { Bytes, getEnumMemberName, num2hex, pickDeep } from "@zwave-js/shared";
import type { ReadFile, ReadFileSystemInfo } from "@zwave-js/shared/bindings";
import type {
Database,
ReadFile,
ReadFileSystemInfo,
} from "@zwave-js/shared/bindings";
import { isArray, isObject } from "alcalzone-shared/typeguards";
import path from "pathe";
import {
Expand Down Expand Up @@ -621,8 +624,8 @@ const legacyPaths = {

export async function migrateLegacyNetworkCache(
homeId: number,
networkCache: JsonlDB,
valueDB: JsonlDB,
networkCache: Database<any>,
valueDB: Database<unknown>,
fs: ReadFileSystemInfo & ReadFile,
cacheDir: string,
): Promise<void> {
Expand Down
7 changes: 6 additions & 1 deletion packages/zwave-js/src/lib/driver/ZWaveOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
} from "@zwave-js/core";
import { type Serial, type ZWaveSerialStream } from "@zwave-js/serial";
import { type DeepPartial, type Expand } from "@zwave-js/shared";
import type { FileSystem } from "@zwave-js/shared/bindings";
import type { DatabaseFactory, FileSystem } from "@zwave-js/shared/bindings";
import type {
InclusionUserCallbacks,
JoinNetworkUserCallbacks,
Expand Down Expand Up @@ -137,6 +137,11 @@ export interface ZWaveOptions {
* Specifies which bindings are used interact with serial ports.
*/
serial?: Serial;

/**
* Specifies which bindings are used to interact with the database used to store the cache.
*/
db?: DatabaseFactory;
};

storage: {
Expand Down
7 changes: 6 additions & 1 deletion packages/zwave-js/src/lib/zniffer/Zniffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,12 @@ export class Zniffer extends TypedEventTarget<ZnifferEventCallbacks> {
* The host bindings used to access file system etc.
*/
// This is set during `init()` and should not be accessed before
private bindings!: Required<NonNullable<ZWaveOptions["host"]>>;
private bindings!: Omit<
Required<
NonNullable<ZWaveOptions["host"]>
>,
"db"
>;

private serialFactory: ZnifferSerialStreamFactory | undefined;
/** The serial port instance */
Expand Down
Loading