Updating pouchdb keys based on an updated version of docuri.

This commit is contained in:
Eric van der Vlist 2022-11-13 20:02:11 +01:00
parent 12f0a8a10d
commit 00ad6285d0
22 changed files with 344 additions and 181 deletions

View File

@ -21,7 +21,6 @@
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-router": "^5.1.11", "@types/react-router": "^5.1.11",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"docuri": "^4.2.2",
"ionicons": "^6.0.3", "ionicons": "^6.0.3",
"jotai": "^1.8.6", "jotai": "^1.8.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@ -1,4 +1,4 @@
import { route } from 'docuri'; import { route } from '../../lib/docuri';
/** /**
* A [docuri](https://github.com/jo/docuri) route for {@link components/map/types!TileKeyObject} * A [docuri](https://github.com/jo/docuri) route for {@link components/map/types!TileKeyObject}

View File

@ -17,9 +17,9 @@ describe('The gpx module', () => {
globalThis.Date.now = originalDateNow; globalThis.Date.now = originalDateNow;
}); });
test('db.put() a new Gpx when required', async () => { test('db.put() a new Gpx when required', async () => {
await putNewGpx({ gpx: 'whatever' }); await putNewGpx({ gpx: 0n });
expect(globalThis.db.put).toBeCalledWith({ expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/whatever', _id: 'gpx/00000000000000000',
_rev: undefined, _rev: undefined,
doc: { doc: {
$: { $: {
@ -57,6 +57,6 @@ describe('The gpx module', () => {
}); });
test('db.put() generates an id if needed', async () => { test('db.put() generates an id if needed', async () => {
const id = await putNewGpx(); const id = await putNewGpx();
expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z' }); expect(id).toEqual({ gpx: 8640000000000000n });
}); });
}); });

View File

@ -32,7 +32,7 @@ const emptyGpx: Gpx = {
}; };
export const putNewGpx = async ( export const putNewGpx = async (
id: IdGpx = { gpx: new Date(Date.now()).toISOString() } id: IdGpx = { gpx: BigInt(Date.now()) + 8640000000000000n }
) => { ) => {
const uri = getUri('gpx', id); const uri = getUri('gpx', id);
await put( await put(

View File

@ -26,23 +26,23 @@ describe('getFamily', () => {
}); });
test('returns two rows after a gpx and a track have been inserted.', async () => { test('returns two rows after a gpx and a track have been inserted.', async () => {
await putNewTrk(); await putNewTrk();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
Object { Object {
"offset": 0, "offset": 0,
"rows": Array [ "rows": Array [
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "id": "gpx/08640000000000000/2trk/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "key": "gpx/08640000000000000/2trk/000000",
"value": Object { "value": Object {
"rev": "1-e1e6888ad978a5fcc405a75296604bee", "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7",
}, },
}, },
], ],
@ -52,7 +52,7 @@ Object {
}); });
test('also returns the docs if required.', async () => { test('also returns the docs if required.', async () => {
await putNewTrk(); await putNewTrk();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' }), { const allDocs: any = await getFamily('gpx/', {
include_docs: true, include_docs: true,
}); });
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
@ -61,8 +61,8 @@ Object {
"rows": Array [ "rows": Array [
Object { Object {
"doc": Object { "doc": Object {
"_id": "gpx/1970-01-01T00%3A00%3A00.000Z", "_id": "gpx/08640000000000000",
"_rev": "1-649694b1e46839ec63d2a93f483dd023", "_rev": "1-e8125b3d924c831968288bf8786e8651",
"doc": Object { "doc": Object {
"$": Object { "$": Object {
"creator": "dyomedea version 0.000002", "creator": "dyomedea version 0.000002",
@ -81,25 +81,25 @@ Object {
}, },
"type": "gpx", "type": "gpx",
}, },
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"doc": Object { "doc": Object {
"_id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "_id": "gpx/08640000000000000/2trk/000000",
"_rev": "1-e1e6888ad978a5fcc405a75296604bee", "_rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7",
"doc": Object { "doc": Object {
"number": 0, "number": 0,
}, },
"type": "trk", "type": "trk",
}, },
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "id": "gpx/08640000000000000/2trk/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "key": "gpx/08640000000000000/2trk/000000",
"value": Object { "value": Object {
"rev": "1-e1e6888ad978a5fcc405a75296604bee", "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7",
}, },
}, },
], ],
@ -109,30 +109,30 @@ Object {
}); });
test('returns three rows after a gpx and a track segment have been inserted.', async () => { test('returns three rows after a gpx and a track segment have been inserted.', async () => {
await putNewTrkseg(); await putNewTrkseg();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
Object { Object {
"offset": 0, "offset": 0,
"rows": Array [ "rows": Array [
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "id": "gpx/08640000000000000/2trk/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "key": "gpx/08640000000000000/2trk/000000",
"value": Object { "value": Object {
"rev": "1-e1e6888ad978a5fcc405a75296604bee", "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", "id": "gpx/08640000000000000/2trk/000000/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", "key": "gpx/08640000000000000/2trk/000000/000000",
"value": Object { "value": Object {
"rev": "1-9d8fbcd9cb7d2a45d6caf57a2cbee7ca", "rev": "1-9d45ff67006abf9bd493c0e38e3b9d2d",
}, },
}, },
], ],
@ -142,37 +142,37 @@ Object {
}); });
test('returns four rows after a gpx and a track point have been inserted.', async () => { test('returns four rows after a gpx and a track point have been inserted.', async () => {
await putNewTrkpt(); await putNewTrkpt();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
Object { Object {
"offset": 0, "offset": 0,
"rows": Array [ "rows": Array [
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "id": "gpx/08640000000000000/2trk/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", "key": "gpx/08640000000000000/2trk/000000",
"value": Object { "value": Object {
"rev": "1-e1e6888ad978a5fcc405a75296604bee", "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", "id": "gpx/08640000000000000/2trk/000000/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", "key": "gpx/08640000000000000/2trk/000000/000000",
"value": Object { "value": Object {
"rev": "1-9d8fbcd9cb7d2a45d6caf57a2cbee7ca", "rev": "1-9d45ff67006abf9bd493c0e38e3b9d2d",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000/00000", "id": "gpx/08640000000000000/2trk/000000/000000/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000/00000", "key": "gpx/08640000000000000/2trk/000000/000000/000000",
"value": Object { "value": Object {
"rev": "1-42b858110cde8bec09e5d34b9e480936", "rev": "1-8ed7435552a904ab50c836156fdde2b4",
}, },
}, },
], ],
@ -183,23 +183,23 @@ Object {
test('returns two rows after a gpx and a waypoint have been inserted.', async () => { test('returns two rows after a gpx and a waypoint have been inserted.', async () => {
await putNewWpt(); await putNewWpt();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
Object { Object {
"offset": 0, "offset": 0,
"rows": Array [ "rows": Array [
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/0wpt/00000", "id": "gpx/08640000000000000/0wpt/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/0wpt/00000", "key": "gpx/08640000000000000/0wpt/000000",
"value": Object { "value": Object {
"rev": "1-f98b9048879fc7b97771bf2bafc77e42", "rev": "1-5641f46ee0e0bb75493540be43e6d3b6",
}, },
}, },
], ],
@ -210,23 +210,23 @@ Object {
test('returns two rows after a gpx and a route have been inserted.', async () => { test('returns two rows after a gpx and a route have been inserted.', async () => {
await putNewRte(); await putNewRte();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
Object { Object {
"offset": 0, "offset": 0,
"rows": Array [ "rows": Array [
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", "id": "gpx/08640000000000000/1rte/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", "key": "gpx/08640000000000000/1rte/000000",
"value": Object { "value": Object {
"rev": "1-f17cd2939d1e63764478613f54835884", "rev": "1-1ce2ce3ed4fdc8cd652625325fa4249e",
}, },
}, },
], ],
@ -237,30 +237,30 @@ Object {
test('returns three rows after a gpx and a route point have been inserted.', async () => { test('returns three rows after a gpx and a route point have been inserted.', async () => {
await putNewRtept(); await putNewRtept();
const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(` expect(allDocs).toMatchInlineSnapshot(`
Object { Object {
"offset": 0, "offset": 0,
"rows": Array [ "rows": Array [
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z", "id": "gpx/08640000000000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z", "key": "gpx/08640000000000000",
"value": Object { "value": Object {
"rev": "1-649694b1e46839ec63d2a93f483dd023", "rev": "1-e8125b3d924c831968288bf8786e8651",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", "id": "gpx/08640000000000000/1rte/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", "key": "gpx/08640000000000000/1rte/000000",
"value": Object { "value": Object {
"rev": "1-f17cd2939d1e63764478613f54835884", "rev": "1-1ce2ce3ed4fdc8cd652625325fa4249e",
}, },
}, },
Object { Object {
"id": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000/00000", "id": "gpx/08640000000000000/1rte/000000/000000",
"key": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000/00000", "key": "gpx/08640000000000000/1rte/000000/000000",
"value": Object { "value": Object {
"rev": "1-ba3f25be70b620dd06b453ec29853bae", "rev": "1-89cbde37e6c4a85f43e2b68c881183c0",
}, },
}, },
], ],

View File

@ -1,5 +1,4 @@
import { putNewRte } from './rte'; import { putNewRte } from './rte';
import { putNewTrk } from './trk';
declare global { declare global {
var db: any; var db: any;
@ -19,9 +18,9 @@ describe('The rte module', () => {
globalThis.Date.now = originalDateNow; globalThis.Date.now = originalDateNow;
}); });
test('db.put() a new rte when required', async () => { test('db.put() a new rte when required', async () => {
putNewRte({ gpx: 'gpxId', rte: 'rteid' }); putNewRte({ gpx: 8640000000000000n, rte: 25 });
await expect(globalThis.db.put).toBeCalledWith({ await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/gpxId/1rte/rteid', _id: 'gpx/08640000000000000/1rte/000025',
_rev: undefined, _rev: undefined,
doc: { doc: {
cmt: undefined, cmt: undefined,
@ -30,19 +29,19 @@ describe('The rte module', () => {
link: undefined, link: undefined,
name: undefined, name: undefined,
number: 0, number: 0,
rtept: undefined,
src: undefined, src: undefined,
trkseg: undefined,
type: undefined, type: undefined,
}, },
type: 'rte', type: 'rte',
}); });
}); });
test('db.put() generates an id for the trk if needed', async () => { test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewRte({ gpx: 'gpxId' }); const id = await putNewRte({ gpx: 0n });
expect(id).toEqual({ gpx: 'gpxId', rte: '00000' }); expect(id).toEqual({ gpx: 0n, rte: 0 });
}); });
test('db.put() generates ids for both gpx and trk if needed', async () => { test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewRte(); const id = await putNewRte();
expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', rte: '00000' }); expect(id).toEqual({ gpx: 8640000000000000n, rte: 0 });
}); });
}); });

View File

@ -18,7 +18,7 @@ export const putNewRte = async (id?: IdRte | IdGpx) => {
let finalId = { ...id }; let finalId = { ...id };
if (!('rte' in finalId)) { if (!('rte' in finalId)) {
const gpxId = await putNewGpx(id); const gpxId = await putNewGpx(id);
finalId = { ...gpxId, rte: '00000' }; finalId = { ...gpxId, rte: 0 };
} }
const uri = getUri('rte', finalId); const uri = getUri('rte', finalId);
await put( await put(

View File

@ -18,9 +18,9 @@ describe('The rtept module', () => {
globalThis.Date.now = originalDateNow; globalThis.Date.now = originalDateNow;
}); });
test('db.put() a new rtept when required', async () => { test('db.put() a new rtept when required', async () => {
putNewRtept({ gpx: 'gpxId', rte: 'rteid', rtept: 'rteptid' }); putNewRtept({ gpx: 0n, rte: 0, rtept: 0 });
await expect(globalThis.db.put).toBeCalledWith({ await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/gpxId/1rte/rteid/rteptid', _id: 'gpx/00000000000000000/1rte/000000/000000',
_rev: undefined, _rev: undefined,
doc: { doc: {
$: { lat: 0, lon: 0 }, $: { lat: 0, lon: 0 },
@ -48,11 +48,11 @@ describe('The rtept module', () => {
}); });
}); });
test('db.put() generates an id for the rtept if needed', async () => { test('db.put() generates an id for the rtept if needed', async () => {
const id = await putNewRtept({ gpx: 'gpxId' }); const id = await putNewRtept({ gpx: 0n });
expect(id).toEqual({ gpx: 'gpxId', rte: '00000', rtept: '00000' }); expect(id).toEqual({ gpx: 0n, rte: 0, rtept: 0 });
}); });
test('db.put() generates ids for both gpx and rte if needed', async () => { test('db.put() generates ids for both gpx and rte if needed', async () => {
const id = await putNewRtept(); const id = await putNewRtept();
expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', rte: '00000', rtept: '00000' }); expect(id).toEqual({ gpx: 8640000000000000n, rte: 0, rtept: 0 });
}); });
}); });

View File

@ -29,7 +29,7 @@ export const putNewRtept = async (id?: IdGpx | IdRte | IdRtept) => {
let finalId = { ...id }; let finalId = { ...id };
if (!('rtept' in finalId)) { if (!('rtept' in finalId)) {
const rteId = await putNewRte(id); const rteId = await putNewRte(id);
finalId = { ...rteId, rtept: '00000' }; finalId = { ...rteId, rtept: 0 };
} }
const uri = getUri('rtept', finalId); const uri = getUri('rtept', finalId);
await put( await put(

View File

@ -18,9 +18,9 @@ describe('The trk module', () => {
globalThis.Date.now = originalDateNow; globalThis.Date.now = originalDateNow;
}); });
test('db.put() a new trk when required', async () => { test('db.put() a new trk when required', async () => {
putNewTrk({ gpx: 'gpxId', trk: 'trkid' }); putNewTrk({ gpx: 1n, trk: 2 });
await expect(globalThis.db.put).toBeCalledWith({ await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/gpxId/2trk/trkid', _id: 'gpx/00000000000000001/2trk/000002',
_rev: undefined, _rev: undefined,
doc: { doc: {
cmt: undefined, cmt: undefined,
@ -37,11 +37,11 @@ describe('The trk module', () => {
}); });
}); });
test('db.put() generates an id for the trk if needed', async () => { test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrk({ gpx: 'gpxId' }); const id = await putNewTrk({ gpx: 2n });
expect(id).toEqual({ gpx: 'gpxId', trk: '00000' }); expect(id).toEqual({ gpx: 2n, trk: 0});
}); });
test('db.put() generates ids for both gpx and trk if needed', async () => { test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrk(); const id = await putNewTrk();
expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', trk: '00000' }); expect(id).toEqual({ gpx: 8640000000000000n, trk: 0});
}); });
}); });

View File

@ -18,7 +18,7 @@ export const putNewTrk = async (id?: IdTrk | IdGpx) => {
let finalId = { ...id }; let finalId = { ...id };
if (!('trk' in finalId)) { if (!('trk' in finalId)) {
const gpxId = await putNewGpx(id); const gpxId = await putNewGpx(id);
finalId = { ...gpxId, trk: '00000' }; finalId = { ...gpxId, trk: 0 };
} }
const uri = getUri('trk', finalId); const uri = getUri('trk', finalId);
await put( await put(

View File

@ -19,13 +19,13 @@ describe('The trkpt module', () => {
}); });
test('db.put() a new trkpt when required', async () => { test('db.put() a new trkpt when required', async () => {
putNewTrkpt({ putNewTrkpt({
gpx: 'gpxId', gpx: 1n,
trk: 'trkid', trk: 2,
trkseg: 'trksegid', trkseg: 3,
trkpt: 'trkptid', trkpt: 4,
}); });
await expect(globalThis.db.put).toBeCalledWith({ await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/gpxId/2trk/trkid/trksegid/trkptid', _id: 'gpx/00000000000000001/2trk/000002/000003/000004',
_rev: undefined, _rev: undefined,
doc: { doc: {
$: { lat: 0, lon: 0 }, $: { lat: 0, lon: 0 },
@ -59,21 +59,21 @@ describe('The trkpt module', () => {
}); });
}); });
test('db.put() generates an id for the trk if needed', async () => { test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrkpt({ gpx: 'gpxId' }); const id = await putNewTrkpt({ gpx: 5n });
expect(id).toEqual({ expect(id).toEqual({
gpx: 'gpxId', gpx: 5n,
trk: '00000', trk: 0,
trkseg: '00000', trkseg: 0,
trkpt: '00000', trkpt: 0,
}); });
}); });
test('db.put() generates ids for both gpx and trk if needed', async () => { test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrkpt(); const id = await putNewTrkpt();
expect(id).toEqual({ expect(id).toEqual({
gpx: '1970-01-01T00:00:00.000Z', gpx: 8640000000000000n,
trk: '00000', trk: 0,
trkseg: '00000', trkseg: 0,
trkpt: '00000', trkpt: 0,
}); });
}); });
}); });

View File

@ -35,7 +35,7 @@ export const putNewTrkpt = async (id?: IdTrk | IdGpx | IdTrkseg | IdTrkpt) => {
let finalId = { ...id }; let finalId = { ...id };
if (!('trkpt' in finalId)) { if (!('trkpt' in finalId)) {
const trksegId = await putNewTrkseg(id); const trksegId = await putNewTrkseg(id);
finalId = { ...trksegId, trkpt: '00000' }; finalId = { ...trksegId, trkpt: 0 };
} }
const uri = getUri('trkpt', finalId); const uri = getUri('trkpt', finalId);
await put( await put(

View File

@ -18,9 +18,9 @@ describe('The trkseg module', () => {
globalThis.Date.now = originalDateNow; globalThis.Date.now = originalDateNow;
}); });
test('db.put() a new trk when required', async () => { test('db.put() a new trk when required', async () => {
putNewTrkseg({ gpx: 'gpxId', trk: 'trkid', trkseg: 'trksegid' }); putNewTrkseg({ gpx: 12345678901234567n, trk: 123456, trkseg: 5 });
await expect(globalThis.db.put).toBeCalledWith({ await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/gpxId/2trk/trkid/trksegid', _id: 'gpx/12345678901234567/2trk/123456/000005',
_rev: undefined, _rev: undefined,
doc: { doc: {
trkpt: undefined, trkpt: undefined,
@ -30,11 +30,11 @@ describe('The trkseg module', () => {
}); });
}); });
test('db.put() generates an id for the trk if needed', async () => { test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrkseg({ gpx: 'gpxId' }); const id = await putNewTrkseg({ gpx: 1n });
expect(id).toEqual({ gpx: 'gpxId', trk: '00000', trkseg: '00000' }); expect(id).toEqual({ gpx: 1n, trk: 0, trkseg: 0 });
}); });
test('db.put() generates ids for both gpx and trk if needed', async () => { test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrkseg(); const id = await putNewTrkseg();
expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', trk: '00000', trkseg: '00000' }); expect(id).toEqual({ gpx: 8640000000000000n, trk: 0, trkseg: 0 });
}); });
}); });

View File

@ -11,7 +11,7 @@ export const putNewTrkseg = async (id?: IdTrk | IdGpx | IdTrkseg) => {
let finalId = { ...id }; let finalId = { ...id };
if (!('trkseg' in finalId)) { if (!('trkseg' in finalId)) {
const trkId = await putNewTrk(id); const trkId = await putNewTrk(id);
finalId = { ...trkId, trkseg: '00000' }; finalId = { ...trkId, trkseg: 0 };
} }
const uri = getUri('trkseg', finalId); const uri = getUri('trkseg', finalId);
await put( await put(

View File

@ -18,9 +18,9 @@ describe('The wpt module', () => {
globalThis.Date.now = originalDateNow; globalThis.Date.now = originalDateNow;
}); });
test('db.put() a new wpt when required', async () => { test('db.put() a new wpt when required', async () => {
putNewWpt({ gpx: 'gpxId', wpt: 'wptid' }); putNewWpt({ gpx: 1n, wpt: 2 });
await expect(globalThis.db.put).toBeCalledWith({ await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/gpxId/0wpt/wptid', _id: 'gpx/00000000000000001/0wpt/000002',
_rev: undefined, _rev: undefined,
doc: { doc: {
$: { lat: 0, lon: 0 }, $: { lat: 0, lon: 0 },
@ -48,11 +48,11 @@ describe('The wpt module', () => {
}); });
}); });
test('db.put() generates an id for the wpt if needed', async () => { test('db.put() generates an id for the wpt if needed', async () => {
const id = await putNewWpt({ gpx: 'gpxId' }); const id = await putNewWpt({ gpx: 1n });
expect(id).toEqual({ gpx: 'gpxId', wpt: '00000' }); expect(id).toEqual({ gpx: 1n, wpt: 0 });
}); });
test('db.put() generates ids for both gpx and trk if needed', async () => { test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewWpt(); const id = await putNewWpt();
expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', wpt: '00000' }); expect(id).toEqual({ gpx: 8640000000000000n, wpt: 0 });
}); });
}); });

View File

@ -29,7 +29,7 @@ export const putNewWpt = async (id?: IdGpx | IdWpt) => {
let finalId = { ...id }; let finalId = { ...id };
if (!('wpt' in finalId)) { if (!('wpt' in finalId)) {
const gpxId = await putNewGpx(id); const gpxId = await putNewGpx(id);
finalId = { ...gpxId, wpt: '00000' }; finalId = { ...gpxId, wpt: 0 };
} }
const uri = getUri('wpt', finalId); const uri = getUri('wpt', finalId);
await put( await put(

137
src/lib/docuri/index.js Normal file
View File

@ -0,0 +1,137 @@
/*
* DocURI: Rich document ids for CouchDB.
*
* Copyright (c) 2014 null2 GmbH Berlin
* Licensed under the MIT license.
*/
// type/id/subtype/index/version
var docuri = module.exports = exports = {};
// Cached regular expressions for matching named param parts and splatted parts
// of route strings.
// http://backbonejs.org/docs/backbone.html#section-158
var optionalParam = /\((.*?)\)/g;
var namedParam = /(\(\?)?:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
var paramKeys = /[*:]\w+/g;
// Convert a route string into a regular expression,
// with named regular expressions for named arguments.
// http://backbonejs.org/docs/backbone.html#section-165
function routeToRegExp(src) {
var keys = [], match;
while ( ( match = paramKeys.exec( src ) ) !== null )
{
keys.push( match[0] );
}
var route = src.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function(match, optional) {
return optional ? match : '([^/?]+)';
})
.replace(splatParam, '([^?]*?)');
keys = keys.reduce(function(memo, key) {
var value = '\\' + key;
memo[key] = new RegExp(value + '(\\/|\\)|\\(|$)');
return memo;
}, {});
return {
src: src,
exp: new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'),
keys: keys
}
}
// Given a route and a DocURI return an object of extracted parameters.
// Unmatched DocURIs will be treated as false.
// http://backbonejs.org/docs/backbone.html#section-166
function extractParameters(route, fragment, coding) {
var params = route.exp.exec(fragment);
if (!params) {
return false;
}
params = params.slice(1);
return Object.keys(route.keys).reduce(function(memo, key, i) {
var param = params[i];
if (param) {
var k = key.substr(1);
var decoder = (coding[k] ?? {decoder:decodeURIComponent}).decoder;
if (key[0] === '*') {
param = param.split('/').map(decoder);
} else {
param = decoder(param);
}
memo[key.substr(1)] = param;
}
return memo;
}, {});
}
// Insert named parameters from object.
function insertParameters(route, obj, coding) {
var str = route.src;
Object.keys(route.keys).forEach(function(key) {
var k = key.substr(1);
var value = (obj[k] !== undefined) ? obj[k] : '';
var encoder = (coding[k] ?? {encoder:encodeURIComponent}).encoder;
if (Array.isArray(value)) {
value = value.map(encoder).join('/');
} else {
value = encoder(value);
}
str = str.replace(route.keys[key], value + '$1');
});
// massage optional parameter
return str
.replace(/\(\/\)/g, '')
.replace(/[)(]/g, '');
}
docuri.route = function(route, coding={}) {
route = routeToRegExp(route);
return function(source, target) {
source = source || {};
if (target) {
source = extractParameters(route, source, coding);
Object.keys(target).forEach(function(key) {
source[key] = target[key];
});
}
if (typeof source === 'object') {
return insertParameters(route, source, coding);
}
if (typeof source === 'string') {
return extractParameters(route, source, coding);
}
};
};

