@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,141 @@
1
+ import { and, sql } from 'drizzle-orm'
2
+ import { kCreateWithDocId, kSelect } from './datatype/index.js'
3
+ import { hashObject } from './utils.js'
4
+ import { NotFoundError } from './errors.js'
5
+ /** @import { Translation, TranslationValue } from '@comapeo/schema' */
6
+ /** @import { SetOptional } from 'type-fest' */
7
+
8
+ export const ktranslatedLanguageCodeToSchemaNames = Symbol(
9
+ 'translatedLanguageCodeToSchemaNames'
10
+ )
11
+ export default class TranslationApi {
12
+ /** @type {Map<
13
+ * TranslationValue['languageCode'],
14
+ * Set<import('@comapeo/schema/dist/types.js').SchemaName>>} */
15
+ #translatedLanguageCodeToSchemaNames = new Map()
16
+ #dataType
17
+ #table
18
+ #indexPromise
19
+
20
+ /**
21
+ * @param {Object} opts
22
+ * @param {import('./datatype/index.js').DataType<
23
+ * import('./datastore/index.js').DataStore<'config'>,
24
+ * typeof import('./schema/project.js').translationTable,
25
+ * 'translation',
26
+ * Translation,
27
+ * TranslationValue
28
+ * >} opts.dataType
29
+ * @param {typeof import('./schema/project.js').translationTable} opts.table
30
+ */
31
+ constructor({ dataType, table }) {
32
+ this.#dataType = dataType
33
+ this.#table = table
34
+ this.#indexPromise = this.#dataType
35
+ .getMany()
36
+ .then((docs) => {
37
+ docs.map((doc) => this.index(doc))
38
+ })
39
+ .catch((err) => {
40
+ throw new Error(`error loading Translation cache: ${err}`)
41
+ })
42
+ }
43
+
44
+ /** @returns {Promise<void>} */
45
+ ready() {
46
+ return this.#indexPromise
47
+ }
48
+
49
+ /**
50
+ * @param {TranslationValue} value
51
+ */
52
+ async put(value) {
53
+ /* eslint-disable no-unused-vars */
54
+ const { message, ...identifiers } = value
55
+ const docId = hashObject(identifiers)
56
+ try {
57
+ const doc = await this.#dataType.getByDocId(docId)
58
+ return await this.#dataType.update(doc.versionId, value)
59
+ } catch (e) {
60
+ if (e instanceof NotFoundError) {
61
+ return await this.#dataType[kCreateWithDocId](docId, value)
62
+ } else {
63
+ throw new Error(`Error on translation ${e}`)
64
+ }
65
+ }
66
+ }
67
+
68
+ /** @typedef {SetOptional<TranslationValue['docRef'], 'versionId'>} DocRefWithOptionalVersionId */
69
+
70
+ /**
71
+ * @param {SetOptional<
72
+ * Omit<TranslationValue,'schemaName' | 'message' | 'docRef'>,
73
+ * 'propertyRef' | 'regionCode'> & {docRef: DocRefWithOptionalVersionId}} value
74
+ * @returns {Promise<import('@comapeo/schema').Translation[]>}
75
+ */
76
+ async get(value) {
77
+ await this.ready()
78
+
79
+ const docTypeIsTranslatedToLanguage =
80
+ this.#translatedLanguageCodeToSchemaNames
81
+ .get(value.languageCode)
82
+ ?.has(
83
+ /** @type {import('@comapeo/schema/dist/types.js').SchemaName} */ (
84
+ value.docRefType
85
+ )
86
+ )
87
+ if (!docTypeIsTranslatedToLanguage) return []
88
+
89
+ const filters = [
90
+ sql`docRefType = ${value.docRefType}`,
91
+ sql`languageCode = ${value.languageCode}`,
92
+ sql`json_extract(docRef, '$.docId') = ${value.docRef.docId}`,
93
+ ]
94
+
95
+ if (value.docRef?.versionId) {
96
+ filters.push(
97
+ sql`json_extract(docRef,'$.versionId') = ${value.docRef.versionId}`
98
+ )
99
+ }
100
+ if (value.propertyRef) {
101
+ filters.push(sql`propertyRef = ${value.propertyRef}`)
102
+ }
103
+ if (value.regionCode) {
104
+ filters.push(sql`regionCode = ${value.regionCode}`)
105
+ }
106
+
107
+ return (await this.#dataType[kSelect]())
108
+ .where(and.apply(null, filters))
109
+ .prepare()
110
+ .all()
111
+ }
112
+
113
+ /**
114
+ * @param {TranslationValue} doc
115
+ */
116
+ index(doc) {
117
+ let translatedSchemas = this.#translatedLanguageCodeToSchemaNames.get(
118
+ doc.languageCode
119
+ )
120
+ if (!translatedSchemas) {
121
+ translatedSchemas = new Set()
122
+ this.#translatedLanguageCodeToSchemaNames.set(
123
+ doc.languageCode,
124
+ translatedSchemas
125
+ )
126
+ }
127
+ translatedSchemas.add(
128
+ /** @type {import('@comapeo/schema/dist/types.js').SchemaName} */ (
129
+ doc.docRefType
130
+ )
131
+ )
132
+ }
133
+
134
+ // This should only be used by tests.
135
+ get [ktranslatedLanguageCodeToSchemaNames]() {
136
+ return this.#translatedLanguageCodeToSchemaNames
137
+ }
138
+ get dataType() {
139
+ return this.#dataType
140
+ }
141
+ }
package/src/types.ts ADDED
@@ -0,0 +1,149 @@
1
+ import type {
2
+ Simplify,
3
+ TupleToUnion,
4
+ ValueOf,
5
+ RequireAtLeastOne,
6
+ SetOptional,
7
+ } from 'type-fest'
8
+ import { SUPPORTED_BLOB_VARIANTS } from './blob-store/index.js'
9
+ import { MapeoCommon, MapeoDoc, MapeoValue, decode } from '@comapeo/schema'
10
+ import type BigSparseArray from 'big-sparse-array'
11
+ import type Protomux from 'protomux'
12
+ import type NoiseStream from '@hyperswarm/secret-stream'
13
+ import { Duplex } from 'streamx'
14
+ import RandomAccessStorage from 'random-access-storage'
15
+ import { DefaultListener, ListenerSignature } from 'tiny-typed-emitter'
16
+ import type { NAMESPACES } from './constants.js'
17
+
18
+ export type Namespace = (typeof NAMESPACES)[number]
19
+
20
+ type SupportedBlobVariants = typeof SUPPORTED_BLOB_VARIANTS
21
+ export type BlobType = keyof SupportedBlobVariants
22
+ export type BlobVariant<TBlobType extends BlobType> = TupleToUnion<
23
+ SupportedBlobVariants[TBlobType]
24
+ >
25
+
26
+ type BlobIdBase<T extends BlobType> = {
27
+ /** Type of blob */
28
+ type: T
29
+ /** Blob variant (some blob types have smaller previews and thumbnails available) */
30
+ variant: BlobVariant<T>
31
+ /** unique identifier for blob (e.g. hash of content) */
32
+ name: string
33
+ /** discovery key as hex string of hyperdrive where blob is stored */
34
+ driveId: string
35
+ }
36
+
37
+ // Ugly, but the only way I could figure out how to get what I wanted
38
+ export type BlobId = Simplify<
39
+ ValueOf<{
40
+ [KeyType in BlobType]: BlobIdBase<KeyType>
41
+ }>
42
+ >
43
+
44
+ type ArrayAtLeastOne<T> = [T, ...T[]]
45
+
46
+ export type BlobFilter = RequireAtLeastOne<{
47
+ [KeyType in BlobType]: ArrayAtLeastOne<BlobVariant<KeyType>>
48
+ }>
49
+
50
+ export type MapeoDocMap = {
51
+ [K in MapeoDoc['schemaName']]: Extract<MapeoDoc, { schemaName: K }>
52
+ }
53
+
54
+ export type MapeoValueMap = {
55
+ [K in MapeoValue['schemaName']]: Extract<MapeoValue, { schemaName: K }>
56
+ }
57
+
58
+ // TODO: Replace this with exports from @comapeo/schema
59
+ export type CoreOwnershipWithSignatures = Extract<
60
+ ReturnType<typeof decode>,
61
+ { schemaName: 'coreOwnership' }
62
+ >
63
+ export type CoreOwnershipWithSignaturesValue = Omit<
64
+ CoreOwnershipWithSignatures,
65
+ Exclude<keyof MapeoCommon, 'schemaName'>
66
+ >
67
+
68
+ type NullToOptional<T> = SetOptional<T, NullKeys<T>>
69
+ type RemoveNull<T> = {
70
+ [K in keyof T]: Exclude<T[K], null>
71
+ }
72
+
73
+ type NullKeys<Base> = NonNullable<
74
+ // Wrap in `NonNullable` to strip away the `undefined` type from the produced union.
75
+ {
76
+ // Map through all the keys of the given base type.
77
+ [Key in keyof Base]: null extends Base[Key] // Pick only keys with types extending the given `Condition` type.
78
+ ? // Retain this key since the condition passes.
79
+ Key
80
+ : // Discard this key since the condition fails.
81
+ never
82
+
83
+ // Convert the produced object into a union type of the keys which passed the conditional test.
84
+ }[keyof Base]
85
+ >
86
+
87
+ /**
88
+ * Replace an object's `Buffer` values with `string`s. Useful for serialization.
89
+ */
90
+ export type MapBuffers<T> = {
91
+ [K in keyof T]: T[K] extends Buffer ? string : T[K]
92
+ }
93
+
94
+ /**
95
+ * Make any properties whose value include `null` optional, and remove `null`
96
+ * from the type. This converts the types returned from SQLite (which have all
97
+ * top-level optional props set to `null`) to the original types in
98
+ * @comapeo/schema
99
+ */
100
+ export type NullableToOptional<T> = Simplify<RemoveNull<NullToOptional<T>>>
101
+ export type KeyPair = {
102
+ publicKey: PublicKey
103
+ secretKey: SecretKey
104
+ }
105
+
106
+ /** 32 byte buffer */
107
+ export type PublicKey = Buffer
108
+ /** 32 byte buffer */
109
+ export type SecretKey = Buffer
110
+ export type IdentityKeyPair = KeyPair
111
+
112
+ type HypercoreRemoteBitfieldPage = {
113
+ bitfield: Uint32Array
114
+ }
115
+
116
+ /**
117
+ * A subset of Hypercore's `RemoteBitfield` class that we use.
118
+ */
119
+ export type HypercoreRemoteBitfield = {
120
+ _pages: BigSparseArray<HypercoreRemoteBitfieldPage>
121
+ get(index: number): boolean
122
+ }
123
+
124
+ /**
125
+ * A subset of Hypercore's `Peer` class that we use.
126
+ * TODO: Contribute these types upstream.
127
+ */
128
+ export type HypercorePeer = {
129
+ protomux: Protomux
130
+ remotePublicKey: Buffer
131
+ remoteBitfield: HypercoreRemoteBitfield
132
+ onbitfield: (options: { start: number; bitfield: Buffer }) => void
133
+ onrange: (options: { drop: boolean; start: number; length: number }) => void
134
+ }
135
+
136
+ export { NoiseStream }
137
+ type ProtocolStream = Omit<NoiseStream, 'userData'> & {
138
+ userData: Protomux
139
+ }
140
+ export type ReplicationStream = Duplex & { noiseStream: ProtocolStream }
141
+
142
+ export type CoreStorage = (name: string) => RandomAccessStorage
143
+
144
+ export type DefaultEmitterEvents<
145
+ L extends ListenerSignature<L> = DefaultListener
146
+ > = {
147
+ newListener: (event: keyof L, listener: L[keyof L]) => void
148
+ removeListener: (event: keyof L, listener: L[keyof L]) => void
149
+ }
package/src/utils.js ADDED
@@ -0,0 +1,210 @@
1
+ import b4a from 'b4a'
2
+ import sodium from 'sodium-universal'
3
+ import { keyToPublicId } from '@mapeo/crypto'
4
+ import { createHash } from 'node:crypto'
5
+ import stableStringify from 'json-stable-stringify'
6
+
7
+ const PROJECT_INVITE_ID_SALT = Buffer.from('mapeo project invite id', 'ascii')
8
+
9
+ /**
10
+ * @param {String|Buffer} id
11
+ * @returns {Buffer | Uint8Array}
12
+ */
13
+ export function idToKey(id) {
14
+ if (b4a.isBuffer(id)) {
15
+ return /** @type {Buffer} */ (id)
16
+ }
17
+
18
+ return b4a.from(/** @type {String} */ (id), 'hex')
19
+ }
20
+
21
+ /**
22
+ *
23
+ * @param {Buffer|String} key
24
+ * @returns {String}
25
+ */
26
+ export function keyToId(key) {
27
+ if (typeof key === 'string') {
28
+ return key
29
+ }
30
+
31
+ return key.toString('hex')
32
+ }
33
+
34
+ /**
35
+ * @param {String} version
36
+ * @returns {{coreId: String, blockIndex: Number}}
37
+ */
38
+ export function parseVersion(version) {
39
+ const [coreId, blockIndex] = version.split('@')
40
+ return {
41
+ coreId,
42
+ blockIndex: Number(blockIndex),
43
+ }
44
+ }
45
+
46
+ export class ExhaustivenessError extends Error {
47
+ /** @param {never} value */
48
+ constructor(value) {
49
+ super(`Exhaustiveness check failed. ${value} should be impossible`)
50
+ this.name = 'ExhaustivenessError'
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @returns {void}
56
+ */
57
+ export function noop() {}
58
+
59
+ /**
60
+ * @param {unknown} condition
61
+ * @param {string} message
62
+ * @returns {asserts condition}
63
+ */
64
+ export function assert(condition, message) {
65
+ if (!condition) throw new Error(message)
66
+ }
67
+
68
+ /**
69
+ * Return a function that itself returns whether a value is part of the set.
70
+ *
71
+ * Similar to binding `Set.prototype.has`, but (1) is shorter (2) refines the type.
72
+ *
73
+ * @template T
74
+ * @param {Readonly<Set<T>>} set
75
+ * @example
76
+ * const mySet = new Set([1, 2, 3])
77
+ * const isInMySet = setHas(mySet)
78
+ *
79
+ * console.log(isInMySet(2))
80
+ * // => true
81
+ */
82
+ export function setHas(set) {
83
+ /**
84
+ * @param {unknown} value
85
+ * @returns {value is T}
86
+ */
87
+ return (value) => set.has(/** @type {*} */ (value))
88
+ }
89
+
90
+ /**
91
+ * @template T
92
+ * @param {undefined | T} value
93
+ * @returns {value is T}
94
+ */
95
+ export function isDefined(value) {
96
+ return value !== undefined
97
+ }
98
+
99
+ /**
100
+ * When reading from SQLite, any optional properties are set to `null`. This
101
+ * converts `null` back to `undefined` to match the input types (e.g. the types
102
+ * defined in @comapeo/schema)
103
+ * @template {{}} T
104
+ * @param {T} obj
105
+ * @returns {import('./types.js').NullableToOptional<T>}
106
+ */
107
+
108
+ export function deNullify(obj) {
109
+ /** @type {Record<string, any>} */
110
+ const objNoNulls = {}
111
+ for (const [key, value] of Object.entries(obj)) {
112
+ objNoNulls[key] = value === null ? undefined : value
113
+ }
114
+ return /** @type {import('./types.js').NullableToOptional<T>} */ (objNoNulls)
115
+ }
116
+
117
+ /**
118
+ * @template {import('@comapeo/schema').MapeoDoc & { forks?: string[] }} T
119
+ * @param {T} doc
120
+ * @returns {Omit<T, 'docId' | 'versionId' | 'originalVersionId' | 'links' | 'forks' | 'createdAt' | 'updatedAt' | 'deleted'>}
121
+ */
122
+ export function valueOf(doc) {
123
+ /* eslint-disable no-unused-vars */
124
+ const {
125
+ docId,
126
+ versionId,
127
+ originalVersionId,
128
+ links,
129
+ forks,
130
+ createdAt,
131
+ updatedAt,
132
+ deleted,
133
+ ...rest
134
+ } = doc
135
+ /* eslint-enable no-unused-vars */
136
+ return rest
137
+ }
138
+
139
+ /**
140
+ * Create an internal ID from a project key
141
+ * @param {Buffer} projectKey
142
+ * @returns {string}
143
+ */
144
+ export function projectKeyToId(projectKey) {
145
+ return projectKey.toString('hex')
146
+ }
147
+
148
+ /**
149
+ * Create a public ID from a project key
150
+ * @param {Buffer} projectKey
151
+ * @returns {string}
152
+ */
153
+ export function projectKeyToPublicId(projectKey) {
154
+ return keyToPublicId(projectKey)
155
+ }
156
+
157
+ /**
158
+ * Generate an invite ID from a project key
159
+ * @param {Readonly<Buffer>} projectKey
160
+ * @returns {Buffer}
161
+ */
162
+ export function projectKeyToProjectInviteId(projectKey) {
163
+ const result = Buffer.allocUnsafe(32)
164
+ sodium.crypto_generichash(result, PROJECT_INVITE_ID_SALT, projectKey)
165
+ return result
166
+ }
167
+
168
+ /**
169
+ * @param {string} projectId Project internal ID
170
+ * @returns {Buffer} 24-byte nonce (same length as sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES)
171
+ */
172
+ export function projectIdToNonce(projectId) {
173
+ return Buffer.from(projectId, 'hex').subarray(0, 24)
174
+ }
175
+
176
+ /**
177
+ * @param {import('@mapeo/crypto').KeyManager} keyManager
178
+ * @returns {string}
179
+ */
180
+ export function getDeviceId(keyManager) {
181
+ return keyManager.getIdentityKeypair().publicKey.toString('hex')
182
+ }
183
+
184
+ /**
185
+ * Small helper to create a typed map
186
+ *
187
+ * @template {string} K
188
+ * @template {any} V
189
+ * @param {ReadonlyArray<K>} keys
190
+ * @param {V} value
191
+ * @returns {Record<K, V extends () => infer T ? T : V>} */
192
+ export function createMap(keys, value) {
193
+ const map = /** @type {Record<K, V extends () => infer T ? T : V>} */ ({})
194
+ for (const key of keys) {
195
+ map[key] = typeof value === 'function' ? value() : value
196
+ }
197
+ return map
198
+ }
199
+
200
+ /**
201
+ * create a sha256 hash of an object using json-stable-stringify for deterministic results
202
+ * @param {Object} obj
203
+ * @returns {String} hash of the object
204
+ */
205
+ export function hashObject(obj) {
206
+ return createHash('sha256')
207
+ .update(stableStringify(obj))
208
+ .digest()
209
+ .toString('hex')
210
+ }
@@ -0,0 +1,14 @@
1
+ import type { TypedEmitter } from 'tiny-typed-emitter'
2
+
3
+ export type TypedEvents<T extends TypedEmitter<any>> = T extends TypedEmitter<
4
+ infer Result
5
+ >
6
+ ? Result
7
+ : never
8
+
9
+ export type TypedEventsFor<T extends TypedEmitter<any>> = keyof TypedEvents<T>
10
+
11
+ export type TypedEventArgs<
12
+ Emitter extends TypedEmitter<any>,
13
+ Event extends TypedEventsFor<Emitter>
14
+ > = Parameters<TypedEvents<Emitter>[Event]>