Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
import type { GunFactoryConfig, GunNodeLike } from "../../src/types/types.js";
/**
* In-memory deterministic implementation of the `GunNodeLike` surface
* used by the relay daemon. The real `gun` runtime is never pulled
* into the test environment — it has heavy side-effects and requires
* browser globals — so every test wires this fake via the injected
* factory.
*
* Behavioural model:
*
* - The "graph" is a `Map<string, unknown>`. `get(path)` returns a
* node-handle scoped to that path; chained `get(child)` extends the
* path.
* - `map().on(handler)` subscribes the current scope's children. The
* handler is invoked once for every existing child at subscription
* time, then again on every subsequent injected `put`.
* - `off()` detaches every subscription created on the current scope.
* - `inject(scope, key, value)` is a test affordance that fires the
* subscribed handlers as if a remote peer had `put` the value.
*/
export class FakeGun implements GunNodeLike {
public readonly trackers: readonly string[];
public disposed = false;
private readonly store: Map<string, unknown>;
private readonly listeners: Map<
string,
Array<(data: unknown, key: string) => void>
>;
private readonly path: string;
public constructor(
config: GunFactoryConfig,
store?: Map<string, unknown>,
listeners?: Map<string, Array<(data: unknown, key: string) => void>>,
path = ""
) {
this.trackers = config.peers;
this.store = store ?? new Map();
this.listeners = listeners ?? new Map();
this.path = path;
}
public get(segment: string): GunNodeLike {
const next = this.path === "" ? segment : `${this.path}/${segment}`;
return new FakeGun(
{ web: undefined, peers: this.trackers },
this.store,
this.listeners,
next
);
}
public map(): GunNodeLike {
return this;
}
public on(handler: (data: unknown, key: string) => void): GunNodeLike {
const list = this.listeners.get(this.path) ?? [];
list.push(handler);
this.listeners.set(this.path, list);
// Replay existing children at subscription time.
const prefix = this.path === "" ? "" : `${this.path}/`;
for (const [k, v] of this.store.entries()) {
if (!k.startsWith(prefix)) continue;
const rest = k.slice(prefix.length);
if (rest === "" || rest.includes("/")) continue;
handler(v, rest);
}
return this;
}
public off(): GunNodeLike {
this.listeners.delete(this.path);
return this;
}
/** Test affordance: simulate a remote peer's `put` at the given path. */
public inject(scope: string, key: string, value: unknown): void {
const fullPath = scope === "" ? key : `${scope}/${key}`;
this.store.set(fullPath, value);
const handlers = this.listeners.get(scope);
if (handlers === undefined) return;
for (const h of [...handlers]) {
h(value, key);
}
}
}
/**
* Factory helper producing `FakeGun` instances and recording every
* call so tests can introspect or drive the captured reference.
*/
export function makeFakeGunFactory(): {
readonly instances: FakeGun[];
readonly factory: (config: GunFactoryConfig) => FakeGun;
} {
const instances: FakeGun[] = [];
return {
instances,
factory: (config) => {
const inst = new FakeGun(config);
instances.push(inst);
return inst;
},
};
}

View File

@@ -0,0 +1,7 @@
import { webcrypto } from "node:crypto";
type WritableGlobal = { crypto?: Crypto };
const g = globalThis as WritableGlobal;
if (g.crypto === undefined) {
g.crypto = webcrypto as unknown as Crypto;
}