@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.
Files changed (75) hide show
  1. package/dist/blob-store/index.d.ts +1 -1
  2. package/dist/config-import.d.ts.map +1 -1
  3. package/dist/core-manager/core-index.d.ts +1 -1
  4. package/dist/core-manager/core-index.d.ts.map +1 -1
  5. package/dist/core-manager/index.d.ts +1 -0
  6. package/dist/core-manager/index.d.ts.map +1 -1
  7. package/dist/fastify-controller.d.ts.map +1 -1
  8. package/dist/fastify-plugins/{maps/index.d.ts → maps.d.ts} +8 -8
  9. package/dist/fastify-plugins/maps.d.ts.map +1 -0
  10. package/dist/fastify-plugins/utils.d.ts +4 -0
  11. package/dist/fastify-plugins/utils.d.ts.map +1 -1
  12. package/dist/index.d.ts +1 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/lib/hashmap.d.ts +2 -2
  15. package/dist/lib/hashmap.d.ts.map +1 -1
  16. package/dist/lib/key-by.d.ts +15 -0
  17. package/dist/lib/key-by.d.ts.map +1 -0
  18. package/dist/lib/noise-secret-stream-helpers.d.ts.map +1 -1
  19. package/dist/local-peers.d.ts +3 -2
  20. package/dist/local-peers.d.ts.map +1 -1
  21. package/dist/logger.d.ts +12 -9
  22. package/dist/logger.d.ts.map +1 -1
  23. package/dist/mapeo-manager.d.ts +9 -1
  24. package/dist/mapeo-manager.d.ts.map +1 -1
  25. package/dist/mapeo-project.d.ts.map +1 -1
  26. package/dist/member-api.d.ts +3 -1
  27. package/dist/member-api.d.ts.map +1 -1
  28. package/dist/roles.d.ts.map +1 -1
  29. package/dist/schema/utils.d.ts.map +1 -1
  30. package/dist/sync/core-sync-state.d.ts +10 -3
  31. package/dist/sync/core-sync-state.d.ts.map +1 -1
  32. package/dist/sync/namespace-sync-state.d.ts +8 -12
  33. package/dist/sync/namespace-sync-state.d.ts.map +1 -1
  34. package/dist/sync/peer-sync-controller.d.ts.map +1 -1
  35. package/dist/sync/sync-api.d.ts.map +1 -1
  36. package/dist/sync/sync-state.d.ts +7 -1
  37. package/dist/sync/sync-state.d.ts.map +1 -1
  38. package/dist/translation-api.d.ts +1 -3
  39. package/dist/translation-api.d.ts.map +1 -1
  40. package/dist/types.d.ts +1 -1
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/utils.d.ts +0 -13
  43. package/dist/utils.d.ts.map +1 -1
  44. package/package.json +12 -11
  45. package/src/core-manager/index.js +13 -10
  46. package/src/datastore/README.md +2 -2
  47. package/src/datatype/README.md +1 -1
  48. package/src/fastify-controller.js +7 -1
  49. package/src/fastify-plugins/maps.js +122 -0
  50. package/src/fastify-plugins/utils.js +6 -0
  51. package/src/index-writer/index.js +1 -1
  52. package/src/index.js +1 -3
  53. package/src/lib/hashmap.js +1 -1
  54. package/src/lib/key-by.js +24 -0
  55. package/src/local-peers.js +2 -1
  56. package/src/logger.js +52 -16
  57. package/src/mapeo-manager.js +36 -2
  58. package/src/mapeo-project.js +0 -2
  59. package/src/member-api.js +12 -5
  60. package/src/sync/core-sync-state.js +35 -7
  61. package/src/sync/namespace-sync-state.js +26 -24
  62. package/src/sync/peer-sync-controller.js +44 -37
  63. package/src/sync/sync-api.js +9 -6
  64. package/src/sync/sync-state.js +12 -1
  65. package/src/translation-api.js +1 -4
  66. package/src/types.ts +0 -1
  67. package/src/utils.js +0 -25
  68. package/dist/fastify-plugins/maps/index.d.ts.map +0 -1
  69. package/dist/fastify-plugins/maps/offline-fallback-map.d.ts +0 -12
  70. package/dist/fastify-plugins/maps/offline-fallback-map.d.ts.map +0 -1
  71. package/dist/fastify-plugins/maps/static-maps.d.ts +0 -11
  72. package/dist/fastify-plugins/maps/static-maps.d.ts.map +0 -1
  73. package/src/fastify-plugins/maps/index.js +0 -173
  74. package/src/fastify-plugins/maps/offline-fallback-map.js +0 -114
  75. 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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,4BAHW,SAAO,MAAM,GACX,MAAM,GAAG,UAAU,CAQ/B;AAED;;;;GAIG;AACH,6BAHW,MAAM,SAAO,UASvB;AAED;;;GAGG;AACH,+CAFa;IAAC,MAAM,SAAS;IAAC,UAAU,SAAQ;CAAC,CAQhD;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"}
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": "1.0.1",
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 tests/*.js tests/**/*.js",
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.33",
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.1.0",
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.6",
148
- "typedoc-plugin-markdown": "^4.2.5",
148
+ "typedoc": "^0.26.7",
149
+ "typedoc-plugin-markdown": "^4.2.7",
149
150
  "typedoc-plugin-missing-exports": "^3.0.0",
