Ship built output for consumers

This commit is contained in:
Meta Vens 2026-05-06 03:17:20 +01:00
commit 9562d6a603
16 changed files with 302 additions and 1 deletions

1
.gitignore vendored
View file

@ -1,2 +1 @@
dist/
node_modules/

15
dist/cult-cache.d.ts vendored Normal file
View file

@ -0,0 +1,15 @@
import type { CacheBackingStore, CultCacheDocumentDefinition, CultCacheDocumentValue, CultCacheEnvelope } from "./types";
export declare class CultCache {
#private;
registerDocumentType<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition): TDefinition;
addBackingStore(store: CacheBackingStore, ...types: string[]): void;
pullAllBackingStores(): Promise<void>;
get<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition, key: string): CultCacheDocumentValue<TDefinition> | undefined;
getRequired<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition, key: string): CultCacheDocumentValue<TDefinition>;
getAll<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition): CultCacheDocumentValue<TDefinition>[];
put<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition, key: string, value: CultCacheDocumentValue<TDefinition>): Promise<CultCacheDocumentValue<TDefinition>>;
update<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition, key: string, updater: (current: CultCacheDocumentValue<TDefinition> | undefined) => CultCacheDocumentValue<TDefinition>): Promise<CultCacheDocumentValue<TDefinition>>;
delete<TDefinition extends CultCacheDocumentDefinition>(definition: TDefinition, key: string): Promise<boolean>;
snapshot(): CultCacheEnvelope[];
}
//# sourceMappingURL=cult-cache.d.ts.map

1
dist/cult-cache.d.ts.map vendored Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"cult-cache.d.ts","sourceRoot":"","sources":["../src/cult-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,2BAA2B,EAC3B,sBAAsB,EACtB,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAOjB,qBAAa,SAAS;;IAKpB,oBAAoB,CAAC,WAAW,SAAS,2BAA2B,EAClE,UAAU,EAAE,WAAW,GACtB,WAAW;IAUd,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAO7D,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB3C,GAAG,CAAC,WAAW,SAAS,2BAA2B,EACjD,UAAU,EAAE,WAAW,EACvB,GAAG,EAAE,MAAM,GACV,sBAAsB,CAAC,WAAW,CAAC,GAAG,SAAS;IAMlD,WAAW,CAAC,WAAW,SAAS,2BAA2B,EACzD,UAAU,EAAE,WAAW,EACvB,GAAG,EAAE,MAAM,GACV,sBAAsB,CAAC,WAAW,CAAC;IAStC,MAAM,CAAC,WAAW,SAAS,2BAA2B,EACpD,UAAU,EAAE,WAAW,GACtB,sBAAsB,CAAC,WAAW,CAAC,EAAE;IAalC,GAAG,CAAC,WAAW,SAAS,2BAA2B,EACvD,UAAU,EAAE,WAAW,EACvB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,sBAAsB,CAAC,WAAW,CAAC,GACzC,OAAO,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;IAqBzC,MAAM,CAAC,WAAW,SAAS,2BAA2B,EAC1D,UAAU,EAAE,WAAW,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CACP,OAAO,EAAE,sBAAsB,CAAC,WAAW,CAAC,GAAG,SAAS,KACrD,sBAAsB,CAAC,WAAW,CAAC,GACvC,OAAO,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;IAKzC,MAAM,CAAC,WAAW,SAAS,2BAA2B,EAC1D,UAAU,EAAE,WAAW,EACvB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC;IAkBnB,QAAQ,IAAI,iBAAiB,EAAE;CAkChC"}

126
dist/cult-cache.js vendored Normal file
View file

