Skip to content

Commit f6e6c1c

Browse files
authored
Merge pull request #7 from jsonjoy-com/local
Local history
2 parents 48d64e8 + cc84946 commit f6e6c1c

File tree

26 files changed

+1036
-216
lines changed

26 files changed

+1036
-216
lines changed

.github/workflows/lint.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Lint
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
commitlint:
7+
name: Commitlint
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v4
11+
with:
12+
fetch-depth: 0
13+
- uses: wagoid/commitlint-github-action@v5
14+
with:
15+
configFile: './package.json'
16+
env:
17+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/mirror.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
name: Node.js CI
1+
name: Mirror
22

33
on:
44
push:
55
branches: [ master ]
66

77
jobs:
88
mirror:
9+
name: Push To Gitlab
910
runs-on: ubuntu-latest
1011
steps:
1112
- uses: actions/checkout@v4

.github/workflows/pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Node.js CI
1+
name: PR Checks
22

33
on:
44
pull_request:

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@
7474
"@jsonjoy.com/jit-router": "^1.0.1",
7575
"@jsonjoy.com/json-pack": "^1.0.2",
7676
"@jsonjoy.com/util": "^1.0.0",
77-
"json-joy": "^15.4.1",
78-
"memfs": "^4.8.1",
79-
"sonic-forest": "^1.0.0"
77+
"json-joy": "^15.8.0",
78+
"memfs": "^5.0.0-next.1",
79+
"sonic-forest": "^1.0.0",
80+
"thingies": "^2.1.0"
8081
},
8182
"devDependencies": {
8283
"@types/benchmark": "^2.1.5",
@@ -87,7 +88,6 @@
8788
"prettier": "^3.2.5",
8889
"rimraf": "^5.0.5",
8990
"rxjs": "^7.8.1",
90-
"thingies": "^1.20.0",
9191
"ts-jest": "^29.1.2",
9292
"ts-node": "^10.9.2",
9393
"tslib": "^2.6.2",

src/__demos__/json-crdt-server/routes/block/methods/upd.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export const upd =
1414
title: 'Patches',
1515
description: 'The patches to apply to the document.',
1616
}),
17+
t.propOpt('create', t.bool).options({
18+
title: 'Create if not exists',
19+
description: 'If true, creates a new document if it does not exist.',
20+
}),
1721
);
1822

1923
const Response = t.Object(
@@ -29,8 +33,8 @@ export const upd =
2933
description: 'Applies patches to an existing document and returns the latest concurrent changes.',
3034
});
3135

32-
return r.prop('block.upd', Func, async ({id, patches}) => {
33-
const res = await services.blocks.edit(id, patches);
36+
return r.prop('block.upd', Func, async ({id, patches, create}) => {
37+
const res = await services.blocks.edit(id, patches, !!create);
3438
const patchesReturn: ResolveType<typeof BlockPatchPartialReturnRef>[] = res.patches.map((patch) => ({
3539
ts: patch.created,
3640
}));

src/__demos__/json-crdt-server/routes/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ export const createCaller = (services: Services = new Services()) => {
3030
return RpcError.valueFrom(error);
3131
},
3232
});
33-
return {router, caller};
33+
return {router, caller, services};
3434
};

src/__demos__/json-crdt-server/services/blocks/BlocksServices.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@ export class BlocksServices {
119119
return {patches};
120120
}
121121

122-
public async edit(id: string, patches: Pick<StorePatch, 'blob'>[]) {
122+
public async edit(id: string, patches: Pick<StorePatch, 'blob'>[], createIfNotExists: boolean) {
123+
if (createIfNotExists) {
124+
const exists = await this.store.exists(id);
125+
if (!exists) {
126+
return await this.create(id, patches);
127+
}
128+
}
123129
this.maybeGc();
124130
if (!Array.isArray(patches)) throw RpcError.validation('patches must be an array');
125131
if (!patches.length) throw RpcError.validation('patches must not be empty');

src/__demos__/json-crdt-server/services/blocks/MemoryStore.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Model, Patch} from 'json-joy/lib/json-crdt';
22
import type * as types from './types';
3+
import {RpcError} from '../../../../common/rpc/caller';
34

45
const tick = new Promise((resolve) => setImmediate(resolve));
56

@@ -14,6 +15,11 @@ export class MemoryStore implements types.Store {
1415
return {snapshot};
1516
}
1617

18+
public async exists(id: string): Promise<boolean> {
19+
await tick;
20+
return this.snapshots.has(id);
21+
}
22+
1723
public async seq(id: string): Promise<number | undefined> {
1824
await tick;
1925
return this.snapshots.get(id)?.seq;
@@ -32,7 +38,7 @@ export class MemoryStore implements types.Store {
3238
if (!Array.isArray(patches) || !patches.length) throw new Error('NO_PATCHES');
3339
const snapshot = this.snapshots.get(id);
3440
const existingPatches = this.patches.get(id);
35-
if (!snapshot || !existingPatches) throw new Error('BLOCK_NOT_FOUND');
41+
if (!snapshot || !existingPatches) throw RpcError.notFound();
3642
let seq = patches[0].seq;
3743
const diff = seq - snapshot.seq - 1;
3844
if (snapshot.seq + 1 < seq) throw new Error('PATCH_SEQ_TOO_HIGH');

src/__tests__/json-crdt-server/block.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,60 @@ export const runBlockTests = (_setup: ApiTestSetup, params: {staticOnly?: true}
114114
});
115115

116116
describe('block.upd', () => {
117+
test('can create a new block', async () => {
118+
const {call, stop} = await setup();
119+
const id = getId();
120+
const model = Model.withLogicalClock();
121+
model.api.root({
122+
text: 'Hell',
123+
});
124+
const patch1 = model.api.flush();
125+
const result = await call('block.upd', {
126+
create: true,
127+
id,
128+
patches: [
129+
{
130+
blob: patch1.toBinary(),
131+
},
132+
],
133+
});
134+
expect(result).toMatchObject({
135+
patches: [
136+
{
137+
ts: expect.any(Number),
138+
},
139+
],
140+
});
141+
stop();
142+
});
143+
144+
test('throws NOT_FOUND when "create" flag missing', async () => {
145+
const {call, stop} = await setup();
146+
const id = getId();
147+
const model = Model.withLogicalClock();
148+
model.api.root({
149+
text: 'Hell',
150+
});
151+
const patch1 = model.api.flush();
152+
try {
153+
const result = await call('block.upd', {
154+
create: false,
155+
id,
156+
patches: [
157+
{
158+
blob: patch1.toBinary(),
159+
},
160+
],
161+
});
162+
throw new Error('not this error');
163+
} catch (error) {
164+
expect(error).toMatchObject({
165+
code: 'NOT_FOUND',
166+
});
167+
}
168+
stop();
169+
});
170+
117171
test('can edit a document sequentially', async () => {
118172
const {call, stop} = await setup();
119173
const id = getId();

src/common/rpc/caller/error/RpcError.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export class RpcError extends Error implements IRpcError {
5757
return RpcError.fromCode(RpcErrorCodes.BAD_REQUEST, message);
5858
}
5959

60+
public static notFound(message = 'Not Found'): RpcError {
61+
return RpcError.fromCode(RpcErrorCodes.NOT_FOUND, message);
62+
}
63+
6064
public static validation(message: string, meta?: unknown): RpcError {
6165
return RpcError.fromCode(RpcErrorCodes.BAD_REQUEST, message, meta);
6266
}

0 commit comments

Comments
 (0)