import { CreateCloudFunctionCaller } from "./CloudFunction"; import { DeepReadonlyJson, Json, JsonObject, JsonPrimitive, } from "./Primitives"; import { StartCloudSynchronizer } from "./Table"; // SECTION: Reactivity export type ReactiveStore = { readonly data: DeepReadonlyJson; readonly update: (doUpdate: (data: T) => void) => void; readonly dispose: () => void; }; export type ReactiveUtilities = ReturnType; export type ReactivityConfig = Parameters[0]; export function createReactiveUtilities(reactivity: { /** These should both be root level. */ createStore(init: T): ReactiveStore; doWatch(doSomething: () => void): { dispose: () => void; }; untrack(compute: () => T): T; }) { return { createStore: reactivity.createStore, doWatch: reactivity.doWatch, rootFormula: (compute: () => T) => { let dispose: (() => void) | undefined = undefined; let haveSetUpStore = false; const store = reactivity.createStore({ value: null as any as T }); return { get value() { if (!haveSetUpStore) { dispose = reactivity.doWatch(() => { const newValue = compute(); const oldValue = reactivity.untrack(() => store.data.value); if (oldValue !== newValue) { store.update((data) => (data.value = newValue)); } haveSetUpStore = true; }).dispose; reactivity.untrack(() => { const newValue = compute(); store.update((data) => (data.value = newValue)); }); } return store.data.value; }, dispose() { dispose?.(); store.dispose(); }, }; }, }; } // SECTION: Storage export type StorageConfig = { readFile(path: string): Promise; writeFile(path: string, data: string): Promise; deleteFile(path: string): Promise; }; export type SavedJson = ReturnType[`savedJson`]; export function createStorageUtils(config: { storage?: StorageConfig; reactivity: ReactivityConfig; }) { return { readFile: config.storage?.readFile, writeFile: config.storage?.writeFile, deleteFile: config.storage?.deleteFile, savedJson: ( path: string, init: T, ): { readonly data: DeepReadonlyJson | null; update: (doUpdate: (data: T) => void) => void; dispose: () => void; readonly haveLoaded: boolean; readonly loadPromise: Promise; } => { const { storage, reactivity } = config; const store = reactivity.createStore({ value: null as null | T, }); const savedJsonString = Promise.resolve(storage?.readFile(path)); let haveLoaded = false; const loadPromise = savedJsonString.then((savedJsonString) => { store.update((data) => { data.value = savedJsonString !== undefined ? JSON.parse(savedJsonString) : init; }); haveLoaded = true; }); return { get data() { return store.data.value; }, update: (doUpdate: (data: T) => void) => { if (!haveLoaded) { throw new Error( `Cannot update a savedJson before it has loaded the data`, ); } store.update((data) => { doUpdate(data.value!); storage?.writeFile(path, JSON.stringify(store.data.value)); }); }, // TODO: Eventually provide a way to delete the file. dispose: store.dispose, haveLoaded, loadPromise, }; }, }; } // SECTION: Cloud export type CloudConfig = { startCloudSynchronizer: StartCloudSynchronizer; createCloudFunctionCaller: CreateCloudFunctionCaller; }; export type CloudUtilities = ReturnType; export function createCloudUtils( cloudConfig: CloudConfig, ) { return { startCloudSynchronizer: cloudConfig.startCloudSynchronizer, createCloudFunctionCaller: cloudConfig.createCloudFunctionCaller, }; } export type DocUpdatesForCloud = { [docId: string]: { [key: string]: | JsonPrimitive | JsonPrimitive[] | { [mapKey: string]: JsonPrimitive }; }; };