effect 4.0.0-beta.44 → 4.0.0-beta.45

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 (151) hide show
  1. package/dist/Equal.d.ts.map +1 -1
  2. package/dist/Equal.js +16 -0
  3. package/dist/Equal.js.map +1 -1
  4. package/dist/Hash.js +1 -1
  5. package/dist/Hash.js.map +1 -1
  6. package/dist/Semaphore.d.ts +1 -1
  7. package/dist/Semaphore.d.ts.map +1 -1
  8. package/dist/Semaphore.js +1 -3
  9. package/dist/Semaphore.js.map +1 -1
  10. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  11. package/dist/unstable/ai/McpServer.js +24 -21
  12. package/dist/unstable/ai/McpServer.js.map +1 -1
  13. package/dist/unstable/eventlog/Event.d.ts +0 -6
  14. package/dist/unstable/eventlog/Event.d.ts.map +1 -1
  15. package/dist/unstable/eventlog/Event.js +0 -5
  16. package/dist/unstable/eventlog/Event.js.map +1 -1
  17. package/dist/unstable/eventlog/EventGroup.d.ts +0 -2
  18. package/dist/unstable/eventlog/EventGroup.d.ts.map +1 -1
  19. package/dist/unstable/eventlog/EventGroup.js +0 -2
  20. package/dist/unstable/eventlog/EventGroup.js.map +1 -1
  21. package/dist/unstable/eventlog/EventJournal.d.ts +22 -5
  22. package/dist/unstable/eventlog/EventJournal.d.ts.map +1 -1
  23. package/dist/unstable/eventlog/EventJournal.js +126 -67
  24. package/dist/unstable/eventlog/EventJournal.js.map +1 -1
  25. package/dist/unstable/eventlog/EventLog.d.ts +88 -34
  26. package/dist/unstable/eventlog/EventLog.d.ts.map +1 -1
  27. package/dist/unstable/eventlog/EventLog.js +215 -141
  28. package/dist/unstable/eventlog/EventLog.js.map +1 -1
  29. package/dist/unstable/eventlog/EventLogEncryption.d.ts +9 -7
  30. package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
  31. package/dist/unstable/eventlog/EventLogEncryption.js +13 -15
  32. package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
  33. package/dist/unstable/eventlog/EventLogMessage.d.ts +228 -0
  34. package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -0
  35. package/dist/unstable/eventlog/EventLogMessage.js +214 -0
  36. package/dist/unstable/eventlog/EventLogMessage.js.map +1 -0
  37. package/dist/unstable/eventlog/EventLogRemote.d.ts +109 -194
  38. package/dist/unstable/eventlog/EventLogRemote.d.ts.map +1 -1
  39. package/dist/unstable/eventlog/EventLogRemote.js +165 -320
  40. package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
  41. package/dist/unstable/eventlog/EventLogServer.d.ts +25 -47
  42. package/dist/unstable/eventlog/EventLogServer.d.ts.map +1 -1
  43. package/dist/unstable/eventlog/EventLogServer.js +127 -198
  44. package/dist/unstable/eventlog/EventLogServer.js.map +1 -1
  45. package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts +60 -0
  46. package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts.map +1 -0
  47. package/dist/unstable/eventlog/EventLogServerEncrypted.js +166 -0
  48. package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -0
  49. package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts +183 -0
  50. package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts.map +1 -0
  51. package/dist/unstable/eventlog/EventLogServerUnencrypted.js +461 -0
  52. package/dist/unstable/eventlog/EventLogServerUnencrypted.js.map +1 -0
  53. package/dist/unstable/eventlog/EventLogSessionAuth.d.ts +117 -0
  54. package/dist/unstable/eventlog/EventLogSessionAuth.d.ts.map +1 -0
  55. package/dist/unstable/eventlog/EventLogSessionAuth.js +284 -0
  56. package/dist/unstable/eventlog/EventLogSessionAuth.js.map +1 -0
  57. package/dist/unstable/eventlog/{SqlEventLogJournal.d.ts → SqlEventJournal.d.ts} +2 -2
  58. package/dist/unstable/eventlog/SqlEventJournal.d.ts.map +1 -0
  59. package/dist/unstable/eventlog/{SqlEventLogJournal.js → SqlEventJournal.js} +20 -14
  60. package/dist/unstable/eventlog/SqlEventJournal.js.map +1 -0
  61. package/dist/unstable/eventlog/{SqlEventLogServer.d.ts → SqlEventLogServerEncrypted.d.ts} +5 -5
  62. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -0
  63. package/dist/unstable/eventlog/{SqlEventLogServer.js → SqlEventLogServerEncrypted.js} +65 -24
  64. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.js.map +1 -0
  65. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts +25 -0
  66. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts.map +1 -0
  67. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js +354 -0
  68. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js.map +1 -0
  69. package/dist/unstable/eventlog/index.d.ts +22 -2
  70. package/dist/unstable/eventlog/index.d.ts.map +1 -1
  71. package/dist/unstable/eventlog/index.js +22 -2
  72. package/dist/unstable/eventlog/index.js.map +1 -1
  73. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts +2 -0
  74. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts.map +1 -0
  75. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js +89 -0
  76. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js.map +1 -0
  77. package/dist/unstable/reactivity/AtomHttpApi.d.ts +1 -2
  78. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  79. package/dist/unstable/reactivity/AtomHttpApi.js +2 -2
  80. package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
  81. package/dist/unstable/reactivity/AtomRpc.d.ts +1 -2
  82. package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
  83. package/dist/unstable/reactivity/AtomRpc.js +3 -3
  84. package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
  85. package/dist/unstable/rpc/Rpc.d.ts +25 -4
  86. package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
  87. package/dist/unstable/rpc/Rpc.js +26 -0
  88. package/dist/unstable/rpc/Rpc.js.map +1 -1
  89. package/dist/unstable/rpc/RpcClient.d.ts +3 -13
  90. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  91. package/dist/unstable/rpc/RpcClient.js +47 -23
  92. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  93. package/dist/unstable/rpc/RpcGroup.d.ts +1 -1
  94. package/dist/unstable/rpc/RpcGroup.d.ts.map +1 -1
  95. package/dist/unstable/rpc/RpcMiddleware.d.ts +2 -2
  96. package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
  97. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  98. package/dist/unstable/rpc/RpcServer.js +3 -2
  99. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  100. package/dist/unstable/rpc/Utils.d.ts +6 -0
  101. package/dist/unstable/rpc/Utils.d.ts.map +1 -1
  102. package/dist/unstable/rpc/Utils.js +44 -0
  103. package/dist/unstable/rpc/Utils.js.map +1 -1
  104. package/dist/unstable/schema/Model.d.ts +2 -2
  105. package/dist/unstable/schema/Model.d.ts.map +1 -1
  106. package/dist/unstable/schema/Model.js +2 -4
  107. package/dist/unstable/schema/Model.js.map +1 -1
  108. package/dist/unstable/schema/VariantSchema.d.ts +1 -1
  109. package/dist/unstable/schema/VariantSchema.d.ts.map +1 -1
  110. package/dist/unstable/schema/VariantSchema.js +1 -12
  111. package/dist/unstable/schema/VariantSchema.js.map +1 -1
  112. package/dist/unstable/workers/Transferable.d.ts +1 -1
  113. package/dist/unstable/workers/Transferable.d.ts.map +1 -1
  114. package/dist/unstable/workers/Transferable.js +1 -1
  115. package/dist/unstable/workers/Transferable.js.map +1 -1
  116. package/package.json +1 -1
  117. package/src/Equal.ts +17 -0
  118. package/src/Hash.ts +2 -2
  119. package/src/Semaphore.ts +2 -4
  120. package/src/unstable/ai/McpServer.ts +24 -22
  121. package/src/unstable/eventlog/Event.ts +0 -8
  122. package/src/unstable/eventlog/EventGroup.ts +0 -4
  123. package/src/unstable/eventlog/EventJournal.ts +144 -76
  124. package/src/unstable/eventlog/EventLog.ts +342 -221
  125. package/src/unstable/eventlog/EventLogEncryption.ts +16 -30
  126. package/src/unstable/eventlog/EventLogMessage.ts +277 -0
  127. package/src/unstable/eventlog/EventLogRemote.ts +261 -408
  128. package/src/unstable/eventlog/EventLogServer.ts +182 -274
  129. package/src/unstable/eventlog/EventLogServerEncrypted.ts +206 -0
  130. package/src/unstable/eventlog/EventLogServerUnencrypted.ts +749 -0
  131. package/src/unstable/eventlog/EventLogSessionAuth.ts +437 -0
  132. package/src/unstable/eventlog/{SqlEventLogJournal.ts → SqlEventJournal.ts} +26 -18
  133. package/src/unstable/eventlog/{SqlEventLogServer.ts → SqlEventLogServerEncrypted.ts} +102 -40
  134. package/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts +500 -0
  135. package/src/unstable/eventlog/index.ts +27 -2
  136. package/src/unstable/eventlog/internal/identityRootSecretDerivation.ts +153 -0
  137. package/src/unstable/reactivity/AtomHttpApi.ts +23 -8
  138. package/src/unstable/reactivity/AtomRpc.ts +16 -5
  139. package/src/unstable/rpc/Rpc.ts +42 -4
  140. package/src/unstable/rpc/RpcClient.ts +59 -24
  141. package/src/unstable/rpc/RpcGroup.ts +1 -1
  142. package/src/unstable/rpc/RpcMiddleware.ts +2 -2
  143. package/src/unstable/rpc/RpcServer.ts +5 -3
  144. package/src/unstable/rpc/Utils.ts +59 -0
  145. package/src/unstable/schema/Model.ts +4 -6
  146. package/src/unstable/schema/VariantSchema.ts +4 -17
  147. package/src/unstable/workers/Transferable.ts +9 -11
  148. package/dist/unstable/eventlog/SqlEventLogJournal.d.ts.map +0 -1
  149. package/dist/unstable/eventlog/SqlEventLogJournal.js.map +0 -1
  150. package/dist/unstable/eventlog/SqlEventLogServer.d.ts.map +0 -1
  151. package/dist/unstable/eventlog/SqlEventLogServer.js.map +0 -1
