@comapeo/core 1.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 (186) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +31 -0
  3. package/dist/blob-api.d.ts +92 -0
  4. package/dist/blob-api.d.ts.map +1 -0
  5. package/dist/blob-store/index.d.ts +163 -0
  6. package/dist/blob-store/index.d.ts.map +1 -0
  7. package/dist/blob-store/live-download.d.ts +107 -0
  8. package/dist/blob-store/live-download.d.ts.map +1 -0
  9. package/dist/config-import.d.ts +74 -0
  10. package/dist/config-import.d.ts.map +1 -0
  11. package/dist/constants.d.ts +14 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/core-manager/bitfield-rle.d.ts +25 -0
  14. package/dist/core-manager/bitfield-rle.d.ts.map +1 -0
  15. package/dist/core-manager/core-index.d.ts +56 -0
  16. package/dist/core-manager/core-index.d.ts.map +1 -0
  17. package/dist/core-manager/index.d.ts +125 -0
  18. package/dist/core-manager/index.d.ts.map +1 -0
  19. package/dist/core-manager/random-access-file-pool.d.ts +17 -0
  20. package/dist/core-manager/random-access-file-pool.d.ts.map +1 -0
  21. package/dist/core-manager/remote-bitfield.d.ts +146 -0
  22. package/dist/core-manager/remote-bitfield.d.ts.map +1 -0
  23. package/dist/core-ownership.d.ts +112 -0
  24. package/dist/core-ownership.d.ts.map +1 -0
  25. package/dist/datastore/index.d.ts +91 -0
  26. package/dist/datastore/index.d.ts.map +1 -0
  27. package/dist/datatype/index.d.ts +108 -0
  28. package/dist/discovery/local-discovery.d.ts +64 -0
  29. package/dist/discovery/local-discovery.d.ts.map +1 -0
  30. package/dist/errors.d.ts +4 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/fastify-controller.d.ts +27 -0
  33. package/dist/fastify-controller.d.ts.map +1 -0
  34. package/dist/fastify-plugins/blobs.d.ts +6 -0
  35. package/dist/fastify-plugins/blobs.d.ts.map +1 -0
  36. package/dist/fastify-plugins/constants.d.ts +3 -0
  37. package/dist/fastify-plugins/constants.d.ts.map +1 -0
  38. package/dist/fastify-plugins/icons.d.ts +6 -0
  39. package/dist/fastify-plugins/icons.d.ts.map +1 -0
  40. package/dist/fastify-plugins/maps/index.d.ts +11 -0
  41. package/dist/fastify-plugins/maps/index.d.ts.map +1 -0
  42. package/dist/fastify-plugins/maps/offline-fallback-map.d.ts +12 -0
  43. package/dist/fastify-plugins/maps/offline-fallback-map.d.ts.map +1 -0
  44. package/dist/fastify-plugins/maps/static-maps.d.ts +11 -0
  45. package/dist/fastify-plugins/maps/static-maps.d.ts.map +1 -0
  46. package/dist/fastify-plugins/utils.d.ts +23 -0
  47. package/dist/fastify-plugins/utils.d.ts.map +1 -0
  48. package/dist/generated/extensions.d.ts +44 -0
  49. package/dist/generated/extensions.d.ts.map +1 -0
  50. package/dist/generated/keys.d.ts +36 -0
  51. package/dist/generated/keys.d.ts.map +1 -0
  52. package/dist/generated/rpc.d.ts +87 -0
  53. package/dist/generated/rpc.d.ts.map +1 -0
  54. package/dist/icon-api.d.ts +109 -0
  55. package/dist/icon-api.d.ts.map +1 -0
  56. package/dist/index-writer/index.d.ts +51 -0
  57. package/dist/index-writer/index.d.ts.map +1 -0
  58. package/dist/index.d.ts +14 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/invite-api.d.ts +70 -0
  61. package/dist/invite-api.d.ts.map +1 -0
  62. package/dist/lib/hashmap.d.ts +62 -0
  63. package/dist/lib/hashmap.d.ts.map +1 -0
  64. package/dist/lib/hypercore-helpers.d.ts +6 -0
  65. package/dist/lib/hypercore-helpers.d.ts.map +1 -0
  66. package/dist/lib/noise-secret-stream-helpers.d.ts +45 -0
  67. package/dist/lib/noise-secret-stream-helpers.d.ts.map +1 -0
  68. package/dist/lib/ponyfills.d.ts +10 -0
  69. package/dist/lib/ponyfills.d.ts.map +1 -0
  70. package/dist/lib/string.d.ts +2 -0
  71. package/dist/lib/string.d.ts.map +1 -0
  72. package/dist/lib/timing-safe-equal.d.ts +15 -0
  73. package/dist/lib/timing-safe-equal.d.ts.map +1 -0
  74. package/dist/local-peers.d.ts +151 -0
  75. package/dist/local-peers.d.ts.map +1 -0
  76. package/dist/logger.d.ts +32 -0
  77. package/dist/logger.d.ts.map +1 -0
  78. package/dist/mapeo-manager.d.ts +178 -0
  79. package/dist/mapeo-manager.d.ts.map +1 -0
  80. package/dist/mapeo-project.d.ts +3233 -0
  81. package/dist/mapeo-project.d.ts.map +1 -0
  82. package/dist/member-api.d.ts +114 -0
  83. package/dist/member-api.d.ts.map +1 -0
  84. package/dist/roles.d.ts +157 -0
  85. package/dist/roles.d.ts.map +1 -0
  86. package/dist/schema/client.d.ts +284 -0
  87. package/dist/schema/client.d.ts.map +1 -0
  88. package/dist/schema/project.d.ts +1812 -0
  89. package/dist/schema/project.d.ts.map +1 -0
  90. package/dist/schema/schema-to-drizzle.d.ts +20 -0
  91. package/dist/schema/schema-to-drizzle.d.ts.map +1 -0
  92. package/dist/schema/types.d.ts +98 -0
  93. package/dist/schema/types.d.ts.map +1 -0
  94. package/dist/schema/utils.d.ts +55 -0
  95. package/dist/schema/utils.d.ts.map +1 -0
  96. package/dist/sync/core-sync-state.d.ts +252 -0
  97. package/dist/sync/core-sync-state.d.ts.map +1 -0
  98. package/dist/sync/namespace-sync-state.d.ts +47 -0
  99. package/dist/sync/namespace-sync-state.d.ts.map +1 -0
  100. package/dist/sync/peer-sync-controller.d.ts +44 -0
  101. package/dist/sync/peer-sync-controller.d.ts.map +1 -0
  102. package/dist/sync/sync-api.d.ts +158 -0
  103. package/dist/sync/sync-api.d.ts.map +1 -0
  104. package/dist/sync/sync-state.d.ts +40 -0
  105. package/dist/sync/sync-state.d.ts.map +1 -0
  106. package/dist/translation-api.d.ts +288 -0
  107. package/dist/translation-api.d.ts.map +1 -0
  108. package/dist/types.d.ts +115 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/utils.d.ts +115 -0
  111. package/dist/utils.d.ts.map +1 -0
  112. package/dist/utils_types.d.ts +14 -0
  113. package/drizzle/client/0000_bumpy_carnage.sql +33 -0
  114. package/drizzle/client/meta/0000_snapshot.json +199 -0
  115. package/drizzle/client/meta/_journal.json +13 -0
  116. package/drizzle/project/0000_spooky_lady_ursula.sql +192 -0
  117. package/drizzle/project/meta/0000_snapshot.json +1137 -0
  118. package/drizzle/project/meta/_journal.json +13 -0
  119. package/package.json +202 -0
  120. package/src/blob-api.js +139 -0
  121. package/src/blob-store/index.js +325 -0
  122. package/src/blob-store/live-download.js +373 -0
  123. package/src/config-import.js +604 -0
  124. package/src/constants.js +34 -0
  125. package/src/core-manager/bitfield-rle.js +235 -0
  126. package/src/core-manager/core-index.js +87 -0
  127. package/src/core-manager/index.js +504 -0
  128. package/src/core-manager/random-access-file-pool.js +30 -0
  129. package/src/core-manager/remote-bitfield.js +416 -0
  130. package/src/core-ownership.js +235 -0
  131. package/src/datastore/README.md +46 -0
  132. package/src/datastore/index.js +234 -0
  133. package/src/datatype/README.md +33 -0
  134. package/src/datatype/index.d.ts +108 -0
  135. package/src/datatype/index.js +358 -0
  136. package/src/discovery/local-discovery.js +303 -0
  137. package/src/errors.js +5 -0
  138. package/src/fastify-controller.js +84 -0
  139. package/src/fastify-plugins/blobs.js +139 -0
  140. package/src/fastify-plugins/constants.js +5 -0
  141. package/src/fastify-plugins/icons.js +158 -0
  142. package/src/fastify-plugins/maps/index.js +173 -0
  143. package/src/fastify-plugins/maps/offline-fallback-map.js +114 -0
  144. package/src/fastify-plugins/maps/static-maps.js +271 -0
  145. package/src/fastify-plugins/utils.js +52 -0
  146. package/src/generated/README.md +3 -0
  147. package/src/generated/extensions.d.ts +44 -0
  148. package/src/generated/extensions.js +196 -0
  149. package/src/generated/extensions.ts +237 -0
  150. package/src/generated/keys.d.ts +36 -0
  151. package/src/generated/keys.js +148 -0
  152. package/src/generated/keys.ts +185 -0
  153. package/src/generated/rpc.d.ts +87 -0
  154. package/src/generated/rpc.js +389 -0
  155. package/src/generated/rpc.ts +463 -0
  156. package/src/icon-api.js +282 -0
  157. package/src/index-writer/README.md +38 -0
  158. package/src/index-writer/index.js +124 -0
  159. package/src/index.js +16 -0
  160. package/src/invite-api.js +450 -0
  161. package/src/lib/hashmap.js +91 -0
  162. package/src/lib/hypercore-helpers.js +18 -0
  163. package/src/lib/noise-secret-stream-helpers.js +37 -0
  164. package/src/lib/ponyfills.js +25 -0
  165. package/src/lib/string.js +7 -0
  166. package/src/lib/timing-safe-equal.js +34 -0
  167. package/src/local-peers.js +737 -0
  168. package/src/logger.js +99 -0
  169. package/src/mapeo-manager.js +914 -0
  170. package/src/mapeo-project.js +980 -0
  171. package/src/member-api.js +319 -0
  172. package/src/roles.js +412 -0
  173. package/src/schema/client.js +55 -0
  174. package/src/schema/project.js +44 -0
  175. package/src/schema/schema-to-drizzle.js +118 -0
  176. package/src/schema/types.ts +153 -0
  177. package/src/schema/utils.js +51 -0
  178. package/src/sync/core-sync-state.js +440 -0
  179. package/src/sync/namespace-sync-state.js +193 -0
  180. package/src/sync/peer-sync-controller.js +332 -0
  181. package/src/sync/sync-api.js +588 -0
  182. package/src/sync/sync-state.js +63 -0
  183. package/src/translation-api.js +141 -0
  184. package/src/types.ts +149 -0
  185. package/src/utils.js +210 -0
  186. package/src/utils_types.d.ts +14 -0
