From 00ad6285d04eb5047610da86bd067c2e05080c19 Mon Sep 17 00:00:00 2001 From: evlist Date: Sun, 13 Nov 2022 20:02:11 +0100 Subject: [PATCH] Updating pouchdb keys based on an updated version of docuri. --- package.json | 1 - src/components/map/uris.ts | 2 +- src/db/gpx.test.ts | 6 +- src/db/gpx.ts | 2 +- src/db/lib.test.ts | 130 +++++++++++++++++------------------ src/db/rte.test.ts | 13 ++-- src/db/rte.ts | 2 +- src/db/rtept.test.ts | 10 +-- src/db/rtept.ts | 2 +- src/db/trk.test.ts | 10 +-- src/db/trk.ts | 2 +- src/db/trkpt.test.ts | 28 ++++---- src/db/trkpt.ts | 2 +- src/db/trkseg.test.ts | 10 +-- src/db/trkseg.ts | 2 +- src/db/wpt.test.ts | 10 +-- src/db/wpt.ts | 2 +- src/lib/docuri/index.js | 137 +++++++++++++++++++++++++++++++++++++ src/lib/ids.test.ts | 74 ++++++++++---------- src/lib/ids.ts | 44 +++++++++--- src/lib/types.d.ts | 34 ++++----- tsconfig.json | 2 +- 22 files changed, 344 insertions(+), 181 deletions(-) create mode 100644 src/lib/docuri/index.js diff --git a/package.json b/package.json index 2216694..8aa04e2 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@types/react-dom": "^18.0.6", "@types/react-router": "^5.1.11", "@types/react-router-dom": "^5.1.7", - "docuri": "^4.2.2", "ionicons": "^6.0.3", "jotai": "^1.8.6", "lodash": "^4.17.21", diff --git a/src/components/map/uris.ts b/src/components/map/uris.ts index 9a094b5..990a2c9 100644 --- a/src/components/map/uris.ts +++ b/src/components/map/uris.ts @@ -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} diff --git a/src/db/gpx.test.ts b/src/db/gpx.test.ts index f8eb97f..f2c3bab 100644 --- a/src/db/gpx.test.ts +++ b/src/db/gpx.test.ts @@ -17,9 +17,9 @@ describe('The gpx module', () => { globalThis.Date.now = originalDateNow; }); test('db.put() a new Gpx when required', async () => { - await putNewGpx({ gpx: 'whatever' }); + await putNewGpx({ gpx: 0n }); expect(globalThis.db.put).toBeCalledWith({ - _id: 'gpx/whatever', + _id: 'gpx/00000000000000000', _rev: undefined, doc: { $: { @@ -57,6 +57,6 @@ describe('The gpx module', () => { }); test('db.put() generates an id if needed', async () => { const id = await putNewGpx(); - expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z' }); + expect(id).toEqual({ gpx: 8640000000000000n }); }); }); diff --git a/src/db/gpx.ts b/src/db/gpx.ts index 49b49c2..d44a0c9 100644 --- a/src/db/gpx.ts +++ b/src/db/gpx.ts @@ -32,7 +32,7 @@ const emptyGpx: Gpx = { }; export const putNewGpx = async ( - id: IdGpx = { gpx: new Date(Date.now()).toISOString() } + id: IdGpx = { gpx: BigInt(Date.now()) + 8640000000000000n } ) => { const uri = getUri('gpx', id); await put( diff --git a/src/db/lib.test.ts b/src/db/lib.test.ts index efb09f3..6e24459 100644 --- a/src/db/lib.test.ts +++ b/src/db/lib.test.ts @@ -26,23 +26,23 @@ describe('getFamily', () => { }); test('returns two rows after a gpx and a track have been inserted.', async () => { await putNewTrk(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); + const allDocs: any = await getFamily('gpx/'); expect(allDocs).toMatchInlineSnapshot(` Object { "offset": 0, "rows": Array [ Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", + "id": "gpx/08640000000000000/2trk/000000", + "key": "gpx/08640000000000000/2trk/000000", "value": Object { - "rev": "1-e1e6888ad978a5fcc405a75296604bee", + "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7", }, }, ], @@ -52,7 +52,7 @@ Object { }); test('also returns the docs if required.', async () => { await putNewTrk(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' }), { + const allDocs: any = await getFamily('gpx/', { include_docs: true, }); expect(allDocs).toMatchInlineSnapshot(` @@ -61,8 +61,8 @@ Object { "rows": Array [ Object { "doc": Object { - "_id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "_rev": "1-649694b1e46839ec63d2a93f483dd023", + "_id": "gpx/08640000000000000", + "_rev": "1-e8125b3d924c831968288bf8786e8651", "doc": Object { "$": Object { "creator": "dyomedea version 0.000002", @@ -81,25 +81,25 @@ Object { }, "type": "gpx", }, - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { "doc": Object { - "_id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", - "_rev": "1-e1e6888ad978a5fcc405a75296604bee", + "_id": "gpx/08640000000000000/2trk/000000", + "_rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7", "doc": Object { "number": 0, }, "type": "trk", }, - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", + "id": "gpx/08640000000000000/2trk/000000", + "key": "gpx/08640000000000000/2trk/000000", "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 () => { await putNewTrkseg(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); + const allDocs: any = await getFamily('gpx/'); expect(allDocs).toMatchInlineSnapshot(` Object { "offset": 0, "rows": Array [ Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", + "id": "gpx/08640000000000000/2trk/000000", + "key": "gpx/08640000000000000/2trk/000000", "value": Object { - "rev": "1-e1e6888ad978a5fcc405a75296604bee", + "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", + "id": "gpx/08640000000000000/2trk/000000/000000", + "key": "gpx/08640000000000000/2trk/000000/000000", "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 () => { await putNewTrkpt(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); + const allDocs: any = await getFamily('gpx/'); expect(allDocs).toMatchInlineSnapshot(` Object { "offset": 0, "rows": Array [ Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000", + "id": "gpx/08640000000000000/2trk/000000", + "key": "gpx/08640000000000000/2trk/000000", "value": Object { - "rev": "1-e1e6888ad978a5fcc405a75296604bee", + "rev": "1-a4d5a6dfa66766fd3e42c6ee328436f7", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000", + "id": "gpx/08640000000000000/2trk/000000/000000", + "key": "gpx/08640000000000000/2trk/000000/000000", "value": Object { - "rev": "1-9d8fbcd9cb7d2a45d6caf57a2cbee7ca", + "rev": "1-9d45ff67006abf9bd493c0e38e3b9d2d", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/2trk/00000/00000/00000", + "id": "gpx/08640000000000000/2trk/000000/000000/000000", + "key": "gpx/08640000000000000/2trk/000000/000000/000000", "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 () => { await putNewWpt(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); + const allDocs: any = await getFamily('gpx/'); expect(allDocs).toMatchInlineSnapshot(` Object { "offset": 0, "rows": Array [ Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/0wpt/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/0wpt/00000", + "id": "gpx/08640000000000000/0wpt/000000", + "key": "gpx/08640000000000000/0wpt/000000", "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 () => { await putNewRte(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); + const allDocs: any = await getFamily('gpx/'); expect(allDocs).toMatchInlineSnapshot(` Object { "offset": 0, "rows": Array [ Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", + "id": "gpx/08640000000000000/1rte/000000", + "key": "gpx/08640000000000000/1rte/000000", "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 () => { await putNewRtept(); - const allDocs: any = await getFamily(uri('gpx', { gpx: '' })); + const allDocs: any = await getFamily('gpx/'); expect(allDocs).toMatchInlineSnapshot(` Object { "offset": 0, "rows": Array [ Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z", + "id": "gpx/08640000000000000", + "key": "gpx/08640000000000000", "value": Object { - "rev": "1-649694b1e46839ec63d2a93f483dd023", + "rev": "1-e8125b3d924c831968288bf8786e8651", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000", + "id": "gpx/08640000000000000/1rte/000000", + "key": "gpx/08640000000000000/1rte/000000", "value": Object { - "rev": "1-f17cd2939d1e63764478613f54835884", + "rev": "1-1ce2ce3ed4fdc8cd652625325fa4249e", }, }, Object { - "id": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000/00000", - "key": "gpx/1970-01-01T00%3A00%3A00.000Z/1rte/00000/00000", + "id": "gpx/08640000000000000/1rte/000000/000000", + "key": "gpx/08640000000000000/1rte/000000/000000", "value": Object { - "rev": "1-ba3f25be70b620dd06b453ec29853bae", + "rev": "1-89cbde37e6c4a85f43e2b68c881183c0", }, }, ], diff --git a/src/db/rte.test.ts b/src/db/rte.test.ts index 97d30cb..35bf25b 100644 --- a/src/db/rte.test.ts +++ b/src/db/rte.test.ts @@ -1,5 +1,4 @@ import { putNewRte } from './rte'; -import { putNewTrk } from './trk'; declare global { var db: any; @@ -19,9 +18,9 @@ describe('The rte module', () => { globalThis.Date.now = originalDateNow; }); 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({ - _id: 'gpx/gpxId/1rte/rteid', + _id: 'gpx/08640000000000000/1rte/000025', _rev: undefined, doc: { cmt: undefined, @@ -30,19 +29,19 @@ describe('The rte module', () => { link: undefined, name: undefined, number: 0, + rtept: undefined, src: undefined, - trkseg: undefined, type: undefined, }, type: 'rte', }); }); test('db.put() generates an id for the trk if needed', async () => { - const id = await putNewRte({ gpx: 'gpxId' }); - expect(id).toEqual({ gpx: 'gpxId', rte: '00000' }); + const id = await putNewRte({ gpx: 0n }); + expect(id).toEqual({ gpx: 0n, rte: 0 }); }); test('db.put() generates ids for both gpx and trk if needed', async () => { const id = await putNewRte(); - expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', rte: '00000' }); + expect(id).toEqual({ gpx: 8640000000000000n, rte: 0 }); }); }); diff --git a/src/db/rte.ts b/src/db/rte.ts index 08c83e6..8febbc9 100644 --- a/src/db/rte.ts +++ b/src/db/rte.ts @@ -18,7 +18,7 @@ export const putNewRte = async (id?: IdRte | IdGpx) => { let finalId = { ...id }; if (!('rte' in finalId)) { const gpxId = await putNewGpx(id); - finalId = { ...gpxId, rte: '00000' }; + finalId = { ...gpxId, rte: 0 }; } const uri = getUri('rte', finalId); await put( diff --git a/src/db/rtept.test.ts b/src/db/rtept.test.ts index 6ce9a04..70dc6f7 100644 --- a/src/db/rtept.test.ts +++ b/src/db/rtept.test.ts @@ -18,9 +18,9 @@ describe('The rtept module', () => { globalThis.Date.now = originalDateNow; }); 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({ - _id: 'gpx/gpxId/1rte/rteid/rteptid', + _id: 'gpx/00000000000000000/1rte/000000/000000', _rev: undefined, doc: { $: { lat: 0, lon: 0 }, @@ -48,11 +48,11 @@ describe('The rtept module', () => { }); }); test('db.put() generates an id for the rtept if needed', async () => { - const id = await putNewRtept({ gpx: 'gpxId' }); - expect(id).toEqual({ gpx: 'gpxId', rte: '00000', rtept: '00000' }); + const id = await putNewRtept({ gpx: 0n }); + expect(id).toEqual({ gpx: 0n, rte: 0, rtept: 0 }); }); test('db.put() generates ids for both gpx and rte if needed', async () => { 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 }); }); }); diff --git a/src/db/rtept.ts b/src/db/rtept.ts index 709ef8e..cb4a8c1 100644 --- a/src/db/rtept.ts +++ b/src/db/rtept.ts @@ -29,7 +29,7 @@ export const putNewRtept = async (id?: IdGpx | IdRte | IdRtept) => { let finalId = { ...id }; if (!('rtept' in finalId)) { const rteId = await putNewRte(id); - finalId = { ...rteId, rtept: '00000' }; + finalId = { ...rteId, rtept: 0 }; } const uri = getUri('rtept', finalId); await put( diff --git a/src/db/trk.test.ts b/src/db/trk.test.ts index f29fdb6..ced7833 100644 --- a/src/db/trk.test.ts +++ b/src/db/trk.test.ts @@ -18,9 +18,9 @@ describe('The trk module', () => { globalThis.Date.now = originalDateNow; }); 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({ - _id: 'gpx/gpxId/2trk/trkid', + _id: 'gpx/00000000000000001/2trk/000002', _rev: undefined, doc: { cmt: undefined, @@ -37,11 +37,11 @@ describe('The trk module', () => { }); }); test('db.put() generates an id for the trk if needed', async () => { - const id = await putNewTrk({ gpx: 'gpxId' }); - expect(id).toEqual({ gpx: 'gpxId', trk: '00000' }); + const id = await putNewTrk({ gpx: 2n }); + expect(id).toEqual({ gpx: 2n, trk: 0}); }); test('db.put() generates ids for both gpx and trk if needed', async () => { const id = await putNewTrk(); - expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', trk: '00000' }); + expect(id).toEqual({ gpx: 8640000000000000n, trk: 0}); }); }); diff --git a/src/db/trk.ts b/src/db/trk.ts index f3f8b71..2f1b224 100644 --- a/src/db/trk.ts +++ b/src/db/trk.ts @@ -18,7 +18,7 @@ export const putNewTrk = async (id?: IdTrk | IdGpx) => { let finalId = { ...id }; if (!('trk' in finalId)) { const gpxId = await putNewGpx(id); - finalId = { ...gpxId, trk: '00000' }; + finalId = { ...gpxId, trk: 0 }; } const uri = getUri('trk', finalId); await put( diff --git a/src/db/trkpt.test.ts b/src/db/trkpt.test.ts index f2097ce..a02f0db 100644 --- a/src/db/trkpt.test.ts +++ b/src/db/trkpt.test.ts @@ -19,13 +19,13 @@ describe('The trkpt module', () => { }); test('db.put() a new trkpt when required', async () => { putNewTrkpt({ - gpx: 'gpxId', - trk: 'trkid', - trkseg: 'trksegid', - trkpt: 'trkptid', + gpx: 1n, + trk: 2, + trkseg: 3, + trkpt: 4, }); await expect(globalThis.db.put).toBeCalledWith({ - _id: 'gpx/gpxId/2trk/trkid/trksegid/trkptid', + _id: 'gpx/00000000000000001/2trk/000002/000003/000004', _rev: undefined, doc: { $: { lat: 0, lon: 0 }, @@ -59,21 +59,21 @@ describe('The trkpt module', () => { }); }); 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({ - gpx: 'gpxId', - trk: '00000', - trkseg: '00000', - trkpt: '00000', + gpx: 5n, + trk: 0, + trkseg: 0, + trkpt: 0, }); }); test('db.put() generates ids for both gpx and trk if needed', async () => { const id = await putNewTrkpt(); expect(id).toEqual({ - gpx: '1970-01-01T00:00:00.000Z', - trk: '00000', - trkseg: '00000', - trkpt: '00000', + gpx: 8640000000000000n, + trk: 0, + trkseg: 0, + trkpt: 0, }); }); }); diff --git a/src/db/trkpt.ts b/src/db/trkpt.ts index 473ef65..484be6a 100644 --- a/src/db/trkpt.ts +++ b/src/db/trkpt.ts @@ -35,7 +35,7 @@ export const putNewTrkpt = async (id?: IdTrk | IdGpx | IdTrkseg | IdTrkpt) => { let finalId = { ...id }; if (!('trkpt' in finalId)) { const trksegId = await putNewTrkseg(id); - finalId = { ...trksegId, trkpt: '00000' }; + finalId = { ...trksegId, trkpt: 0 }; } const uri = getUri('trkpt', finalId); await put( diff --git a/src/db/trkseg.test.ts b/src/db/trkseg.test.ts index da26cb2..dbd3fc3 100644 --- a/src/db/trkseg.test.ts +++ b/src/db/trkseg.test.ts @@ -18,9 +18,9 @@ describe('The trkseg module', () => { globalThis.Date.now = originalDateNow; }); 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({ - _id: 'gpx/gpxId/2trk/trkid/trksegid', + _id: 'gpx/12345678901234567/2trk/123456/000005', _rev: undefined, doc: { trkpt: undefined, @@ -30,11 +30,11 @@ describe('The trkseg module', () => { }); }); test('db.put() generates an id for the trk if needed', async () => { - const id = await putNewTrkseg({ gpx: 'gpxId' }); - expect(id).toEqual({ gpx: 'gpxId', trk: '00000', trkseg: '00000' }); + const id = await putNewTrkseg({ gpx: 1n }); + expect(id).toEqual({ gpx: 1n, trk: 0, trkseg: 0 }); }); test('db.put() generates ids for both gpx and trk if needed', async () => { 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 }); }); }); diff --git a/src/db/trkseg.ts b/src/db/trkseg.ts index 9a27cea..0a147cc 100644 --- a/src/db/trkseg.ts +++ b/src/db/trkseg.ts @@ -11,7 +11,7 @@ export const putNewTrkseg = async (id?: IdTrk | IdGpx | IdTrkseg) => { let finalId = { ...id }; if (!('trkseg' in finalId)) { const trkId = await putNewTrk(id); - finalId = { ...trkId, trkseg: '00000' }; + finalId = { ...trkId, trkseg: 0 }; } const uri = getUri('trkseg', finalId); await put( diff --git a/src/db/wpt.test.ts b/src/db/wpt.test.ts index 812a0c2..b500948 100644 --- a/src/db/wpt.test.ts +++ b/src/db/wpt.test.ts @@ -18,9 +18,9 @@ describe('The wpt module', () => { globalThis.Date.now = originalDateNow; }); 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({ - _id: 'gpx/gpxId/0wpt/wptid', + _id: 'gpx/00000000000000001/0wpt/000002', _rev: undefined, doc: { $: { lat: 0, lon: 0 }, @@ -48,11 +48,11 @@ describe('The wpt module', () => { }); }); test('db.put() generates an id for the wpt if needed', async () => { - const id = await putNewWpt({ gpx: 'gpxId' }); - expect(id).toEqual({ gpx: 'gpxId', wpt: '00000' }); + const id = await putNewWpt({ gpx: 1n }); + expect(id).toEqual({ gpx: 1n, wpt: 0 }); }); test('db.put() generates ids for both gpx and trk if needed', async () => { const id = await putNewWpt(); - expect(id).toEqual({ gpx: '1970-01-01T00:00:00.000Z', wpt: '00000' }); + expect(id).toEqual({ gpx: 8640000000000000n, wpt: 0 }); }); }); diff --git a/src/db/wpt.ts b/src/db/wpt.ts index 3a69205..fd8c8e0 100644 --- a/src/db/wpt.ts +++ b/src/db/wpt.ts @@ -29,7 +29,7 @@ export const putNewWpt = async (id?: IdGpx | IdWpt) => { let finalId = { ...id }; if (!('wpt' in finalId)) { const gpxId = await putNewGpx(id); - finalId = { ...gpxId, wpt: '00000' }; + finalId = { ...gpxId, wpt: 0 }; } const uri = getUri('wpt', finalId); await put( diff --git a/src/lib/docuri/index.js b/src/lib/docuri/index.js new file mode 100644 index 0000000..49c7af2 --- /dev/null +++ b/src/lib/docuri/index.js @@ -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); + } + }; +}; + diff --git a/src/lib/ids.test.ts b/src/lib/ids.test.ts index 83a3fdc..1ea73a7 100644 --- a/src/lib/ids.test.ts +++ b/src/lib/ids.test.ts @@ -1,4 +1,4 @@ -import { route } from 'docuri'; +import { route } from './docuri'; import uri from './ids'; describe('Checking some DocURI features', () => { @@ -43,64 +43,68 @@ describe('Checking a multilevel route with optional part', () => { }); describe('Checking gpx ids', () => { + const id = { + gpx: 12345678901234567n, + }; + const key = 'gpx/12345678901234567'; test(', vice', () => { - const gpx = uri('gpx', { gpx: 'id' }); - expect(gpx).toBe('gpx/id'); + const gpx = uri('gpx', id); + expect(gpx).toBe(key); }); test(', and versa', () => { - const gpx = uri('gpx', 'gpx/id'); - expect(gpx).toMatchObject({ gpx: 'id' }); + const gpx = uri('gpx', key); + expect(gpx).toMatchObject(id); }); }); describe('Checking trk ids', () => { + const id = { + gpx: 12345678901234567n, + trk: 123456, + }; + const key = 'gpx/12345678901234567/2trk/123456'; test(', vice', () => { - const rte = uri('trk', { gpx: 'gpxid', trk: 'trkid' }); - expect(rte).toBe('gpx/gpxid/2trk/trkid'); + const rte = uri('trk', id); + expect(rte).toBe(key); }); test(', and versa', () => { - const rte = uri('trk', 'gpx/gpxid/2trk/trkid'); - expect(rte).toMatchObject({ gpx: 'gpxid', trk: 'trkid' }); + const rte = uri('trk', key); + expect(rte).toMatchObject(id); }); }); describe('Checking trkseg ids', () => { + const id = { + gpx: 111n, + trk: 0, + trkseg: 3, + }; + const key = 'gpx/00000000000000111/2trk/000000/000003'; test(', vice', () => { - const rte = uri('trkseg', { - gpx: 'gpxid', - trk: 'trkid', - trkseg: 'trksegid', - }); - expect(rte).toBe('gpx/gpxid/2trk/trkid/trksegid'); + const rte = uri('trkseg', id); + expect(rte).toBe(key); }); test(', and versa', () => { - const rte = uri('trkseg', 'gpx/gpxid/2trk/trkid/trksegid'); - expect(rte).toMatchObject({ - gpx: 'gpxid', - trk: 'trkid', - trkseg: 'trksegid', - }); + const rte = uri('trkseg', key); + expect(rte).toMatchObject(id); }); }); describe('Checking trkpt ids', () => { + const id = { + gpx: 25n, + trk: 8, + trkseg: 0, + trkpt: 155, + }; + const key = 'gpx/00000000000000025/2trk/000008/000000/000155'; test(', vice', () => { - const rte = uri('trkpt', { - gpx: 'gpxid', - trk: 'trkid', - trkseg: 'trksegid', - trkpt: 'trkptid', - }); - expect(rte).toBe('gpx/gpxid/2trk/trkid/trksegid/trkptid'); + const rte = uri('trkpt', id); + expect(rte).toBe(key); }); test(', and versa', () => { - const rte = uri('trkpt', 'gpx/gpxid/2trk/trkid/trksegid/trkptid'); - expect(rte).toMatchObject({ - gpx: 'gpxid', - trk: 'trkid', - trkseg: 'trksegid', - trkpt: 'trkptid', - }); + const rte = uri('trkpt', key); + expect(rte).toMatchObject(id); }); }); diff --git a/src/lib/ids.ts b/src/lib/ids.ts index 46b2927..002993f 100644 --- a/src/lib/ids.ts +++ b/src/lib/ids.ts @@ -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 = { - dbdef: route('dbdef'), - settings: route('settings'), - gpx: route('gpx/:gpx'), - wpt: route('gpx/:gpx/0wpt/:wpt'), - rte: route('gpx/:gpx/1rte/:rte'), - rtept: route('gpx/:gpx/1rte/:rte/:rtept'), - trk: route('gpx/:gpx/2trk/:trk'), - trkseg: route('gpx/:gpx/2trk/:trk/:trkseg'), - trkpt: route('gpx/:gpx/2trk/:trk/:trkseg/:trkpt'), + dbdef: route('dbdef', coding), + settings: route('settings', coding), + gpx: route('gpx/:gpx', coding), + wpt: route('gpx/:gpx/0wpt/:wpt', coding), + rte: route('gpx/:gpx/1rte/:rte', coding), + rtept: route('gpx/:gpx/1rte/:rte/:rtept', coding), + trk: route('gpx/:gpx/2trk/:trk', coding), + trkseg: route('gpx/:gpx/2trk/:trk/:trkseg', coding), + trkpt: route('gpx/:gpx/2trk/:trk/:trkseg/:trkpt', coding), }; type RouteKey = keyof typeof routes; diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts index 2808948..8ed64a1 100644 --- a/src/lib/types.d.ts +++ b/src/lib/types.d.ts @@ -1,34 +1,34 @@ interface IdGpx { - gpx: string; + gpx: BigInt; } interface IdTrk { - gpx: string; - trk: string; + gpx: BigInt; + trk: number; } interface IdTrkseg { - gpx: string; - trk: string; - trkseg: string; + gpx: BigInt; + trk: number; + trkseg: number; } interface IdTrkpt { - gpx: string; - trk: string; - trkseg: string; - trkpt: string; + gpx: BigInt; + trk: number; + trkseg: number; + trkpt: number; } interface IdWpt { - gpx: string; - wpt: string; + gpx: BigInt; + wpt: number; } interface IdRte { - gpx: string; - rte: string; + gpx: BigInt; + rte: number; } interface IdRtept { - gpx: string; - rte: string; - rtept: string; + gpx: BigInt; + rte: number; + rtept: number; } diff --git a/tsconfig.json b/tsconfig.json index a273b0c..eab4d9e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2020", "lib": [ "dom", "dom.iterable",