View File

@ -1,4 +1,4 @@
import { route } from 'docuri'; import { route } from './docuri';
import uri from './ids'; import uri from './ids';
describe('Checking some DocURI features', () => { describe('Checking some DocURI features', () => {
@ -43,64 +43,68 @@ describe('Checking a multilevel route with optional part', () => {
}); });
describe('Checking gpx ids', () => { describe('Checking gpx ids', () => {
const id = {
gpx: 12345678901234567n,
};
const key = 'gpx/12345678901234567';
test(', vice', () => { test(', vice', () => {
const gpx = uri('gpx', { gpx: 'id' }); const gpx = uri('gpx', id);
expect(gpx).toBe('gpx/id'); expect(gpx).toBe(key);
}); });
test(', and versa', () => { test(', and versa', () => {
const gpx = uri('gpx', 'gpx/id'); const gpx = uri('gpx', key);
expect(gpx).toMatchObject({ gpx: 'id' }); expect(gpx).toMatchObject(id);
}); });
}); });
describe('Checking trk ids', () => { describe('Checking trk ids', () => {
const id = {
gpx: 12345678901234567n,
trk: 123456,
};
const key = 'gpx/12345678901234567/2trk/123456';
test(', vice', () => { test(', vice', () => {
const rte = uri('trk', { gpx: 'gpxid', trk: 'trkid' }); const rte = uri('trk', id);
expect(rte).toBe('gpx/gpxid/2trk/trkid'); expect(rte).toBe(key);
}); });
test(', and versa', () => { test(', and versa', () => {
const rte = uri('trk', 'gpx/gpxid/2trk/trkid'); const rte = uri('trk', key);
expect(rte).toMatchObject({ gpx: 'gpxid', trk: 'trkid' }); expect(rte).toMatchObject(id);
}); });
}); });
describe('Checking trkseg ids', () => { describe('Checking trkseg ids', () => {
const id = {
gpx: 111n,
trk: 0,
trkseg: 3,
};
const key = 'gpx/00000000000000111/2trk/000000/000003';
test(', vice', () => { test(', vice', () => {
const rte = uri('trkseg', { const rte = uri('trkseg', id);
gpx: 'gpxid', expect(rte).toBe(key);
trk: 'trkid',
trkseg: 'trksegid',
});
expect(rte).toBe('gpx/gpxid/2trk/trkid/trksegid');
}); });
test(', and versa', () => { test(', and versa', () => {
const rte = uri('trkseg', 'gpx/gpxid/2trk/trkid/trksegid'); const rte = uri('trkseg', key);
expect(rte).toMatchObject({ expect(rte).toMatchObject(id);
gpx: 'gpxid',
trk: 'trkid',
trkseg: 'trksegid',
});
}); });
}); });
describe('Checking trkpt ids', () => { describe('Checking trkpt ids', () => {
const id = {
gpx: 25n,
trk: 8,
trkseg: 0,
trkpt: 155,
};
const key = 'gpx/00000000000000025/2trk/000008/000000/000155';
test(', vice', () => { test(', vice', () => {
const rte = uri('trkpt', { const rte = uri('trkpt', id);
gpx: 'gpxid', expect(rte).toBe(key);
trk: 'trkid',
trkseg: 'trksegid',
trkpt: 'trkptid',
});
expect(rte).toBe('gpx/gpxid/2trk/trkid/trksegid/trkptid');
}); });
test(', and versa', () => { test(', and versa', () => {
const rte = uri('trkpt', 'gpx/gpxid/2trk/trkid/trksegid/trkptid'); const rte = uri('trkpt', key);
expect(rte).toMatchObject({ expect(rte).toMatchObject(id);
gpx: 'gpxid',
trk: 'trkid',
trkseg: 'trksegid',
trkpt: 'trkptid',
});
}); });
}); });

View File

@ -1,15 +1,39 @@
import { route } from 'docuri'; import { route } from './docuri';
const integerType = (n: number) => {
return {
encoder: (v: number) => v.toString().padStart(n, '0'),
decoder: parseInt,
};
};
const bigIntType = (n: number) => {
return {
encoder: (v: number) => v.toString().padStart(n, '0'),
decoder: BigInt,
};
};
const coding = {
gpx: bigIntType(17),
wpt: integerType(6),
rte: integerType(6),
rtept: integerType(6),
trk: integerType(6),
trkseg: integerType(6),
trkpt: integerType(6),
};
const routes = { const routes = {
dbdef: route('dbdef'), dbdef: route('dbdef', coding),
settings: route('settings'), settings: route('settings', coding),
gpx: route('gpx/:gpx'), gpx: route('gpx/:gpx', coding),
wpt: route('gpx/:gpx/0wpt/:wpt'), wpt: route('gpx/:gpx/0wpt/:wpt', coding),
rte: route('gpx/:gpx/1rte/:rte'), rte: route('gpx/:gpx/1rte/:rte', coding),
rtept: route('gpx/:gpx/1rte/:rte/:rtept'), rtept: route('gpx/:gpx/1rte/:rte/:rtept', coding),
trk: route('gpx/:gpx/2trk/:trk'), trk: route('gpx/:gpx/2trk/:trk', coding),
trkseg: route('gpx/:gpx/2trk/:trk/:trkseg'), trkseg: route('gpx/:gpx/2trk/:trk/:trkseg', coding),
trkpt: route('gpx/:gpx/2trk/:trk/:trkseg/:trkpt'), trkpt: route('gpx/:gpx/2trk/:trk/:trkseg/:trkpt', coding),
}; };
type RouteKey = keyof typeof routes; type RouteKey = keyof typeof routes;

34
src/lib/types.d.ts vendored
View File

@ -1,34 +1,34 @@
interface IdGpx { interface IdGpx {
gpx: string; gpx: BigInt;
} }
interface IdTrk { interface IdTrk {
gpx: string; gpx: BigInt;
trk: string; trk: number;
} }
interface IdTrkseg { interface IdTrkseg {
gpx: string; gpx: BigInt;
trk: string; trk: number;
trkseg: string; trkseg: number;
} }
interface IdTrkpt { interface IdTrkpt {
gpx: string; gpx: BigInt;
trk: string; trk: number;
trkseg: string; trkseg: number;
trkpt: string; trkpt: number;
} }
interface IdWpt { interface IdWpt {
gpx: string; gpx: BigInt;
wpt: string; wpt: number;
} }
interface IdRte { interface IdRte {
gpx: string; gpx: BigInt;
rte: string; rte: number;
} }
interface IdRtept { interface IdRtept {
gpx: string; gpx: BigInt;
rte: string; rte: number;
rtept: string; rtept: number;
} }

View File

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es2020",
"lib": [ "lib": [
"dom", "dom",
"dom.iterable", "dom.iterable",