@ -0,0 +1,126 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CultCache = void 0;
class CultCache {
#definitions = new Map();
#entries = new Map();
#stores = [];
registerDocumentType(definition) {
const existing = this.#definitions.get(definition.type);
if (existing && existing !== definition) {
throw new Error(`CultCache already has a different definition registered for type "${definition.type}".`);
}
this.#definitions.set(definition.type, definition);
return definition;
}
addBackingStore(store, ...types) {
this.#stores.push({
store,
types: [...new Set(types)],
});
}
async pullAllBackingStores() {
this.#entries.clear();
for (const registration of this.#stores) {
const entries = await registration.store.pullAll();
for (const entry of entries) {
const definition = this.#definitions.get(entry.type);
if (!definition) {
throw new Error(`No schema is registered for persisted document type "${entry.type}".`);
}
const parsed = definition.schema.parse(entry.payload);
this.#entries.set(this.#entryId(entry.type, entry.key), {
...entry,
payload: parsed,
});
}
}
}
get(definition, key) {
this.#requireDefinition(definition);
const entry = this.#entries.get(this.#entryId(definition.type, key));
return entry ? entry.payload : undefined;
}
getRequired(definition, key) {
const value = this.get(definition, key);
if (value === undefined) {
throw new Error(`CultCache has no "${definition.type}" document at key "${key}".`);
}
return value;
}
getAll(definition) {
this.#requireDefinition(definition);
const values = [];
for (const entry of this.#entries.values()) {
if (entry.type === definition.type) {
values.push(entry.payload);
}
}
return values;
}
async put(definition, key, value) {
this.#requireDefinition(definition);
const parsed = definition.schema.parse(value);
const entry = {
key,
type: definition.type,
payload: parsed,
storedAt: new Date().toISOString(),
};
const route = this.#resolveRoute(definition.type);
if (!route.primary) {
throw new Error(`No backing store is registered for document type "${definition.type}".`);
}
await route.primary.push(entry);
await Promise.all(route.mirrors.map(async (mirror) => mirror.push(entry)));
this.#entries.set(this.#entryId(definition.type, key), entry);
return parsed;
}
async update(definition, key, updater) {
const current = this.get(definition, key);
return this.put(definition, key, updater(current));
}
async delete(definition, key) {
this.#requireDefinition(definition);
const entry = this.#entries.get(this.#entryId(definition.type, key));
if (!entry) {
return false;
}
const route = this.#resolveRoute(definition.type);
if (!route.primary) {
throw new Error(`No backing store is registered for document type "${definition.type}".`);
}
await route.primary.delete(entry);
await Promise.all(route.mirrors.map(async (mirror) => mirror.delete(entry)));
this.#entries.delete(this.#entryId(definition.type, key));
return true;
}
snapshot() {
return [...this.#entries.values()].map((entry) => structuredClone(entry));
}
#requireDefinition(definition) {
const registered = this.#definitions.get(definition.type);
if (!registered || registered !== definition) {
throw new Error(`CultCache document type "${definition.type}" is not registered on this cache instance.`);
}
return definition;
}
#resolveRoute(type) {
const typeSpecific = this.#stores.filter((registration) => registration.types.includes(type));
if (typeSpecific.length > 0) {
return {
primary: typeSpecific[0]?.store,
mirrors: typeSpecific.slice(1).map((registration) => registration.store),
};
}
const generic = this.#stores.filter((registration) => registration.types.length === 0);
return {
primary: generic[0]?.store,
mirrors: generic.slice(1).map((registration) => registration.store),
};
}
#entryId(type, key) {
return `${type}::${key}`;
}
}
exports.CultCache = CultCache;

4
dist/document.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
import type { ZodType } from "zod";
import type { CultCacheDocumentDefinition } from "./types";
export declare function defineDocumentType<TSchema extends ZodType>(definition: CultCacheDocumentDefinition<TSchema>): CultCacheDocumentDefinition<TSchema>;
//# sourceMappingURL=document.d.ts.map

1
dist/document.d.ts.map vendored Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,OAAO,EACxD,UAAU,EAAE,2BAA2B,CAAC,OAAO,CAAC,GAC/C,2BAA2B,CAAC,OAAO,CAAC,CAMtC"}

9
dist/document.js vendored Normal file
View file

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineDocumentType = defineDocumentType;
function defineDocumentType(definition) {
if (!definition.type || definition.type.trim().length === 0) {
throw new Error("CultCache document types must declare a non-empty type.");
}
return definition;
}

5
dist/index.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
export { CultCache } from "./cult-cache";
export { defineDocumentType } from "./document";
export { SingleFileMessagePackBackingStore } from "./single-file-messagepack-backing-store";
export type { CacheBackingStore, CultCacheDocumentDefinition, CultCacheDocumentValue, CultCacheEnvelope, CultCacheStoreRegistration, PushAllOptions, } from "./types";
//# sourceMappingURL=index.d.ts.map

1
dist/index.d.ts.map vendored Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,iCAAiC,EAAE,MAAM,yCAAyC,CAAC;AAC5F,YAAY,EACV,iBAAiB,EACjB,2BAA2B,EAC3B,sBAAsB,EACtB,iBAAiB,EACjB,0BAA0B,EAC1B,cAAc,GACf,MAAM,SAAS,CAAC"}

9
dist/index.js vendored Normal file
View file

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SingleFileMessagePackBackingStore = exports.defineDocumentType = exports.CultCache = void 0;
var cult_cache_1 = require("./cult-cache");
Object.defineProperty(exports, "CultCache", { enumerable: true, get: function () { return cult_cache_1.CultCache; } });
var document_1 = require("./document");
Object.defineProperty(exports, "defineDocumentType", { enumerable: true, get: function () { return document_1.defineDocumentType; } });
var single_file_messagepack_backing_store_1 = require("./single-file-messagepack-backing-store");
Object.defineProperty(exports, "SingleFileMessagePackBackingStore", { enumerable: true, get: function () { return single_file_messagepack_backing_store_1.SingleFileMessagePackBackingStore; } });

View file