@@ -0,0 +1,500 @@
1
+ /**
2
+ * @since 4.0.0
3
+ */
4
+ import * as Arr from "../../Array.ts"
5
+ import * as Effect from "../../Effect.ts"
6
+ import * as Layer from "../../Layer.ts"
7
+ import * as PubSub from "../../PubSub.ts"
8
+ import * as RcMap from "../../RcMap.ts"
9
+ import * as Schema from "../../Schema.ts"
10
+ import type * as Scope from "../../Scope.ts"
11
+ import * as Stream from "../../Stream.ts"
12
+ import * as SqlClient from "../sql/SqlClient.ts"
13
+ import * as SqlError from "../sql/SqlError.ts"
14
+ import { Entry, EntryId, makeRemoteIdUnsafe, RemoteEntry, type RemoteId } from "./EventJournal.ts"
15
+ import * as EventLogServerUnencrypted from "./EventLogServerUnencrypted.ts"
16
+
17
+ /**
18
+ * @since 4.0.0
19
+ * @category constructors
20
+ */
21
+ export const makeStorage = (options?: {
22
+ readonly entryTablePrefix?: string
23
+ readonly remoteIdTable?: string
24
+ readonly insertBatchSize?: number
25
+ }): Effect.Effect<
26
+ EventLogServerUnencrypted.Storage["Service"],
27
+ SqlError.SqlError,
28
+ SqlClient.SqlClient | Scope.Scope
29
+ > =>
30
+ Effect.gen(function*() {
31
+ const sql = (yield* SqlClient.SqlClient).withoutTransforms()
32
+
33
+ const entriesTable = options?.entryTablePrefix ?? "effect_events"
34
+ const remoteIdTable = options?.remoteIdTable ?? "effect_remote_id"
35
+ const insertBatchSize = options?.insertBatchSize ?? 200
36
+ const storesTable = `${entriesTable}_stores`
37
+ const sessionAuthBindingsTable = `${entriesTable}_session_auth_bindings`
38
+
39
+ const remoteIdTableSql = sql(remoteIdTable)
40
+ const entriesTableSql = sql(entriesTable)
41
+ const storesTableSql = sql(storesTable)
42
+ const sessionAuthBindingsTableSql = sql(sessionAuthBindingsTable)
43
+
44
+ yield* sql.onDialectOrElse({
45
+ pg: () =>
46
+ sql`
47
+ CREATE TABLE IF NOT EXISTS ${remoteIdTableSql} (
48
+ singleton INT PRIMARY KEY,
49
+ remote_id BYTEA NOT NULL
50
+ )`,
51
+ mysql: () =>
52
+ sql`
53
+ CREATE TABLE IF NOT EXISTS ${remoteIdTableSql} (
54
+ singleton INT PRIMARY KEY,
55
+ remote_id BINARY(16) NOT NULL
56
+ )`,
57
+ mssql: () =>
58
+ sql`
59
+ CREATE TABLE IF NOT EXISTS ${remoteIdTableSql} (
60
+ singleton INT PRIMARY KEY,
61
+ remote_id VARBINARY(16) NOT NULL
62
+ )`,
63
+ orElse: () =>
64
+ sql`
65
+ CREATE TABLE IF NOT EXISTS ${remoteIdTableSql} (
66
+ singleton INTEGER PRIMARY KEY,
67
+ remote_id BLOB NOT NULL
68
+ )`
69
+ })
70
+
71
+ yield* sql.onDialectOrElse({
72
+ pg: () =>
73
+ sql`
74
+ CREATE TABLE IF NOT EXISTS ${entriesTableSql} (
75
+ store_id TEXT NOT NULL,
76
+ sequence BIGINT NOT NULL,
77
+ entry_id BYTEA NOT NULL,
78
+ event TEXT NOT NULL,
79
+ primary_key TEXT NOT NULL,
80
+ payload BYTEA NOT NULL,
81
+ PRIMARY KEY (store_id, sequence),
82
+ UNIQUE (store_id, entry_id)
83
+ )`,
84
+ mysql: () =>
85
+ sql`
86
+ CREATE TABLE IF NOT EXISTS ${entriesTableSql} (
87
+ store_id VARCHAR(191) NOT NULL,
88
+ sequence BIGINT NOT NULL,
89
+ entry_id BINARY(16) NOT NULL,
90
+ event TEXT NOT NULL,
91
+ primary_key TEXT NOT NULL,
92
+ payload BLOB NOT NULL,
93
+ PRIMARY KEY (store_id, sequence),
94
+ UNIQUE (store_id, entry_id)
95
+ )`,
96
+ mssql: () =>
97
+ sql`
98
+ CREATE TABLE IF NOT EXISTS ${entriesTableSql} (
99
+ store_id NVARCHAR(191) NOT NULL,
100
+ sequence BIGINT NOT NULL,
101
+ entry_id VARBINARY(16) NOT NULL,
102
+ event NVARCHAR(MAX) NOT NULL,
103
+ primary_key NVARCHAR(MAX) NOT NULL,
104
+ payload VARBINARY(MAX) NOT NULL,
105
+ PRIMARY KEY (store_id, sequence),
106
+ UNIQUE (store_id, entry_id)
107
+ )`,
108
+ orElse: () =>
109
+ sql`
110
+ CREATE TABLE IF NOT EXISTS ${entriesTableSql} (
111
+ store_id TEXT NOT NULL,
112
+ sequence INTEGER NOT NULL,
113
+ entry_id BLOB NOT NULL,
114
+ event TEXT NOT NULL,
115
+ primary_key TEXT NOT NULL,
116
+ payload BLOB NOT NULL,
117
+ PRIMARY KEY (store_id, sequence),
118
+ UNIQUE (store_id, entry_id)
119
+ )`
120
+ })
121
+
122
+ yield* sql.onDialectOrElse({
123
+ pg: () =>
124
+ sql`
125
+ CREATE TABLE IF NOT EXISTS ${storesTableSql} (
126
+ store_id TEXT PRIMARY KEY,
127
+ next_sequence BIGINT NOT NULL
128
+ )`,
129
+ mysql: () =>
130
+ sql`
131
+ CREATE TABLE IF NOT EXISTS ${storesTableSql} (
132
+ store_id VARCHAR(191) PRIMARY KEY,
133
+ next_sequence BIGINT NOT NULL
134
+ )`,
135
+ mssql: () =>
136
+ sql`
137
+ CREATE TABLE IF NOT EXISTS ${storesTableSql} (
138
+ store_id NVARCHAR(191) PRIMARY KEY,
139
+ next_sequence BIGINT NOT NULL
140
+ )`,
141
+ orElse: () =>
142
+ sql`
143
+ CREATE TABLE IF NOT EXISTS ${storesTableSql} (
144
+ store_id TEXT PRIMARY KEY,
145
+ next_sequence INTEGER NOT NULL
146
+ )`
147
+ })
148
+
149
+ yield* sql.onDialectOrElse({
150
+ pg: () =>
151
+ sql`
152
+ CREATE TABLE IF NOT EXISTS ${sessionAuthBindingsTableSql} (
153
+ public_key TEXT PRIMARY KEY,
154
+ signing_public_key BYTEA NOT NULL
155
+ )`,
156
+ mysql: () =>
157
+ sql`
158
+ CREATE TABLE IF NOT EXISTS ${sessionAuthBindingsTableSql} (
159
+ public_key VARCHAR(191) PRIMARY KEY,
160
+ signing_public_key BINARY(32) NOT NULL
161
+ )`,
162
+ mssql: () =>
163
+ sql`
164
+ CREATE TABLE IF NOT EXISTS ${sessionAuthBindingsTableSql} (
165
+ public_key NVARCHAR(191) PRIMARY KEY,
166
+ signing_public_key VARBINARY(32) NOT NULL
167
+ )`,
168
+ orElse: () =>
169
+ sql`
170
+ CREATE TABLE IF NOT EXISTS ${sessionAuthBindingsTableSql} (
171
+ public_key TEXT PRIMARY KEY,
172
+ signing_public_key BLOB NOT NULL
173
+ )`
174
+ })
175
+
176
+ const selectRemoteId = sql<{ remote_id: Uint8Array }>`
177
+ SELECT remote_id
178
+ FROM ${remoteIdTableSql}
179
+ WHERE singleton = 1
180
+ `
181
+
182
+ const remoteId = yield* selectRemoteId.pipe(
183
+ Effect.flatMap((rows) => {
184
+ const existing = rows[0]
185
+ if (existing !== undefined) {
186
+ return Effect.succeed(existing.remote_id as RemoteId)
187
+ }
188
+
189
+ const created = makeRemoteIdUnsafe()
190
+ return sql`
191
+ INSERT INTO ${remoteIdTableSql} (singleton, remote_id)
192
+ VALUES (1, ${created})
193
+ `.pipe(
194
+ Effect.catchIf(
195
+ (error: SqlError.SqlError) => error.reason._tag === "ConstraintError",
196
+ () => Effect.void
197
+ ),
198
+ Effect.andThen(selectRemoteId),
199
+ Effect.map((rows) => rows[0]?.remote_id as RemoteId | undefined),
200
+ Effect.map((persisted) => persisted ?? created)
201
+ )
202
+ })
203
+ )
204
+
205
+ const pubsubs = yield* RcMap.make({
206
+ lookup: (_storeId: string) =>
207
+ Effect.acquireRelease(
208
+ PubSub.unbounded<RemoteEntry>(),
209
+ PubSub.shutdown
210
+ ),
211
+ idleTimeToLive: "5 minutes"
212
+ })
213
+
214
+ const ensureStore = (storeId: string) =>
215
+ sql.onDialectOrElse({
216
+ pg: () =>
217
+ sql`
218
+ INSERT INTO ${storesTableSql} (store_id, next_sequence)
219
+ VALUES (${storeId}, 1)
220
+ ON CONFLICT (store_id) DO NOTHING
221
+ `,
222
+ mysql: () =>
223
+ sql`
224
+ INSERT INTO ${storesTableSql} (store_id, next_sequence)
225
+ VALUES (${storeId}, 1)
226
+ ON DUPLICATE KEY UPDATE store_id = store_id
227
+ `,
228
+ mssql: () =>
229
+ sql`
230
+ MERGE ${storesTableSql} WITH (HOLDLOCK) AS target
231
+ USING (SELECT ${storeId} AS store_id, 1 AS next_sequence) AS source
232
+ ON target.store_id = source.store_id
233
+ WHEN NOT MATCHED THEN
234
+ INSERT (store_id, next_sequence)
235
+ VALUES (source.store_id, source.next_sequence);
236
+ `,
237
+ orElse: () =>
238
+ sql`
239
+ INSERT INTO ${storesTableSql} (store_id, next_sequence)
240
+ VALUES (${storeId}, 1)
241
+ ON CONFLICT DO NOTHING
242
+ `
243
+ })
244
+
245
+ const lockStore = (storeId: string) =>
246
+ sql.onDialectOrElse({
247
+ pg: () =>
248
+ sql`
249
+ SELECT next_sequence
250
+ FROM ${storesTableSql}
251
+ WHERE store_id = ${storeId}
252
+ FOR UPDATE
253
+ `,
254
+ mysql: () =>
255
+ sql`
256
+ SELECT next_sequence
257
+ FROM ${storesTableSql}
258
+ WHERE store_id = ${storeId}
259
+ FOR UPDATE
260
+ `,
261
+ mssql: () =>
262
+ sql`
263
+ SELECT next_sequence
264
+ FROM ${storesTableSql} WITH (UPDLOCK, HOLDLOCK)
265
+ WHERE store_id = ${storeId}
266
+ `,
267
+ orElse: () =>
268
+ sql`
269
+ UPDATE ${storesTableSql}
270
+ SET next_sequence = next_sequence
271
+ WHERE store_id = ${storeId}
272
+ RETURNING next_sequence
273
+ `
274
+ }).pipe(
275
+ Effect.flatMap(decodeStoreSequence)
276
+ )
277
+
278
+ const setNextSequence = (storeId: string, nextSequence: number) =>
279
+ sql`
280
+ UPDATE ${storesTableSql}
281
+ SET next_sequence = ${nextSequence}
282
+ WHERE store_id = ${storeId}
283
+ `
284
+
285
+ const selectEntriesAfter = (storeId: string, startSequence: number) =>
286
+ sql`
287
+ SELECT sequence, entry_id, event, primary_key, payload
288
+ FROM ${entriesTableSql}
289
+ WHERE store_id = ${storeId} AND sequence >= ${startSequence}
290
+ ORDER BY sequence ASC
291
+ `.pipe(
292
+ Effect.flatMap(decodeRemoteEntries)
293
+ )
294
+
295
+ const getSessionAuthBinding = (publicKey: string) =>
296
+ sql`
297
+ SELECT public_key, signing_public_key
298
+ FROM ${sessionAuthBindingsTableSql}
299
+ WHERE public_key = ${publicKey}
300
+ `.pipe(
301
+ Effect.flatMap(decodeSessionAuthBindings),
302
+ Effect.map((rows) => {
303
+ const row = rows[0]
304
+ return row === undefined ? undefined : row.signing_public_key as Uint8Array<ArrayBuffer>
305
+ }),
306
+ Effect.orDie
307
+ )
308
+
309
+ return EventLogServerUnencrypted.Storage.of({
310
+ getId: Effect.succeed(remoteId),
311
+ getOrCreateSessionAuthBinding: Effect.fnUntraced(
312
+ function*(publicKey, signingPublicKey) {
313
+ const existing = yield* getSessionAuthBinding(publicKey)
314
+ if (existing !== undefined) {
315
+ return existing
316
+ }
317
+ return yield* sql`
318
+ INSERT INTO ${sessionAuthBindingsTableSql} (public_key, signing_public_key)
319
+ VALUES (${publicKey}, ${signingPublicKey})
320
+ `.pipe(
321
+ Effect.as(signingPublicKey)
322
+ )
323
+ },
324
+ sql.withTransaction,
325
+ Effect.orDie
326
+ ),
327
+ entriesAfter: (storeId, entry) =>
328
+ sql`
329
+ SELECT sequence, entry_id, event, primary_key, payload
330
+ FROM ${entriesTableSql}
331
+ WHERE store_id = ${storeId} AND entry_id >= ${entry.id}
332
+ ORDER BY sequence ASC
333
+ `.pipe(
334
+ Effect.flatMap(decodeRemoteEntries),
335
+ Effect.map(Arr.map((r) => r.entry)),
336
+ Effect.orDie
337
+ ),
338
+ write: Effect.fnUntraced(
339
+ function*(storeId, entries) {
340
+ if (entries.length === 0) {
341
+ return []
342
+ }
343
+
344
+ yield* ensureStore(storeId)
345
+ const currentNextSequence = yield* lockStore(storeId)
346
+
347
+ const committed: Array<RemoteEntry> = []
348
+ let rowsForInsert: Array<{
349
+ readonly store_id: string
350
+ readonly sequence: number
351
+ readonly entry_id: Uint8Array
352
+ readonly event: string
353
+ readonly primary_key: string
354
+ readonly payload: Uint8Array
355
+ }> = []
356
+
357
+ for (let index = 0; index < entries.length; index++) {
358
+ const entry = entries[index]
359
+ const remoteSequence = currentNextSequence + index
360
+ committed.push(
361
+ new RemoteEntry({
362
+ remoteSequence,
363
+ entry
364
+ }, { disableChecks: true })
365
+ )
366
+ rowsForInsert.push({
367
+ store_id: storeId,
368
+ sequence: remoteSequence,
369
+ entry_id: entry.id,
370
+ event: entry.event,
371
+ primary_key: entry.primaryKey,
372
+ payload: entry.payload
373
+ })
374
+ if (rowsForInsert.length >= insertBatchSize) {
375
+ yield* sql`INSERT INTO ${entriesTableSql} ${sql.insert(rowsForInsert)}`
376
+ rowsForInsert = []
377
+ }
378
+ }
379
+ if (rowsForInsert.length > 0) {
380
+ yield* sql`INSERT INTO ${entriesTableSql} ${sql.insert(rowsForInsert)}`
381
+ }
382
+ const nextSequence = currentNextSequence + entries.length
383
+ yield* setNextSequence(storeId, nextSequence)
384
+
385
+ const pubsub = yield* RcMap.get(pubsubs, storeId)
386
+ yield* PubSub.publishAll(pubsub, committed)
387
+
388
+ return committed
389
+ },
390
+ Effect.scoped,
391
+ sql.withTransaction,
392
+ Effect.orDie
393
+ ),
394
+ changes: Effect.fnUntraced(
395
+ function*({ storeId, startSequence, compactors }) {
396
+ const pubsub = yield* RcMap.get(pubsubs, storeId)
397
+ const subscription = yield* PubSub.subscribe(pubsub)
398
+ const backlog = yield* EventLogServerUnencrypted.compactBacklog({
399
+ compactors,
400
+ remoteEntries: yield* selectEntriesAfter(storeId, startSequence)
401
+ })
402
+ let watermark = backlog.length > 0 ? backlog[backlog.length - 1]!.remoteSequence : startSequence
403
+
404
+ return Stream.fromArray(backlog).pipe(
405
+ Stream.concat(
406
+ Stream.fromSubscription(subscription).pipe(
407
+ Stream.filter((entry) => entry.remoteSequence > watermark)
408
+ )
409
+ )
410
+ )
411
+ },
412
+ Effect.orDie,
413
+ Stream.unwrap
414
+ ),
415
+ withTransaction: (effect) =>
416
+ sql.withTransaction(effect).pipe(
417
+ Effect.catchIf(SqlError.isSqlError, Effect.die)
418
+ )
419
+ })
420
+ })
421
+
422
+ /**
423
+ * @since 4.0.0
424
+ * @category layers
425
+ */
426
+ export const layerStorage = (options?: {
427
+ readonly entryTablePrefix?: string
428
+ readonly remoteIdTable?: string
429
+ readonly insertBatchSize?: number
430
+ }): Layer.Layer<EventLogServerUnencrypted.Storage, SqlError.SqlError, SqlClient.SqlClient> =>
431
+ Layer.effect(EventLogServerUnencrypted.Storage)(makeStorage(options))
432
+
433
+ const EntrySql = Schema.Struct({
434
+ entry_id: EntryId,
435
+ event: Schema.String,
436
+ primary_key: Schema.String,
437
+ payload: Schema.Uint8Array
438
+ })
439
+
440
+ type EntrySql = Schema.Schema.Type<typeof EntrySql>
441
+
442
+ const SqlNumber = Schema.Union([Schema.Number, Schema.NumberFromString])
443
+
444
+ const RemoteEntrySql = Schema.Struct({
445
+ ...EntrySql.fields,
446
+ sequence: SqlNumber
447
+ })
448
+
449
+ type RemoteEntrySql = Schema.Schema.Type<typeof RemoteEntrySql>
450
+
451
+ const StoreSequenceSql = Schema.Struct({
452
+ next_sequence: SqlNumber
453
+ })
454
+
455
+ const SessionAuthBindingSql = Schema.Struct({
456
+ public_key: Schema.String,
457
+ signing_public_key: Schema.Uint8Array
458
+ })
459
+
460
+ type SessionAuthBindingSql = Schema.Schema.Type<typeof SessionAuthBindingSql>
461
+
462
+ const decodeRemoteEntryRows = Schema.decodeUnknownEffect(Schema.mutable(Schema.Array(RemoteEntrySql)))
463
+ const decodeStoreSequenceRows = Schema.decodeUnknownEffect(Schema.Array(StoreSequenceSql))
464
+ const decodeSessionAuthBindingRows = Schema.decodeUnknownEffect(Schema.Array(SessionAuthBindingSql))
465
+
466
+ const toEntry = (row: EntrySql): Entry =>
467
+ new Entry({
468
+ id: row.entry_id,
469
+ event: row.event,
470
+ primaryKey: row.primary_key,
471
+ payload: row.payload
472
+ }, { disableChecks: true })
473
+
474
+ const toRemoteEntry = (row: RemoteEntrySql): RemoteEntry =>
475
+ new RemoteEntry({
476
+ remoteSequence: row.sequence,
477
+ entry: toEntry(row)
478
+ }, { disableChecks: true })
479
+
480
+ const decodeRemoteEntries = (
481
+ rows: unknown
482
+ ): Effect.Effect<Array<RemoteEntry>, Schema.SchemaError> =>
483
+ decodeRemoteEntryRows(rows).pipe(
484
+ Effect.map((rows) => rows.map(toRemoteEntry))
485
+ )
486
+
487
+ const decodeStoreSequence = (rows: unknown): Effect.Effect<number, Schema.SchemaError> =>
488
+ decodeStoreSequenceRows(rows).pipe(
489
+ Effect.flatMap((rows) => {
490
+ const row = rows[0]
491
+ if (row === undefined) {
492
+ return Effect.die("SqlEventLogServerUnencrypted missing store sequence row")
493
+ }
494
+ return Effect.succeed(row.next_sequence)
495
+ })
496
+ )
497
+
498
+ const decodeSessionAuthBindings = (
499
+ rows: unknown
500
+ ): Effect.Effect<ReadonlyArray<SessionAuthBindingSql>, Schema.SchemaError> => decodeSessionAuthBindingRows(rows)
@@ -29,6 +29,11 @@ export * as EventLog from "./EventLog.ts"
29
29
  */
