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-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",

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}

View File

@ -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 });
});
});

View File

@ -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(

View File

@ -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",
},
},
],

View File

@ -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 });
});
});

View File

@ -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(

View File

@ -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 });
});
});

View File

@ -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(

View File

@ -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});
});
});

View File

@ -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(

View File

@ -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,
});
});
});

View File

@ -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(

View File

@ -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 });
});
});

View File

@ -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(

View File

@ -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 });
});
});

View File

@ -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(

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';
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);
});
});

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 = {
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;

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

@ -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;
}

View File

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