150
- "typescript": "^5.5.4",
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/static": "^7.0.3",
159
- "@fastify/type-provider-typebox": "^4.0.0",
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 {Core} */
37
- #creatorCore
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.#creatorCore = writer.core
131
+ this.#creatorCoreRecord = writer
132
132
  }
133
133
  }
134
134
 
135
135
  // For anyone other than the project creator, creatorCore is readonly
136
- this.#creatorCore ??= this.#addCore({ publicKey: projectKey }, 'auth').core
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.#creatorCore.registerExtension(
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.#creatorCore.registerExtension('mapeo/have', {
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.#creatorCore.on('peer-add', (peer) => {
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.#creatorCore
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.#creatorCore.peers) {
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
  })
@@ -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](../../tests/datastore.js):
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 [tests/datastore.js](../../tests/datastore.js)
46
+ Tests for this module are in [test/datastore.js](../../test/datastore.js)
@@ -30,4 +30,4 @@ TODO!
30
30
 
31
31
  ## Tests
32
32
 
33
- Tests for this module are in [tests/datatype.js](../../tests/datatype.js)
33
+ Tests for this module are in [test/datatype.js](../../test/datatype.js)
@@ -64,7 +64,13 @@ export class FastifyController {
64
64
 
65
65
  async #stopServer() {
66
66
  const { server } = this.#fastify
67
- await promisify(server.close.bind(server))()
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 MapeoStaticMapsFastifyPlugin } from './fastify-plugins/maps/static-maps.js'
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
 
@@ -1,4 +1,4 @@
1
- /** @typedef {string | number | bigint | boolean | undefined | symbol | null} Primitive */
1
+ /** @import { Primitive } from 'type-fest' */
2
2
 
3
3
  /**
4
4
  * `Map` uses same-value-zero equality for keys, which makes it more difficult
@@ -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
+ }
@@ -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 {import('./types.js').NoiseStream<any>} stream a NoiseSecretStream from @hyperswarm/secret-stream
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
- this.#log = this.#baseLogger.extend(this.deviceId.slice(0, TRIM))
78
- }
79
- get enabled() {
80
- return this.#log.enabled
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
  }
@@ -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.#fastify.mapeoMaps.getStyleJsonUrl()
903
+ return (await this.#getMediaBaseUrl('maps')) + '/style.json'
870
904
  }
871
905
  }
872
906
 
@@ -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
  }