30
30
  export * as EventLogEncryption from "./EventLogEncryption.ts"
31
31
 
32
+ /**
33
+ * @since 4.0.0
34
+ */
35
+ export * as EventLogMessage from "./EventLogMessage.ts"
36
+
32
37
  /**
33
38
  * @since 4.0.0
34
39
  */
@@ -42,9 +47,29 @@ export * as EventLogServer from "./EventLogServer.ts"
42
47
  /**
43
48
  * @since 4.0.0
44
49
  */
45
- export * as SqlEventLogJournal from "./SqlEventLogJournal.ts"
50
+ export * as EventLogServerEncrypted from "./EventLogServerEncrypted.ts"
51
+
52
+ /**
53
+ * @since 4.0.0
54
+ */
55
+ export * as EventLogServerUnencrypted from "./EventLogServerUnencrypted.ts"
56
+
57
+ /**
58
+ * @since 4.0.0
59
+ */
60
+ export * as EventLogSessionAuth from "./EventLogSessionAuth.ts"
61
+
62
+ /**
63
+ * @since 4.0.0
64
+ */
65
+ export * as SqlEventJournal from "./SqlEventJournal.ts"
66
+
67
+ /**
68
+ * @since 4.0.0
69
+ */
70
+ export * as SqlEventLogServerEncrypted from "./SqlEventLogServerEncrypted.ts"
46
71
 
