refactored collection into abstract and basic in preparation for adding relational

This commit is contained in:
Ladd Hoffman 2025-01-02 21:21:16 -06:00
parent c6f6ece504
commit 6e0dccdfea
8 changed files with 70 additions and 50 deletions

View File

@ -1,5 +1,5 @@
import Debug from 'debug';
import {Collection} from "../src/collection";
import {BasicCollection} from '../src/collection-basic';
import {Entity} from "../src/entity";
import {RhizomeNode} from "../src/node";
const debug = Debug('example-app');
@ -23,7 +23,7 @@ type User = {
// Enable API to read lossless view
rhizomeNode.httpServer.httpApi.serveLossless();
const users = new Collection("user");
const users = new BasicCollection("user");
users.rhizomeConnect(rhizomeNode);
users.onUpdate((u: Entity) => {

View File

@ -1,38 +1,31 @@
// A basic collection of entities
// This may be extended to house a collection of objects that all follow a common schema.
// It should enable operations like removing a property removes the value from the entities in the collection
// It could then be further extended with e.g. table semantics like filter, sort, join
import Debug from 'debug';
import {randomUUID} from "node:crypto";
import EventEmitter from "node:events";
import {Delta} from "./delta";
import {Entity, EntityProperties} from "./entity";
import {LastWriteWins, ResolvedViewOne} from './last-write-wins';
import {ResolvedViewOne} from './last-write-wins';
import {RhizomeNode} from "./node";
import {DomainEntityID} from "./types";
const debug = Debug('rz:collection');
const debug = Debug('rz:abstract-collection');
export class Collection {
export abstract class Collection<T> {
rhizomeNode?: RhizomeNode;
name: string;
eventStream = new EventEmitter();
lossy?: LastWriteWins;
lossy?: T;
constructor(name: string) {
this.name = name;
}
// Instead of trying to update our final view of the entity with every incoming delta,
// let's try this:
// - keep a lossless view (of everything)
// - build a lossy view when needed
// This approach is simplistic, but can then be optimized and enhanced.
abstract initializeView(): void;
abstract resolve(id: string): ResolvedViewOne | undefined;
rhizomeConnect(rhizomeNode: RhizomeNode) {
this.rhizomeNode = rhizomeNode;
this.lossy = new LastWriteWins(this.rhizomeNode.lossless);
this.initializeView();
// Listen for completed transactions, and emit updates to event stream
this.rhizomeNode.lossless.eventStream.on("updated", (id) => {
@ -43,13 +36,12 @@ export class Collection {
this.eventStream.emit("update", res);
});
rhizomeNode.httpServer.httpApi.serveCollection(this);
// TODO: Fix this
rhizomeNode.httpServer.httpApi.serveCollection<T>(this);
debug(`[${this.rhizomeNode.config.peerId}]`, `Connected ${this.name} to rhizome`);
}
// Applies the javascript rules for updating object values,
// e.g. set to `undefined` to delete a property.
// This function is here instead of Entity so that it can:
// - read the current state in order to build its delta
// - include the collection name in the delta it produces
@ -206,14 +198,4 @@ export class Collection {
return res;
}
resolve(
id: string
): ResolvedViewOne | undefined {
if (!this.rhizomeNode) throw new Error('collection not connected to rhizome');
if (!this.lossy) throw new Error('lossy view not initialized');
const res = this.lossy.resolve([id]) || {};
return res[id];
}
}

27
src/collection-basic.ts Normal file
View File

@ -0,0 +1,27 @@
// A basic collection of entities
// This may be extended to house a collection of objects that all follow a common schema.
// It should enable operations like removing a property removes the value from the entities in the collection
// It could then be further extended with e.g. table semantics like filter, sort, join
import {Collection} from './collection-abstract';
import {LastWriteWins, ResolvedViewOne} from './last-write-wins';
export class BasicCollection extends Collection<LastWriteWins> {
lossy?: LastWriteWins;
initializeView() {
if (!this.rhizomeNode) throw new Error('not connected to rhizome');
this.lossy = new LastWriteWins(this.rhizomeNode.lossless);
}
resolve(
id: string
): ResolvedViewOne | undefined {
if (!this.rhizomeNode) throw new Error('collection not connected to rhizome');
if (!this.lossy) throw new Error('lossy view not initialized');
const res = this.lossy.resolve([id]) || {};
return res[id];
}
}

View File

@ -0,0 +1,26 @@
import {Collection} from "./collection-abstract";
import {LastWriteWins, ResolvedViewOne} from "./last-write-wins";
class RelationalView extends LastWriteWins {
}
export class RelationalCollection extends Collection<RelationalView> {
lossy?: RelationalView;
initializeView() {
if (!this.rhizomeNode) throw new Error('not connected to rhizome');
this.lossy = new RelationalView(this.rhizomeNode.lossless);
}
resolve(
id: string
): ResolvedViewOne | undefined {
if (!this.rhizomeNode) throw new Error('collection not connected to rhizome');
if (!this.lossy) throw new Error('lossy view not initialized');
const res = this.lossy.resolve([id]) || {};
return res[id];
}
}

View File

@ -7,7 +7,6 @@
// - As typescript interfaces?
// - As typescript classes?
import {Collection} from "./collection";
import {PropertyTypes} from "./types";
export type EntityProperties = {
@ -20,11 +19,5 @@ export class Entity {
constructor(
readonly id: string,
readonly collection?: Collection
) {}
async save() {
if (!this.collection) throw new Error('to save this entity you must specify the collection');
return this.collection.put(this.id, this.properties);
}
}

View File

@ -1,5 +1,5 @@
import express, {Router} from "express";
import {Collection} from "../collection";
import {Collection} from "../collection-abstract";
import {Delta} from "../delta";
import {RhizomeNode} from "../node";
@ -57,7 +57,8 @@ export class HttpApi {
});
}
serveCollection(collection: Collection) {
// serveCollection<T extends Collection>(collection: T) {
serveCollection<View>(collection: Collection<View>) {
const {name} = collection;
// Get the ID of all domain entities

View File

@ -1,9 +0,0 @@
import {Collection} from "./collection";
export class RelationalCollection extends Collection {
// lossy?:
}

View File

@ -1,4 +1,4 @@
import {Collection} from "../src/collection";
import {BasicCollection} from "../src/collection-basic";
import {RhizomeNode, RhizomeNodeConfig} from "../src/node";
const start = 5000;
@ -17,7 +17,7 @@ export class App extends RhizomeNode {
...config,
});
const users = new Collection("user");
const users = new BasicCollection("user");
users.rhizomeConnect(this);
const {httpAddr, httpPort} = this.config;