use lossless view for full list of entity ids

This commit is contained in:
Ladd Hoffman 2024-12-26 16:52:46 -06:00
parent 3f0b5bec4e
commit c3f896f130
7 changed files with 120 additions and 53 deletions

View File

@ -118,6 +118,36 @@ Query the list of deltas ingested by this node
curl -s http://localhost:3000/deltas | jq curl -s http://localhost:3000/deltas | jq
``` ```
The example creates a `new TypedCollection<User>("user")` and calls `connectRhizome` to join it with the network.
The collection is synchronized across the cluster and optionally CRUD type operations are served via HTTP.
Query the list of User IDs
```bash
curl -s http://localhost:3000/user/ids
```
Query the list of User IDs
```bash
curl -s http://localhost:3000/user/ids
```
Read a User by ID
```bash
curl -s http://localhost:3000/user/taliesin-1
```
Create a User
```bash
cat <<EOF >/tmp/user.json
{"id": "optional-id",
"properties": {
"name": "required",
"nameLong": "optional",
"email": "optional"}}
EOF
curl -s -X PUT -H 'content-type:application/json' -d @/tmp/user.json http://localhost:3000/user | jq
```
# More About Concepts # More About Concepts
## Clocks? ## Clocks?

View File

@ -18,6 +18,8 @@ describe('Run', () => {
}); });
it('can put a new user and fetch it', async () => { it('can put a new user and fetch it', async () => {
// Create a new record
{
const res = await fetch(`${app.apiUrl}/user`, { const res = await fetch(`${app.apiUrl}/user`, {
method: 'PUT', method: 'PUT',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@ -37,18 +39,30 @@ describe('Run', () => {
age: 263 age: 263
} }
}); });
}
// TODO: Optimistic update and remove this delay
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
const res2 = await fetch(`${app.apiUrl}/user/peon-1`); // Read what we wrote
const data2 = await res2.json(); {
expect(data2).toMatchObject({ const res = await fetch(`${app.apiUrl}/user/peon-1`);
const data = await res.json();
expect(data).toMatchObject({
id: "peon-1", id: "peon-1",
properties: { properties: {
name: "Peon", name: "Peon",
age: 263 age: 263
} }
}); });
}
// Verify our record is also in the index
{
const res = await fetch(`${app.apiUrl}/user/ids`);
const data = await res.json();
expect(data).toMatchObject({ids: [ "peon-1"]});
}
}); });
}); });

View File

@ -24,10 +24,12 @@ describe('Run', () => {
await Promise.all(apps.map((app) => app.stop())); await Promise.all(apps.map((app) => app.stop()));
}); });
it('can create a record on node 0 and read it on node 1', async () => { it('can create a record on app0 and read it on app1', async () => {
debug('apps[0].apiUrl', apps[0].apiUrl); debug('apps[0].apiUrl', apps[0].apiUrl);
debug('apps[1].apiUrl', apps[1].apiUrl); debug('apps[1].apiUrl', apps[1].apiUrl);
// Create a new record on app0
{
const res = await fetch(`${apps[0].apiUrl}/user`, { const res = await fetch(`${apps[0].apiUrl}/user`, {
method: 'PUT', method: 'PUT',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@ -35,7 +37,7 @@ describe('Run', () => {
id: "peon-1", id: "peon-1",
properties: { properties: {
name: "Peon", name: "Peon",
age: 263 age: 741
} }
}) })
}); });
@ -44,22 +46,34 @@ describe('Run', () => {
id: "peon-1", id: "peon-1",
properties: { properties: {
name: "Peon", name: "Peon",
age: 263 age: 741
} }
}); });
}
// TODO remove delay
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
const res2 = await fetch(`${apps[1].apiUrl}/user/peon-1`); // Read from app1
const data2 = await res2.json(); {
debug('data2', data2); const res = await fetch(`${apps[1].apiUrl}/user/peon-1`);
expect(data2).toMatchObject({ const data = await res.json();
debug('data', data);
expect(data).toMatchObject({
id: "peon-1", id: "peon-1",
properties: { properties: {
name: "Peon", name: "Peon",
age: 263 age: 741
} }
}); });
}
// Verify our record is also in the index
for (const app of apps) {
const res = await fetch(`${app.apiUrl}/user/ids`);
const data = await res.json();
expect(data).toMatchObject({ids: ["peon-1"]});
}
}); });
}); });

View File

@ -169,6 +169,7 @@ export class Collection {
} }
getIds(): string[] { getIds(): string[] {
return Array.from(this.entities.keys()); // return Array.from(this.entities.keys());
return Array.from(this.lossless.domainEntities.keys());
} }
} }

View File

@ -19,7 +19,7 @@ type User = {
(async () => { (async () => {
const rhizomeNode = new RhizomeNode(); const rhizomeNode = new RhizomeNode();
const users = new TypedCollection<User>("users"); const users = new TypedCollection<User>("user");
users.rhizomeConnect(rhizomeNode); users.rhizomeConnect(rhizomeNode);
users.onUpdate((u: Entity) => { users.onUpdate((u: Entity) => {

View File

@ -97,7 +97,7 @@ export class HttpApi {
const html = this.mdFiles.generateIndex(); const html = this.mdFiles.generateIndex();
this.router.get('/html', (_req: express.Request, res: express.Response) => { this.router.get('/html', (_req: express.Request, res: express.Response) => {
res.setHeader('content-type', 'text/html').send(html); res.setHeader('content-type', 'text/html').send(this.mdFiles.indexHtml);
}); });
} }

View File

@ -6,7 +6,7 @@ const debug = Debug('md-files');
const docConverter = new Converter({ const docConverter = new Converter({
completeHTMLDocument: true, completeHTMLDocument: true,
// simpleLineBreaks: true, simpleLineBreaks: false,
tables: true, tables: true,
tasklists: true tasklists: true
}); });
@ -27,6 +27,7 @@ export class MDFiles {
readme?: mdFileInfo; readme?: mdFileInfo;
dirWatcher?: FSWatcher; dirWatcher?: FSWatcher;
readmeWatcher?: FSWatcher; readmeWatcher?: FSWatcher;
latestIndexHtml?: Html;
readFile(name: string) { readFile(name: string) {
const md = readFileSync(join('./markdown', `${name}.md`)).toString(); const md = readFileSync(join('./markdown', `${name}.md`)).toString();
@ -69,6 +70,13 @@ export class MDFiles {
return htmlDocFromMarkdown(md); return htmlDocFromMarkdown(md);
} }
get indexHtml(): Html {
if (!this.latestIndexHtml) {
this.latestIndexHtml = this.generateIndex();
}
return this.latestIndexHtml;
}
readDir() { readDir() {
// Read list of markdown files from directory and // Read list of markdown files from directory and
// render each markdown file as html // render each markdown file as html