47
72
  /**
48
73
  * @since 4.0.0
49
74
  */
50
- export * as SqlEventLogServer from "./SqlEventLogServer.ts"
75
+ export * as SqlEventLogServerUnencrypted from "./SqlEventLogServerUnencrypted.ts"
@@ -0,0 +1,153 @@
1
+ import * as Effect from "../../../Effect.ts"
2
+ import * as Redacted from "../../../Redacted.ts"
3
+ import type { Identity } from "../EventLog.ts"
4
+
5
+ const textEncoder = new TextEncoder()
6
+
7
+ const Ed25519PublicKeyLength = 32
8
+
9
+ const Ed25519Pkcs8SeedPrefix = Uint8Array.from([
10
+ 0x30,
11
+ 0x2e,
12
+ 0x02,
13
+ 0x01,
14
+ 0x00,
15
+ 0x30,
16
+ 0x05,
17
+ 0x06,
18
+ 0x03,
19
+ 0x2b,
20
+ 0x65,
21
+ 0x70,
22
+ 0x04,
23
+ 0x22,
24
+ 0x04,
25
+ 0x20
26
+ ])
27
+
28
+ /** @internal */
29
+ export const EncryptionDerivationLabelV1 = "effect/eventlog/identity/v1/encryption"
30
+
31
+ /** @internal */
32
+ export const SigningDerivationLabelV1 = "effect/eventlog/identity/v1/signing"
33
+
34
+ /** @internal */
35
+ export interface IdentityRootSecretMaterial {
36
+ readonly encryptionKeyMaterial: Uint8Array<ArrayBuffer>
37
+ readonly encryptionKey: CryptoKey
38
+ readonly signingPublicKey: Uint8Array<ArrayBuffer>
39
+ readonly signingPrivateKey: Redacted.Redacted<Uint8Array<ArrayBuffer>>
40
+ }
41
+
42
+ const toArrayBuffer = (data: Uint8Array): ArrayBuffer => {
43
+ const copy = new Uint8Array(data.byteLength)
44
+ copy.set(data)
45
+ return copy.buffer
46
+ }
47
+
48
+ const decodeBase64Url = (value: string): Uint8Array<ArrayBuffer> => {
49
+ const normalized = value.replaceAll("-", "+").replaceAll("_", "/")
50
+ const remainder = normalized.length % 4
51
+ const padded = remainder === 0 ? normalized : `${normalized}${"=".repeat(4 - remainder)}`
52
+ const decoded = atob(padded)
53
+ const bytes = new Uint8Array(decoded.length)
54
+ for (let i = 0; i < decoded.length; i++) {
55
+ bytes[i] = decoded.charCodeAt(i)
56
+ }
57
+ return bytes
58
+ }
59
+
60
+ const makeEd25519Pkcs8FromSeed = (seed: Uint8Array): Uint8Array<ArrayBuffer> => {
61
+ const key = new Uint8Array(Ed25519Pkcs8SeedPrefix.byteLength + seed.byteLength)
62
+ key.set(Ed25519Pkcs8SeedPrefix, 0)
63
+ key.set(seed, Ed25519Pkcs8SeedPrefix.byteLength)
64
+ return key
65
+ }
66
+
67
+ const deriveSecretBytes = Effect.fnUntraced(function*(options: {
68
+ readonly crypto: Crypto
69
+ readonly rootSecret: Uint8Array
70
+ readonly label: string
71
+ }) {
72
+ const labelBytes = textEncoder.encode(options.label)
73
+ const derivationInput = new Uint8Array(labelBytes.byteLength + 1 + options.rootSecret.byteLength)
74
+ derivationInput.set(labelBytes, 0)
75
+ derivationInput[labelBytes.byteLength] = 0
76
+ derivationInput.set(options.rootSecret, labelBytes.byteLength + 1)
77
+
78
+ const digest = yield* Effect.promise(() => options.crypto.subtle.digest("SHA-256", toArrayBuffer(derivationInput)))
79
+
80
+ return new Uint8Array(digest)
81
+ })
82
+
83
+ /** @internal */
84
+ export const deriveIdentityRootSecretMaterial = Effect.fnUntraced(function*(options: {
85
+ readonly crypto: Crypto
86
+ readonly rootSecret: Uint8Array
87
+ }) {
88
+ const encryptionKeyMaterial = yield* deriveSecretBytes({
89
+ crypto: options.crypto,
90
+ rootSecret: options.rootSecret,
91
+ label: EncryptionDerivationLabelV1
92
+ })
93
+ const signingSeed = yield* deriveSecretBytes({
94
+ crypto: options.crypto,
95
+ rootSecret: options.rootSecret,
96
+ label: SigningDerivationLabelV1
97
+ })
98
+
99
+ const signingPrivateKeyBytes = makeEd25519Pkcs8FromSeed(signingSeed)
100
+
101
+ const encryptionKey = yield* Effect.promise(() =>
102
+ options.crypto.subtle.importKey("raw", toArrayBuffer(encryptionKeyMaterial), "AES-GCM", true, [
103
+ "encrypt",
104
+ "decrypt"
105
+ ])
106
+ )
107
+ const signingPrivateKey = yield* Effect.promise(() =>
108
+ options.crypto.subtle.importKey("pkcs8", toArrayBuffer(signingPrivateKeyBytes), "Ed25519", true, ["sign"])
109
+ )
110
+ const signingJwk = yield* Effect.promise(() => options.crypto.subtle.exportKey("jwk", signingPrivateKey))
111
+
112
+ if (typeof signingJwk.x !== "string") {
113
+ return yield* Effect.die(new Error("Unable to export deterministic Ed25519 public key"))
114
+ }
115
+
116
+ const signingPublicKey = decodeBase64Url(signingJwk.x)
117
+
118
+ if (signingPublicKey.byteLength !== Ed25519PublicKeyLength) {
119
+ return yield* Effect.die(
120
+ new Error(`Expected derived signing public key to be ${Ed25519PublicKeyLength} bytes`)
121
+ )
122
+ }
123
+
124
+ return {
125
+ encryptionKeyMaterial,
126
+ encryptionKey,
127
+ signingPublicKey,
128
+ signingPrivateKey: Redacted.make(signingPrivateKeyBytes)
129
+ } satisfies IdentityRootSecretMaterial
130
+ })
131
+
132
+ /** @internal */
133
+ export const makeGetIdentityRootSecretMaterial = (crypto: Crypto) => {
134
+ const cache = new WeakMap<Identity["Service"], IdentityRootSecretMaterial>()
135
+
136
+ return Effect.fnUntraced(function*(identity: Identity["Service"]) {
137
+ const cached = cache.get(identity)
138
+ if (cached !== undefined) {
139
+ return cached
140
+ }
141
+
142
+ const derived = yield* deriveIdentityRootSecretMaterial({
143
+ crypto,
144
+ rootSecret: Redacted.value(identity.privateKey)
145
+ })
146
+
147
+ yield* Effect.sync(() => {
148
+ cache.set(identity, derived)
149
+ })
150
+
151
+ return derived
152
+ })
153
+ }