@comapeo/core 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blob-store/index.d.ts +1 -1
- package/dist/config-import.d.ts.map +1 -1
- package/dist/core-manager/core-index.d.ts +1 -1
- package/dist/core-manager/core-index.d.ts.map +1 -1
- package/dist/core-manager/index.d.ts +1 -0
- package/dist/core-manager/index.d.ts.map +1 -1
- package/dist/fastify-controller.d.ts.map +1 -1
- package/dist/fastify-plugins/{maps/index.d.ts → maps.d.ts} +8 -8
- package/dist/fastify-plugins/maps.d.ts.map +1 -0
- package/dist/fastify-plugins/utils.d.ts +4 -0
- package/dist/fastify-plugins/utils.d.ts.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/hashmap.d.ts +2 -2
- package/dist/lib/hashmap.d.ts.map +1 -1
- package/dist/lib/key-by.d.ts +15 -0
- package/dist/lib/key-by.d.ts.map +1 -0
- package/dist/lib/noise-secret-stream-helpers.d.ts.map +1 -1
- package/dist/local-peers.d.ts +3 -2
- package/dist/local-peers.d.ts.map +1 -1
- package/dist/logger.d.ts +12 -9
- package/dist/logger.d.ts.map +1 -1
- package/dist/mapeo-manager.d.ts +9 -1
- package/dist/mapeo-manager.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/member-api.d.ts +3 -1
- package/dist/member-api.d.ts.map +1 -1
- package/dist/roles.d.ts.map +1 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/sync/core-sync-state.d.ts +10 -3
- package/dist/sync/core-sync-state.d.ts.map +1 -1
- package/dist/sync/namespace-sync-state.d.ts +8 -12
- package/dist/sync/namespace-sync-state.d.ts.map +1 -1
- package/dist/sync/peer-sync-controller.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/sync/sync-state.d.ts +7 -1
- package/dist/sync/sync-state.d.ts.map +1 -1
- package/dist/translation-api.d.ts +1 -3
- package/dist/translation-api.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +0 -13
- package/dist/utils.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/core-manager/index.js +13 -10
- package/src/datastore/README.md +2 -2
- package/src/datatype/README.md +1 -1
- package/src/fastify-controller.js +7 -1
- package/src/fastify-plugins/maps.js +122 -0
- package/src/fastify-plugins/utils.js +6 -0
- package/src/index-writer/index.js +1 -1
- package/src/index.js +1 -3
- package/src/lib/hashmap.js +1 -1
- package/src/lib/key-by.js +24 -0
- package/src/local-peers.js +2 -1
- package/src/logger.js +52 -16
- package/src/mapeo-manager.js +36 -2
- package/src/mapeo-project.js +0 -2
- package/src/member-api.js +12 -5
- package/src/sync/core-sync-state.js +35 -7
- package/src/sync/namespace-sync-state.js +26 -24
- package/src/sync/peer-sync-controller.js +44 -37
- package/src/sync/sync-api.js +9 -6
- package/src/sync/sync-state.js +12 -1
- package/src/translation-api.js +1 -4
- package/src/types.ts +0 -1
- package/src/utils.js +0 -25
- package/dist/fastify-plugins/maps/index.d.ts.map +0 -1
- package/dist/fastify-plugins/maps/offline-fallback-map.d.ts +0 -12
- package/dist/fastify-plugins/maps/offline-fallback-map.d.ts.map +0 -1
- package/dist/fastify-plugins/maps/static-maps.d.ts +0 -11
- package/dist/fastify-plugins/maps/static-maps.d.ts.map +0 -1
- package/src/fastify-plugins/maps/index.js +0 -173
- package/src/fastify-plugins/maps/offline-fallback-map.js +0 -114
- package/src/fastify-plugins/maps/static-maps.js +0 -271
package/dist/utils.d.ts
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {String|Buffer} id
|
|
3
|
-
* @returns {Buffer | Uint8Array}
|
|
4
|
-
*/
|
|
5
|
-
export function idToKey(id: string | Buffer): Buffer | Uint8Array;
|
|
6
1
|
/**
|
|
7
2
|
*
|
|
8
3
|
* @param {Buffer|String} key
|
|
9
4
|
* @returns {String}
|
|
10
5
|
*/
|
|
11
6
|
export function keyToId(key: Buffer | string): string;
|
|
12
|
-
/**
|
|
13
|
-
* @param {String} version
|
|
14
|
-
* @returns {{coreId: String, blockIndex: Number}}
|
|
15
|
-
*/
|
|
16
|
-
export function parseVersion(version: string): {
|
|
17
|
-
coreId: string;
|
|
18
|
-
blockIndex: number;
|
|
19
|
-
};
|
|
20
7
|
/**
|
|
21
8
|
* @returns {void}
|
|
22
9
|
*/
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,6BAHW,MAAM,SAAO,UASvB;AAUD;;GAEG;AACH,wBAFa,IAAI,CAEQ;AAEzB;;;;GAIG;AACH,kCAJW,OAAO,WACP,MAAM,GACJ,QAAQ,SAAS,CAI7B;AAED;;;;;;;;;;;;;GAaG;AACH,uBATa,CAAC,OACH,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAUd,OAAO,KACL,KAAK,IAAI,CAAC,CAGxB;AAED;;;;GAIG;AACH,0BAJa,CAAC,SACH,SAAS,GAAG,CAAC,GACX,KAAK,IAAI,CAAC,CAItB;AAED;;;;;;;GAOG;AAEH,0BALkB,CAAC,SAAN,EAAI,OACN,CAAC,GACC,OAAO,YAAY,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAUtD;AAED;;;;GAIG;AACH,wBAJyE,CAAC,SAA5D,OAAO,iBAAiB,EAAE,QAAQ,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAG,OAC7D,CAAC,GACC,IAAI,CAAC,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,mBAAmB,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC,CAiB5H;AAED;;;;GAIG;AACH,2CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,iDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,wDAHW,QAAQ,CAAC,MAAM,CAAC,GACd,MAAM,CAMlB;AAED;;;GAGG;AACH,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;GAGG;AACH,wCAHW,2CAAkC,GAChC,MAAM,CAIlB;AAED;;;;;;;2DAO2D;AAC3D,0BALsB,CAAC,SAAV,MAAQ,EACF,CAAC,wBACT,aAAa,CAAC,CAAC,CAAC,SAChB,CAAC,GACC,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAOtD;AAED;;;;GAIG;AACH,gCAHW,MAAM,UAQhB;AApKD;IACE,2BAA2B;IAC3B,mBADY,KAAK,EAIhB;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comapeo/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Offline p2p mapping library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"test": "npm-run-all lint test:prettier build:types type test:buildConfigs test:unit test:e2e test:types",
|
|
12
12
|
"test:prettier": "prettier --check .",
|
|
13
13
|
"test:buildConfigs": "node scripts/build-config-fixtures.js",
|
|
14
|
-
"test:unit": "node --test
|
|
14
|
+
"test:unit": "node --test",
|
|
15
15
|
"test:e2e": "node --test test-e2e/*.js test-e2e/**/*.js",
|
|
16
16
|
"test:types": "tsc -p test-types/tsconfig.json",
|
|
17
17
|
"build:types": "tsc -p tsconfig.npm.json && cpy 'src/**/*.d.ts' dist",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"@types/json-schema": "^7.0.11",
|
|
119
119
|
"@types/json-stable-stringify": "^1.0.36",
|
|
120
120
|
"@types/nanobench": "^3.0.0",
|
|
121
|
-
"@types/node": "^18.19.
|
|
121
|
+
"@types/node": "^18.19.54",
|
|
122
122
|
"@types/sinonjs__fake-timers": "^8.1.2",
|
|
123
123
|
"@types/streamx": "^2.9.5",
|
|
124
124
|
"@types/sub-encoder": "^2.1.0",
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"@types/varint": "^6.0.1",
|
|
127
127
|
"@types/yauzl-promise": "^4.0.0",
|
|
128
128
|
"@types/yazl": "^2.4.5",
|
|
129
|
-
"bitfield": "^4.
|
|
129
|
+
"bitfield": "^4.2.0",
|
|
130
130
|
"cpy": "^10.1.0",
|
|
131
131
|
"cpy-cli": "^5.0.0",
|
|
132
132
|
"drizzle-kit": "^0.20.14",
|
|
@@ -142,22 +142,22 @@
|
|
|
142
142
|
"random-access-file": "^4.0.7",
|
|
143
143
|
"random-access-memory": "^6.2.1",
|
|
144
144
|
"rimraf": "^5.0.5",
|
|
145
|
+
"supports-color": "^9.4.0",
|
|
145
146
|
"tempy": "^3.1.0",
|
|
146
147
|
"ts-proto": "^1.156.7",
|
|
147
|
-
"typedoc": "^0.26.
|
|
148
|
-
"typedoc-plugin-markdown": "^4.2.
|
|
148
|
+
"typedoc": "^0.26.7",
|
|
149
|
+
"typedoc-plugin-markdown": "^4.2.7",
|
|
149
150
|
"typedoc-plugin-missing-exports": "^3.0.0",
|
|
150
|
-
"typescript": "^5.
|
|
151
|
+
"typescript": "^5.6.2",
|
|
151
152
|
"yazl": "^2.5.1"
|
|
152
153
|
},
|
|
153
154
|
"dependencies": {
|
|
155
|
+
"@comapeo/fallback-smp": "^1.0.0",
|
|
154
156
|
"@comapeo/schema": "1.0.0",
|
|
155
157
|
"@digidem/types": "^2.3.0",
|
|
156
|
-
"@electron/asar": "^3.2.8",
|
|
157
158
|
"@fastify/error": "^3.4.1",
|
|
158
|
-
"@fastify/
|
|
159
|
-
"@
|
|
160
|
-
"@hyperswarm/secret-stream": "^6.1.2",
|
|
159
|
+
"@fastify/type-provider-typebox": "^4.1.0",
|
|
160
|
+
"@hyperswarm/secret-stream": "^6.6.3",
|
|
161
161
|
"@mapeo/crypto": "1.0.0-alpha.10",
|
|
162
162
|
"@mapeo/sqlite-indexer": "1.0.0-alpha.9",
|
|
163
163
|
"@sinclair/typebox": "^0.29.6",
|
|
@@ -191,6 +191,7 @@
|
|
|
191
191
|
"sodium-universal": "^4.0.0",
|
|
192
192
|
"start-stop-state-machine": "^1.2.0",
|
|
193
193
|
"streamx": "^2.19.0",
|
|
194
|
+
"styled-map-package": "^1.1.0",
|
|
194
195
|
"sub-encoder": "^2.1.1",
|
|
195
196
|
"throttle-debounce": "^5.0.0",
|
|
196
197
|
"tiny-typed-emitter": "^2.1.0",
|
|
@@ -33,8 +33,8 @@ export const kCoreManagerReplicate = Symbol('replicate core manager')
|
|
|
33
33
|
export class CoreManager extends TypedEmitter {
|
|
34
34
|
#corestore
|
|
35
35
|
#coreIndex
|
|
36
|
-
/** @type {
|
|
37
|
-
#
|
|
36
|
+
/** @type {CoreRecord} */
|
|
37
|
+
#creatorCoreRecord
|
|
38
38
|
#projectKey
|
|
39
39
|
#queries
|
|
40
40
|
#encryptionKeys
|
|
@@ -128,12 +128,12 @@ export class CoreManager extends TypedEmitter {
|
|
|
128
128
|
}
|
|
129
129
|
const writer = this.#addCore(keyPair, namespace)
|
|
130
130
|
if (namespace === 'auth' && projectSecretKey) {
|
|
131
|
-
this.#
|
|
131
|
+
this.#creatorCoreRecord = writer
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// For anyone other than the project creator, creatorCore is readonly
|
|
136
|
-
this.#
|
|
136
|
+
this.#creatorCoreRecord ??= this.#addCore({ publicKey: projectKey }, 'auth')
|
|
137
137
|
|
|
138
138
|
// Load persisted cores
|
|
139
139
|
const rows = db.select().from(coresTable).all()
|
|
@@ -141,7 +141,7 @@ export class CoreManager extends TypedEmitter {
|
|
|
141
141
|
this.#addCore({ publicKey }, namespace)
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
this.#projectExtension = this
|
|
144
|
+
this.#projectExtension = this.creatorCore.registerExtension(
|
|
145
145
|
'mapeo/project',
|
|
146
146
|
{
|
|
147
147
|
encoding: ProjectExtensionCodec,
|
|
@@ -151,14 +151,14 @@ export class CoreManager extends TypedEmitter {
|
|
|
151
151
|
}
|
|
152
152
|
)
|
|
153
153
|
|
|
154
|
-
this.#haveExtension = this
|
|
154
|
+
this.#haveExtension = this.creatorCore.registerExtension('mapeo/have', {
|
|
155
155
|
encoding: HaveExtensionCodec,
|
|
156
156
|
onmessage: (msg, peer) => {
|
|
157
157
|
this.#handleHaveMessage(msg, peer)
|
|
158
158
|
},
|
|
159
159
|
})
|
|
160
160
|
|
|
161
|
-
this
|
|
161
|
+
this.creatorCore.on('peer-add', (peer) => {
|
|
162
162
|
this.#sendHaves(peer, this.#coreIndex).catch(() => {
|
|
163
163
|
this.#l.log('Failed to send pre-haves to newly-connected peer')
|
|
164
164
|
})
|
|
@@ -179,7 +179,11 @@ export class CoreManager extends TypedEmitter {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
get creatorCore() {
|
|
182
|
-
return this.#
|
|
182
|
+
return this.#creatorCoreRecord.core
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get creatorCoreRecord() {
|
|
186
|
+
return this.#creatorCoreRecord
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
/**
|
|
@@ -255,7 +259,6 @@ export class CoreManager extends TypedEmitter {
|
|
|
255
259
|
* @returns {CoreRecord}
|
|
256
260
|
*/
|
|
257
261
|
addCore(key, namespace) {
|
|
258
|
-
this.#l.log('Adding remote core %k to %s', key, namespace)
|
|
259
262
|
return this.#addCore({ publicKey: key }, namespace, true)
|
|
260
263
|
}
|
|
261
264
|
|
|
@@ -298,7 +301,7 @@ export class CoreManager extends TypedEmitter {
|
|
|
298
301
|
|
|
299
302
|
if (writer) {
|
|
300
303
|
const sendHaves = debounce(WRITER_CORE_PREHAVES_DEBOUNCE_DELAY, () => {
|
|
301
|
-
for (const peer of this
|
|
304
|
+
for (const peer of this.creatorCore.peers) {
|
|
302
305
|
this.#sendHaves(peer, [{ core, namespace }]).catch(() => {
|
|
303
306
|
this.#l.log('Failed to send new pre-haves to other peers')
|
|
304
307
|
})
|
package/src/datastore/README.md
CHANGED
|
@@ -10,7 +10,7 @@ The `DataStore` class is an API over a CoreManager namespace, responsible for re
|
|
|
10
10
|
|
|
11
11
|
The `DataStore` class is used internally by the [`DataType`](../datatype/) class.
|
|
12
12
|
|
|
13
|
-
An example of `DataStore` usage taken from the [datastore tests](../../
|
|
13
|
+
An example of `DataStore` usage taken from the [datastore tests](../../test/datastore.js):
|
|
14
14
|
|
|
15
15
|
```js
|
|
16
16
|
const datastore = new DataStore({
|
|
@@ -43,4 +43,4 @@ TODO!
|
|
|
43
43
|
|
|
44
44
|
## Tests
|
|
45
45
|
|
|
46
|
-
Tests for this module are in [
|
|
46
|
+
Tests for this module are in [test/datastore.js](../../test/datastore.js)
|
package/src/datatype/README.md
CHANGED
|
@@ -64,7 +64,13 @@ export class FastifyController {
|
|
|
64
64
|
|
|
65
65
|
async #stopServer() {
|
|
66
66
|
const { server } = this.#fastify
|
|
67
|
-
|
|
67
|
+
|
|
68
|
+
const closePromise = promisify(server.close.bind(server))()
|
|
69
|
+
|
|
70
|
+
// We call this after `server.close()` as recommended by the Node docs (see https://nodejs.org/docs/latest-v20.x/api/http.html#servercloseidleconnections)
|
|
71
|
+
server.closeIdleConnections()
|
|
72
|
+
|
|
73
|
+
await closePromise
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
/**
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fetch } from 'undici'
|
|
4
|
+
import { Server as SMPServerPlugin } from 'styled-map-package'
|
|
5
|
+
|
|
6
|
+
import { noop } from '../utils.js'
|
|
7
|
+
import { NotFoundError, ENOENTError } from './utils.js'
|
|
8
|
+
|
|
9
|
+
/** @import { FastifyPluginAsync } from 'fastify' */
|
|
10
|
+
/** @import { Stats } from 'node:fs' */
|
|
11
|
+
|
|
12
|
+
export const CUSTOM_MAP_PREFIX = 'custom'
|
|
13
|
+
export const FALLBACK_MAP_PREFIX = 'fallback'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} MapsPluginOpts
|
|
17
|
+
*
|
|
18
|
+
* @property {string | URL} defaultOnlineStyleUrl
|
|
19
|
+
* @property {string} [customMapPath]
|
|
20
|
+
* @property {string} fallbackMapPath
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** @type {FastifyPluginAsync<MapsPluginOpts>} */
|
|
24
|
+
export async function plugin(fastify, opts) {
|
|
25
|
+
if (opts.customMapPath) {
|
|
26
|
+
const { customMapPath } = opts
|
|
27
|
+
|
|
28
|
+
fastify.get(`/${CUSTOM_MAP_PREFIX}/info`, async () => {
|
|
29
|
+
const baseUrl = new URL(fastify.prefix, fastify.listeningOrigin)
|
|
30
|
+
|
|
31
|
+
if (!baseUrl.href.endsWith('/')) {
|
|
32
|
+
baseUrl.href += '/'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const customStyleJsonUrl = new URL(
|
|
36
|
+
`${CUSTOM_MAP_PREFIX}/style.json`,
|
|
37
|
+
baseUrl
|
|
38
|
+
)
|
|
39
|
+
const response = await fetch(customStyleJsonUrl)
|
|
40
|
+
|
|
41
|
+
if (response.status === 404) {
|
|
42
|
+
throw new NotFoundError(customStyleJsonUrl.href)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`Failed to get style from ${customStyleJsonUrl.href}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @type {Stats | undefined} */
|
|
50
|
+
let stats
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
stats = await fs.stat(customMapPath)
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
56
|
+
throw new ENOENTError(customMapPath)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw err
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const style = await response.json()
|
|
63
|
+
|
|
64
|
+
const styleJsonName =
|
|
65
|
+
typeof style === 'object' &&
|
|
66
|
+
style &&
|
|
67
|
+
'name' in style &&
|
|
68
|
+
typeof style.name === 'string'
|
|
69
|
+
? style.name
|
|
70
|
+
: undefined
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
created: stats.ctime,
|
|
74
|
+
size: stats.size,
|
|
75
|
+
name: styleJsonName || path.parse(customMapPath).name,
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
fastify.register(SMPServerPlugin, {
|
|
80
|
+
prefix: CUSTOM_MAP_PREFIX,
|
|
81
|
+
filepath: customMapPath,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fastify.register(SMPServerPlugin, {
|
|
86
|
+
prefix: FALLBACK_MAP_PREFIX,
|
|
87
|
+
filepath: opts.fallbackMapPath,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
fastify.get('/style.json', async (_request, reply) => {
|
|
91
|
+
const baseUrl = new URL(fastify.prefix, fastify.listeningOrigin)
|
|
92
|
+
|
|
93
|
+
// Important for using as a base for creating new URL objects
|
|
94
|
+
if (!baseUrl.href.endsWith('/')) {
|
|
95
|
+
baseUrl.href += '/'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @type {Array<string | URL>}*/
|
|
99
|
+
const styleUrls = [
|
|
100
|
+
opts.defaultOnlineStyleUrl,
|
|
101
|
+
new URL(`${FALLBACK_MAP_PREFIX}/style.json`, baseUrl),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
if (opts.customMapPath) {
|
|
105
|
+
styleUrls.unshift(new URL(`${CUSTOM_MAP_PREFIX}/style.json`, baseUrl))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const url of styleUrls) {
|
|
109
|
+
const resp = await fetch(url, { method: 'HEAD' }).catch(noop)
|
|
110
|
+
|
|
111
|
+
if (resp && resp.status === 200) {
|
|
112
|
+
return reply
|
|
113
|
+
.headers({
|
|
114
|
+
'cache-control': 'no-cache',
|
|
115
|
+
})
|
|
116
|
+
.redirect(url.toString())
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return reply.status(404).send()
|
|
121
|
+
})
|
|
122
|
+
}
|
|
@@ -7,6 +7,12 @@ export const NotFoundError = createError(
|
|
|
7
7
|
404
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
+
export const ENOENTError = createError(
|
|
11
|
+
'FST_ENOENT',
|
|
12
|
+
"ENOENT: no such file or directory '%s'",
|
|
13
|
+
404
|
|
14
|
+
)
|
|
15
|
+
|
|
10
16
|
/**
|
|
11
17
|
* @param {import('node:http').Server} server
|
|
12
18
|
* @param {{ timeout?: number }} [options]
|
|
@@ -97,7 +97,7 @@ export class IndexWriter {
|
|
|
97
97
|
const indexer = this.#indexers.get(schemaName)
|
|
98
98
|
if (!indexer) continue // Won't happen, but TS doesn't know that
|
|
99
99
|
indexer.batch(docs)
|
|
100
|
-
if (this.#l.enabled) {
|
|
100
|
+
if (this.#l.log.enabled) {
|
|
101
101
|
for (const doc of docs) {
|
|
102
102
|
this.#l.log(
|
|
103
103
|
'Indexed %s %S @ %S',
|
package/src/index.js
CHANGED
|
@@ -3,9 +3,7 @@ import {
|
|
|
3
3
|
COORDINATOR_ROLE_ID,
|
|
4
4
|
MEMBER_ROLE_ID,
|
|
5
5
|
} from './roles.js'
|
|
6
|
-
export { plugin as
|
|
7
|
-
export { plugin as MapeoOfflineFallbackMapFastifyPlugin } from './fastify-plugins/maps/offline-fallback-map.js'
|
|
8
|
-
export { plugin as MapeoMapsFastifyPlugin } from './fastify-plugins/maps/index.js'
|
|
6
|
+
export { plugin as CoMapeoMapsFastifyPlugin } from './fastify-plugins/maps.js'
|
|
9
7
|
export { FastifyController } from './fastify-controller.js'
|
|
10
8
|
export { MapeoManager } from './mapeo-manager.js'
|
|
11
9
|
|
package/src/lib/hashmap.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Like [`Map.groupBy`][0], but the result's values aren't arrays.
|
|
3
|
+
*
|
|
4
|
+
* If multiple values resolve to the same key, an error is thrown.
|
|
5
|
+
*
|
|
6
|
+
* [0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy
|
|
7
|
+
*
|
|
8
|
+
* @template T
|
|
9
|
+
* @template K
|
|
10
|
+
* @param {Iterable<T>} items
|
|
11
|
+
* @param {(item: T) => K} callbackFn
|
|
12
|
+
* @returns {Map<K, T>}
|
|
13
|
+
*/
|
|
14
|
+
export function keyBy(items, callbackFn) {
|
|
15
|
+
/** @type {Map<K, T>} */ const result = new Map()
|
|
16
|
+
for (const item of items) {
|
|
17
|
+
const key = callbackFn(item)
|
|
18
|
+
if (result.has(key)) {
|
|
19
|
+
throw new Error(`keyBy found duplicate key ${JSON.stringify(key)}`)
|
|
20
|
+
}
|
|
21
|
+
result.set(key, item)
|
|
22
|
+
}
|
|
23
|
+
return result
|
|
24
|
+
}
|
package/src/local-peers.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import pDefer from 'p-defer'
|
|
14
14
|
import { Logger } from './logger.js'
|
|
15
15
|
import pTimeout, { TimeoutError } from 'p-timeout'
|
|
16
|
+
/** @import NoiseStream from '@hyperswarm/secret-stream' */
|
|
16
17
|
/** @import { OpenedNoiseStream } from './lib/noise-secret-stream-helpers.js' */
|
|
17
18
|
|
|
18
19
|
// Unique identifier for the mapeo rpc protocol
|
|
@@ -329,7 +330,7 @@ export class LocalPeers extends TypedEmitter {
|
|
|
329
330
|
/**
|
|
330
331
|
* Connect to a peer over an existing NoiseSecretStream
|
|
331
332
|
*
|
|
332
|
-
* @param {
|
|
333
|
+
* @param {NoiseStream<any>} stream
|
|
333
334
|
* @returns {import('./types.js').ReplicationStream}
|
|
334
335
|
*/
|
|
335
336
|
connect(stream) {
|
package/src/logger.js
CHANGED
|
@@ -5,6 +5,32 @@ import util from 'util'
|
|
|
5
5
|
|
|
6
6
|
const TRIM = 7
|
|
7
7
|
|
|
8
|
+
const selectColorOriginal = createDebug.selectColor
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Selects a color for a debug namespace (warning: overrides private api).
|
|
12
|
+
* Rather than the default behaviour of creating a unique color for each
|
|
13
|
+
* namespace, we only hash the last 7 characters of the namespace, which is the
|
|
14
|
+
* deviceId. This results in debug output where each deviceId has a different
|
|
15
|
+
* colour, which is more useful for debugging.
|
|
16
|
+
* @param {string} namespace The namespace string for the debug instance to be colored
|
|
17
|
+
* @return {number|string} An ANSI color code for the given namespace
|
|
18
|
+
*/
|
|
19
|
+
createDebug.selectColor = function (namespace) {
|
|
20
|
+
if (!namespace.startsWith('mapeo:')) {
|
|
21
|
+
return selectColorOriginal(namespace)
|
|
22
|
+
}
|
|
23
|
+
let hash = 0
|
|
24
|
+
|
|
25
|
+
for (let i = namespace.length - TRIM - 1; i < namespace.length; i++) {
|
|
26
|
+
hash = (hash << 5) - hash + namespace.charCodeAt(i)
|
|
27
|
+
hash |= 0 // Convert to 32bit integer
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// @ts-expect-error - private debug api
|
|
31
|
+
return createDebug.colors[Math.abs(hash) % createDebug.colors.length]
|
|
32
|
+
}
|
|
33
|
+
|
|
8
34
|
createDebug.formatters.h = function (v) {
|
|
9
35
|
if (!Buffer.isBuffer(v)) return '[undefined]'
|
|
10
36
|
return v.toString('hex').slice(0, TRIM)
|
|
@@ -56,13 +82,14 @@ export class Logger {
|
|
|
56
82
|
/**
|
|
57
83
|
* @param {string} ns
|
|
58
84
|
* @param {Logger} [logger]
|
|
85
|
+
* @param {{ prefix?: string }} [opts]
|
|
59
86
|
*/
|
|
60
|
-
static create(ns, logger) {
|
|
61
|
-
if (logger) return logger.extend(ns)
|
|
87
|
+
static create(ns, logger, opts) {
|
|
88
|
+
if (logger) return logger.extend(ns, opts)
|
|
62
89
|
const i = (counts.get(ns) || 0) + 1
|
|
63
90
|
counts.set(ns, i)
|
|
64
91
|
const deviceId = String(i).padStart(TRIM, '0')
|
|
65
|
-
return new Logger({ deviceId, ns })
|
|
92
|
+
return new Logger({ deviceId, ns, prefix: opts?.prefix })
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
/**
|
|
@@ -70,30 +97,39 @@ export class Logger {
|
|
|
70
97
|
* @param {string} opts.deviceId
|
|
71
98
|
* @param {createDebug.Debugger} [opts.baseLogger]
|
|
72
99
|
* @param {string} [opts.ns]
|
|
100
|
+
* @param {string} [opts.prefix] optional prefix to add to the start of each log message. Used to add context e.g. the core ID that is syncing. Use this as an alternative to the debug namespace.
|
|
73
101
|
*/
|
|
74
|
-
constructor({ deviceId, baseLogger, ns }) {
|
|
102
|
+
constructor({ deviceId, baseLogger, ns, prefix }) {
|
|
75
103
|
this.deviceId = deviceId
|
|
76
104
|
this.#baseLogger = baseLogger || createDebug('mapeo' + (ns ? `:${ns}` : ''))
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
const log = this.#baseLogger.extend(this.deviceId.slice(0, TRIM))
|
|
106
|
+
if (prefix) {
|
|
107
|
+
this.#log = Object.assign(
|
|
108
|
+
/**
|
|
109
|
+
* @param {any} formatter
|
|
110
|
+
* @param {...any} args
|
|
111
|
+
*/
|
|
112
|
+
(formatter, ...args) => {
|
|
113
|
+
return log.apply(null, [`${prefix}${formatter}`, ...args])
|
|
114
|
+
},
|
|
115
|
+
log
|
|
116
|
+
)
|
|
117
|
+
} else {
|
|
118
|
+
this.#log = log
|
|
119
|
+
}
|
|
81
120
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* @param {Parameters<createDebug.Debugger>} args
|
|
85
|
-
*/
|
|
86
|
-
log = (...args) => {
|
|
87
|
-
this.#log.apply(this, args)
|
|
121
|
+
get log() {
|
|
122
|
+
return this.#log
|
|
88
123
|
}
|
|
89
124
|
/**
|
|
90
|
-
*
|
|
91
125
|
* @param {string} ns
|
|
126
|
+
* @param {{ prefix?: string }} [opts]
|
|
92
127
|
*/
|
|
93
|
-
extend(ns) {
|
|
128
|
+
extend(ns, { prefix } = {}) {
|
|
94
129
|
return new Logger({
|
|
95
130
|
deviceId: this.deviceId,
|
|
96
131
|
baseLogger: this.#baseLogger.extend(ns),
|
|
132
|
+
prefix,
|
|
97
133
|
})
|
|
98
134
|
}
|
|
99
135
|
}
|
package/src/mapeo-manager.js
CHANGED
|
@@ -8,6 +8,7 @@ import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
|
|
|
8
8
|
import Hypercore from 'hypercore'
|
|
9
9
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
10
10
|
import pTimeout from 'p-timeout'
|
|
11
|
+
import { createRequire } from 'module'
|
|
11
12
|
|
|
12
13
|
import { IndexWriter } from './index-writer/index.js'
|
|
13
14
|
import {
|
|
@@ -36,6 +37,7 @@ import { openedNoiseSecretStream } from './lib/noise-secret-stream-helpers.js'
|
|
|
36
37
|
import { RandomAccessFilePool } from './core-manager/random-access-file-pool.js'
|
|
37
38
|
import BlobServerPlugin from './fastify-plugins/blobs.js'
|
|
38
39
|
import IconServerPlugin from './fastify-plugins/icons.js'
|
|
40
|
+
import { plugin as MapServerPlugin } from './fastify-plugins/maps.js'
|
|
39
41
|
import { getFastifyServerAddress } from './fastify-plugins/utils.js'
|
|
40
42
|
import { LocalPeers } from './local-peers.js'
|
|
41
43
|
import { InviteApi } from './invite-api.js'
|
|
@@ -52,7 +54,6 @@ import {
|
|
|
52
54
|
/** @import { SetNonNullable } from 'type-fest' */
|
|
53
55
|
/** @import { CoreStorage, Namespace } from './types.js' */
|
|
54
56
|
/** @import { DeviceInfoParam } from './schema/client.js' */
|
|
55
|
-
/** @import { OpenedNoiseStream } from './lib/noise-secret-stream-helpers.js' */
|
|
56
57
|
|
|
57
58
|
/** @typedef {SetNonNullable<ProjectKeys, 'encryptionKeys'>} ValidatedProjectKeys */
|
|
58
59
|
|
|
@@ -67,6 +68,15 @@ const MAX_FILE_DESCRIPTORS = 768
|
|
|
67
68
|
// Prefix names for routes registered with http server
|
|
68
69
|
const BLOBS_PREFIX = 'blobs'
|
|
69
70
|
const ICONS_PREFIX = 'icons'
|
|
71
|
+
const MAPS_PREFIX = 'maps'
|
|
72
|
+
|
|
73
|
+
const require = createRequire(import.meta.url)
|
|
74
|
+
export const DEFAULT_FALLBACK_MAP_FILE_PATH = require.resolve(
|
|
75
|
+
'@comapeo/fallback-smp'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
export const DEFAULT_ONLINE_STYLE_URL =
|
|
79
|
+
'https://demotiles.maplibre.org/style.json'
|
|
70
80
|
|
|
71
81
|
export const kRPC = Symbol('rpc')
|
|
72
82
|
export const kManagerReplicate = Symbol('replicate manager')
|
|
@@ -113,6 +123,9 @@ export class MapeoManager extends TypedEmitter {
|
|
|
113
123
|
* @param {string | CoreStorage} opts.coreStorage Folder for hypercore storage or a function that returns a RandomAccessStorage instance
|
|
114
124
|
* @param {import('fastify').FastifyInstance} opts.fastify Fastify server instance
|
|
115
125
|
* @param {String} [opts.defaultConfigPath]
|
|
126
|
+
* @param {string} [opts.customMapPath] File path to a locally stored Styled Map Package (SMP).
|
|
127
|
+
* @param {string} [opts.fallbackMapPath] File path to a locally stored Styled Map Package (SMP)
|
|
128
|
+
* @param {string} [opts.defaultOnlineStyleUrl] URL for an online-hosted StyleJSON asset.
|
|
116
129
|
*/
|
|
117
130
|
constructor({
|
|
118
131
|
rootKey,
|
|
@@ -122,6 +135,9 @@ export class MapeoManager extends TypedEmitter {
|
|
|
122
135
|
coreStorage,
|
|
123
136
|
fastify,
|
|
124
137
|
defaultConfigPath,
|
|
138
|
+
customMapPath,
|
|
139
|
+
fallbackMapPath = DEFAULT_FALLBACK_MAP_FILE_PATH,
|
|
140
|
+
defaultOnlineStyleUrl = DEFAULT_ONLINE_STYLE_URL,
|
|
125
141
|
}) {
|
|
126
142
|
super()
|
|
127
143
|
this.#keyManager = new KeyManager(rootKey)
|
|
@@ -190,6 +206,12 @@ export class MapeoManager extends TypedEmitter {
|
|
|
190
206
|
prefix: ICONS_PREFIX,
|
|
191
207
|
getProject: this.getProject.bind(this),
|
|
192
208
|
})
|
|
209
|
+
this.#fastify.register(MapServerPlugin, {
|
|
210
|
+
prefix: MAPS_PREFIX,
|
|
211
|
+
customMapPath,
|
|
212
|
+
defaultOnlineStyleUrl,
|
|
213
|
+
fallbackMapPath,
|
|
214
|
+
})
|
|
193
215
|
|
|
194
216
|
this.#localDiscovery = new LocalDiscovery({
|
|
195
217
|
identityKeypair: this.#keyManager.getIdentityKeypair(),
|
|
@@ -242,6 +264,10 @@ export class MapeoManager extends TypedEmitter {
|
|
|
242
264
|
prefix = ICONS_PREFIX
|
|
243
265
|
break
|
|
244
266
|
}
|
|
267
|
+
case 'maps': {
|
|
268
|
+
prefix = MAPS_PREFIX
|
|
269
|
+
break
|
|
270
|
+
}
|
|
245
271
|
default: {
|
|
246
272
|
throw new Error(`Unsupported media type ${mediaType}`)
|
|
247
273
|
}
|
|
@@ -675,6 +701,14 @@ export class MapeoManager extends TypedEmitter {
|
|
|
675
701
|
isConfigSynced
|
|
676
702
|
) {
|
|
677
703
|
return true
|
|
704
|
+
} else {
|
|
705
|
+
this.#l.log(
|
|
706
|
+
'Pending initial sync: role %s, projectSettings %o, auth %o, config %o',
|
|
707
|
+
isRoleSynced,
|
|
708
|
+
isProjectSettingsSynced,
|
|
709
|
+
isAuthSynced,
|
|
710
|
+
isConfigSynced
|
|
711
|
+
)
|
|
678
712
|
}
|
|
679
713
|
return new Promise((resolve, reject) => {
|
|
680
714
|
/** @param {import('./sync/sync-state.js').State} syncState */
|
|
@@ -866,7 +900,7 @@ export class MapeoManager extends TypedEmitter {
|
|
|
866
900
|
|
|
867
901
|
async getMapStyleJsonUrl() {
|
|
868
902
|
await pTimeout(this.#fastify.ready(), { milliseconds: 1000 })
|
|
869
|
-
return this.#
|
|
903
|
+
return (await this.#getMediaBaseUrl('maps')) + '/style.json'
|
|
870
904
|
}
|
|
871
905
|
}
|
|
872
906
|
|
package/src/mapeo-project.js
CHANGED
|
@@ -345,7 +345,6 @@ export class MapeoProject extends TypedEmitter {
|
|
|
345
345
|
|
|
346
346
|
this.#translationApi = new TranslationApi({
|
|
347
347
|
dataType: this.#dataTypes.translation,
|
|
348
|
-
table: translationTable,
|
|
349
348
|
})
|
|
350
349
|
|
|
351
350
|
///////// 4. Replicate local peers automatically
|
|
@@ -553,7 +552,6 @@ export class MapeoProject extends TypedEmitter {
|
|
|
553
552
|
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
|
|
554
553
|
)
|
|
555
554
|
} catch (e) {
|
|
556
|
-
this.#l.log('No project settings')
|
|
557
555
|
return /** @type {EditableProjectSettings} */ (EMPTY_PROJECT_SETTINGS)
|
|
558
556
|
}
|
|
559
557
|
}
|