diff --git a/README.md b/README.md index 343e9f0..93cd376 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,36 @@ Query the list of deltas ingested by this node curl -s http://localhost:3000/deltas | jq ``` +The example creates a `new TypedCollection("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 </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 ## Clocks? diff --git a/__tests__/run/001-single-node.ts b/__tests__/run/001-single-node.ts index 8a2a728..eef4ea1 100644 --- a/__tests__/run/001-single-node.ts +++ b/__tests__/run/001-single-node.ts @@ -18,37 +18,51 @@ describe('Run', () => { }); it('can put a new user and fetch it', async () => { - const res = await fetch(`${app.apiUrl}/user`, { - method: 'PUT', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ + // Create a new record + { + const res = await fetch(`${app.apiUrl}/user`, { + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + id: "peon-1", + properties: { + name: "Peon", + age: 263 + } + }) + }); + const data = await res.json(); + expect(data).toMatchObject({ id: "peon-1", properties: { name: "Peon", age: 263 } - }) - }); - const data = await res.json(); - expect(data).toMatchObject({ - id: "peon-1", - properties: { - name: "Peon", - age: 263 - } - }); + }); + } + // TODO: Optimistic update and remove this delay await new Promise((resolve) => setTimeout(resolve, 100)); - const res2 = await fetch(`${app.apiUrl}/user/peon-1`); - const data2 = await res2.json(); - expect(data2).toMatchObject({ - id: "peon-1", - properties: { - name: "Peon", - age: 263 - } - }); + // Read what we wrote + { + const res = await fetch(`${app.apiUrl}/user/peon-1`); + const data = await res.json(); + expect(data).toMatchObject({ + id: "peon-1", + properties: { + name: "Peon", + 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"]}); + } }); }); diff --git a/__tests__/run/002-two-nodes.ts b/__tests__/run/002-two-nodes.ts index c22b75b..cdb7631 100644 --- a/__tests__/run/002-two-nodes.ts +++ b/__tests__/run/002-two-nodes.ts @@ -24,42 +24,56 @@ describe('Run', () => { 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[1].apiUrl', apps[1].apiUrl); - const res = await fetch(`${apps[0].apiUrl}/user`, { - method: 'PUT', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ + // Create a new record on app0 + { + const res = await fetch(`${apps[0].apiUrl}/user`, { + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + id: "peon-1", + properties: { + name: "Peon", + age: 741 + } + }) + }); + const data = await res.json(); + expect(data).toMatchObject({ id: "peon-1", properties: { name: "Peon", - age: 263 + age: 741 } - }) - }); - const data = await res.json(); - expect(data).toMatchObject({ - id: "peon-1", - properties: { - name: "Peon", - age: 263 - } - }); + }); + } + // TODO remove delay await new Promise((resolve) => setTimeout(resolve, 100)); - const res2 = await fetch(`${apps[1].apiUrl}/user/peon-1`); - const data2 = await res2.json(); - debug('data2', data2); - expect(data2).toMatchObject({ - id: "peon-1", - properties: { - name: "Peon", - age: 263 - } - }); + // Read from app1 + { + const res = await fetch(`${apps[1].apiUrl}/user/peon-1`); + const data = await res.json(); + debug('data', data); + expect(data).toMatchObject({ + id: "peon-1", + properties: { + name: "Peon", + 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"]}); + } }); }); diff --git a/src/collection.ts b/src/collection.ts index 9e15652..341a5cf 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -169,6 +169,7 @@ export class Collection { } getIds(): string[] { - return Array.from(this.entities.keys()); + // return Array.from(this.entities.keys()); + return Array.from(this.lossless.domainEntities.keys()); } } diff --git a/src/example-app.ts b/src/example-app.ts index 962c604..50816bf 100644 --- a/src/example-app.ts +++ b/src/example-app.ts @@ -19,7 +19,7 @@ type User = { (async () => { const rhizomeNode = new RhizomeNode(); - const users = new TypedCollection("users"); + const users = new TypedCollection("user"); users.rhizomeConnect(rhizomeNode); users.onUpdate((u: Entity) => { diff --git a/src/http-api.ts b/src/http-api.ts index 1cf61f8..a00aeb6 100644 --- a/src/http-api.ts +++ b/src/http-api.ts @@ -97,7 +97,7 @@ export class HttpApi { const html = this.mdFiles.generateIndex(); 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); }); } diff --git a/src/util/md-files.ts b/src/util/md-files.ts index ce96150..f7e53fc 100644 --- a/src/util/md-files.ts +++ b/src/util/md-files.ts @@ -6,7 +6,7 @@ const debug = Debug('md-files'); const docConverter = new Converter({ completeHTMLDocument: true, - // simpleLineBreaks: true, + simpleLineBreaks: false, tables: true, tasklists: true }); @@ -27,6 +27,7 @@ export class MDFiles { readme?: mdFileInfo; dirWatcher?: FSWatcher; readmeWatcher?: FSWatcher; + latestIndexHtml?: Html; readFile(name: string) { const md = readFileSync(join('./markdown', `${name}.md`)).toString(); @@ -69,6 +70,13 @@ export class MDFiles { return htmlDocFromMarkdown(md); } + get indexHtml(): Html { + if (!this.latestIndexHtml) { + this.latestIndexHtml = this.generateIndex(); + } + return this.latestIndexHtml; + } + readDir() { // Read list of markdown files from directory and // render each markdown file as html