@@ -0,0 +1,282 @@
1
+ /** @import { PresetValue, IconValue } from '@comapeo/schema' */
2
+
3
+ export const kGetIconBlob = Symbol('getIcon')
4
+
5
+ /** @typedef {PresetValue['iconRef']} IconRef */
6
+ /** @typedef {IconValue['variants']} IconVariants */
7
+ /** @typedef {IconVariants[number]} IconVariant */
8
+
9
+ /**
10
+ * @typedef {Exclude<IconVariant['size'], 'size_unspecified'>} ValidSizes
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} BitmapOpts
15
+ * @property {Extract<IconVariant['mimeType'], 'image/png'>} mimeType
16
+ * @property {Extract<IconVariant, {mimeType: 'image/png'}>['pixelDensity']} pixelDensity
17
+ * @property {ValidSizes} size
18
+ *
19
+ * @typedef {Object} SvgOpts
20
+ * @property {Extract<IconVariant['mimeType'], 'image/svg+xml'>} mimeType
21
+ * @property {ValidSizes} size
22
+ */
23
+
24
+ /** @type {{ [mime in IconVariant['mimeType']]: string }} */
25
+ const MIME_TO_EXTENSION = {
26
+ 'image/png': '.png',
27
+ 'image/svg+xml': '.svg',
28
+ }
29
+
30
+ export class IconApi {
31
+ #dataType
32
+ #dataStore
33
+ #getMediaBaseUrl
34
+
35
+ /**
36
+ * @param {Object} opts
37
+ * @param {import('./datatype/index.js').DataType<
38
+ * import('./datastore/index.js').DataStore<'config'>,
39
+ * typeof import('./schema/project.js').iconTable,
40
+ * 'icon',
41
+ * import('@comapeo/schema').Icon,
42
+ * import('@comapeo/schema').IconValue
43
+ * >} opts.iconDataType
44
+ * @param {import('./datastore/index.js').DataStore<'config'>} opts.iconDataStore
45
+ * @param {() => Promise<string>} opts.getMediaBaseUrl
46
+ */
47
+ constructor({ iconDataType, iconDataStore, getMediaBaseUrl }) {
48
+ this.#dataType = iconDataType
49
+ this.#dataStore = iconDataStore
50
+ this.#getMediaBaseUrl = getMediaBaseUrl
51
+ }
52
+
53
+ /**
54
+ * @param {object} icon
55
+ * @param {import('@comapeo/schema').IconValue['name']} icon.name
56
+ * @param {Array<(BitmapOpts | SvgOpts) & { blob: Buffer }>} icon.variants
57
+ *
58
+ * @returns {Promise<import('@comapeo/schema').Icon>}
59
+ */
60
+ async create(icon) {
61
+ if (icon.variants.length < 1) {
62
+ throw new Error('empty variants array')
63
+ }
64
+
65
+ const savedVariants = await Promise.all(
66
+ icon.variants.map(async ({ blob, ...variant }) => {
67
+ const blobVersionId = await this.#dataStore.writeRaw(blob)
68
+ return { ...variant, blobVersionId }
69
+ })
70
+ )
71
+
72
+ return await this.#dataType.create({
73
+ schemaName: 'icon',
74
+ name: icon.name,
75
+ variants: savedVariants,
76
+ })
77
+ }
78
+
79
+ /**
80
+ * @param {string} iconId
81
+ * @param {BitmapOpts | SvgOpts} opts
82
+ *
83
+ * @returns {Promise<Buffer>}
84
+ */
85
+ async [kGetIconBlob](iconId, opts) {
86
+ const iconRecord = await this.#dataType.getByDocId(iconId)
87
+ const iconVariant = getBestVariant(iconRecord.variants, opts)
88
+ const blob = await this.#dataStore.readRaw(iconVariant.blobVersionId)
89
+ return blob
90
+ }
91
+
92
+ /**
93
+ * @param {string} iconId
94
+ * @param {BitmapOpts | SvgOpts} opts
95
+ *
96
+ * @returns {Promise<string>}
97
+ */
98
+ async getIconUrl(iconId, opts) {
99
+ let base = await this.#getMediaBaseUrl()
100
+
101
+ if (!base.endsWith('/')) {
102
+ base += '/'
103
+ }
104
+
105
+ const mimeExtension = MIME_TO_EXTENSION[opts.mimeType]
106
+
107
+ const pixelDensity =
108
+ opts.mimeType === 'image/svg+xml' ||
109
+ // if the pixel density is 1, we can omit the density suffix in the resulting url
110
+ // and assume the pixel density is 1 for applicable mime types when using the url
111
+ opts.pixelDensity === 1
112
+ ? undefined
113
+ : opts.pixelDensity
114
+
115
+ return (
116
+ base +
117
+ constructIconPath({
118
+ pixelDensity,
119
+ size: opts.size,
120
+ extension: mimeExtension,
121
+ iconId,
122
+ })
123
+ )
124
+ }
125
+ }
126
+
127
+ /**
128
+ * @type {Record<IconVariant['size'], number>}
129
+ */
130
+ const SIZE_AS_NUMERIC = {
131
+ // NOTE: when size is unspecified, we fallback to medium
132
+ size_unspecified: 2,
133
+ small: 1,
134
+ medium: 2,
135
+ large: 3,
136
+ }
137
+
138
+ /**
139
+ * Given a list of icon variants returns the variant that most closely matches the desired parameters.
140
+ * Rules, in order of precedence:
141
+ *
142
+ * 1. Matching mime type (throw if no matches)
143
+ * 2. Matching size. If no exact match:
144
+ * 1. If smaller ones exist, prefer closest smaller size.
145
+ * 2. Otherwise prefer closest larger size.
146
+ * 3. Matching pixel density (when asking for PNGs). If no exact match:
147
+ * 1. If smaller ones exist, prefer closest smaller density.
148
+ * 2. Otherwise prefer closest larger density.
149
+ *
150
+ * @param {IconVariants} variants
151
+ * @param {BitmapOpts | SvgOpts} opts
152
+ */
153
+ export function getBestVariant(variants, opts) {
154
+ const { size: wantedSize, mimeType: wantedMimeType } = opts
155
+ /** @type {BitmapOpts['pixelDensity']} */
156
+ let wantedPixelDensity
157
+ if (opts.mimeType === 'image/png') {
158
+ wantedPixelDensity = opts.pixelDensity
159
+ }
160
+ if (variants.length === 0) {
161
+ throw new Error('No variants exist')
162
+ }
163
+
164
+ const matchingMime = variants.filter((v) => v.mimeType === wantedMimeType)
165
+
166
+ if (matchingMime.length === 0) {
167
+ throw new Error(
168
+ `No variants with desired mime type ${wantedMimeType} exist`
169
+ )
170
+ }
171
+ const wantedSizeNum = SIZE_AS_NUMERIC[wantedSize]
172
+
173
+ // Sort the relevant variants based on the desired size and pixel density, using the rules of the preference.
174
+ // Sorted from closest match to furthest match.
175
+ matchingMime.sort((a, b) => {
176
+ const aSizeNum = SIZE_AS_NUMERIC[a.size]
177
+ const bSizeNum = SIZE_AS_NUMERIC[b.size]
178
+
179
+ const aSizeDiff = aSizeNum - wantedSizeNum
180
+ const bSizeDiff = bSizeNum - wantedSizeNum
181
+
182
+ // Both variants match desired size, use pixel density (when png) to determine preferred match
183
+ if (aSizeDiff === 0 && bSizeDiff === 0) {
184
+ // What to do if asking for an svg and both (a and b) are svgs and have the same size, what criteria do we use?
185
+ // For now, we don't change sort order
186
+ if (wantedMimeType === 'image/svg+xml') {
187
+ return 0
188
+ } else if (
189
+ wantedMimeType === 'image/png' &&
190
+ a.mimeType === 'image/png' &&
191
+ b.mimeType === 'image/png'
192
+ ) {
193
+ return determineSortValue(
194
+ wantedPixelDensity,
195
+ a.pixelDensity,
196
+ b.pixelDensity
197
+ )
198
+ }
199
+ }
200
+
201
+ return determineSortValue(wantedSizeNum, aSizeNum, bSizeNum)
202
+ })
203
+
204
+ // Closest match will be first element
205
+ return matchingMime[0]
206
+ }
207
+
208
+ /**
209
+ * Determines a sort value based on the order of precedence outlined below. Winning value moves closer to front.
210
+ *
211
+ * 1. Exactly match `target`
212
+ * 2. Closest value smaller than `target`
213
+ * 3. Closest value larger than `target`
214
+ *
215
+ * @param {number} target
216
+ * @param {number} a
217
+ * @param {number} b
218
+ *
219
+ * @returns {-1 | 0 | 1}
220
+ */
221
+ function determineSortValue(target, a, b) {
222
+ const aDiff = a - target
223
+ const bDiff = b - target
224
+
225
+ // Both match exactly, don't change sort order
226
+ if (aDiff === 0 && bDiff === 0) {
227
+ return 0
228
+ }
229
+
230
+ // a matches but b doesn't, prefer a
231
+ if (aDiff === 0 && bDiff !== 0) {
232
+ return -1
233
+ }
234
+
235
+ // b matches but a doesn't, prefer b
236
+ if (bDiff === 0 && aDiff !== 0) {
237
+ return 1
238
+ }
239
+
240
+ // Both are larger than desired, prefer smaller of the two
241
+ if (aDiff > 0 && bDiff > 0) {
242
+ return a < b ? -1 : 1
243
+ }
244
+
245
+ // Both are smaller than desired, prefer larger of the two
246
+ if (aDiff < 0 && bDiff < 0) {
247
+ return a < b ? 1 : -1
248
+ }
249
+
250
+ // Mix of smaller and larger than desired, prefer smaller of the two
251
+ return a < b ? -1 : 1
252
+ }
253
+
254
+ /**
255
+ * General purpose path builder for an icon
256
+ *
257
+ * @param {object} opts
258
+ * @param {string} opts.iconId
259
+ * @param {string} opts.size
260
+ * @param {number} [opts.pixelDensity]
261
+ * @param {string} opts.extension
262
+ *
263
+ * @returns {string}
264
+ */
265
+ export function constructIconPath({ size, pixelDensity, iconId, extension }) {
266
+ if (iconId.length === 0 || size.length === 0 || extension.length === 0) {
267
+ throw new Error('iconId, size, and extension cannot be empty strings')
268
+ }
269
+
270
+ let result = `${iconId}/${size}`
271
+
272
+ if (typeof pixelDensity === 'number') {
273
+ if (pixelDensity < 1) {
274
+ throw new Error('pixelDensity must be a positive number')
275
+ }
276
+ result += `@${pixelDensity}x`
277
+ }
278
+
279
+ result += extension.startsWith('.') ? extension : '.' + extension
280
+
281
+ return result
282
+ }
@@ -0,0 +1,38 @@
1
+ # IndexWriter
2
+
3
+ > Index documents by `docId` in a SQLite database
4
+
5
+ ## Purpose
6
+
7
+ The `IndexWriter` class resolves the DAG (Directed Acyclic Graph) for each `docId` to create a materialized view in SQLite of the current "head" for each `docId`. A single `IndexWriter` is responsible for decoding hypercore entries and writing them to the appropriate table (one table for each Mapeo data type). Unknown entries / data types are ignored.
8
+
9
+ ## Usage
10
+
11
+ `IndexWriter` is used by `DataStore` instances for writing entries that are read from the cores in the namespace managed by each `DataStore`. It exports a single method `batch(entries)` where an entry is:
12
+
13
+ ```ts
14
+ type Entry = {
15
+ block: Buffer // raw data entry read from a hypercore
16
+ key: Buffer // public key of the hypercore where the block was read from
17
+ index: number // index of the block in the hypercore
18
+ }
19
+ ```
20
+
21
+ A usage example:
22
+
23
+ ```js
24
+ const indexWriter = new IndexWriter({
25
+ tables, // Array of Drizzle table schema definitions to index
26
+ sqlite, // better-sqlite3 Database instance
27
+ })
28
+
29
+ indexWriter.batch(entries)
30
+ ```
31
+
32
+ ## API docs
33
+
34
+ TODO!
35
+
36
+ ## Tests
37
+
38
+ There are no unit tests for the IndexWriter, tests coverage will be from end-to-end / integration tests.
@@ -0,0 +1,124 @@
1
+ import { decode } from '@comapeo/schema'
2
+ import SqliteIndexer from '@mapeo/sqlite-indexer'
3
+ import { getTableConfig } from 'drizzle-orm/sqlite-core'
4
+ import { getBacklinkTableName } from '../schema/utils.js'
5
+ import { discoveryKey } from 'hypercore-crypto'
6
+ import { Logger } from '../logger.js'
7
+ /** @import { MapeoDoc, VersionIdObject } from '@comapeo/schema' */
8
+ /** @import { MapeoDocTables } from '../datatype/index.js' */
9
+
10
+ /**
11
+ * @typedef {{ [K in MapeoDoc['schemaName']]?: string[] }} IndexedDocIds
12
+ */
13
+ /**
14
+ * @typedef {ReturnType<typeof decode>} MapeoDocInternal
15
+ */
16
+
17
+ /**
18
+ * @template {MapeoDocTables} [TTables=MapeoDocTables]
19
+ */
20
+ export class IndexWriter {
21
+ /**
22
+ * @internal
23
+ * @typedef {TTables['_']['name']} SchemaName
24
+ */
25
+
26
+ /** @type {Map<SchemaName, SqliteIndexer>} */
27
+ #indexers = new Map()
28
+ #mapDoc
29
+ #l
30
+ /**
31
+ *
32
+ * @param {object} opts
33
+ * @param {import('better-sqlite3').Database} opts.sqlite
34
+ * @param {TTables[]} opts.tables
35
+ * @param {(doc: MapeoDocInternal, version: VersionIdObject) => MapeoDoc} [opts.mapDoc] optionally transform a document prior to indexing. Can also validate, if an error is thrown then the document will not be indexed
36
+ * @param {typeof import('@mapeo/sqlite-indexer').defaultGetWinner} [opts.getWinner] custom function to determine the "winner" of two forked documents. Defaults to choosing the document with the most recent `updatedAt`
37
+ * @param {Logger} [opts.logger]
38
+ */
39
+ constructor({ tables, sqlite, mapDoc = (d) => d, getWinner, logger }) {
40
+ this.#l = Logger.create('indexWriter', logger)
41
+ this.#mapDoc = mapDoc
42
+ for (const table of tables) {
43
+ const config = getTableConfig(table)
44
+ const schemaName = /** @type {(typeof table)['_']['name']} */ (
45
+ config.name
46
+ )
47
+ const indexer = new SqliteIndexer(sqlite, {
48
+ docTableName: config.name,
49
+ backlinkTableName: getBacklinkTableName(config.name),
50
+ getWinner,
51
+ })
52
+ this.#indexers.set(schemaName, indexer)
53
+ }
54
+ }
55
+
56
+ /**
57
+ * @returns {Iterable<SchemaName>}
58
+ */
59
+ get schemas() {
60
+ return this.#indexers.keys()
61
+ }
62
+
63
+ /**
64
+ * @param {import('multi-core-indexer').Entry[]} entries
65
+ * @returns {Promise<IndexedDocIds>} map of indexed docIds by schemaName
66
+ */
67
+ async batch(entries) {
68
+ // sqlite-indexer is _significantly_ faster when batching even <10 at a
69
+ // time, so best to queue docs here before calling sliteIndexer.batch()
70
+ /** @type {Record<string, MapeoDoc[]>} */
71
+ const queued = {}
72
+ /** @type {IndexedDocIds} */
73
+ const indexed = {}
74
+ for (const { block, key, index } of entries) {
75
+ /** @type {MapeoDoc} */ let doc
76
+ try {
77
+ const version = { coreDiscoveryKey: discoveryKey(key), index }
78
+ doc = this.#mapDoc(decode(block, version), version)
79
+ } catch (e) {
80
+ this.#l.log('Could not decode entry %d of %h', index, key)
81
+ // Unknown or invalid entry - silently ignore
82
+ continue
83
+ }
84
+ // Don't have an indexer for this type - silently ignore
85
+ if (!this.#indexers.has(doc.schemaName)) continue
86
+ if (queued[doc.schemaName]) {
87
+ queued[doc.schemaName].push(doc)
88
+ // @ts-expect-error - we know this is defined, TS doesn't
89
+ indexed[doc.schemaName].push(doc.docId)
90
+ } else {
91
+ queued[doc.schemaName] = [doc]
92
+ indexed[doc.schemaName] = [doc.docId]
93
+ }
94
+ }
95
+ for (const [schemaName, docs] of Object.entries(queued)) {
96
+ // @ts-expect-error
97
+ const indexer = this.#indexers.get(schemaName)
98
+ if (!indexer) continue // Won't happen, but TS doesn't know that
99
+ indexer.batch(docs)
100
+ if (this.#l.enabled) {
101
+ for (const doc of docs) {
102
+ this.#l.log(
103
+ 'Indexed %s %S @ %S',
104
+ doc.schemaName,
105
+ doc.docId,
106
+ doc.versionId
107
+ )
108
+ }
109
+ }
110
+ }
111
+ return indexed
112
+ }
113
+
114
+ /**
115
+ * @param {SchemaName} schemaName
116
+ */
117
+ deleteSchema(schemaName) {
118
+ const indexer = this.#indexers.get(schemaName)
119
+ if (!indexer) {
120
+ throw new Error(`IndexWriter doesn't know a schema named "${schemaName}"`)
121
+ }
122
+ indexer.deleteAll()
123
+ }
124
+ }
package/src/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ CREATOR_ROLE_ID,
3
+ COORDINATOR_ROLE_ID,
4
+ MEMBER_ROLE_ID,
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'
9
+ export { FastifyController } from './fastify-controller.js'
10
+ export { MapeoManager } from './mapeo-manager.js'
11
+
12
+ export const roles = /** @type {const} */ ({
13
+ CREATOR_ROLE_ID,
14
+ COORDINATOR_ROLE_ID,
15
+ MEMBER_ROLE_ID,
16
+ })