@ -0,0 +1,11 @@
import type { CacheBackingStore, CultCacheEnvelope, PushAllOptions } from "./types";
export declare class SingleFileMessagePackBackingStore implements CacheBackingStore {
#private;
readonly filePath: string;
constructor(filePath: string);
pullAll(): Promise<CultCacheEnvelope[]>;
push(entry: CultCacheEnvelope): Promise<void>;
delete(entry: CultCacheEnvelope): Promise<void>;
pushAll(entries: CultCacheEnvelope[], options?: PushAllOptions): Promise<void>;
}
//# sourceMappingURL=single-file-messagepack-backing-store.d.ts.map

View file

@ -0,0 +1 @@
{"version":3,"file":"single-file-messagepack-backing-store.d.ts","sourceRoot":"","sources":["../src/single-file-messagepack-backing-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAWpF,qBAAa,iCAAkC,YAAW,iBAAiB;;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAId,QAAQ,EAAE,MAAM;IAItB,OAAO,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAcvC,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7C,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAiDzF"}

View file

@ -0,0 +1,90 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SingleFileMessagePackBackingStore = void 0;
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const msgpack_1 = require("@msgpack/msgpack");
const zod_1 = require("zod");
const envelopeSchema = zod_1.z.object({
key: zod_1.z.string().min(1),
type: zod_1.z.string().min(1),
payload: zod_1.z.unknown(),
storedAt: zod_1.z.string().min(1),
});
const envelopeArraySchema = zod_1.z.array(envelopeSchema);
class SingleFileMessagePackBackingStore {
filePath;
#writeQueue = Promise.resolve();
constructor(filePath) {
this.filePath = (0, node_path_1.resolve)(filePath);
}
async pullAll() {
try {
const data = await (0, promises_1.readFile)(this.filePath);
return envelopeArraySchema.parse((0, msgpack_1.decode)(data));
}
catch (error) {
const code = error.code;
if (code === "ENOENT") {
return [];
}
throw error;
}
}
async push(entry) {
await this.#enqueue(async () => {
const existing = await this.pullAll();
const filtered = existing.filter((candidate) => !(candidate.type === entry.type && candidate.key === entry.key));
filtered.push(entry);
await this.#writeAll(filtered);
});
}
async delete(entry) {
await this.#enqueue(async () => {
const existing = await this.pullAll();
const filtered = existing.filter((candidate) => !(candidate.type === entry.type && candidate.key === entry.key));
await this.#writeAll(filtered);
});
}
async pushAll(entries, options = {}) {
await this.#enqueue(async () => {
if (options.soft) {
try {
await (0, promises_1.readFile)(this.filePath);
return;
}
catch (error) {
const code = error.code;
if (code !== "ENOENT") {
throw error;
}
}
}
await this.#writeAll(entries);
});
}
async #enqueue(operation) {
let result;
const next = this.#writeQueue.then(async () => {
result = await operation();
});
this.#writeQueue = next.then(() => undefined, () => undefined);
await next;
return result;
}
async #writeAll(entries) {
await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.filePath), { recursive: true });
const tempPath = `${this.filePath}.tmp-${process.pid}-${Date.now()}-${Math.random()
.toString(36)
.slice(2)}`;
try {
await (0, promises_1.writeFile)(tempPath, (0, msgpack_1.encode)(entries));
await (0, promises_1.rename)(tempPath, this.filePath);
}
catch (error) {
await (0, promises_1.rm)(tempPath, { force: true }).catch(() => undefined);
throw error;
}
}
}
exports.SingleFileMessagePackBackingStore = SingleFileMessagePackBackingStore;

26
dist/types.d.ts vendored Normal file
View file

@ -0,0 +1,26 @@
import { z, type ZodType } from "zod";
export interface CultCacheDocumentDefinition<TSchema extends ZodType = ZodType> {
type: string;
schema: TSchema;
}
export type CultCacheDocumentValue<TDefinition extends CultCacheDocumentDefinition> = z.infer<TDefinition["schema"]>;
export interface CultCacheEnvelope {
key: string;
type: string;
payload: unknown;
storedAt: string;
}
export interface PushAllOptions {
soft?: boolean;
}
export interface CacheBackingStore {
pullAll(): Promise<CultCacheEnvelope[]>;
push(entry: CultCacheEnvelope): Promise<void>;
delete(entry: CultCacheEnvelope): Promise<void>;
pushAll?(entries: CultCacheEnvelope[], options?: PushAllOptions): Promise<void>;
}
export interface CultCacheStoreRegistration {
store: CacheBackingStore;
types: string[];
}
//# sourceMappingURL=types.d.ts.map

1
dist/types.d.ts.map vendored Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,KAAK,OAAO,EAAE,MAAM,KAAK,CAAC;AAEtC,MAAM,WAAW,2BAA2B,CAAC,OAAO,SAAS,OAAO,GAAG,OAAO;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,sBAAsB,CAAC,WAAW,SAAS,2BAA2B,IAAI,CAAC,CAAC,KAAK,CAC3F,WAAW,CAAC,QAAQ,CAAC,CACtB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjF;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB"}

2
dist/types.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });