2024-12-21 21:16:18 -06:00
|
|
|
// We can start to use deltas to express relational data in a given context
|
|
|
|
|
|
|
|
import express from "express";
|
2024-12-22 14:00:51 -06:00
|
|
|
import {Collection} from "./collection-layer";
|
|
|
|
import {HTTP_API_ENABLE, HTTP_API_ADDR, HTTP_API_PORT, SEED_PEERS} from "./config";
|
|
|
|
import {deltasAccepted, deltasProposed, runDeltas} from "./deltas";
|
|
|
|
import {Entity} from "./object-layer";
|
|
|
|
import {askAllPeersForDeltas, peers, subscribeToSeeds} from "./peers";
|
|
|
|
import {bindPublish, } from "./pub-sub";
|
|
|
|
import {bindReply, runRequestHandlers} from "./request-reply";
|
|
|
|
import {Delta, PeerAddress} from "./types";
|
2024-12-21 21:16:18 -06:00
|
|
|
|
|
|
|
// As an app we want to be able to write and read data.
|
|
|
|
// The data is whatever shape we define it to be in a given context.
|
|
|
|
// So we want access to an API that is integrated with our declarations of
|
|
|
|
// e.g. entities and their properties.
|
|
|
|
|
|
|
|
// This implies at least one layer on top of the underlying primitive deltas.
|
|
|
|
type UserProperties = {
|
|
|
|
id?: string;
|
|
|
|
name: string;
|
|
|
|
nameLong?: string;
|
|
|
|
email?: string;
|
|
|
|
age: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
class Users {
|
2024-12-22 09:13:44 -06:00
|
|
|
db = new Collection();
|
2024-12-21 21:16:18 -06:00
|
|
|
create(properties: UserProperties): Entity {
|
|
|
|
// We provide undefined for the id, to let the database generate it
|
|
|
|
// This call returns the id
|
|
|
|
const user = this.db.put(undefined, properties);
|
|
|
|
console.log(`Users.create(${user.id}, ${JSON.stringify(properties)}`);
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
upsert(properties: UserProperties): Entity {
|
|
|
|
const user = this.db.put(properties.id, properties);
|
|
|
|
console.log(`Users.upsert(${user.id}, ${JSON.stringify(properties)}`);
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
getOne(id: string): Entity | undefined {
|
|
|
|
return this.db.get(id);
|
|
|
|
}
|
|
|
|
getIds(): string[] {
|
|
|
|
return this.db.getIds();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(async () => {
|
2024-12-22 09:13:44 -06:00
|
|
|
const users = new Users();
|
2024-12-21 21:16:18 -06:00
|
|
|
|
2024-12-22 09:13:44 -06:00
|
|
|
const app = express()
|
2024-12-21 21:16:18 -06:00
|
|
|
app.get("/ids", (req: express.Request, res: express.Response) => {
|
2024-12-22 14:00:51 -06:00
|
|
|
res.json({ids: users.getIds()});
|
2024-12-21 21:16:18 -06:00
|
|
|
});
|
|
|
|
|
2024-12-22 09:13:44 -06:00
|
|
|
app.get("/deltas", (req: express.Request, res: express.Response) => {
|
|
|
|
// TODO: streaming
|
|
|
|
res.json(deltasAccepted);
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get("/deltas/count", (req: express.Request, res: express.Response) => {
|
|
|
|
// TODO: streaming
|
|
|
|
res.json(deltasAccepted.length);
|
|
|
|
});
|
|
|
|
|
2024-12-22 14:00:51 -06:00
|
|
|
app.get("/peers", (req: express.Request, res: express.Response) => {
|
|
|
|
res.json(peers.map(({reqAddr, publishAddr}) => {
|
|
|
|
const isSeedPeer = !!SEED_PEERS.find(({addr, port}) =>
|
|
|
|
addr === reqAddr.addr && port === reqAddr.port);
|
|
|
|
const deltasAcceptedCount = deltasAccepted
|
|
|
|
.filter((delta: Delta) => {
|
|
|
|
return delta.receivedFrom?.addr == reqAddr.addr &&
|
|
|
|
delta.receivedFrom?.port == reqAddr.port;
|
|
|
|
})
|
|
|
|
.length;
|
|
|
|
const peerInfo = {
|
|
|
|
reqAddr: reqAddr.toAddrString(),
|
|
|
|
publishAddr: publishAddr?.toAddrString(),
|
|
|
|
isSeedPeer,
|
|
|
|
deltaCount: {
|
|
|
|
accepted: deltasAcceptedCount
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return peerInfo;
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
if (HTTP_API_ENABLE) {
|
2024-12-21 21:16:18 -06:00
|
|
|
app.listen(HTTP_API_PORT, HTTP_API_ADDR, () => {
|
2024-12-22 14:00:51 -06:00
|
|
|
console.log(`HTTP API bound to http://${HTTP_API_ADDR}:${HTTP_API_PORT}`);
|
2024-12-21 21:16:18 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await bindPublish();
|
|
|
|
await bindReply();
|
|
|
|
runDeltas();
|
|
|
|
runRequestHandlers();
|
2024-12-22 09:13:44 -06:00
|
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
2024-12-21 21:16:18 -06:00
|
|
|
subscribeToSeeds();
|
2024-12-22 09:13:44 -06:00
|
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
askAllPeersForDeltas();
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
2024-12-21 21:16:18 -06:00
|
|
|
|
|
|
|
const taliesin = users.upsert({
|
2024-12-22 09:13:44 -06:00
|
|
|
// id: 'taliesin-1',
|
2024-12-21 21:16:18 -06:00
|
|
|
name: 'Taliesin',
|
|
|
|
nameLong: 'Taliesin (Ladd)',
|
|
|
|
age: Math.floor(Math.random() * 1000)
|
|
|
|
});
|
|
|
|
|
2024-12-22 09:13:44 -06:00
|
|
|
users.db.onUpdate((u: Entity) => {
|
|
|
|
console.log('User updated:', u);
|
|
|
|
});
|
|
|
|
|
|
|
|
users.db.onCreate((u: Entity) => {
|
|
|
|
console.log('New user!:', u);
|
2024-12-21 21:16:18 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: Allow configuration regarding read/write concern i.e.
|
|
|
|
// if we perform a read immediately do we see the value we wrote?
|
|
|
|
// Intuition says yes, we want that-- but how do we expose the propagation status?
|
|
|
|
|
|
|
|
const result = users.getOne(taliesin.id);
|
|
|
|
const matches: boolean = JSON.stringify(result) === JSON.stringify(taliesin);
|
|
|
|
console.log(`Result ${matches ? 'matches' : 'does not match'} expected.` +
|
|
|
|
`\n\nExpected \n${JSON.stringify(taliesin)}` +
|
|
|
|
`\nReceived\n${JSON.stringify(result)}`);
|
|
|
|
|
|
|
|
})();
|
|
|
|
|