tablinum 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1330 -910
- package/dist/storage/idb.d.ts +0 -1
- package/dist/svelte/index.svelte.js +1399 -948
- package/dist/sync/compact-event.d.ts +3 -0
- package/dist/sync/sync-service.d.ts +2 -0
- package/package.json +8 -27
- package/README.md +0 -298
package/dist/index.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
2
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
3
|
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined")
|
|
5
|
-
return require.apply(this, arguments);
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
6
|
});
|
|
8
7
|
|
|
9
8
|
// src/schema/field.ts
|
|
10
9
|
function make(kind, isOptional, isArray, fields) {
|
|
11
|
-
return { _tag: "FieldDef", kind, isOptional, isArray, ...fields !==
|
|
10
|
+
return { _tag: "FieldDef", kind, isOptional, isArray, ...fields !== void 0 && { fields } };
|
|
12
11
|
}
|
|
13
12
|
var field = {
|
|
14
13
|
string: () => make("string", false, false),
|
|
@@ -19,8 +18,9 @@ var field = {
|
|
|
19
18
|
optional: (inner) => make(inner.kind, true, inner.isArray, inner.fields),
|
|
20
19
|
array: (inner) => make(inner.kind, inner.isOptional, true, inner.fields)
|
|
21
20
|
};
|
|
21
|
+
|
|
22
22
|
// src/schema/collection.ts
|
|
23
|
-
var RESERVED_NAMES = new Set(["id", "_d", "_u", "_e", "_a"]);
|
|
23
|
+
var RESERVED_NAMES = /* @__PURE__ */ new Set(["id", "_d", "_u", "_e", "_a"]);
|
|
24
24
|
function collection(name, fields, options) {
|
|
25
25
|
if (!name || name.trim().length === 0) {
|
|
26
26
|
throw new Error("Collection name must not be empty");
|
|
@@ -42,7 +42,9 @@ function collection(name, fields, options) {
|
|
|
42
42
|
throw new Error(`Index field "${idx}" does not exist in collection "${name}"`);
|
|
43
43
|
}
|
|
44
44
|
if (fieldDef.kind === "json" || fieldDef.kind === "object" || fieldDef.isArray) {
|
|
45
|
-
throw new Error(
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Field "${idx}" cannot be indexed (type: ${fieldDef.kind}${fieldDef.isArray ? "[]" : ""})`
|
|
47
|
+
);
|
|
46
48
|
}
|
|
47
49
|
indices.push(idx);
|
|
48
50
|
}
|
|
@@ -53,6 +55,7 @@ function collection(name, fields, options) {
|
|
|
53
55
|
}
|
|
54
56
|
return { _tag: "CollectionDef", name, fields, indices, eventRetention };
|
|
55
57
|
}
|
|
58
|
+
|
|
56
59
|
// src/db/create-tablinum.ts
|
|
57
60
|
import { Effect as Effect22, Layer as Layer10, References as References4, ServiceMap as ServiceMap10 } from "effect";
|
|
58
61
|
|
|
@@ -61,27 +64,20 @@ import { Effect, Schema as Schema2 } from "effect";
|
|
|
61
64
|
|
|
62
65
|
// src/errors.ts
|
|
63
66
|
import { Data } from "effect";
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
class NotFoundError extends Data.TaggedError("NotFoundError") {
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
class ClosedError extends Data.TaggedError("ClosedError") {
|
|
84
|
-
}
|
|
67
|
+
var ValidationError = class extends Data.TaggedError("ValidationError") {
|
|
68
|
+
};
|
|
69
|
+
var StorageError = class extends Data.TaggedError("StorageError") {
|
|
70
|
+
};
|
|
71
|
+
var CryptoError = class extends Data.TaggedError("CryptoError") {
|
|
72
|
+
};
|
|
73
|
+
var RelayError = class extends Data.TaggedError("RelayError") {
|
|
74
|
+
};
|
|
75
|
+
var SyncError = class extends Data.TaggedError("SyncError") {
|
|
76
|
+
};
|
|
77
|
+
var NotFoundError = class extends Data.TaggedError("NotFoundError") {
|
|
78
|
+
};
|
|
79
|
+
var ClosedError = class extends Data.TaggedError("ClosedError") {
|
|
80
|
+
};
|
|
85
81
|
|
|
86
82
|
// src/db/epoch.ts
|
|
87
83
|
import { Option, Schema } from "effect";
|
|
@@ -109,15 +105,17 @@ var PersistedEpochStoreSchema = Schema.Struct({
|
|
|
109
105
|
epochs: Schema.Array(PersistedEpochSchema),
|
|
110
106
|
currentEpochId: Schema.String
|
|
111
107
|
});
|
|
112
|
-
var decodePersistedEpochStore = Schema.decodeUnknownSync(
|
|
108
|
+
var decodePersistedEpochStore = Schema.decodeUnknownSync(
|
|
109
|
+
Schema.fromJsonString(PersistedEpochStoreSchema)
|
|
110
|
+
);
|
|
113
111
|
function createEpochKey(id, privateKeyHex, createdBy, parentEpoch) {
|
|
114
112
|
const publicKey = getPublicKey(hexToBytes(privateKeyHex));
|
|
115
113
|
const base = { id, privateKey: privateKeyHex, publicKey, createdBy };
|
|
116
|
-
return parentEpoch !==
|
|
114
|
+
return parentEpoch !== void 0 ? { ...base, parentEpoch } : base;
|
|
117
115
|
}
|
|
118
116
|
function createEpochStore(initialEpoch) {
|
|
119
|
-
const epochs = new Map;
|
|
120
|
-
const keysByPublicKey = new Map;
|
|
117
|
+
const epochs = /* @__PURE__ */ new Map();
|
|
118
|
+
const keysByPublicKey = /* @__PURE__ */ new Map();
|
|
121
119
|
epochs.set(initialEpoch.id, initialEpoch);
|
|
122
120
|
keysByPublicKey.set(initialEpoch.publicKey, hexToBytes(initialEpoch.privateKey));
|
|
123
121
|
return { epochs, keysByPublicKey, currentEpochId: initialEpoch.id };
|
|
@@ -127,7 +125,14 @@ function addEpoch(store, epoch) {
|
|
|
127
125
|
store.keysByPublicKey.set(epoch.publicKey, hexToBytes(epoch.privateKey));
|
|
128
126
|
}
|
|
129
127
|
function hydrateEpochStore(snapshot) {
|
|
130
|
-
const [firstEpoch, ...remainingEpochs] = snapshot.epochs.map(
|
|
128
|
+
const [firstEpoch, ...remainingEpochs] = snapshot.epochs.map(
|
|
129
|
+
(epoch) => createEpochKey(
|
|
130
|
+
EpochId(epoch.id),
|
|
131
|
+
epoch.privateKey,
|
|
132
|
+
epoch.createdBy,
|
|
133
|
+
epoch.parentEpoch !== void 0 ? EpochId(epoch.parentEpoch) : void 0
|
|
134
|
+
)
|
|
135
|
+
);
|
|
131
136
|
if (!firstEpoch) {
|
|
132
137
|
throw new Error("Epoch snapshot must contain at least one epoch");
|
|
133
138
|
}
|
|
@@ -143,9 +148,16 @@ function createEpochStoreFromInputs(epochKeys, options = {}) {
|
|
|
143
148
|
throw new Error("Epoch input must contain at least one key");
|
|
144
149
|
}
|
|
145
150
|
const createdBy = options.createdBy ?? "";
|
|
146
|
-
const epochs = epochKeys.map(
|
|
151
|
+
const epochs = epochKeys.map(
|
|
152
|
+
(epochKey, index) => createEpochKey(
|
|
153
|
+
epochKey.epochId,
|
|
154
|
+
epochKey.key,
|
|
155
|
+
createdBy,
|
|
156
|
+
index > 0 ? epochKeys[index - 1].epochId : void 0
|
|
157
|
+
)
|
|
158
|
+
);
|
|
147
159
|
const store = createEpochStore(epochs[0]);
|
|
148
|
-
for (let i = 1;i < epochs.length; i++) {
|
|
160
|
+
for (let i = 1; i < epochs.length; i++) {
|
|
149
161
|
addEpoch(store, epochs[i]);
|
|
150
162
|
}
|
|
151
163
|
store.currentEpochId = epochs[epochs.length - 1].id;
|
|
@@ -172,7 +184,7 @@ function serializeEpochStore(store) {
|
|
|
172
184
|
id: epoch.id,
|
|
173
185
|
privateKey: epoch.privateKey,
|
|
174
186
|
createdBy: epoch.createdBy,
|
|
175
|
-
...epoch.parentEpoch !==
|
|
187
|
+
...epoch.parentEpoch !== void 0 ? { parentEpoch: epoch.parentEpoch } : {}
|
|
176
188
|
})),
|
|
177
189
|
currentEpochId: store.currentEpochId
|
|
178
190
|
};
|
|
@@ -197,49 +209,61 @@ var RuntimeConfigSchema = Schema2.Struct({
|
|
|
197
209
|
epochKeys: Schema2.optional(Schema2.Array(EpochKeyInputSchema))
|
|
198
210
|
});
|
|
199
211
|
function resolveRuntimeConfig(source) {
|
|
200
|
-
return Schema2.decodeUnknownEffect(RuntimeConfigSchema)(source).pipe(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
return Schema2.decodeUnknownEffect(RuntimeConfigSchema)(source).pipe(
|
|
213
|
+
Effect.map((config) => ({
|
|
214
|
+
relays: [...config.relays],
|
|
215
|
+
privateKey: config.privateKey,
|
|
216
|
+
epochKeys: config.epochKeys?.map((ek) => ({ epochId: EpochId(ek.epochId), key: ek.key })),
|
|
217
|
+
dbName: DatabaseName(config.dbName ?? "tablinum")
|
|
218
|
+
})),
|
|
219
|
+
Effect.mapError(
|
|
220
|
+
(error) => new ValidationError({
|
|
221
|
+
message: `Invalid Tablinum configuration: ${error.message}`
|
|
222
|
+
})
|
|
223
|
+
)
|
|
224
|
+
);
|
|
208
225
|
}
|
|
209
226
|
|
|
210
227
|
// src/services/Config.ts
|
|
211
228
|
import { ServiceMap } from "effect";
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
229
|
+
var Config = class extends ServiceMap.Service()("tablinum/Config") {
|
|
230
|
+
};
|
|
215
231
|
|
|
216
232
|
// src/services/Tablinum.ts
|
|
217
233
|
import { ServiceMap as ServiceMap2 } from "effect";
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
234
|
+
var Tablinum = class extends ServiceMap2.Service()(
|
|
235
|
+
"tablinum/Tablinum"
|
|
236
|
+
) {
|
|
237
|
+
};
|
|
221
238
|
|
|
222
239
|
// src/layers/TablinumLive.ts
|
|
223
240
|
import { Effect as Effect21, Exit, Layer as Layer9, Option as Option9, PubSub as PubSub2, References as References3, Ref as Ref5, Scope as Scope4 } from "effect";
|
|
224
241
|
|
|
225
242
|
// src/crud/watch.ts
|
|
226
243
|
import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
|
|
227
|
-
function watchCollection(ctx, storage, collectionName, filter,
|
|
244
|
+
function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
|
|
228
245
|
const query = () => Effect2.map(storage.getAllRecords(collectionName), (all) => {
|
|
229
246
|
const filtered = all.filter((r) => !r._d && (filter ? filter(r) : true));
|
|
230
|
-
return
|
|
247
|
+
return mapRecord2 ? filtered.map(mapRecord2) : filtered;
|
|
231
248
|
});
|
|
232
|
-
const changes = Stream.fromPubSub(ctx.pubsub).pipe(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
const changes = Stream.fromPubSub(ctx.pubsub).pipe(
|
|
250
|
+
Stream.filter((event) => event.collection === collectionName),
|
|
251
|
+
Stream.mapEffect(
|
|
252
|
+
() => Effect2.gen(function* () {
|
|
253
|
+
const replaying = yield* Ref.get(ctx.replayingRef);
|
|
254
|
+
if (replaying) return void 0;
|
|
255
|
+
return yield* query();
|
|
256
|
+
})
|
|
257
|
+
),
|
|
258
|
+
Stream.filter((result) => result !== void 0)
|
|
259
|
+
);
|
|
260
|
+
return Stream.unwrap(
|
|
261
|
+
Effect2.gen(function* () {
|
|
262
|
+
yield* Effect2.sleep(0);
|
|
263
|
+
const initial = yield* query();
|
|
264
|
+
return Stream.concat(Stream.make(initial), changes);
|
|
265
|
+
})
|
|
266
|
+
);
|
|
243
267
|
}
|
|
244
268
|
function notifyChange(ctx, event) {
|
|
245
269
|
return PubSub.publish(ctx.pubsub, event).pipe(Effect2.asVoid);
|
|
@@ -262,12 +286,9 @@ import { Effect as Effect3 } from "effect";
|
|
|
262
286
|
|
|
263
287
|
// src/storage/lww.ts
|
|
264
288
|
function resolveWinner(existing, incoming) {
|
|
265
|
-
if (existing === null)
|
|
266
|
-
|
|
267
|
-
if (incoming.createdAt
|
|
268
|
-
return incoming;
|
|
269
|
-
if (incoming.createdAt < existing.createdAt)
|
|
270
|
-
return existing;
|
|
289
|
+
if (existing === null) return incoming;
|
|
290
|
+
if (incoming.createdAt > existing.createdAt) return incoming;
|
|
291
|
+
if (incoming.createdAt < existing.createdAt) return existing;
|
|
271
292
|
return incoming.id < existing.id ? incoming : existing;
|
|
272
293
|
}
|
|
273
294
|
|
|
@@ -334,8 +355,7 @@ function applyEvent(storage, event) {
|
|
|
334
355
|
};
|
|
335
356
|
const incomingMeta = { id: event.id, createdAt: event.createdAt };
|
|
336
357
|
const winner = resolveWinner(existingMeta, incomingMeta);
|
|
337
|
-
if (winner.id !== event.id)
|
|
338
|
-
return false;
|
|
358
|
+
if (winner.id !== event.id) return false;
|
|
339
359
|
}
|
|
340
360
|
if (existing && event.kind === "u") {
|
|
341
361
|
yield* storage.putRecord(event.collection, deepMerge(existing, buildRecord(event)));
|
|
@@ -351,10 +371,11 @@ function rebuild(storage, collections) {
|
|
|
351
371
|
yield* storage.clearRecords(col);
|
|
352
372
|
}
|
|
353
373
|
const allEvents = yield* storage.getAllEvents();
|
|
354
|
-
const sorted = [...allEvents].sort(
|
|
374
|
+
const sorted = [...allEvents].sort(
|
|
375
|
+
(a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1)
|
|
376
|
+
);
|
|
355
377
|
for (const event of sorted) {
|
|
356
|
-
if (event.data === null && event.kind !== "d")
|
|
357
|
-
continue;
|
|
378
|
+
if (event.data === null && event.kind !== "d") continue;
|
|
358
379
|
yield* applyEvent(storage, event);
|
|
359
380
|
}
|
|
360
381
|
});
|
|
@@ -407,9 +428,16 @@ function buildStructSchema(def, options = {}) {
|
|
|
407
428
|
}
|
|
408
429
|
function buildValidator(collectionName, def) {
|
|
409
430
|
const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { includeId: true }));
|
|
410
|
-
return (input) => decode(input).pipe(
|
|
411
|
-
|
|
412
|
-
|
|
431
|
+
return (input) => decode(input).pipe(
|
|
432
|
+
Effect4.map(
|
|
433
|
+
(result) => result
|
|
434
|
+
),
|
|
435
|
+
Effect4.mapError(
|
|
436
|
+
(e) => new ValidationError({
|
|
437
|
+
message: `Validation failed for collection "${collectionName}": ${e.message}`
|
|
438
|
+
})
|
|
439
|
+
)
|
|
440
|
+
);
|
|
413
441
|
}
|
|
414
442
|
function buildPartialValidator(collectionName, def) {
|
|
415
443
|
const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { allOptional: true }));
|
|
@@ -421,15 +449,20 @@ function buildPartialValidator(collectionName, def) {
|
|
|
421
449
|
}
|
|
422
450
|
const record = input;
|
|
423
451
|
const unknownField = Object.keys(record).find((key) => !Object.hasOwn(def.fields, key));
|
|
424
|
-
if (unknownField !==
|
|
452
|
+
if (unknownField !== void 0) {
|
|
425
453
|
return yield* new ValidationError({
|
|
426
454
|
message: `Unknown field "${unknownField}" in collection "${collectionName}"`,
|
|
427
455
|
field: unknownField
|
|
428
456
|
});
|
|
429
457
|
}
|
|
430
|
-
return yield* decode(record).pipe(
|
|
431
|
-
|
|
432
|
-
|
|
458
|
+
return yield* decode(record).pipe(
|
|
459
|
+
Effect4.map((result) => result),
|
|
460
|
+
Effect4.mapError(
|
|
461
|
+
(e) => new ValidationError({
|
|
462
|
+
message: `Validation failed for collection "${collectionName}": ${e.message}`
|
|
463
|
+
})
|
|
464
|
+
)
|
|
465
|
+
);
|
|
433
466
|
});
|
|
434
467
|
}
|
|
435
468
|
|
|
@@ -440,16 +473,14 @@ import { Effect as Effect6, Option as Option3, References } from "effect";
|
|
|
440
473
|
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
441
474
|
function toBase64url(bytes) {
|
|
442
475
|
let result = "";
|
|
443
|
-
for (let i = 0;i < bytes.length; i += 3) {
|
|
476
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
444
477
|
const b0 = bytes[i];
|
|
445
478
|
const b1 = bytes[i + 1] ?? 0;
|
|
446
479
|
const b2 = bytes[i + 2] ?? 0;
|
|
447
480
|
result += alphabet[b0 >> 2];
|
|
448
481
|
result += alphabet[(b0 & 3) << 4 | b1 >> 4];
|
|
449
|
-
if (i + 1 < bytes.length)
|
|
450
|
-
|
|
451
|
-
if (i + 2 < bytes.length)
|
|
452
|
-
result += alphabet[b2 & 63];
|
|
482
|
+
if (i + 1 < bytes.length) result += alphabet[(b1 & 15) << 2 | b2 >> 6];
|
|
483
|
+
if (i + 2 < bytes.length) result += alphabet[b2 & 63];
|
|
453
484
|
}
|
|
454
485
|
return result;
|
|
455
486
|
}
|
|
@@ -494,16 +525,28 @@ function executeQuery(ctx, plan) {
|
|
|
494
525
|
if (plan.indexQuery && ctx.def.indices.includes(plan.indexQuery.field)) {
|
|
495
526
|
if (plan.indexQuery.type === "value") {
|
|
496
527
|
results = [
|
|
497
|
-
...yield* ctx.storage.getByIndex(
|
|
528
|
+
...yield* ctx.storage.getByIndex(
|
|
529
|
+
ctx.collectionName,
|
|
530
|
+
plan.indexQuery.field,
|
|
531
|
+
plan.indexQuery.range
|
|
532
|
+
)
|
|
498
533
|
];
|
|
499
534
|
} else {
|
|
500
535
|
results = [
|
|
501
|
-
...yield* ctx.storage.getByIndexRange(
|
|
536
|
+
...yield* ctx.storage.getByIndexRange(
|
|
537
|
+
ctx.collectionName,
|
|
538
|
+
plan.indexQuery.field,
|
|
539
|
+
plan.indexQuery.range
|
|
540
|
+
)
|
|
502
541
|
];
|
|
503
542
|
}
|
|
504
543
|
} else if (plan.orderBy && ctx.def.indices.includes(plan.orderBy.field) && plan.filters.length === 0) {
|
|
505
544
|
results = [
|
|
506
|
-
...yield* ctx.storage.getAllSorted(
|
|
545
|
+
...yield* ctx.storage.getAllSorted(
|
|
546
|
+
ctx.collectionName,
|
|
547
|
+
plan.orderBy.field,
|
|
548
|
+
plan.orderBy.direction === "desc" ? "prev" : "next"
|
|
549
|
+
)
|
|
507
550
|
];
|
|
508
551
|
} else {
|
|
509
552
|
results = [...yield* ctx.storage.getAllRecords(ctx.collectionName)];
|
|
@@ -527,7 +570,7 @@ function executeQuery(ctx, plan) {
|
|
|
527
570
|
if (plan.offset) {
|
|
528
571
|
results = results.slice(plan.offset);
|
|
529
572
|
}
|
|
530
|
-
if (plan.limit !== null && plan.limit !==
|
|
573
|
+
if (plan.limit !== null && plan.limit !== void 0) {
|
|
531
574
|
results = results.slice(0, plan.limit);
|
|
532
575
|
}
|
|
533
576
|
return results.map(ctx.mapRecord);
|
|
@@ -535,16 +578,23 @@ function executeQuery(ctx, plan) {
|
|
|
535
578
|
}
|
|
536
579
|
function watchQuery(ctx, plan) {
|
|
537
580
|
const query = () => executeQuery(ctx, plan);
|
|
538
|
-
const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
581
|
+
const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
|
|
582
|
+
Stream2.filter((event) => event.collection === ctx.collectionName),
|
|
583
|
+
Stream2.mapEffect(
|
|
584
|
+
() => Effect5.gen(function* () {
|
|
585
|
+
const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
|
|
586
|
+
if (replaying) return void 0;
|
|
587
|
+
return yield* query();
|
|
588
|
+
})
|
|
589
|
+
),
|
|
590
|
+
Stream2.filter((result) => result !== void 0)
|
|
591
|
+
);
|
|
592
|
+
return Stream2.unwrap(
|
|
593
|
+
Effect5.gen(function* () {
|
|
594
|
+
const initial = yield* query();
|
|
595
|
+
return Stream2.concat(Stream2.make(initial), changes);
|
|
596
|
+
})
|
|
597
|
+
);
|
|
548
598
|
}
|
|
549
599
|
function makeQueryBuilder(ctx, plan) {
|
|
550
600
|
return {
|
|
@@ -566,15 +616,18 @@ function makeQueryBuilder(ctx, plan) {
|
|
|
566
616
|
offset: (n) => makeQueryBuilder(ctx, { ...plan, offset: n }),
|
|
567
617
|
limit: (n) => makeQueryBuilder(ctx, { ...plan, limit: n }),
|
|
568
618
|
get: () => executeQuery(ctx, plan),
|
|
569
|
-
first: () => Effect5.map(
|
|
619
|
+
first: () => Effect5.map(
|
|
620
|
+
executeQuery(ctx, { ...plan, limit: 1 }),
|
|
621
|
+
(results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()
|
|
622
|
+
),
|
|
570
623
|
count: () => Effect5.map(executeQuery(ctx, plan), (results) => results.length),
|
|
571
624
|
watch: () => watchQuery(ctx, plan)
|
|
572
625
|
};
|
|
573
626
|
}
|
|
574
|
-
function createWhereClause(storage, watchCtx, collectionName, def, fieldName,
|
|
575
|
-
const ctx = { storage, watchCtx, collectionName, def, mapRecord };
|
|
627
|
+
function createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord2) {
|
|
628
|
+
const ctx = { storage, watchCtx, collectionName, def, mapRecord: mapRecord2 };
|
|
576
629
|
const fieldDef = def.fields[fieldName];
|
|
577
|
-
const isIndexed = def.indices.includes(fieldName) && fieldDef !== null && fieldDef !==
|
|
630
|
+
const isIndexed = def.indices.includes(fieldName) && fieldDef !== null && fieldDef !== void 0 && fieldDef.kind !== "boolean";
|
|
578
631
|
const withFilter = (filterFn, indexQuery) => {
|
|
579
632
|
const plan = {
|
|
580
633
|
...emptyPlan(),
|
|
@@ -585,30 +638,51 @@ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, ma
|
|
|
585
638
|
return makeQueryBuilder(ctx, plan);
|
|
586
639
|
};
|
|
587
640
|
return {
|
|
588
|
-
equals: (value) => withFilter(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
641
|
+
equals: (value) => withFilter(
|
|
642
|
+
(r) => r[fieldName] === value,
|
|
643
|
+
isIndexed ? { field: fieldName, range: value, type: "value" } : void 0
|
|
644
|
+
),
|
|
645
|
+
above: (value) => withFilter(
|
|
646
|
+
(r) => r[fieldName] > value,
|
|
647
|
+
isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, true), type: "range" } : void 0
|
|
648
|
+
),
|
|
649
|
+
aboveOrEqual: (value) => withFilter(
|
|
650
|
+
(r) => r[fieldName] >= value,
|
|
651
|
+
isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, false), type: "range" } : void 0
|
|
652
|
+
),
|
|
653
|
+
below: (value) => withFilter(
|
|
654
|
+
(r) => r[fieldName] < value,
|
|
655
|
+
isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, true), type: "range" } : void 0
|
|
656
|
+
),
|
|
657
|
+
belowOrEqual: (value) => withFilter(
|
|
658
|
+
(r) => r[fieldName] <= value,
|
|
659
|
+
isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, false), type: "range" } : void 0
|
|
660
|
+
),
|
|
593
661
|
between: (lower, upper, options) => {
|
|
594
662
|
const includeLower = options?.includeLower ?? true;
|
|
595
663
|
const includeUpper = options?.includeUpper ?? true;
|
|
596
|
-
return withFilter(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
664
|
+
return withFilter(
|
|
665
|
+
(r) => {
|
|
666
|
+
const v = r[fieldName];
|
|
667
|
+
const aboveLower = includeLower ? v >= lower : v > lower;
|
|
668
|
+
const belowUpper = includeUpper ? v <= upper : v < upper;
|
|
669
|
+
return aboveLower && belowUpper;
|
|
670
|
+
},
|
|
671
|
+
isIndexed ? {
|
|
672
|
+
field: fieldName,
|
|
673
|
+
range: IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper),
|
|
674
|
+
type: "range"
|
|
675
|
+
} : void 0
|
|
676
|
+
);
|
|
677
|
+
},
|
|
678
|
+
startsWith: (prefix) => withFilter(
|
|
679
|
+
(r) => typeof r[fieldName] === "string" && r[fieldName].startsWith(prefix),
|
|
680
|
+
isIndexed ? {
|
|
602
681
|
field: fieldName,
|
|
603
|
-
range: IDBKeyRange.bound(
|
|
682
|
+
range: IDBKeyRange.bound(prefix, prefix + "\uFFFF", false, false),
|
|
604
683
|
type: "range"
|
|
605
|
-
} :
|
|
606
|
-
|
|
607
|
-
startsWith: (prefix) => withFilter((r) => typeof r[fieldName] === "string" && r[fieldName].startsWith(prefix), isIndexed ? {
|
|
608
|
-
field: fieldName,
|
|
609
|
-
range: IDBKeyRange.bound(prefix, prefix + "", false, false),
|
|
610
|
-
type: "range"
|
|
611
|
-
} : undefined),
|
|
684
|
+
} : void 0
|
|
685
|
+
),
|
|
612
686
|
anyOf: (values) => {
|
|
613
687
|
const set = new Set(values);
|
|
614
688
|
return withFilter((r) => set.has(r[fieldName]));
|
|
@@ -619,8 +693,8 @@ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, ma
|
|
|
619
693
|
}
|
|
620
694
|
};
|
|
621
695
|
}
|
|
622
|
-
function createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName,
|
|
623
|
-
const ctx = { storage, watchCtx, collectionName, def, mapRecord };
|
|
696
|
+
function createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord2) {
|
|
697
|
+
const ctx = { storage, watchCtx, collectionName, def, mapRecord: mapRecord2 };
|
|
624
698
|
const plan = {
|
|
625
699
|
...emptyPlan(),
|
|
626
700
|
orderBy: { field: fieldName, direction: "asc" }
|
|
@@ -645,7 +719,7 @@ function replayState(recordId, events, stopAtId) {
|
|
|
645
719
|
state = deepMerge(state, e.data);
|
|
646
720
|
}
|
|
647
721
|
}
|
|
648
|
-
if (stopAtId !==
|
|
722
|
+
if (stopAtId !== void 0 && e.id === stopAtId) {
|
|
649
723
|
break;
|
|
650
724
|
}
|
|
651
725
|
}
|
|
@@ -663,8 +737,7 @@ function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
|
|
|
663
737
|
function pruneEvents(storage, collection2, recordId, retention) {
|
|
664
738
|
return Effect6.gen(function* () {
|
|
665
739
|
const events = yield* storage.getEventsByRecord(collection2, recordId);
|
|
666
|
-
if (events.length <= retention)
|
|
667
|
-
return;
|
|
740
|
+
if (events.length <= retention) return;
|
|
668
741
|
const sorted = [...events].sort((a, b) => b.createdAt - a.createdAt || (a.id < b.id ? 1 : -1));
|
|
669
742
|
const retained = sorted.slice(0, retention);
|
|
670
743
|
const toStrip = sorted.slice(retention);
|
|
@@ -673,8 +746,7 @@ function pruneEvents(storage, collection2, recordId, retention) {
|
|
|
673
746
|
yield* promoteToSnapshot(storage, collection2, recordId, oldestRetained, sorted);
|
|
674
747
|
}
|
|
675
748
|
for (const e of toStrip) {
|
|
676
|
-
if (e.data !== null)
|
|
677
|
-
yield* storage.stripEventData(e.id);
|
|
749
|
+
if (e.data !== null) yield* storage.stripEventData(e.id);
|
|
678
750
|
}
|
|
679
751
|
});
|
|
680
752
|
}
|
|
@@ -688,8 +760,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
|
|
|
688
760
|
const commitEvent = (event) => Effect6.gen(function* () {
|
|
689
761
|
yield* storage.putEvent(event);
|
|
690
762
|
yield* applyEvent(storage, event);
|
|
691
|
-
if (onWrite)
|
|
692
|
-
yield* onWrite(event);
|
|
763
|
+
if (onWrite) yield* onWrite(event);
|
|
693
764
|
yield* notifyChange(watchCtx, {
|
|
694
765
|
collection: collectionName,
|
|
695
766
|
recordId: event.recordId,
|
|
@@ -697,70 +768,84 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
|
|
|
697
768
|
});
|
|
698
769
|
});
|
|
699
770
|
const handle = {
|
|
700
|
-
add: (data) => withLog(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
recordId: id,
|
|
708
|
-
kind: "c",
|
|
709
|
-
data: fullRecord,
|
|
710
|
-
createdAt: Date.now(),
|
|
711
|
-
author: localAuthor
|
|
712
|
-
};
|
|
713
|
-
yield* commitEvent(event);
|
|
714
|
-
yield* Effect6.logDebug("Record added", { collection: collectionName, recordId: id, data: fullRecord });
|
|
715
|
-
return id;
|
|
716
|
-
})),
|
|
717
|
-
update: (id, data) => withLog(Effect6.gen(function* () {
|
|
718
|
-
const existing = yield* storage.getRecord(collectionName, id);
|
|
719
|
-
if (!existing || existing._d) {
|
|
720
|
-
return yield* new NotFoundError({
|
|
771
|
+
add: (data) => withLog(
|
|
772
|
+
Effect6.gen(function* () {
|
|
773
|
+
const id = uuidv7();
|
|
774
|
+
const fullRecord = { id, ...data };
|
|
775
|
+
yield* validator(fullRecord);
|
|
776
|
+
const event = {
|
|
777
|
+
id: makeEventId(),
|
|
721
778
|
collection: collectionName,
|
|
722
|
-
id
|
|
779
|
+
recordId: id,
|
|
780
|
+
kind: "c",
|
|
781
|
+
data: fullRecord,
|
|
782
|
+
createdAt: Date.now(),
|
|
783
|
+
author: localAuthor
|
|
784
|
+
};
|
|
785
|
+
yield* commitEvent(event);
|
|
786
|
+
yield* Effect6.logDebug("Record added", {
|
|
787
|
+
collection: collectionName,
|
|
788
|
+
recordId: id,
|
|
789
|
+
data: fullRecord
|
|
723
790
|
});
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
delete: (id) => withLog(Effect6.gen(function* () {
|
|
744
|
-
const existing = yield* storage.getRecord(collectionName, id);
|
|
745
|
-
if (!existing || existing._d) {
|
|
746
|
-
return yield* new NotFoundError({
|
|
791
|
+
return id;
|
|
792
|
+
})
|
|
793
|
+
),
|
|
794
|
+
update: (id, data) => withLog(
|
|
795
|
+
Effect6.gen(function* () {
|
|
796
|
+
const existing = yield* storage.getRecord(collectionName, id);
|
|
797
|
+
if (!existing || existing._d) {
|
|
798
|
+
return yield* new NotFoundError({
|
|
799
|
+
collection: collectionName,
|
|
800
|
+
id
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
yield* partialValidator(data);
|
|
804
|
+
const { _d, _u, _a, _e, ...existingFields } = existing;
|
|
805
|
+
const merged = { ...existingFields, ...data, id };
|
|
806
|
+
yield* validator(merged);
|
|
807
|
+
const diff = deepDiff(existingFields, merged);
|
|
808
|
+
const event = {
|
|
809
|
+
id: makeEventId(),
|
|
747
810
|
collection: collectionName,
|
|
748
|
-
id
|
|
811
|
+
recordId: id,
|
|
812
|
+
kind: "u",
|
|
813
|
+
data: diff ?? { id },
|
|
814
|
+
createdAt: Date.now(),
|
|
815
|
+
author: localAuthor
|
|
816
|
+
};
|
|
817
|
+
yield* commitEvent(event);
|
|
818
|
+
yield* Effect6.logDebug("Record updated", {
|
|
819
|
+
collection: collectionName,
|
|
820
|
+
recordId: id,
|
|
821
|
+
data: diff
|
|
749
822
|
});
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
823
|
+
yield* pruneEvents(storage, collectionName, id, def.eventRetention);
|
|
824
|
+
})
|
|
825
|
+
),
|
|
826
|
+
delete: (id) => withLog(
|
|
827
|
+
Effect6.gen(function* () {
|
|
828
|
+
const existing = yield* storage.getRecord(collectionName, id);
|
|
829
|
+
if (!existing || existing._d) {
|
|
830
|
+
return yield* new NotFoundError({
|
|
831
|
+
collection: collectionName,
|
|
832
|
+
id
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
const event = {
|
|
836
|
+
id: makeEventId(),
|
|
837
|
+
collection: collectionName,
|
|
838
|
+
recordId: id,
|
|
839
|
+
kind: "d",
|
|
840
|
+
data: null,
|
|
841
|
+
createdAt: Date.now(),
|
|
842
|
+
author: localAuthor
|
|
843
|
+
};
|
|
844
|
+
yield* commitEvent(event);
|
|
845
|
+
yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
|
|
846
|
+
yield* pruneEvents(storage, collectionName, id, def.eventRetention);
|
|
847
|
+
})
|
|
848
|
+
),
|
|
764
849
|
undo: (id) => Effect6.gen(function* () {
|
|
765
850
|
const existing = yield* storage.getRecord(collectionName, id);
|
|
766
851
|
if (!existing) {
|
|
@@ -801,15 +886,29 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
|
|
|
801
886
|
return found ? Option3.some(mapRecord(found)) : Option3.none();
|
|
802
887
|
}),
|
|
803
888
|
count: () => Effect6.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
|
|
804
|
-
watch: () => watchCollection(watchCtx, storage, collectionName,
|
|
805
|
-
where: (fieldName) => createWhereClause(
|
|
806
|
-
|
|
889
|
+
watch: () => watchCollection(watchCtx, storage, collectionName, void 0, mapRecord),
|
|
890
|
+
where: (fieldName) => createWhereClause(
|
|
891
|
+
storage,
|
|
892
|
+
watchCtx,
|
|
893
|
+
collectionName,
|
|
894
|
+
def,
|
|
895
|
+
fieldName,
|
|
896
|
+
mapRecord
|
|
897
|
+
),
|
|
898
|
+
orderBy: (fieldName) => createOrderByBuilder(
|
|
899
|
+
storage,
|
|
900
|
+
watchCtx,
|
|
901
|
+
collectionName,
|
|
902
|
+
def,
|
|
903
|
+
fieldName,
|
|
904
|
+
mapRecord
|
|
905
|
+
)
|
|
807
906
|
};
|
|
808
907
|
return handle;
|
|
809
908
|
}
|
|
810
909
|
|
|
811
910
|
// src/sync/sync-service.ts
|
|
812
|
-
import { Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
|
|
911
|
+
import { Duration, Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
|
|
813
912
|
import { unwrapEvent } from "nostr-tools/nip59";
|
|
814
913
|
import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
|
|
815
914
|
|
|
@@ -825,8 +924,7 @@ var Mode = {
|
|
|
825
924
|
Fingerprint: 1,
|
|
826
925
|
IdList: 2
|
|
827
926
|
};
|
|
828
|
-
|
|
829
|
-
class WrappedBuffer {
|
|
927
|
+
var WrappedBuffer = class {
|
|
830
928
|
constructor(buffer) {
|
|
831
929
|
this._raw = new Uint8Array(buffer || 512);
|
|
832
930
|
this.length = buffer ? buffer.length : 0;
|
|
@@ -838,10 +936,8 @@ class WrappedBuffer {
|
|
|
838
936
|
return this._raw.byteLength;
|
|
839
937
|
}
|
|
840
938
|
extend(buf) {
|
|
841
|
-
if (buf._raw)
|
|
842
|
-
|
|
843
|
-
if (typeof buf.length !== "number")
|
|
844
|
-
throw Error("bad length");
|
|
939
|
+
if (buf._raw) buf = buf.unwrap();
|
|
940
|
+
if (typeof buf.length !== "number") throw Error("bad length");
|
|
845
941
|
const targetSize = buf.length + this.length;
|
|
846
942
|
if (this.capacity < targetSize) {
|
|
847
943
|
const oldRaw = this._raw;
|
|
@@ -864,42 +960,36 @@ class WrappedBuffer {
|
|
|
864
960
|
this.length -= n;
|
|
865
961
|
return firstSubarray;
|
|
866
962
|
}
|
|
867
|
-
}
|
|
963
|
+
};
|
|
868
964
|
function decodeVarInt(buf) {
|
|
869
965
|
let res = 0;
|
|
870
|
-
while (
|
|
871
|
-
if (buf.length === 0)
|
|
872
|
-
throw Error("parse ends prematurely");
|
|
966
|
+
while (1) {
|
|
967
|
+
if (buf.length === 0) throw Error("parse ends prematurely");
|
|
873
968
|
let byte = buf.shift();
|
|
874
969
|
res = res << 7 | byte & 127;
|
|
875
|
-
if ((byte & 128) === 0)
|
|
876
|
-
break;
|
|
970
|
+
if ((byte & 128) === 0) break;
|
|
877
971
|
}
|
|
878
972
|
return res;
|
|
879
973
|
}
|
|
880
974
|
function encodeVarInt(n) {
|
|
881
|
-
if (n === 0)
|
|
882
|
-
return new WrappedBuffer([0]);
|
|
975
|
+
if (n === 0) return new WrappedBuffer([0]);
|
|
883
976
|
let o = [];
|
|
884
977
|
while (n !== 0) {
|
|
885
978
|
o.push(n & 127);
|
|
886
979
|
n >>>= 7;
|
|
887
980
|
}
|
|
888
981
|
o.reverse();
|
|
889
|
-
for (let i = 0;i < o.length - 1; i++)
|
|
890
|
-
o[i] |= 128;
|
|
982
|
+
for (let i = 0; i < o.length - 1; i++) o[i] |= 128;
|
|
891
983
|
return new WrappedBuffer(o);
|
|
892
984
|
}
|
|
893
985
|
function getByte(buf) {
|
|
894
986
|
return getBytes(buf, 1)[0];
|
|
895
987
|
}
|
|
896
988
|
function getBytes(buf, n) {
|
|
897
|
-
if (buf.length < n)
|
|
898
|
-
throw Error("parse ends prematurely");
|
|
989
|
+
if (buf.length < n) throw Error("parse ends prematurely");
|
|
899
990
|
return buf.shiftN(n);
|
|
900
991
|
}
|
|
901
|
-
|
|
902
|
-
class Accumulator {
|
|
992
|
+
var Accumulator = class {
|
|
903
993
|
constructor() {
|
|
904
994
|
this.setToZero();
|
|
905
995
|
if (typeof window === "undefined") {
|
|
@@ -916,15 +1006,14 @@ class Accumulator {
|
|
|
916
1006
|
let currCarry = 0, nextCarry = 0;
|
|
917
1007
|
let p = new DataView(this.buf.buffer);
|
|
918
1008
|
let po = new DataView(otherBuf.buffer);
|
|
919
|
-
for (let i = 0;i < 8; i++) {
|
|
1009
|
+
for (let i = 0; i < 8; i++) {
|
|
920
1010
|
let offset = i * 4;
|
|
921
1011
|
let orig = p.getUint32(offset, true);
|
|
922
1012
|
let otherV = po.getUint32(offset, true);
|
|
923
1013
|
let next = orig;
|
|
924
1014
|
next += currCarry;
|
|
925
1015
|
next += otherV;
|
|
926
|
-
if (next > 4294967295)
|
|
927
|
-
nextCarry = 1;
|
|
1016
|
+
if (next > 4294967295) nextCarry = 1;
|
|
928
1017
|
p.setUint32(offset, next & 4294967295, true);
|
|
929
1018
|
currCarry = nextCarry;
|
|
930
1019
|
nextCarry = 0;
|
|
@@ -932,7 +1021,7 @@ class Accumulator {
|
|
|
932
1021
|
}
|
|
933
1022
|
negate() {
|
|
934
1023
|
let p = new DataView(this.buf.buffer);
|
|
935
|
-
for (let i = 0;i < 8; i++) {
|
|
1024
|
+
for (let i = 0; i < 8; i++) {
|
|
936
1025
|
let offset = i * 4;
|
|
937
1026
|
p.setUint32(offset, ~p.getUint32(offset, true));
|
|
938
1027
|
}
|
|
@@ -941,33 +1030,29 @@ class Accumulator {
|
|
|
941
1030
|
this.add(one);
|
|
942
1031
|
}
|
|
943
1032
|
async getFingerprint(n) {
|
|
944
|
-
let input = new WrappedBuffer;
|
|
1033
|
+
let input = new WrappedBuffer();
|
|
945
1034
|
input.extend(this.buf);
|
|
946
1035
|
input.extend(encodeVarInt(n));
|
|
947
1036
|
let hash = await this.sha256(input.unwrap());
|
|
948
1037
|
return hash.subarray(0, FINGERPRINT_SIZE);
|
|
949
1038
|
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
class NegentropyStorageVector {
|
|
1039
|
+
};
|
|
1040
|
+
var NegentropyStorageVector = class {
|
|
953
1041
|
constructor() {
|
|
954
1042
|
this.items = [];
|
|
955
1043
|
this.sealed = false;
|
|
956
1044
|
}
|
|
957
1045
|
insert(timestamp, id) {
|
|
958
|
-
if (this.sealed)
|
|
959
|
-
throw Error("already sealed");
|
|
1046
|
+
if (this.sealed) throw Error("already sealed");
|
|
960
1047
|
id = loadInputBuffer(id);
|
|
961
|
-
if (id.byteLength !== ID_SIZE)
|
|
962
|
-
throw Error("bad id size for added item");
|
|
1048
|
+
if (id.byteLength !== ID_SIZE) throw Error("bad id size for added item");
|
|
963
1049
|
this.items.push({ timestamp, id });
|
|
964
1050
|
}
|
|
965
1051
|
seal() {
|
|
966
|
-
if (this.sealed)
|
|
967
|
-
throw Error("already sealed");
|
|
1052
|
+
if (this.sealed) throw Error("already sealed");
|
|
968
1053
|
this.sealed = true;
|
|
969
1054
|
this.items.sort(itemCompare);
|
|
970
|
-
for (let i = 1;i < this.items.length; i++) {
|
|
1055
|
+
for (let i = 1; i < this.items.length; i++) {
|
|
971
1056
|
if (itemCompare(this.items[i - 1], this.items[i]) === 0)
|
|
972
1057
|
throw Error("duplicate item inserted");
|
|
973
1058
|
}
|
|
@@ -981,16 +1066,14 @@ class NegentropyStorageVector {
|
|
|
981
1066
|
}
|
|
982
1067
|
getItem(i) {
|
|
983
1068
|
this._checkSealed();
|
|
984
|
-
if (i >= this.items.length)
|
|
985
|
-
throw Error("out of range");
|
|
1069
|
+
if (i >= this.items.length) throw Error("out of range");
|
|
986
1070
|
return this.items[i];
|
|
987
1071
|
}
|
|
988
1072
|
iterate(begin, end, cb) {
|
|
989
1073
|
this._checkSealed();
|
|
990
1074
|
this._checkBounds(begin, end);
|
|
991
|
-
for (let i = begin;i < end; ++i) {
|
|
992
|
-
if (!cb(this.items[i], i))
|
|
993
|
-
break;
|
|
1075
|
+
for (let i = begin; i < end; ++i) {
|
|
1076
|
+
if (!cb(this.items[i], i)) break;
|
|
994
1077
|
}
|
|
995
1078
|
}
|
|
996
1079
|
findLowerBound(begin, end, bound) {
|
|
@@ -999,7 +1082,7 @@ class NegentropyStorageVector {
|
|
|
999
1082
|
return this._binarySearch(this.items, begin, end, (a) => itemCompare(a, bound) < 0);
|
|
1000
1083
|
}
|
|
1001
1084
|
async fingerprint(begin, end) {
|
|
1002
|
-
let out = new Accumulator;
|
|
1085
|
+
let out = new Accumulator();
|
|
1003
1086
|
out.setToZero();
|
|
1004
1087
|
this.iterate(begin, end, (item, i) => {
|
|
1005
1088
|
out.add(item.id);
|
|
@@ -1008,12 +1091,10 @@ class NegentropyStorageVector {
|
|
|
1008
1091
|
return await out.getFingerprint(end - begin);
|
|
1009
1092
|
}
|
|
1010
1093
|
_checkSealed() {
|
|
1011
|
-
if (!this.sealed)
|
|
1012
|
-
throw Error("not sealed");
|
|
1094
|
+
if (!this.sealed) throw Error("not sealed");
|
|
1013
1095
|
}
|
|
1014
1096
|
_checkBounds(begin, end) {
|
|
1015
|
-
if (begin > end || end > this.items.length)
|
|
1016
|
-
throw Error("bad range");
|
|
1097
|
+
if (begin > end || end > this.items.length) throw Error("bad range");
|
|
1017
1098
|
}
|
|
1018
1099
|
_binarySearch(arr, first, last, cmp) {
|
|
1019
1100
|
let count = last - first;
|
|
@@ -1030,12 +1111,10 @@ class NegentropyStorageVector {
|
|
|
1030
1111
|
}
|
|
1031
1112
|
return first;
|
|
1032
1113
|
}
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
class Negentropy {
|
|
1114
|
+
};
|
|
1115
|
+
var Negentropy = class {
|
|
1036
1116
|
constructor(storage, frameSizeLimit = 0) {
|
|
1037
|
-
if (frameSizeLimit !== 0 && frameSizeLimit < 4096)
|
|
1038
|
-
throw Error("frameSizeLimit too small");
|
|
1117
|
+
if (frameSizeLimit !== 0 && frameSizeLimit < 4096) throw Error("frameSizeLimit too small");
|
|
1039
1118
|
this.storage = storage;
|
|
1040
1119
|
this.frameSizeLimit = frameSizeLimit;
|
|
1041
1120
|
this.lastTimestampIn = 0;
|
|
@@ -1045,10 +1124,9 @@ class Negentropy {
|
|
|
1045
1124
|
return { timestamp, id: id ? id : new Uint8Array(0) };
|
|
1046
1125
|
}
|
|
1047
1126
|
async initiate() {
|
|
1048
|
-
if (this.isInitiator)
|
|
1049
|
-
throw Error("already initiated");
|
|
1127
|
+
if (this.isInitiator) throw Error("already initiated");
|
|
1050
1128
|
this.isInitiator = true;
|
|
1051
|
-
let output = new WrappedBuffer;
|
|
1129
|
+
let output = new WrappedBuffer();
|
|
1052
1130
|
output.extend([PROTOCOL_VERSION]);
|
|
1053
1131
|
await this.splitRange(0, this.storage.size(), this._bound(Number.MAX_VALUE), output);
|
|
1054
1132
|
return this._renderOutput(output);
|
|
@@ -1060,23 +1138,24 @@ class Negentropy {
|
|
|
1060
1138
|
let haveIds = [], needIds = [];
|
|
1061
1139
|
query = new WrappedBuffer(loadInputBuffer(query));
|
|
1062
1140
|
this.lastTimestampIn = this.lastTimestampOut = 0;
|
|
1063
|
-
let fullOutput = new WrappedBuffer;
|
|
1141
|
+
let fullOutput = new WrappedBuffer();
|
|
1064
1142
|
fullOutput.extend([PROTOCOL_VERSION]);
|
|
1065
1143
|
let protocolVersion = getByte(query);
|
|
1066
1144
|
if (protocolVersion < 96 || protocolVersion > 111)
|
|
1067
1145
|
throw Error("invalid negentropy protocol version byte");
|
|
1068
1146
|
if (protocolVersion !== PROTOCOL_VERSION) {
|
|
1069
1147
|
if (this.isInitiator)
|
|
1070
|
-
throw Error(
|
|
1071
|
-
|
|
1072
|
-
|
|
1148
|
+
throw Error(
|
|
1149
|
+
"unsupported negentropy protocol version requested: " + (protocolVersion - 96)
|
|
1150
|
+
);
|
|
1151
|
+
else return [this._renderOutput(fullOutput), haveIds, needIds];
|
|
1073
1152
|
}
|
|
1074
1153
|
let storageSize = this.storage.size();
|
|
1075
1154
|
let prevBound = this._bound(0);
|
|
1076
1155
|
let prevIndex = 0;
|
|
1077
1156
|
let skip = false;
|
|
1078
1157
|
while (query.length !== 0) {
|
|
1079
|
-
let o = new WrappedBuffer;
|
|
1158
|
+
let o = new WrappedBuffer();
|
|
1080
1159
|
let doSkip = () => {
|
|
1081
1160
|
if (skip) {
|
|
1082
1161
|
skip = false;
|
|
@@ -1102,10 +1181,9 @@ class Negentropy {
|
|
|
1102
1181
|
} else if (mode === Mode.IdList) {
|
|
1103
1182
|
let numIds = decodeVarInt(query);
|
|
1104
1183
|
let theirElems = {};
|
|
1105
|
-
for (let i = 0;i < numIds; i++) {
|
|
1184
|
+
for (let i = 0; i < numIds; i++) {
|
|
1106
1185
|
let e = getBytes(query, ID_SIZE);
|
|
1107
|
-
if (this.isInitiator)
|
|
1108
|
-
theirElems[e] = e;
|
|
1186
|
+
if (this.isInitiator) theirElems[e] = e;
|
|
1109
1187
|
}
|
|
1110
1188
|
if (this.isInitiator) {
|
|
1111
1189
|
skip = true;
|
|
@@ -1124,7 +1202,7 @@ class Negentropy {
|
|
|
1124
1202
|
}
|
|
1125
1203
|
} else {
|
|
1126
1204
|
doSkip();
|
|
1127
|
-
let responseIds = new WrappedBuffer;
|
|
1205
|
+
let responseIds = new WrappedBuffer();
|
|
1128
1206
|
let numResponseIds = 0;
|
|
1129
1207
|
let endBound = currBound;
|
|
1130
1208
|
this.storage.iterate(lower, upper, (item, index) => {
|
|
@@ -1142,7 +1220,7 @@ class Negentropy {
|
|
|
1142
1220
|
o.extend(encodeVarInt(numResponseIds));
|
|
1143
1221
|
o.extend(responseIds);
|
|
1144
1222
|
fullOutput.extend(o);
|
|
1145
|
-
o = new WrappedBuffer;
|
|
1223
|
+
o = new WrappedBuffer();
|
|
1146
1224
|
}
|
|
1147
1225
|
} else {
|
|
1148
1226
|
throw Error("unexpected mode");
|
|
@@ -1180,7 +1258,7 @@ class Negentropy {
|
|
|
1180
1258
|
let itemsPerBucket = Math.floor(numElems / buckets);
|
|
1181
1259
|
let bucketsWithExtra = numElems % buckets;
|
|
1182
1260
|
let curr = lower;
|
|
1183
|
-
for (let i = 0;i < buckets; i++) {
|
|
1261
|
+
for (let i = 0; i < buckets; i++) {
|
|
1184
1262
|
let bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);
|
|
1185
1263
|
let ourFingerprint = await this.storage.fingerprint(curr, curr + bucketSize);
|
|
1186
1264
|
curr += bucketSize;
|
|
@@ -1190,10 +1268,8 @@ class Negentropy {
|
|
|
1190
1268
|
} else {
|
|
1191
1269
|
let prevItem, currItem;
|
|
1192
1270
|
this.storage.iterate(curr - 1, curr + 1, (item, index) => {
|
|
1193
|
-
if (index === curr - 1)
|
|
1194
|
-
|
|
1195
|
-
else
|
|
1196
|
-
currItem = item;
|
|
1271
|
+
if (index === curr - 1) prevItem = item;
|
|
1272
|
+
else currItem = item;
|
|
1197
1273
|
return true;
|
|
1198
1274
|
});
|
|
1199
1275
|
nextBound = this.getMinimalBound(prevItem, currItem);
|
|
@@ -1206,13 +1282,13 @@ class Negentropy {
|
|
|
1206
1282
|
}
|
|
1207
1283
|
_renderOutput(o) {
|
|
1208
1284
|
o = o.unwrap();
|
|
1209
|
-
if (!this.wantUint8ArrayOutput)
|
|
1210
|
-
o = uint8ArrayToHex(o);
|
|
1285
|
+
if (!this.wantUint8ArrayOutput) o = uint8ArrayToHex(o);
|
|
1211
1286
|
return o;
|
|
1212
1287
|
}
|
|
1213
1288
|
exceededFrameSizeLimit(n) {
|
|
1214
1289
|
return this.frameSizeLimit && n > this.frameSizeLimit - 200;
|
|
1215
1290
|
}
|
|
1291
|
+
// Decoding
|
|
1216
1292
|
decodeTimestampIn(encoded) {
|
|
1217
1293
|
let timestamp = decodeVarInt(encoded);
|
|
1218
1294
|
timestamp = timestamp === 0 ? Number.MAX_VALUE : timestamp - 1;
|
|
@@ -1227,11 +1303,11 @@ class Negentropy {
|
|
|
1227
1303
|
decodeBound(encoded) {
|
|
1228
1304
|
let timestamp = this.decodeTimestampIn(encoded);
|
|
1229
1305
|
let len = decodeVarInt(encoded);
|
|
1230
|
-
if (len > ID_SIZE)
|
|
1231
|
-
throw Error("bound key too long");
|
|
1306
|
+
if (len > ID_SIZE) throw Error("bound key too long");
|
|
1232
1307
|
let id = getBytes(encoded, len);
|
|
1233
1308
|
return { timestamp, id };
|
|
1234
1309
|
}
|
|
1310
|
+
// Encoding
|
|
1235
1311
|
encodeTimestampOut(timestamp) {
|
|
1236
1312
|
if (timestamp === Number.MAX_VALUE) {
|
|
1237
1313
|
this.lastTimestampOut = Number.MAX_VALUE;
|
|
@@ -1243,7 +1319,7 @@ class Negentropy {
|
|
|
1243
1319
|
return encodeVarInt(timestamp + 1);
|
|
1244
1320
|
}
|
|
1245
1321
|
encodeBound(key) {
|
|
1246
|
-
let output = new WrappedBuffer;
|
|
1322
|
+
let output = new WrappedBuffer();
|
|
1247
1323
|
output.extend(this.encodeTimestampOut(key.timestamp));
|
|
1248
1324
|
output.extend(encodeVarInt(key.id.length));
|
|
1249
1325
|
output.extend(key.id);
|
|
@@ -1256,30 +1332,24 @@ class Negentropy {
|
|
|
1256
1332
|
let sharedPrefixBytes = 0;
|
|
1257
1333
|
let currKey = curr.id;
|
|
1258
1334
|
let prevKey = prev.id;
|
|
1259
|
-
for (let i = 0;i < ID_SIZE; i++) {
|
|
1260
|
-
if (currKey[i] !== prevKey[i])
|
|
1261
|
-
break;
|
|
1335
|
+
for (let i = 0; i < ID_SIZE; i++) {
|
|
1336
|
+
if (currKey[i] !== prevKey[i]) break;
|
|
1262
1337
|
sharedPrefixBytes++;
|
|
1263
1338
|
}
|
|
1264
1339
|
return this._bound(curr.timestamp, curr.id.subarray(0, sharedPrefixBytes + 1));
|
|
1265
1340
|
}
|
|
1266
1341
|
}
|
|
1267
|
-
}
|
|
1342
|
+
};
|
|
1268
1343
|
function loadInputBuffer(inp) {
|
|
1269
|
-
if (typeof inp === "string")
|
|
1270
|
-
|
|
1271
|
-
else if (__proto__ !== Uint8Array.prototype)
|
|
1272
|
-
inp = new Uint8Array(inp);
|
|
1344
|
+
if (typeof inp === "string") inp = hexToUint8Array(inp);
|
|
1345
|
+
else if (__proto__ !== Uint8Array.prototype) inp = new Uint8Array(inp);
|
|
1273
1346
|
return inp;
|
|
1274
1347
|
}
|
|
1275
1348
|
function hexToUint8Array(h) {
|
|
1276
|
-
if (h.startsWith("0x"))
|
|
1277
|
-
|
|
1278
|
-
if (h.length % 2 === 1)
|
|
1279
|
-
throw Error("odd length of hex string");
|
|
1349
|
+
if (h.startsWith("0x")) h = h.substr(2);
|
|
1350
|
+
if (h.length % 2 === 1) throw Error("odd length of hex string");
|
|
1280
1351
|
let arr = new Uint8Array(h.length / 2);
|
|
1281
|
-
for (let i = 0;i < arr.length; i++)
|
|
1282
|
-
arr[i] = parseInt(h.substr(i * 2, 2), 16);
|
|
1352
|
+
for (let i = 0; i < arr.length; i++) arr[i] = parseInt(h.substr(i * 2, 2), 16);
|
|
1283
1353
|
return arr;
|
|
1284
1354
|
}
|
|
1285
1355
|
var uint8ArrayToHexLookupTable = new Array(256);
|
|
@@ -1302,28 +1372,24 @@ var uint8ArrayToHexLookupTable = new Array(256);
|
|
|
1302
1372
|
"e",
|
|
1303
1373
|
"f"
|
|
1304
1374
|
];
|
|
1305
|
-
for (let i = 0;i < 256; i++) {
|
|
1375
|
+
for (let i = 0; i < 256; i++) {
|
|
1306
1376
|
uint8ArrayToHexLookupTable[i] = hexAlphabet[i >>> 4 & 15] + hexAlphabet[i & 15];
|
|
1307
1377
|
}
|
|
1308
1378
|
}
|
|
1309
1379
|
function uint8ArrayToHex(arr) {
|
|
1310
1380
|
let out = "";
|
|
1311
|
-
for (let i = 0, edx = arr.length;i < edx; i++) {
|
|
1381
|
+
for (let i = 0, edx = arr.length; i < edx; i++) {
|
|
1312
1382
|
out += uint8ArrayToHexLookupTable[arr[i]];
|
|
1313
1383
|
}
|
|
1314
1384
|
return out;
|
|
1315
1385
|
}
|
|
1316
1386
|
function compareUint8Array(a, b) {
|
|
1317
|
-
for (let i = 0;i < a.byteLength; i++) {
|
|
1318
|
-
if (a[i] < b[i])
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
if (a.byteLength > b.byteLength)
|
|
1324
|
-
return 1;
|
|
1325
|
-
if (a.byteLength < b.byteLength)
|
|
1326
|
-
return -1;
|
|
1387
|
+
for (let i = 0; i < a.byteLength; i++) {
|
|
1388
|
+
if (a[i] < b[i]) return -1;
|
|
1389
|
+
if (a[i] > b[i]) return 1;
|
|
1390
|
+
}
|
|
1391
|
+
if (a.byteLength > b.byteLength) return 1;
|
|
1392
|
+
if (a.byteLength < b.byteLength) return -1;
|
|
1327
1393
|
return 0;
|
|
1328
1394
|
}
|
|
1329
1395
|
function itemCompare(a, b) {
|
|
@@ -1339,7 +1405,7 @@ import { GiftWrap } from "nostr-tools/kinds";
|
|
|
1339
1405
|
function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
|
|
1340
1406
|
return Effect7.gen(function* () {
|
|
1341
1407
|
const allGiftWraps = yield* storage.getAllGiftWraps();
|
|
1342
|
-
const storageVector = new NegentropyStorageVector;
|
|
1408
|
+
const storageVector = new NegentropyStorageVector();
|
|
1343
1409
|
for (const gw of allGiftWraps) {
|
|
1344
1410
|
storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
|
|
1345
1411
|
}
|
|
@@ -1363,8 +1429,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
|
|
|
1363
1429
|
let currentMsg = initialMsg;
|
|
1364
1430
|
while (currentMsg !== null) {
|
|
1365
1431
|
const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
|
|
1366
|
-
if (response.msgHex === null)
|
|
1367
|
-
break;
|
|
1432
|
+
if (response.msgHex === null) break;
|
|
1368
1433
|
const reconcileResult = yield* Effect7.try({
|
|
1369
1434
|
try: () => neg.reconcile(response.msgHex),
|
|
1370
1435
|
catch: (e) => new SyncError({
|
|
@@ -1374,10 +1439,8 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
|
|
|
1374
1439
|
})
|
|
1375
1440
|
});
|
|
1376
1441
|
const [nextMsg, haveIds, needIds] = reconcileResult;
|
|
1377
|
-
for (const id of haveIds)
|
|
1378
|
-
|
|
1379
|
-
for (const id of needIds)
|
|
1380
|
-
allNeedIds.push(id);
|
|
1442
|
+
for (const id of haveIds) allHaveIds.push(id);
|
|
1443
|
+
for (const id of needIds) allNeedIds.push(id);
|
|
1381
1444
|
currentMsg = nextMsg;
|
|
1382
1445
|
}
|
|
1383
1446
|
yield* Effect7.logDebug("Negentropy reconciliation complete", {
|
|
@@ -1425,12 +1488,11 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
|
|
|
1425
1488
|
kind: 1,
|
|
1426
1489
|
content: JSON.stringify(rotationData),
|
|
1427
1490
|
tags: [["d", `_system:rotation:${epochId}`]],
|
|
1428
|
-
created_at: Math.floor(Date.now() /
|
|
1491
|
+
created_at: Math.floor(Date.now() / 1e3)
|
|
1429
1492
|
};
|
|
1430
1493
|
const wrappedEvents = [];
|
|
1431
1494
|
for (const memberPubkey of remainingMemberPubkeys) {
|
|
1432
|
-
if (memberPubkey === senderPublicKey)
|
|
1433
|
-
continue;
|
|
1495
|
+
if (memberPubkey === senderPublicKey) continue;
|
|
1434
1496
|
const wrapped = wrapEvent(rumor, senderPrivateKey, memberPubkey);
|
|
1435
1497
|
wrappedEvents.push(wrapped);
|
|
1436
1498
|
}
|
|
@@ -1443,7 +1505,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
|
|
|
1443
1505
|
kind: 1,
|
|
1444
1506
|
content: JSON.stringify(removalData),
|
|
1445
1507
|
tags: [["d", `_system:removed:${epochId}`]],
|
|
1446
|
-
created_at: Math.floor(Date.now() /
|
|
1508
|
+
created_at: Math.floor(Date.now() / 1e3)
|
|
1447
1509
|
};
|
|
1448
1510
|
const removalNotices = [];
|
|
1449
1511
|
for (const removedPubkey of removedMemberPubkeys) {
|
|
@@ -1453,8 +1515,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
|
|
|
1453
1515
|
return { epoch, wrappedEvents, removalNotices };
|
|
1454
1516
|
}
|
|
1455
1517
|
function parseRotationEvent(content, dTag) {
|
|
1456
|
-
if (!dTag.startsWith("_system:rotation:"))
|
|
1457
|
-
return Option4.none();
|
|
1518
|
+
if (!dTag.startsWith("_system:rotation:")) return Option4.none();
|
|
1458
1519
|
try {
|
|
1459
1520
|
return Option4.some(decodeRotationData(content));
|
|
1460
1521
|
} catch {
|
|
@@ -1462,8 +1523,7 @@ function parseRotationEvent(content, dTag) {
|
|
|
1462
1523
|
}
|
|
1463
1524
|
}
|
|
1464
1525
|
function parseRemovalNotice(content, dTag) {
|
|
1465
|
-
if (!dTag.startsWith("_system:removed:"))
|
|
1466
|
-
return Option4.none();
|
|
1526
|
+
if (!dTag.startsWith("_system:removed:")) return Option4.none();
|
|
1467
1527
|
try {
|
|
1468
1528
|
return Option4.some(decodeRemovalNotice(content));
|
|
1469
1529
|
} catch {
|
|
@@ -1483,65 +1543,79 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
|
|
|
1483
1543
|
kind: "create"
|
|
1484
1544
|
});
|
|
1485
1545
|
const forkHandled = (effect) => {
|
|
1486
|
-
Effect8.runFork(
|
|
1546
|
+
Effect8.runFork(
|
|
1547
|
+
effect.pipe(
|
|
1548
|
+
Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))),
|
|
1549
|
+
Effect8.ignore,
|
|
1550
|
+
Effect8.provide(logLayer),
|
|
1551
|
+
Effect8.forkIn(scope)
|
|
1552
|
+
)
|
|
1553
|
+
);
|
|
1487
1554
|
};
|
|
1488
1555
|
let autoFlushActive = false;
|
|
1489
1556
|
const autoFlushEffect = Effect8.gen(function* () {
|
|
1490
1557
|
const size = yield* publishQueue.size();
|
|
1491
|
-
if (size === 0)
|
|
1492
|
-
return;
|
|
1558
|
+
if (size === 0) return;
|
|
1493
1559
|
yield* syncStatus.set("syncing");
|
|
1494
1560
|
yield* publishQueue.flush(relayUrls);
|
|
1495
1561
|
const remaining = yield* publishQueue.size();
|
|
1496
|
-
if (remaining > 0)
|
|
1497
|
-
|
|
1498
|
-
|
|
1562
|
+
if (remaining > 0) yield* Effect8.fail("pending");
|
|
1563
|
+
}).pipe(
|
|
1564
|
+
Effect8.ensuring(syncStatus.set("idle")),
|
|
1565
|
+
Effect8.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
|
|
1566
|
+
Effect8.ignore
|
|
1567
|
+
);
|
|
1499
1568
|
const scheduleAutoFlush = () => {
|
|
1500
|
-
if (autoFlushActive)
|
|
1501
|
-
return;
|
|
1569
|
+
if (autoFlushActive) return;
|
|
1502
1570
|
autoFlushActive = true;
|
|
1503
|
-
forkHandled(
|
|
1504
|
-
|
|
1505
|
-
|
|
1571
|
+
forkHandled(
|
|
1572
|
+
autoFlushEffect.pipe(
|
|
1573
|
+
Effect8.ensuring(
|
|
1574
|
+
Effect8.sync(() => {
|
|
1575
|
+
autoFlushActive = false;
|
|
1576
|
+
})
|
|
1577
|
+
)
|
|
1578
|
+
)
|
|
1579
|
+
);
|
|
1506
1580
|
};
|
|
1507
1581
|
const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
|
|
1508
1582
|
const memberRecord = yield* storage.getRecord("_members", authorPubkey);
|
|
1509
|
-
if (!memberRecord)
|
|
1510
|
-
return false;
|
|
1583
|
+
if (!memberRecord) return false;
|
|
1511
1584
|
return !!memberRecord.removedAt;
|
|
1512
1585
|
});
|
|
1513
1586
|
const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
|
|
1514
1587
|
const existing = yield* storage.getGiftWrap(remoteGw.id);
|
|
1515
|
-
if (existing)
|
|
1516
|
-
return null;
|
|
1588
|
+
if (existing) return null;
|
|
1517
1589
|
const unwrapResult = yield* Effect8.result(giftWrapHandle.unwrap(remoteGw));
|
|
1518
1590
|
if (unwrapResult._tag === "Failure") {
|
|
1519
|
-
yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
|
|
1591
|
+
yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
|
|
1520
1592
|
return null;
|
|
1521
1593
|
}
|
|
1522
1594
|
const rumor = unwrapResult.success;
|
|
1523
1595
|
const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
|
|
1524
1596
|
if (!dTag) {
|
|
1525
|
-
yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
|
|
1597
|
+
yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
|
|
1526
1598
|
return null;
|
|
1527
1599
|
}
|
|
1528
1600
|
const colonIdx = dTag.indexOf(":");
|
|
1529
1601
|
if (colonIdx === -1) {
|
|
1530
|
-
yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
|
|
1602
|
+
yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
|
|
1531
1603
|
return null;
|
|
1532
1604
|
}
|
|
1533
1605
|
const collectionName = dTag.substring(0, colonIdx);
|
|
1534
1606
|
const recordId = dTag.substring(colonIdx + 1);
|
|
1535
1607
|
const retention = knownCollections.get(collectionName);
|
|
1536
|
-
if (retention ===
|
|
1537
|
-
yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
|
|
1608
|
+
if (retention === void 0) {
|
|
1609
|
+
yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
|
|
1538
1610
|
return null;
|
|
1539
1611
|
}
|
|
1540
1612
|
if (rumor.pubkey) {
|
|
1541
1613
|
const reject = yield* shouldRejectWrite(rumor.pubkey);
|
|
1542
1614
|
if (reject) {
|
|
1543
|
-
yield* Effect8.logWarning("Rejected write from removed member", {
|
|
1544
|
-
|
|
1615
|
+
yield* Effect8.logWarning("Rejected write from removed member", {
|
|
1616
|
+
author: rumor.pubkey.slice(0, 12)
|
|
1617
|
+
});
|
|
1618
|
+
yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
|
|
1545
1619
|
return null;
|
|
1546
1620
|
}
|
|
1547
1621
|
}
|
|
@@ -1549,14 +1623,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
|
|
|
1549
1623
|
let kind = "u";
|
|
1550
1624
|
const parsed = yield* Effect8.try({
|
|
1551
1625
|
try: () => JSON.parse(rumor.content),
|
|
1552
|
-
catch: () =>
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
return;
|
|
1557
|
-
}));
|
|
1558
|
-
if (parsed === undefined) {
|
|
1559
|
-
yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
|
|
1626
|
+
catch: () => void 0
|
|
1627
|
+
}).pipe(Effect8.orElseSucceed(() => void 0));
|
|
1628
|
+
if (parsed === void 0) {
|
|
1629
|
+
yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
|
|
1560
1630
|
return null;
|
|
1561
1631
|
}
|
|
1562
1632
|
if (parsed === null || parsed._deleted) {
|
|
@@ -1564,18 +1634,19 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
|
|
|
1564
1634
|
} else {
|
|
1565
1635
|
data = parsed;
|
|
1566
1636
|
}
|
|
1567
|
-
const author = rumor.pubkey ||
|
|
1637
|
+
const author = rumor.pubkey || void 0;
|
|
1568
1638
|
const event = {
|
|
1569
1639
|
id: rumor.id,
|
|
1570
1640
|
collection: collectionName,
|
|
1571
1641
|
recordId,
|
|
1572
1642
|
kind,
|
|
1573
1643
|
data,
|
|
1574
|
-
createdAt: rumor.created_at *
|
|
1644
|
+
createdAt: rumor.created_at * 1e3,
|
|
1575
1645
|
author
|
|
1576
1646
|
};
|
|
1577
1647
|
yield* storage.putGiftWrap({
|
|
1578
1648
|
id: remoteGw.id,
|
|
1649
|
+
event: remoteGw,
|
|
1579
1650
|
createdAt: remoteGw.created_at
|
|
1580
1651
|
});
|
|
1581
1652
|
yield* storage.putEvent(event);
|
|
@@ -1583,7 +1654,12 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
|
|
|
1583
1654
|
if (didApply && (kind === "u" || kind === "d")) {
|
|
1584
1655
|
yield* pruneEvents(storage, collectionName, recordId, retention);
|
|
1585
1656
|
}
|
|
1586
|
-
yield* Effect8.logDebug("Processed gift wrap", {
|
|
1657
|
+
yield* Effect8.logDebug("Processed gift wrap", {
|
|
1658
|
+
collection: collectionName,
|
|
1659
|
+
recordId,
|
|
1660
|
+
kind,
|
|
1661
|
+
author: author?.slice(0, 12)
|
|
1662
|
+
});
|
|
1587
1663
|
if (author && onNewAuthor) {
|
|
1588
1664
|
onNewAuthor(author);
|
|
1589
1665
|
}
|
|
@@ -1596,32 +1672,34 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
|
|
|
1596
1672
|
}
|
|
1597
1673
|
});
|
|
1598
1674
|
const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
|
|
1599
|
-
const unwrapResult = yield* Effect8.result(
|
|
1600
|
-
try
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1675
|
+
const unwrapResult = yield* Effect8.result(
|
|
1676
|
+
Effect8.try({
|
|
1677
|
+
try: () => unwrapEvent(remoteGw, personalPrivateKey),
|
|
1678
|
+
catch: (e) => new CryptoError({
|
|
1679
|
+
message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
1680
|
+
cause: e
|
|
1681
|
+
})
|
|
1604
1682
|
})
|
|
1605
|
-
|
|
1606
|
-
if (unwrapResult._tag === "Failure")
|
|
1607
|
-
return false;
|
|
1683
|
+
);
|
|
1684
|
+
if (unwrapResult._tag === "Failure") return false;
|
|
1608
1685
|
const rumor = unwrapResult.success;
|
|
1609
1686
|
const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
|
|
1610
|
-
if (!dTag)
|
|
1611
|
-
return false;
|
|
1687
|
+
if (!dTag) return false;
|
|
1612
1688
|
const removalNoticeOpt = parseRemovalNotice(rumor.content, dTag);
|
|
1613
1689
|
if (Option5.isSome(removalNoticeOpt)) {
|
|
1614
|
-
if (onRemoved)
|
|
1615
|
-
onRemoved(removalNoticeOpt.value);
|
|
1690
|
+
if (onRemoved) onRemoved(removalNoticeOpt.value);
|
|
1616
1691
|
return true;
|
|
1617
1692
|
}
|
|
1618
1693
|
const rotationDataOpt = parseRotationEvent(rumor.content, dTag);
|
|
1619
|
-
if (Option5.isNone(rotationDataOpt))
|
|
1620
|
-
return false;
|
|
1694
|
+
if (Option5.isNone(rotationDataOpt)) return false;
|
|
1621
1695
|
const rotationData = rotationDataOpt.value;
|
|
1622
|
-
if (epochStore.epochs.has(rotationData.epochId))
|
|
1623
|
-
|
|
1624
|
-
|
|
1696
|
+
if (epochStore.epochs.has(rotationData.epochId)) return false;
|
|
1697
|
+
const epoch = createEpochKey(
|
|
1698
|
+
rotationData.epochId,
|
|
1699
|
+
rotationData.epochKey,
|
|
1700
|
+
rumor.pubkey || "",
|
|
1701
|
+
rotationData.parentEpoch
|
|
1702
|
+
);
|
|
1625
1703
|
addEpoch(epochStore, epoch);
|
|
1626
1704
|
epochStore.currentEpochId = epoch.id;
|
|
1627
1705
|
let membersChanged = false;
|
|
@@ -1641,79 +1719,158 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
|
|
|
1641
1719
|
membersChanged = true;
|
|
1642
1720
|
}
|
|
1643
1721
|
}
|
|
1644
|
-
if (membersChanged && onMembersChanged)
|
|
1645
|
-
onMembersChanged();
|
|
1722
|
+
if (membersChanged && onMembersChanged) onMembersChanged();
|
|
1646
1723
|
yield* handle.addEpochSubscription(epoch.publicKey);
|
|
1647
1724
|
return true;
|
|
1648
1725
|
});
|
|
1649
|
-
const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1726
|
+
const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
|
|
1727
|
+
relayUrls,
|
|
1728
|
+
(url) => Effect8.gen(function* () {
|
|
1729
|
+
yield* relay.subscribe(filter, url, (event) => {
|
|
1730
|
+
forkHandled(onEvent(event));
|
|
1731
|
+
}).pipe(
|
|
1732
|
+
Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
|
|
1733
|
+
Effect8.ignore
|
|
1734
|
+
);
|
|
1735
|
+
}),
|
|
1736
|
+
{ discard: true }
|
|
1737
|
+
);
|
|
1654
1738
|
const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
|
|
1655
1739
|
yield* Effect8.logDebug("Syncing relay", { relay: url });
|
|
1656
|
-
const reconcileResult = yield* Effect8.result(
|
|
1740
|
+
const reconcileResult = yield* Effect8.result(
|
|
1741
|
+
reconcileWithRelay(storage, relay, url, Array.from(pubKeys))
|
|
1742
|
+
);
|
|
1657
1743
|
if (reconcileResult._tag === "Failure") {
|
|
1658
1744
|
onSyncError?.(reconcileResult.failure);
|
|
1659
1745
|
return;
|
|
1660
1746
|
}
|
|
1661
1747
|
const { haveIds, needIds } = reconcileResult.success;
|
|
1662
|
-
yield* Effect8.logDebug("Relay reconciliation result", {
|
|
1748
|
+
yield* Effect8.logDebug("Relay reconciliation result", {
|
|
1749
|
+
relay: url,
|
|
1750
|
+
need: needIds.length,
|
|
1751
|
+
have: haveIds.length
|
|
1752
|
+
});
|
|
1663
1753
|
if (needIds.length > 0) {
|
|
1664
|
-
const fetched = yield* relay.fetchEvents(needIds, url).pipe(
|
|
1754
|
+
const fetched = yield* relay.fetchEvents(needIds, url).pipe(
|
|
1755
|
+
Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
|
|
1756
|
+
Effect8.orElseSucceed(() => [])
|
|
1757
|
+
);
|
|
1665
1758
|
const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
|
|
1666
|
-
yield* Effect8.forEach(
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1759
|
+
yield* Effect8.forEach(
|
|
1760
|
+
sorted,
|
|
1761
|
+
(remoteGw) => Effect8.gen(function* () {
|
|
1762
|
+
const collection2 = yield* processGiftWrap(remoteGw).pipe(
|
|
1763
|
+
Effect8.orElseSucceed(() => null)
|
|
1764
|
+
);
|
|
1765
|
+
if (collection2) changedCollections.add(collection2);
|
|
1766
|
+
}),
|
|
1767
|
+
{ discard: true }
|
|
1768
|
+
);
|
|
1671
1769
|
}
|
|
1672
1770
|
if (haveIds.length > 0) {
|
|
1673
|
-
yield* Effect8.forEach(
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1771
|
+
yield* Effect8.forEach(
|
|
1772
|
+
haveIds,
|
|
1773
|
+
(id) => Effect8.gen(function* () {
|
|
1774
|
+
const gw = yield* storage.getGiftWrap(id);
|
|
1775
|
+
if (!gw?.event) return;
|
|
1776
|
+
yield* relay.publish(gw.event, [url]).pipe(
|
|
1777
|
+
Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
|
|
1778
|
+
Effect8.ignore
|
|
1779
|
+
);
|
|
1780
|
+
}),
|
|
1781
|
+
{ discard: true }
|
|
1782
|
+
);
|
|
1679
1783
|
}
|
|
1680
1784
|
}).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
|
|
1785
|
+
let healingActive = false;
|
|
1786
|
+
const healingEffect = Effect8.gen(function* () {
|
|
1787
|
+
if (!healingActive) return;
|
|
1788
|
+
const status = yield* syncStatus.get();
|
|
1789
|
+
if (status === "syncing") return;
|
|
1790
|
+
yield* syncStatus.set("syncing");
|
|
1791
|
+
yield* Effect8.gen(function* () {
|
|
1792
|
+
const pubKeys = getSubscriptionPubKeys();
|
|
1793
|
+
const changedCollections = /* @__PURE__ */ new Set();
|
|
1794
|
+
yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
|
|
1795
|
+
discard: true
|
|
1796
|
+
});
|
|
1797
|
+
if (changedCollections.size > 0) {
|
|
1798
|
+
yield* notifyReplayComplete(watchCtx, [...changedCollections]);
|
|
1799
|
+
}
|
|
1800
|
+
}).pipe(Effect8.ensuring(syncStatus.set("idle")));
|
|
1801
|
+
}).pipe(Effect8.ignore);
|
|
1681
1802
|
const handle = {
|
|
1682
1803
|
sync: () => Effect8.gen(function* () {
|
|
1683
1804
|
yield* Effect8.logInfo("Sync started");
|
|
1684
1805
|
yield* syncStatus.set("syncing");
|
|
1685
1806
|
yield* Ref3.set(watchCtx.replayingRef, true);
|
|
1686
|
-
const changedCollections = new Set;
|
|
1807
|
+
const changedCollections = /* @__PURE__ */ new Set();
|
|
1687
1808
|
yield* Effect8.gen(function* () {
|
|
1688
1809
|
const pubKeys = getSubscriptionPubKeys();
|
|
1689
1810
|
yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
|
|
1690
1811
|
discard: true
|
|
1691
1812
|
});
|
|
1692
1813
|
yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
|
|
1693
|
-
}).pipe(
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1814
|
+
}).pipe(
|
|
1815
|
+
Effect8.ensuring(
|
|
1816
|
+
Effect8.gen(function* () {
|
|
1817
|
+
yield* notifyReplayComplete(watchCtx, [...changedCollections]);
|
|
1818
|
+
yield* syncStatus.set("idle");
|
|
1819
|
+
})
|
|
1820
|
+
)
|
|
1821
|
+
);
|
|
1697
1822
|
yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
|
|
1698
1823
|
}).pipe(Effect8.withLogSpan("tablinum.sync")),
|
|
1699
1824
|
publishLocal: (giftWrap) => Effect8.gen(function* () {
|
|
1700
|
-
if (!giftWrap.event)
|
|
1701
|
-
|
|
1702
|
-
|
|
1825
|
+
if (!giftWrap.event) return;
|
|
1826
|
+
yield* relay.publish(giftWrap.event, relayUrls).pipe(
|
|
1827
|
+
Effect8.tapError(
|
|
1828
|
+
() => storage.putGiftWrap(giftWrap).pipe(
|
|
1829
|
+
Effect8.andThen(publishQueue.enqueue(giftWrap.id)),
|
|
1830
|
+
Effect8.andThen(Effect8.sync(() => scheduleAutoFlush()))
|
|
1831
|
+
)
|
|
1832
|
+
),
|
|
1833
|
+
Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
|
|
1834
|
+
Effect8.ignore
|
|
1835
|
+
);
|
|
1703
1836
|
}),
|
|
1704
1837
|
startSubscription: () => Effect8.gen(function* () {
|
|
1705
1838
|
const pubKeys = getSubscriptionPubKeys();
|
|
1706
1839
|
yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
|
|
1707
1840
|
if (!pubKeys.includes(personalPublicKey)) {
|
|
1708
|
-
yield* subscribeAcrossRelays(
|
|
1841
|
+
yield* subscribeAcrossRelays(
|
|
1842
|
+
{ kinds: [GiftWrap2], "#p": [personalPublicKey] },
|
|
1843
|
+
(event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid)
|
|
1844
|
+
);
|
|
1709
1845
|
}
|
|
1710
1846
|
}),
|
|
1711
|
-
addEpochSubscription: (publicKey) => subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [publicKey] }, processRealtimeGiftWrap)
|
|
1847
|
+
addEpochSubscription: (publicKey) => subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [publicKey] }, processRealtimeGiftWrap),
|
|
1848
|
+
startHealing: () => {
|
|
1849
|
+
if (healingActive) return;
|
|
1850
|
+
healingActive = true;
|
|
1851
|
+
forkHandled(
|
|
1852
|
+
Effect8.sleep(Duration.minutes(5)).pipe(
|
|
1853
|
+
Effect8.andThen(healingEffect),
|
|
1854
|
+
Effect8.repeat(Schedule.spaced(Duration.minutes(5))),
|
|
1855
|
+
Effect8.ensuring(Effect8.sync(() => {
|
|
1856
|
+
healingActive = false;
|
|
1857
|
+
}))
|
|
1858
|
+
)
|
|
1859
|
+
);
|
|
1860
|
+
},
|
|
1861
|
+
stopHealing: () => {
|
|
1862
|
+
healingActive = false;
|
|
1863
|
+
}
|
|
1712
1864
|
};
|
|
1713
|
-
forkHandled(
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1865
|
+
forkHandled(
|
|
1866
|
+
publishQueue.size().pipe(
|
|
1867
|
+
Effect8.flatMap(
|
|
1868
|
+
(size) => Effect8.sync(() => {
|
|
1869
|
+
if (size > 0) scheduleAutoFlush();
|
|
1870
|
+
})
|
|
1871
|
+
)
|
|
1872
|
+
)
|
|
1873
|
+
);
|
|
1717
1874
|
return handle;
|
|
1718
1875
|
}
|
|
1719
1876
|
|
|
@@ -1769,9 +1926,14 @@ var decodeAuthorProfile = Schema5.decodeUnknownEffect(Schema5.fromJsonString(Aut
|
|
|
1769
1926
|
function fetchAuthorProfile(relay, relayUrls, pubkey) {
|
|
1770
1927
|
return Effect9.gen(function* () {
|
|
1771
1928
|
for (const url of relayUrls) {
|
|
1772
|
-
const result = yield* Effect9.result(
|
|
1929
|
+
const result = yield* Effect9.result(
|
|
1930
|
+
relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url)
|
|
1931
|
+
);
|
|
1773
1932
|
if (result._tag === "Success" && result.success.length > 0) {
|
|
1774
|
-
return yield* decodeAuthorProfile(result.success[0].content).pipe(
|
|
1933
|
+
return yield* decodeAuthorProfile(result.success[0].content).pipe(
|
|
1934
|
+
Effect9.map(Option6.some),
|
|
1935
|
+
Effect9.orElseSucceed(() => Option6.none())
|
|
1936
|
+
);
|
|
1775
1937
|
}
|
|
1776
1938
|
}
|
|
1777
1939
|
return Option6.none();
|
|
@@ -1780,45 +1942,44 @@ function fetchAuthorProfile(relay, relayUrls, pubkey) {
|
|
|
1780
1942
|
|
|
1781
1943
|
// src/services/Identity.ts
|
|
1782
1944
|
import { ServiceMap as ServiceMap3 } from "effect";
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
}
|
|
1945
|
+
var Identity = class extends ServiceMap3.Service()("tablinum/Identity") {
|
|
1946
|
+
};
|
|
1786
1947
|
|
|
1787
1948
|
// src/services/EpochStore.ts
|
|
1788
1949
|
import { ServiceMap as ServiceMap4 } from "effect";
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1950
|
+
var EpochStore = class extends ServiceMap4.Service()(
|
|
1951
|
+
"tablinum/EpochStore"
|
|
1952
|
+
) {
|
|
1953
|
+
};
|
|
1792
1954
|
|
|
1793
1955
|
// src/services/Storage.ts
|
|
1794
1956
|
import { ServiceMap as ServiceMap5 } from "effect";
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
}
|
|
1957
|
+
var Storage = class extends ServiceMap5.Service()("tablinum/Storage") {
|
|
1958
|
+
};
|
|
1798
1959
|
|
|
1799
1960
|
// src/services/Relay.ts
|
|
1800
1961
|
import { ServiceMap as ServiceMap6 } from "effect";
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
}
|
|
1962
|
+
var Relay = class extends ServiceMap6.Service()("tablinum/Relay") {
|
|
1963
|
+
};
|
|
1804
1964
|
|
|
1805
1965
|
// src/services/GiftWrap.ts
|
|
1806
1966
|
import { ServiceMap as ServiceMap7 } from "effect";
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
}
|
|
1967
|
+
var GiftWrap3 = class extends ServiceMap7.Service()("tablinum/GiftWrap") {
|
|
1968
|
+
};
|
|
1810
1969
|
|
|
1811
1970
|
// src/services/PublishQueue.ts
|
|
1812
1971
|
import { ServiceMap as ServiceMap8 } from "effect";
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1972
|
+
var PublishQueue = class extends ServiceMap8.Service()(
|
|
1973
|
+
"tablinum/PublishQueue"
|
|
1974
|
+
) {
|
|
1975
|
+
};
|
|
1816
1976
|
|
|
1817
1977
|
// src/services/SyncStatus.ts
|
|
1818
1978
|
import { ServiceMap as ServiceMap9 } from "effect";
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1979
|
+
var SyncStatus = class extends ServiceMap9.Service()(
|
|
1980
|
+
"tablinum/SyncStatus"
|
|
1981
|
+
) {
|
|
1982
|
+
};
|
|
1822
1983
|
|
|
1823
1984
|
// src/layers/IdentityLive.ts
|
|
1824
1985
|
import { Effect as Effect11, Layer as Layer2 } from "effect";
|
|
@@ -1859,47 +2020,59 @@ function createIdentity(suppliedKey) {
|
|
|
1859
2020
|
}
|
|
1860
2021
|
|
|
1861
2022
|
// src/layers/IdentityLive.ts
|
|
1862
|
-
var IdentityLive = Layer2.effect(
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
})
|
|
2023
|
+
var IdentityLive = Layer2.effect(
|
|
2024
|
+
Identity,
|
|
2025
|
+
Effect11.gen(function* () {
|
|
2026
|
+
const config = yield* Config;
|
|
2027
|
+
const storage = yield* Storage;
|
|
2028
|
+
const idbKey = yield* storage.getMeta("identity_key");
|
|
2029
|
+
const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : void 0);
|
|
2030
|
+
const identity = yield* createIdentity(resolvedKey);
|
|
2031
|
+
yield* storage.putMeta("identity_key", identity.exportKey());
|
|
2032
|
+
yield* Effect11.logInfo("Identity loaded", {
|
|
2033
|
+
publicKey: identity.publicKey.slice(0, 12) + "...",
|
|
2034
|
+
source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
|
|
2035
|
+
});
|
|
2036
|
+
return identity;
|
|
2037
|
+
})
|
|
2038
|
+
);
|
|
1875
2039
|
|
|
1876
2040
|
// src/layers/EpochStoreLive.ts
|
|
1877
2041
|
import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
|
|
1878
2042
|
import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
|
|
1879
2043
|
import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
|
|
1880
|
-
var EpochStoreLive = Layer3.effect(
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
const
|
|
1887
|
-
if (
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
}
|
|
2044
|
+
var EpochStoreLive = Layer3.effect(
|
|
2045
|
+
EpochStore,
|
|
2046
|
+
Effect12.gen(function* () {
|
|
2047
|
+
const config = yield* Config;
|
|
2048
|
+
const identity = yield* Identity;
|
|
2049
|
+
const storage = yield* Storage;
|
|
2050
|
+
const idbRaw = yield* storage.getMeta("epochs");
|
|
2051
|
+
if (typeof idbRaw === "string") {
|
|
2052
|
+
const idbStore = deserializeEpochStore(idbRaw);
|
|
2053
|
+
if (Option7.isSome(idbStore)) {
|
|
2054
|
+
yield* Effect12.logInfo("Epoch store loaded", {
|
|
2055
|
+
source: "storage",
|
|
2056
|
+
epochs: idbStore.value.epochs.size
|
|
2057
|
+
});
|
|
2058
|
+
return idbStore.value;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (config.epochKeys && config.epochKeys.length > 0) {
|
|
2062
|
+
const store2 = createEpochStoreFromInputs(config.epochKeys);
|
|
2063
|
+
yield* storage.putMeta("epochs", stringifyEpochStore(store2));
|
|
2064
|
+
yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
|
|
2065
|
+
return store2;
|
|
2066
|
+
}
|
|
2067
|
+
const store = createEpochStoreFromInputs(
|
|
2068
|
+
[{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }],
|
|
2069
|
+
{ createdBy: identity.publicKey }
|
|
2070
|
+
);
|
|
2071
|
+
yield* storage.putMeta("epochs", stringifyEpochStore(store));
|
|
2072
|
+
yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
|
|
2073
|
+
return store;
|
|
2074
|
+
})
|
|
2075
|
+
);
|
|
1903
2076
|
|
|
1904
2077
|
// src/layers/StorageLive.ts
|
|
1905
2078
|
import { Effect as Effect14, Layer as Layer4 } from "effect";
|
|
@@ -1907,6 +2080,68 @@ import { Effect as Effect14, Layer as Layer4 } from "effect";
|
|
|
1907
2080
|
// src/storage/idb.ts
|
|
1908
2081
|
import { Effect as Effect13 } from "effect";
|
|
1909
2082
|
import { openDB } from "idb";
|
|
2083
|
+
|
|
2084
|
+
// src/sync/compact-event.ts
|
|
2085
|
+
import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
|
|
2086
|
+
var VERSION = 1;
|
|
2087
|
+
var HEADER_SIZE = 133;
|
|
2088
|
+
function base64ToBytes(base64) {
|
|
2089
|
+
const binary = atob(base64);
|
|
2090
|
+
const bytes = new Uint8Array(binary.length);
|
|
2091
|
+
for (let i = 0; i < binary.length; i++) {
|
|
2092
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2093
|
+
}
|
|
2094
|
+
return bytes;
|
|
2095
|
+
}
|
|
2096
|
+
function bytesToBase64(bytes) {
|
|
2097
|
+
let binary = "";
|
|
2098
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2099
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2100
|
+
}
|
|
2101
|
+
return btoa(binary);
|
|
2102
|
+
}
|
|
2103
|
+
function packEvent(event) {
|
|
2104
|
+
const pubkey = hexToBytes4(event.pubkey);
|
|
2105
|
+
const sig = hexToBytes4(event.sig);
|
|
2106
|
+
const recipientTag = event.tags.find((t) => t[0] === "p");
|
|
2107
|
+
if (!recipientTag) throw new Error("Gift wrap missing #p tag");
|
|
2108
|
+
if (event.tags.some((t) => t[0] !== "p")) {
|
|
2109
|
+
throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
|
|
2110
|
+
}
|
|
2111
|
+
const recipient = hexToBytes4(recipientTag[1]);
|
|
2112
|
+
const createdAtBuf = new Uint8Array(4);
|
|
2113
|
+
new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
|
|
2114
|
+
const content = base64ToBytes(event.content);
|
|
2115
|
+
const result = new Uint8Array(HEADER_SIZE + content.length);
|
|
2116
|
+
result[0] = VERSION;
|
|
2117
|
+
result.set(pubkey, 1);
|
|
2118
|
+
result.set(sig, 33);
|
|
2119
|
+
result.set(recipient, 97);
|
|
2120
|
+
result.set(createdAtBuf, 129);
|
|
2121
|
+
result.set(content, HEADER_SIZE);
|
|
2122
|
+
return result;
|
|
2123
|
+
}
|
|
2124
|
+
function unpackEvent(id, compact) {
|
|
2125
|
+
const version = compact[0];
|
|
2126
|
+
if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
|
|
2127
|
+
const pubkey = bytesToHex4(compact.slice(1, 33));
|
|
2128
|
+
const sig = bytesToHex4(compact.slice(33, 97));
|
|
2129
|
+
const recipient = bytesToHex4(compact.slice(97, 129));
|
|
2130
|
+
const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
|
|
2131
|
+
const createdAt = dv.getUint32(0, false);
|
|
2132
|
+
const content = bytesToBase64(compact.slice(HEADER_SIZE));
|
|
2133
|
+
return {
|
|
2134
|
+
id,
|
|
2135
|
+
pubkey,
|
|
2136
|
+
sig,
|
|
2137
|
+
created_at: createdAt,
|
|
2138
|
+
kind: 1059,
|
|
2139
|
+
tags: [["p", recipient]],
|
|
2140
|
+
content
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/storage/idb.ts
|
|
1910
2145
|
var DB_NAME = "tablinum";
|
|
1911
2146
|
function storeName(collection2) {
|
|
1912
2147
|
return `col_${collection2}`;
|
|
@@ -1937,7 +2172,7 @@ function upgradeSchema(database, schema, tx) {
|
|
|
1937
2172
|
if (!database.objectStoreNames.contains("giftwraps")) {
|
|
1938
2173
|
database.createObjectStore("giftwraps", { keyPath: "id" });
|
|
1939
2174
|
}
|
|
1940
|
-
const expectedStores = new Set;
|
|
2175
|
+
const expectedStores = /* @__PURE__ */ new Set();
|
|
1941
2176
|
for (const [, def] of Object.entries(schema)) {
|
|
1942
2177
|
const sn = storeName(def.name);
|
|
1943
2178
|
expectedStores.add(sn);
|
|
@@ -1951,12 +2186,10 @@ function upgradeSchema(database, schema, tx) {
|
|
|
1951
2186
|
const existingIndices = new Set(Array.from(store.indexNames));
|
|
1952
2187
|
const wantedIndices = new Set(def.indices ?? []);
|
|
1953
2188
|
for (const idx of existingIndices) {
|
|
1954
|
-
if (!wantedIndices.has(idx))
|
|
1955
|
-
store.deleteIndex(idx);
|
|
2189
|
+
if (!wantedIndices.has(idx)) store.deleteIndex(idx);
|
|
1956
2190
|
}
|
|
1957
2191
|
for (const idx of wantedIndices) {
|
|
1958
|
-
if (!existingIndices.has(idx))
|
|
1959
|
-
store.createIndex(idx, idx);
|
|
2192
|
+
if (!existingIndices.has(idx)) store.createIndex(idx, idx);
|
|
1960
2193
|
}
|
|
1961
2194
|
}
|
|
1962
2195
|
}
|
|
@@ -1971,6 +2204,13 @@ function openIDBStorage(dbName, schema) {
|
|
|
1971
2204
|
return Effect13.gen(function* () {
|
|
1972
2205
|
const name = dbName ?? DB_NAME;
|
|
1973
2206
|
const schemaSig = computeSchemaSig(schema);
|
|
2207
|
+
if (typeof indexedDB === "undefined") {
|
|
2208
|
+
return yield* Effect13.fail(
|
|
2209
|
+
new StorageError({
|
|
2210
|
+
message: "IndexedDB is not available in this environment"
|
|
2211
|
+
})
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
1974
2214
|
const probeDb = yield* Effect13.tryPromise({
|
|
1975
2215
|
try: () => openDB(name),
|
|
1976
2216
|
catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
|
|
@@ -1981,7 +2221,7 @@ function openIDBStorage(dbName, schema) {
|
|
|
1981
2221
|
const storedSig = yield* Effect13.tryPromise({
|
|
1982
2222
|
try: () => probeDb.get("_meta", "schema_sig"),
|
|
1983
2223
|
catch: () => new StorageError({ message: "Failed to read schema meta" })
|
|
1984
|
-
}).pipe(Effect13.catch(() => Effect13.succeed(
|
|
2224
|
+
}).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
|
|
1985
2225
|
needsUpgrade = storedSig !== schemaSig;
|
|
1986
2226
|
}
|
|
1987
2227
|
probeDb.close();
|
|
@@ -1998,9 +2238,7 @@ function openIDBStorage(dbName, schema) {
|
|
|
1998
2238
|
});
|
|
1999
2239
|
yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
|
|
2000
2240
|
const handle = {
|
|
2001
|
-
putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() =>
|
|
2002
|
-
return;
|
|
2003
|
-
})),
|
|
2241
|
+
putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
|
|
2004
2242
|
getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
|
|
2005
2243
|
getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
|
|
2006
2244
|
countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
|
|
@@ -2020,29 +2258,52 @@ function openIDBStorage(dbName, schema) {
|
|
|
2020
2258
|
}
|
|
2021
2259
|
return results;
|
|
2022
2260
|
}),
|
|
2023
|
-
putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() =>
|
|
2024
|
-
return;
|
|
2025
|
-
})),
|
|
2261
|
+
putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
|
|
2026
2262
|
getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
|
|
2027
2263
|
getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
|
|
2028
|
-
getEventsByRecord: (collection2, recordId) => wrap(
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
const existing = await db.get("giftwraps", id);
|
|
2039
|
-
if (existing) {
|
|
2040
|
-
await db.put("giftwraps", { id: existing.id, createdAt: existing.createdAt });
|
|
2264
|
+
getEventsByRecord: (collection2, recordId) => wrap(
|
|
2265
|
+
"getEventsByRecord",
|
|
2266
|
+
() => db.getAllFromIndex("events", "by-record", [collection2, recordId])
|
|
2267
|
+
),
|
|
2268
|
+
putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
|
|
2269
|
+
if (gw.event) {
|
|
2270
|
+
const compact = packEvent(gw.event);
|
|
2271
|
+
await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
|
|
2272
|
+
} else {
|
|
2273
|
+
await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
|
|
2041
2274
|
}
|
|
2042
2275
|
}),
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2276
|
+
getGiftWrap: (id) => wrap("getGiftWrap", async () => {
|
|
2277
|
+
const raw = await db.get("giftwraps", id);
|
|
2278
|
+
if (!raw) return void 0;
|
|
2279
|
+
if (raw.compact) {
|
|
2280
|
+
return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
|
|
2281
|
+
}
|
|
2282
|
+
if (raw.event) {
|
|
2283
|
+
const compact = packEvent(raw.event);
|
|
2284
|
+
await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
|
|
2285
|
+
return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
|
|
2286
|
+
}
|
|
2287
|
+
return { id: raw.id, createdAt: raw.createdAt };
|
|
2288
|
+
}),
|
|
2289
|
+
getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
|
|
2290
|
+
const raws = await db.getAll("giftwraps");
|
|
2291
|
+
const results = [];
|
|
2292
|
+
for (const raw of raws) {
|
|
2293
|
+
if (raw.compact) {
|
|
2294
|
+
results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
|
|
2295
|
+
} else if (raw.event) {
|
|
2296
|
+
const compact = packEvent(raw.event);
|
|
2297
|
+
await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
|
|
2298
|
+
results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
|
|
2299
|
+
} else {
|
|
2300
|
+
results.push({ id: raw.id, createdAt: raw.createdAt });
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return results;
|
|
2304
|
+
}),
|
|
2305
|
+
deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
|
|
2306
|
+
deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
|
|
2046
2307
|
stripEventData: (id) => wrap("stripEventData", async () => {
|
|
2047
2308
|
const existing = await db.get("events", id);
|
|
2048
2309
|
if (existing) {
|
|
@@ -2050,9 +2311,7 @@ function openIDBStorage(dbName, schema) {
|
|
|
2050
2311
|
}
|
|
2051
2312
|
}),
|
|
2052
2313
|
getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
|
|
2053
|
-
putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() =>
|
|
2054
|
-
return;
|
|
2055
|
-
})),
|
|
2314
|
+
putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
|
|
2056
2315
|
close: () => Effect13.sync(() => db.close())
|
|
2057
2316
|
};
|
|
2058
2317
|
return handle;
|
|
@@ -2060,15 +2319,18 @@ function openIDBStorage(dbName, schema) {
|
|
|
2060
2319
|
}
|
|
2061
2320
|
|
|
2062
2321
|
// src/layers/StorageLive.ts
|
|
2063
|
-
var StorageLive = Layer4.effect(
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
})
|
|
2322
|
+
var StorageLive = Layer4.effect(
|
|
2323
|
+
Storage,
|
|
2324
|
+
Effect14.gen(function* () {
|
|
2325
|
+
const config = yield* Config;
|
|
2326
|
+
const handle = yield* openIDBStorage(config.dbName, {
|
|
2327
|
+
...config.schema,
|
|
2328
|
+
_members: membersCollectionDef
|
|
2329
|
+
});
|
|
2330
|
+
yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
|
|
2331
|
+
return handle;
|
|
2332
|
+
})
|
|
2333
|
+
);
|
|
2072
2334
|
|
|
2073
2335
|
// src/layers/RelayLive.ts
|
|
2074
2336
|
import { Layer as Layer5 } from "effect";
|
|
@@ -2082,32 +2344,41 @@ var NegMessageFrameSchema = Schema6.Tuple([
|
|
|
2082
2344
|
Schema6.String
|
|
2083
2345
|
]);
|
|
2084
2346
|
var NegErrorFrameSchema = Schema6.Tuple([Schema6.Literal("NEG-ERR"), Schema6.String, Schema6.String]);
|
|
2085
|
-
var decodeNegFrame = Schema6.decodeUnknownEffect(
|
|
2347
|
+
var decodeNegFrame = Schema6.decodeUnknownEffect(
|
|
2348
|
+
Schema6.fromJsonString(Schema6.Union([NegMessageFrameSchema, NegErrorFrameSchema]))
|
|
2349
|
+
);
|
|
2086
2350
|
function parseNegMessageFrame(data) {
|
|
2087
|
-
return Effect15.runSync(
|
|
2351
|
+
return Effect15.runSync(
|
|
2352
|
+
decodeNegFrame(data).pipe(
|
|
2353
|
+
Effect15.map(Option8.some),
|
|
2354
|
+
Effect15.orElseSucceed(() => Option8.none())
|
|
2355
|
+
)
|
|
2356
|
+
);
|
|
2088
2357
|
}
|
|
2089
2358
|
function createRelayHandle() {
|
|
2090
2359
|
return Effect15.gen(function* () {
|
|
2091
2360
|
const relayScope = yield* Effect15.scope;
|
|
2092
2361
|
const connections = yield* ScopedCache.make({
|
|
2093
2362
|
capacity: 64,
|
|
2094
|
-
lookup: (url) => Effect15.acquireRelease(
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2363
|
+
lookup: (url) => Effect15.acquireRelease(
|
|
2364
|
+
Effect15.tryPromise({
|
|
2365
|
+
try: () => Relay2.connect(url),
|
|
2366
|
+
catch: (e) => new RelayError({
|
|
2367
|
+
message: `Connect to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2368
|
+
url,
|
|
2369
|
+
cause: e
|
|
2370
|
+
})
|
|
2371
|
+
}),
|
|
2372
|
+
(relay) => Effect15.sync(() => {
|
|
2373
|
+
relay.close();
|
|
2100
2374
|
})
|
|
2101
|
-
|
|
2102
|
-
relay.close();
|
|
2103
|
-
}))
|
|
2375
|
+
)
|
|
2104
2376
|
});
|
|
2105
|
-
const connectedUrls = new Set;
|
|
2106
|
-
const statusListeners = new Set;
|
|
2377
|
+
const connectedUrls = /* @__PURE__ */ new Set();
|
|
2378
|
+
const statusListeners = /* @__PURE__ */ new Set();
|
|
2107
2379
|
const notifyStatus = () => {
|
|
2108
2380
|
const status = { connectedUrls: [...connectedUrls] };
|
|
2109
|
-
for (const listener of statusListeners)
|
|
2110
|
-
listener(status);
|
|
2381
|
+
for (const listener of statusListeners) listener(status);
|
|
2111
2382
|
};
|
|
2112
2383
|
const markConnected = (url) => {
|
|
2113
2384
|
if (!connectedUrls.has(url)) {
|
|
@@ -2121,66 +2392,98 @@ function createRelayHandle() {
|
|
|
2121
2392
|
notifyStatus();
|
|
2122
2393
|
}
|
|
2123
2394
|
};
|
|
2124
|
-
const getRelay = (url) => ScopedCache.get(connections, url).pipe(
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
sub
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2395
|
+
const getRelay = (url) => ScopedCache.get(connections, url).pipe(
|
|
2396
|
+
Effect15.flatMap(
|
|
2397
|
+
(relay) => relay.connected === false ? ScopedCache.invalidate(connections, url).pipe(
|
|
2398
|
+
Effect15.andThen(ScopedCache.get(connections, url))
|
|
2399
|
+
) : Effect15.succeed(relay)
|
|
2400
|
+
)
|
|
2401
|
+
);
|
|
2402
|
+
const withRelay = (url, run) => getRelay(url).pipe(
|
|
2403
|
+
Effect15.tap(() => Effect15.sync(() => markConnected(url))),
|
|
2404
|
+
Effect15.flatMap((relay) => run(relay)),
|
|
2405
|
+
Effect15.tapError(
|
|
2406
|
+
() => ScopedCache.invalidate(connections, url).pipe(
|
|
2407
|
+
Effect15.tap(() => Effect15.sync(() => markDisconnected(url)))
|
|
2408
|
+
)
|
|
2409
|
+
)
|
|
2410
|
+
);
|
|
2411
|
+
const collectEvents = (url, filters) => withRelay(
|
|
2412
|
+
url,
|
|
2413
|
+
(relay) => Effect15.callback((resume) => {
|
|
2414
|
+
const events = [];
|
|
2415
|
+
let settled = false;
|
|
2416
|
+
let timer;
|
|
2417
|
+
let sub;
|
|
2418
|
+
const cleanup = () => {
|
|
2419
|
+
settled = true;
|
|
2420
|
+
if (timer !== void 0) {
|
|
2421
|
+
clearTimeout(timer);
|
|
2422
|
+
timer = void 0;
|
|
2423
|
+
}
|
|
2424
|
+
sub?.close();
|
|
2425
|
+
sub = void 0;
|
|
2426
|
+
};
|
|
2427
|
+
const fail = (e) => resume(
|
|
2428
|
+
Effect15.fail(
|
|
2429
|
+
new RelayError({
|
|
2430
|
+
message: `Fetch from ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2431
|
+
url,
|
|
2432
|
+
cause: e
|
|
2433
|
+
})
|
|
2434
|
+
)
|
|
2435
|
+
);
|
|
2436
|
+
try {
|
|
2437
|
+
sub = relay.subscribe([...filters], {
|
|
2438
|
+
onevent(evt) {
|
|
2439
|
+
if (!settled) {
|
|
2440
|
+
events.push(evt);
|
|
2441
|
+
}
|
|
2442
|
+
},
|
|
2443
|
+
oneose() {
|
|
2444
|
+
if (settled) return;
|
|
2445
|
+
cleanup();
|
|
2446
|
+
resume(Effect15.succeed(events));
|
|
2150
2447
|
}
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
if (settled)
|
|
2154
|
-
return;
|
|
2448
|
+
});
|
|
2449
|
+
timer = setTimeout(() => {
|
|
2450
|
+
if (settled) return;
|
|
2155
2451
|
cleanup();
|
|
2156
2452
|
resume(Effect15.succeed(events));
|
|
2157
|
-
}
|
|
2158
|
-
})
|
|
2159
|
-
timer = setTimeout(() => {
|
|
2160
|
-
if (settled)
|
|
2161
|
-
return;
|
|
2453
|
+
}, 1e4);
|
|
2454
|
+
} catch (e) {
|
|
2162
2455
|
cleanup();
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
}
|
|
2169
|
-
return Effect15.sync(cleanup);
|
|
2170
|
-
}));
|
|
2456
|
+
fail(e);
|
|
2457
|
+
}
|
|
2458
|
+
return Effect15.sync(cleanup);
|
|
2459
|
+
})
|
|
2460
|
+
);
|
|
2171
2461
|
return {
|
|
2172
2462
|
publish: (event, urls) => Effect15.gen(function* () {
|
|
2173
|
-
const results = yield* Effect15.forEach(
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2463
|
+
const results = yield* Effect15.forEach(
|
|
2464
|
+
urls,
|
|
2465
|
+
(url) => Effect15.result(
|
|
2466
|
+
withRelay(
|
|
2467
|
+
url,
|
|
2468
|
+
(relay) => Effect15.tryPromise({
|
|
2469
|
+
try: () => relay.publish(event),
|
|
2470
|
+
catch: (e) => new RelayError({
|
|
2471
|
+
message: `Publish to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2472
|
+
url,
|
|
2473
|
+
cause: e
|
|
2474
|
+
})
|
|
2475
|
+
}).pipe(
|
|
2476
|
+
Effect15.timeoutOrElse({
|
|
2477
|
+
duration: "10 seconds",
|
|
2478
|
+
onTimeout: () => Effect15.fail(
|
|
2479
|
+
new RelayError({ message: `Publish to ${url} timed out`, url })
|
|
2480
|
+
)
|
|
2481
|
+
})
|
|
2482
|
+
)
|
|
2483
|
+
)
|
|
2484
|
+
),
|
|
2485
|
+
{ concurrency: "unbounded" }
|
|
2486
|
+
);
|
|
2184
2487
|
const failures = results.filter((r) => r._tag === "Failure");
|
|
2185
2488
|
if (failures.length === urls.length && urls.length > 0) {
|
|
2186
2489
|
return yield* new RelayError({
|
|
@@ -2189,90 +2492,104 @@ function createRelayHandle() {
|
|
|
2189
2492
|
}
|
|
2190
2493
|
}),
|
|
2191
2494
|
fetchEvents: (ids, url) => Effect15.gen(function* () {
|
|
2192
|
-
if (ids.length === 0)
|
|
2193
|
-
return [];
|
|
2495
|
+
if (ids.length === 0) return [];
|
|
2194
2496
|
return yield* collectEvents(url, [{ ids }]);
|
|
2195
2497
|
}),
|
|
2196
2498
|
fetchByFilter: (filter, url) => collectEvents(url, [filter]),
|
|
2197
|
-
subscribe: (filter, url, onEvent) => withRelay(
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
sub
|
|
2225
|
-
ws
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
return;
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
try {
|
|
2252
|
-
sub = relay.subscribe([filter], {
|
|
2253
|
-
onevent() {},
|
|
2254
|
-
oneose() {}
|
|
2255
|
-
});
|
|
2256
|
-
ws = relay._ws || relay.ws;
|
|
2257
|
-
if (!ws) {
|
|
2499
|
+
subscribe: (filter, url, onEvent) => withRelay(
|
|
2500
|
+
url,
|
|
2501
|
+
(relay) => Effect15.acquireRelease(
|
|
2502
|
+
Effect15.try({
|
|
2503
|
+
try: () => relay.subscribe([filter], {
|
|
2504
|
+
onevent(evt) {
|
|
2505
|
+
onEvent(evt);
|
|
2506
|
+
},
|
|
2507
|
+
oneose() {
|
|
2508
|
+
}
|
|
2509
|
+
}),
|
|
2510
|
+
catch: (e) => new RelayError({
|
|
2511
|
+
message: `Subscribe to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2512
|
+
url,
|
|
2513
|
+
cause: e
|
|
2514
|
+
})
|
|
2515
|
+
}),
|
|
2516
|
+
(sub) => Effect15.sync(() => {
|
|
2517
|
+
sub.close();
|
|
2518
|
+
})
|
|
2519
|
+
).pipe(Effect15.provideService(Scope3.Scope, relayScope), Effect15.asVoid)
|
|
2520
|
+
),
|
|
2521
|
+
sendNegMsg: (url, subId, filter, msgHex) => withRelay(
|
|
2522
|
+
url,
|
|
2523
|
+
(relay) => Effect15.callback((resume) => {
|
|
2524
|
+
let settled = false;
|
|
2525
|
+
let timer;
|
|
2526
|
+
let sub;
|
|
2527
|
+
let ws;
|
|
2528
|
+
const cleanup = () => {
|
|
2529
|
+
settled = true;
|
|
2530
|
+
if (timer !== void 0) {
|
|
2531
|
+
clearTimeout(timer);
|
|
2532
|
+
timer = void 0;
|
|
2533
|
+
}
|
|
2534
|
+
sub?.close();
|
|
2535
|
+
sub = void 0;
|
|
2536
|
+
ws?.removeEventListener("message", handler);
|
|
2537
|
+
ws = void 0;
|
|
2538
|
+
};
|
|
2539
|
+
const fail = (e) => resume(
|
|
2540
|
+
Effect15.fail(
|
|
2541
|
+
new RelayError({
|
|
2542
|
+
message: `NIP-77 negotiation with ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
2543
|
+
url,
|
|
2544
|
+
cause: e
|
|
2545
|
+
})
|
|
2546
|
+
)
|
|
2547
|
+
);
|
|
2548
|
+
const handler = (msg) => {
|
|
2549
|
+
if (settled || typeof msg.data !== "string") return;
|
|
2550
|
+
const frameOpt = parseNegMessageFrame(msg.data);
|
|
2551
|
+
if (Option8.isNone(frameOpt) || frameOpt.value[1] !== subId) return;
|
|
2552
|
+
const frame = frameOpt.value;
|
|
2258
2553
|
cleanup();
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2554
|
+
if (frame[0] === "NEG-MSG") {
|
|
2555
|
+
resume(
|
|
2556
|
+
Effect15.succeed({
|
|
2557
|
+
msgHex: frame[2],
|
|
2558
|
+
haveIds: [],
|
|
2559
|
+
needIds: []
|
|
2560
|
+
})
|
|
2561
|
+
);
|
|
2264
2562
|
return;
|
|
2563
|
+
}
|
|
2564
|
+
fail(new Error(`NEG-ERR: ${frame[2]}`));
|
|
2565
|
+
};
|
|
2566
|
+
try {
|
|
2567
|
+
sub = relay.subscribe([filter], {
|
|
2568
|
+
onevent() {
|
|
2569
|
+
},
|
|
2570
|
+
oneose() {
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
ws = relay._ws || relay.ws;
|
|
2574
|
+
if (!ws) {
|
|
2575
|
+
cleanup();
|
|
2576
|
+
fail(new Error("Cannot access relay WebSocket"));
|
|
2577
|
+
return Effect15.succeed(void 0);
|
|
2578
|
+
}
|
|
2579
|
+
timer = setTimeout(() => {
|
|
2580
|
+
if (settled) return;
|
|
2581
|
+
cleanup();
|
|
2582
|
+
fail(new Error("NIP-77 negotiation timeout"));
|
|
2583
|
+
}, 3e4);
|
|
2584
|
+
ws.addEventListener("message", handler);
|
|
2585
|
+
ws.send(JSON.stringify(["NEG-OPEN", subId, filter, msgHex]));
|
|
2586
|
+
} catch (e) {
|
|
2265
2587
|
cleanup();
|
|
2266
|
-
fail(
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
cleanup();
|
|
2272
|
-
fail(e);
|
|
2273
|
-
}
|
|
2274
|
-
return Effect15.sync(cleanup);
|
|
2275
|
-
})),
|
|
2588
|
+
fail(e);
|
|
2589
|
+
}
|
|
2590
|
+
return Effect15.sync(cleanup);
|
|
2591
|
+
})
|
|
2592
|
+
),
|
|
2276
2593
|
closeAll: () => ScopedCache.invalidateAll(connections),
|
|
2277
2594
|
getStatus: () => ({ connectedUrls: [...connectedUrls] }),
|
|
2278
2595
|
subscribeStatus: (callback) => {
|
|
@@ -2324,11 +2641,14 @@ function createEpochGiftWrapHandle(senderPrivateKey, epochStore) {
|
|
|
2324
2641
|
}
|
|
2325
2642
|
|
|
2326
2643
|
// src/layers/GiftWrapLive.ts
|
|
2327
|
-
var GiftWrapLive = Layer6.effect(
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2644
|
+
var GiftWrapLive = Layer6.effect(
|
|
2645
|
+
GiftWrap3,
|
|
2646
|
+
Effect17.gen(function* () {
|
|
2647
|
+
const identity = yield* Identity;
|
|
2648
|
+
const epochStore = yield* EpochStore;
|
|
2649
|
+
return createEpochGiftWrapHandle(identity.privateKey, epochStore);
|
|
2650
|
+
})
|
|
2651
|
+
);
|
|
2332
2652
|
|
|
2333
2653
|
// src/layers/PublishQueueLive.ts
|
|
2334
2654
|
import { Effect as Effect19, Layer as Layer7 } from "effect";
|
|
@@ -2342,12 +2662,11 @@ function persist(storage, pending) {
|
|
|
2342
2662
|
function createPublishQueue(storage, relay) {
|
|
2343
2663
|
return Effect18.gen(function* () {
|
|
2344
2664
|
const stored = yield* storage.getMeta(META_KEY);
|
|
2345
|
-
const initial = Array.isArray(stored) ? new Set(stored) : new Set;
|
|
2665
|
+
const initial = Array.isArray(stored) ? new Set(stored) : /* @__PURE__ */ new Set();
|
|
2346
2666
|
const pendingRef = yield* Ref4.make(initial);
|
|
2347
|
-
const listeners = new Set;
|
|
2667
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2348
2668
|
const notify = (pending) => {
|
|
2349
|
-
for (const listener of listeners)
|
|
2350
|
-
listener(pending.size);
|
|
2669
|
+
for (const listener of listeners) listener(pending.size);
|
|
2351
2670
|
};
|
|
2352
2671
|
return {
|
|
2353
2672
|
enqueue: (eventId) => Effect18.gen(function* () {
|
|
@@ -2361,13 +2680,11 @@ function createPublishQueue(storage, relay) {
|
|
|
2361
2680
|
}),
|
|
2362
2681
|
flush: (relayUrls) => Effect18.gen(function* () {
|
|
2363
2682
|
const pending = yield* Ref4.get(pendingRef);
|
|
2364
|
-
if (pending.size === 0)
|
|
2365
|
-
|
|
2366
|
-
const succeeded = new Set;
|
|
2683
|
+
if (pending.size === 0) return;
|
|
2684
|
+
const succeeded = /* @__PURE__ */ new Set();
|
|
2367
2685
|
let consecutiveFailures = 0;
|
|
2368
2686
|
for (const eventId of pending) {
|
|
2369
|
-
if (consecutiveFailures >= 3)
|
|
2370
|
-
break;
|
|
2687
|
+
if (consecutiveFailures >= 3) break;
|
|
2371
2688
|
const gw = yield* storage.getGiftWrap(eventId);
|
|
2372
2689
|
if (!gw || !gw.event) {
|
|
2373
2690
|
succeeded.add(eventId);
|
|
@@ -2377,7 +2694,6 @@ function createPublishQueue(storage, relay) {
|
|
|
2377
2694
|
const result = yield* Effect18.result(relay.publish(gw.event, relayUrls));
|
|
2378
2695
|
if (result._tag === "Success") {
|
|
2379
2696
|
succeeded.add(eventId);
|
|
2380
|
-
yield* storage.stripGiftWrapBlob(eventId);
|
|
2381
2697
|
consecutiveFailures = 0;
|
|
2382
2698
|
} else {
|
|
2383
2699
|
consecutiveFailures++;
|
|
@@ -2405,11 +2721,14 @@ function createPublishQueue(storage, relay) {
|
|
|
2405
2721
|
}
|
|
2406
2722
|
|
|
2407
2723
|
// src/layers/PublishQueueLive.ts
|
|
2408
|
-
var PublishQueueLive = Layer7.effect(
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2724
|
+
var PublishQueueLive = Layer7.effect(
|
|
2725
|
+
PublishQueue,
|
|
2726
|
+
Effect19.gen(function* () {
|
|
2727
|
+
const storage = yield* Storage;
|
|
2728
|
+
const relay = yield* Relay;
|
|
2729
|
+
return yield* createPublishQueue(storage, relay);
|
|
2730
|
+
})
|
|
2731
|
+
);
|
|
2413
2732
|
|
|
2414
2733
|
// src/layers/SyncStatusLive.ts
|
|
2415
2734
|
import { Layer as Layer8 } from "effect";
|
|
@@ -2419,13 +2738,12 @@ import { Effect as Effect20, SubscriptionRef } from "effect";
|
|
|
2419
2738
|
function createSyncStatusHandle() {
|
|
2420
2739
|
return Effect20.gen(function* () {
|
|
2421
2740
|
const ref = yield* SubscriptionRef.make("idle");
|
|
2422
|
-
const listeners = new Set;
|
|
2741
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2423
2742
|
return {
|
|
2424
2743
|
get: () => SubscriptionRef.get(ref),
|
|
2425
2744
|
set: (status) => Effect20.gen(function* () {
|
|
2426
2745
|
yield* SubscriptionRef.set(ref, status);
|
|
2427
|
-
for (const listener of listeners)
|
|
2428
|
-
listener(status);
|
|
2746
|
+
for (const listener of listeners) listener(status);
|
|
2429
2747
|
}),
|
|
2430
2748
|
subscribe: (callback) => {
|
|
2431
2749
|
listeners.add(callback);
|
|
@@ -2440,8 +2758,7 @@ var SyncStatusLive = Layer8.effect(SyncStatus, createSyncStatusHandle());
|
|
|
2440
2758
|
|
|
2441
2759
|
// src/layers/TablinumLive.ts
|
|
2442
2760
|
function reportSyncError(onSyncError, error) {
|
|
2443
|
-
if (!onSyncError)
|
|
2444
|
-
return;
|
|
2761
|
+
if (!onSyncError) return;
|
|
2445
2762
|
onSyncError(error instanceof Error ? error : new Error(String(error)));
|
|
2446
2763
|
}
|
|
2447
2764
|
function mapMemberRecord(record) {
|
|
@@ -2449,249 +2766,352 @@ function mapMemberRecord(record) {
|
|
|
2449
2766
|
id: record.id,
|
|
2450
2767
|
addedAt: record.addedAt,
|
|
2451
2768
|
addedInEpoch: record.addedInEpoch,
|
|
2452
|
-
...record.name !==
|
|
2453
|
-
...record.picture !==
|
|
2454
|
-
...record.about !==
|
|
2455
|
-
...record.nip05 !==
|
|
2456
|
-
...record.removedAt !==
|
|
2457
|
-
...record.removedInEpoch !==
|
|
2769
|
+
...record.name !== void 0 ? { name: record.name } : {},
|
|
2770
|
+
...record.picture !== void 0 ? { picture: record.picture } : {},
|
|
2771
|
+
...record.about !== void 0 ? { about: record.about } : {},
|
|
2772
|
+
...record.nip05 !== void 0 ? { nip05: record.nip05 } : {},
|
|
2773
|
+
...record.removedAt !== void 0 ? { removedAt: record.removedAt } : {},
|
|
2774
|
+
...record.removedInEpoch !== void 0 ? { removedInEpoch: record.removedInEpoch } : {}
|
|
2458
2775
|
};
|
|
2459
2776
|
}
|
|
2460
2777
|
var IdentityWithDeps = IdentityLive.pipe(Layer9.provide(StorageLive));
|
|
2461
|
-
var EpochStoreWithDeps = EpochStoreLive.pipe(
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
var
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
const
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2778
|
+
var EpochStoreWithDeps = EpochStoreLive.pipe(
|
|
2779
|
+
Layer9.provide(IdentityWithDeps),
|
|
2780
|
+
Layer9.provide(StorageLive)
|
|
2781
|
+
);
|
|
2782
|
+
var GiftWrapWithDeps = GiftWrapLive.pipe(
|
|
2783
|
+
Layer9.provide(IdentityWithDeps),
|
|
2784
|
+
Layer9.provide(EpochStoreWithDeps)
|
|
2785
|
+
);
|
|
2786
|
+
var PublishQueueWithDeps = PublishQueueLive.pipe(
|
|
2787
|
+
Layer9.provide(StorageLive),
|
|
2788
|
+
Layer9.provide(RelayLive)
|
|
2789
|
+
);
|
|
2790
|
+
var AllServicesLive = Layer9.mergeAll(
|
|
2791
|
+
IdentityWithDeps,
|
|
2792
|
+
EpochStoreWithDeps,
|
|
2793
|
+
StorageLive,
|
|
2794
|
+
RelayLive,
|
|
2795
|
+
GiftWrapWithDeps,
|
|
2796
|
+
PublishQueueWithDeps,
|
|
2797
|
+
SyncStatusLive
|
|
2798
|
+
);
|
|
2799
|
+
var TablinumLive = Layer9.effect(
|
|
2800
|
+
Tablinum,
|
|
2801
|
+
Effect21.gen(function* () {
|
|
2802
|
+
const config = yield* Config;
|
|
2803
|
+
const identity = yield* Identity;
|
|
2804
|
+
const epochStore = yield* EpochStore;
|
|
2805
|
+
const storage = yield* Storage;
|
|
2806
|
+
const relay = yield* Relay;
|
|
2807
|
+
const giftWrap = yield* GiftWrap3;
|
|
2808
|
+
const publishQueue = yield* PublishQueue;
|
|
2809
|
+
const syncStatus = yield* SyncStatus;
|
|
2810
|
+
const scope = yield* Effect21.scope;
|
|
2811
|
+
const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
|
|
2812
|
+
const pubsub = yield* PubSub2.unbounded();
|
|
2813
|
+
const replayingRef = yield* Ref5.make(false);
|
|
2814
|
+
const closedRef = yield* Ref5.make(false);
|
|
2815
|
+
const watchCtx = { pubsub, replayingRef };
|
|
2816
|
+
const schemaEntries = Object.entries(config.schema);
|
|
2817
|
+
const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
|
|
2818
|
+
const knownCollections = new Map(
|
|
2819
|
+
allSchemaEntries.map(([, def]) => [def.name, def.eventRetention])
|
|
2820
|
+
);
|
|
2821
|
+
let notifyAuthor;
|
|
2822
|
+
const syncHandle = createSyncHandle(
|
|
2823
|
+
storage,
|
|
2824
|
+
giftWrap,
|
|
2825
|
+
relay,
|
|
2826
|
+
publishQueue,
|
|
2827
|
+
syncStatus,
|
|
2828
|
+
watchCtx,
|
|
2829
|
+
config.relays,
|
|
2830
|
+
knownCollections,
|
|
2831
|
+
epochStore,
|
|
2832
|
+
identity.privateKey,
|
|
2833
|
+
identity.publicKey,
|
|
2834
|
+
scope,
|
|
2835
|
+
config.logLevel,
|
|
2836
|
+
config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : void 0,
|
|
2837
|
+
(pubkey) => notifyAuthor?.(pubkey),
|
|
2838
|
+
config.onRemoved,
|
|
2839
|
+
config.onMembersChanged
|
|
2840
|
+
);
|
|
2841
|
+
const onWrite = (event) => Effect21.gen(function* () {
|
|
2842
|
+
const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
|
|
2843
|
+
const dTag = `${event.collection}:${event.recordId}`;
|
|
2844
|
+
const wrapResult = yield* Effect21.result(
|
|
2845
|
+
giftWrap.wrap({
|
|
2846
|
+
kind: 1,
|
|
2847
|
+
content,
|
|
2848
|
+
tags: [["d", dTag]],
|
|
2849
|
+
created_at: Math.floor(event.createdAt / 1e3)
|
|
2850
|
+
})
|
|
2851
|
+
);
|
|
2852
|
+
if (wrapResult._tag === "Failure") {
|
|
2853
|
+
reportSyncError(config.onSyncError, wrapResult.failure);
|
|
2854
|
+
return;
|
|
2508
2855
|
}
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
yield* notifyChange(watchCtx, {
|
|
2527
|
-
collection: "_members",
|
|
2528
|
-
recordId: record.id,
|
|
2529
|
-
kind: existing ? "update" : "create"
|
|
2856
|
+
const gw = wrapResult.success;
|
|
2857
|
+
yield* storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
|
|
2858
|
+
yield* Effect21.forkIn(
|
|
2859
|
+
Effect21.gen(function* () {
|
|
2860
|
+
const publishResult = yield* Effect21.result(
|
|
2861
|
+
syncHandle.publishLocal({
|
|
2862
|
+
id: gw.id,
|
|
2863
|
+
event: gw,
|
|
2864
|
+
createdAt: gw.created_at
|
|
2865
|
+
})
|
|
2866
|
+
);
|
|
2867
|
+
if (publishResult._tag === "Failure") {
|
|
2868
|
+
reportSyncError(config.onSyncError, publishResult.failure);
|
|
2869
|
+
}
|
|
2870
|
+
}),
|
|
2871
|
+
scope
|
|
2872
|
+
);
|
|
2530
2873
|
});
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
...profileOpt.value
|
|
2553
|
-
});
|
|
2554
|
-
yield* notifyChange(watchCtx, {
|
|
2555
|
-
collection: "_members",
|
|
2556
|
-
recordId: pubkey,
|
|
2557
|
-
kind: "update"
|
|
2558
|
-
});
|
|
2559
|
-
config.onMembersChanged?.();
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
}).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope)));
|
|
2563
|
-
};
|
|
2564
|
-
const handles = new Map;
|
|
2565
|
-
for (const [, def] of allSchemaEntries) {
|
|
2566
|
-
const validator = buildValidator(def.name, def);
|
|
2567
|
-
const partialValidator = buildPartialValidator(def.name, def);
|
|
2568
|
-
const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite, config.logLevel);
|
|
2569
|
-
handles.set(def.name, handle);
|
|
2570
|
-
}
|
|
2571
|
-
yield* syncHandle.startSubscription();
|
|
2572
|
-
yield* Effect21.logInfo("Tablinum ready", {
|
|
2573
|
-
dbName: config.dbName,
|
|
2574
|
-
collections: schemaEntries.map(([name]) => name),
|
|
2575
|
-
relays: config.relays
|
|
2576
|
-
});
|
|
2577
|
-
const selfMember = yield* storage.getRecord("_members", identity.publicKey);
|
|
2578
|
-
if (!selfMember) {
|
|
2579
|
-
yield* putMemberRecord({
|
|
2580
|
-
id: identity.publicKey,
|
|
2581
|
-
addedAt: Date.now(),
|
|
2582
|
-
addedInEpoch: getCurrentEpoch(epochStore).id
|
|
2874
|
+
const knownAuthors = /* @__PURE__ */ new Set();
|
|
2875
|
+
const putMemberRecord = (record) => Effect21.gen(function* () {
|
|
2876
|
+
const existing = yield* storage.getRecord("_members", record.id);
|
|
2877
|
+
const event = {
|
|
2878
|
+
id: uuidv7(),
|
|
2879
|
+
collection: "_members",
|
|
2880
|
+
recordId: record.id,
|
|
2881
|
+
kind: existing ? "u" : "c",
|
|
2882
|
+
data: record,
|
|
2883
|
+
createdAt: Date.now(),
|
|
2884
|
+
author: identity.publicKey
|
|
2885
|
+
};
|
|
2886
|
+
yield* storage.putEvent(event);
|
|
2887
|
+
yield* applyEvent(storage, event);
|
|
2888
|
+
yield* onWrite(event);
|
|
2889
|
+
yield* notifyChange(watchCtx, {
|
|
2890
|
+
collection: "_members",
|
|
2891
|
+
recordId: record.id,
|
|
2892
|
+
kind: existing ? "update" : "create"
|
|
2893
|
+
});
|
|
2894
|
+
config.onMembersChanged?.();
|
|
2583
2895
|
});
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2896
|
+
notifyAuthor = (pubkey) => {
|
|
2897
|
+
if (knownAuthors.has(pubkey)) return;
|
|
2898
|
+
knownAuthors.add(pubkey);
|
|
2899
|
+
Effect21.runFork(
|
|
2900
|
+
Effect21.gen(function* () {
|
|
2901
|
+
const existing = yield* storage.getRecord("_members", pubkey);
|
|
2902
|
+
if (!existing) {
|
|
2903
|
+
yield* putMemberRecord({
|
|
2904
|
+
id: pubkey,
|
|
2905
|
+
addedAt: Date.now(),
|
|
2906
|
+
addedInEpoch: getCurrentEpoch(epochStore).id
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
const profileOpt = yield* fetchAuthorProfile(relay, config.relays, pubkey).pipe(
|
|
2910
|
+
Effect21.catchTag("RelayError", () => Effect21.succeed(Option9.none()))
|
|
2911
|
+
);
|
|
2912
|
+
if (Option9.isSome(profileOpt)) {
|
|
2913
|
+
const current = yield* storage.getRecord("_members", pubkey);
|
|
2914
|
+
if (current) {
|
|
2915
|
+
yield* storage.putRecord("_members", {
|
|
2916
|
+
...current,
|
|
2917
|
+
...profileOpt.value
|
|
2918
|
+
});
|
|
2919
|
+
yield* notifyChange(watchCtx, {
|
|
2920
|
+
collection: "_members",
|
|
2921
|
+
recordId: pubkey,
|
|
2922
|
+
kind: "update"
|
|
2923
|
+
});
|
|
2924
|
+
config.onMembersChanged?.();
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope))
|
|
2928
|
+
);
|
|
2929
|
+
};
|
|
2930
|
+
const handles = /* @__PURE__ */ new Map();
|
|
2931
|
+
for (const [, def] of allSchemaEntries) {
|
|
2932
|
+
const validator = buildValidator(def.name, def);
|
|
2933
|
+
const partialValidator = buildPartialValidator(def.name, def);
|
|
2934
|
+
const handle = createCollectionHandle(
|
|
2935
|
+
def,
|
|
2936
|
+
storage,
|
|
2937
|
+
watchCtx,
|
|
2938
|
+
validator,
|
|
2939
|
+
partialValidator,
|
|
2940
|
+
uuidv7,
|
|
2941
|
+
identity.publicKey,
|
|
2942
|
+
onWrite,
|
|
2943
|
+
config.logLevel
|
|
2944
|
+
);
|
|
2945
|
+
handles.set(def.name, handle);
|
|
2946
|
+
}
|
|
2947
|
+
yield* syncHandle.startSubscription();
|
|
2948
|
+
yield* Effect21.logInfo("Tablinum ready", {
|
|
2949
|
+
dbName: config.dbName,
|
|
2950
|
+
collections: schemaEntries.map(([name]) => name),
|
|
2951
|
+
relays: config.relays
|
|
2952
|
+
});
|
|
2953
|
+
const selfMember = yield* storage.getRecord("_members", identity.publicKey);
|
|
2954
|
+
if (!selfMember) {
|
|
2631
2955
|
yield* putMemberRecord({
|
|
2632
|
-
id:
|
|
2956
|
+
id: identity.publicKey,
|
|
2633
2957
|
addedAt: Date.now(),
|
|
2634
|
-
addedInEpoch: getCurrentEpoch(epochStore).id
|
|
2635
|
-
...existing ? { removedAt: undefined, removedInEpoch: undefined } : {}
|
|
2958
|
+
addedInEpoch: getCurrentEpoch(epochStore).id
|
|
2636
2959
|
});
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2960
|
+
}
|
|
2961
|
+
const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
|
|
2962
|
+
const ensureOpen = (effect) => withLog(
|
|
2963
|
+
Effect21.gen(function* () {
|
|
2964
|
+
if (yield* Ref5.get(closedRef)) {
|
|
2965
|
+
return yield* new StorageError({ message: "Database is closed" });
|
|
2966
|
+
}
|
|
2967
|
+
return yield* effect;
|
|
2968
|
+
})
|
|
2969
|
+
);
|
|
2970
|
+
const ensureSyncOpen = (effect) => withLog(
|
|
2971
|
+
Effect21.gen(function* () {
|
|
2972
|
+
if (yield* Ref5.get(closedRef)) {
|
|
2973
|
+
return yield* new SyncError({ message: "Database is closed", phase: "init" });
|
|
2974
|
+
}
|
|
2975
|
+
return yield* effect;
|
|
2976
|
+
})
|
|
2977
|
+
);
|
|
2978
|
+
const dbHandle = {
|
|
2979
|
+
collection: (name) => {
|
|
2980
|
+
const handle = handles.get(name);
|
|
2981
|
+
if (!handle) throw new Error(`Collection "${name}" not found in schema`);
|
|
2982
|
+
return handle;
|
|
2983
|
+
},
|
|
2984
|
+
publicKey: identity.publicKey,
|
|
2985
|
+
members: handles.get("_members"),
|
|
2986
|
+
exportKey: () => identity.exportKey(),
|
|
2987
|
+
exportInvite: () => ({
|
|
2988
|
+
epochKeys: [...exportEpochKeys(epochStore)],
|
|
2989
|
+
relays: [...config.relays],
|
|
2990
|
+
dbName: config.dbName
|
|
2991
|
+
}),
|
|
2992
|
+
close: () => withLog(
|
|
2993
|
+
Effect21.gen(function* () {
|
|
2994
|
+
if (yield* Ref5.get(closedRef)) return;
|
|
2995
|
+
yield* Ref5.set(closedRef, true);
|
|
2996
|
+
syncHandle.stopHealing();
|
|
2997
|
+
yield* Scope4.close(scope, Exit.void);
|
|
2998
|
+
})
|
|
2999
|
+
),
|
|
3000
|
+
rebuild: () => ensureOpen(
|
|
3001
|
+
rebuild(
|
|
3002
|
+
storage,
|
|
3003
|
+
allSchemaEntries.map(([, def]) => def.name)
|
|
3004
|
+
)
|
|
3005
|
+
),
|
|
3006
|
+
sync: () => ensureSyncOpen(
|
|
3007
|
+
syncHandle.sync().pipe(
|
|
3008
|
+
Effect21.tap(
|
|
3009
|
+
() => Effect21.sync(() => {
|
|
3010
|
+
syncHandle.startHealing();
|
|
3011
|
+
})
|
|
3012
|
+
)
|
|
3013
|
+
)
|
|
3014
|
+
),
|
|
3015
|
+
getSyncStatus: () => syncStatus.get(),
|
|
3016
|
+
subscribeSyncStatus: (callback) => syncStatus.subscribe(callback),
|
|
3017
|
+
pendingCount: () => publishQueue.size(),
|
|
3018
|
+
subscribePendingCount: (callback) => publishQueue.subscribe(callback),
|
|
3019
|
+
getRelayStatus: () => relay.getStatus(),
|
|
3020
|
+
subscribeRelayStatus: (callback) => relay.subscribeStatus(callback),
|
|
3021
|
+
addMember: (pubkey) => ensureOpen(
|
|
3022
|
+
Effect21.gen(function* () {
|
|
3023
|
+
const existing = yield* storage.getRecord("_members", pubkey);
|
|
3024
|
+
if (existing && !existing.removedAt) return;
|
|
3025
|
+
yield* putMemberRecord({
|
|
3026
|
+
id: pubkey,
|
|
3027
|
+
addedAt: Date.now(),
|
|
3028
|
+
addedInEpoch: getCurrentEpoch(epochStore).id,
|
|
3029
|
+
...existing ? { removedAt: void 0, removedInEpoch: void 0 } : {}
|
|
3030
|
+
});
|
|
3031
|
+
})
|
|
3032
|
+
),
|
|
3033
|
+
removeMember: (pubkey) => ensureOpen(
|
|
3034
|
+
Effect21.gen(function* () {
|
|
3035
|
+
const allMembers = yield* storage.getAllRecords("_members");
|
|
3036
|
+
const activeMembers = allMembers.filter(
|
|
3037
|
+
(member) => !member.removedAt && member.id !== pubkey
|
|
3038
|
+
);
|
|
3039
|
+
const activePubkeys = activeMembers.map((member) => member.id);
|
|
3040
|
+
const result = createRotation(
|
|
3041
|
+
epochStore,
|
|
3042
|
+
identity.privateKey,
|
|
3043
|
+
identity.publicKey,
|
|
3044
|
+
activePubkeys,
|
|
3045
|
+
[pubkey]
|
|
3046
|
+
);
|
|
3047
|
+
addEpoch(epochStore, result.epoch);
|
|
3048
|
+
epochStore.currentEpochId = result.epoch.id;
|
|
3049
|
+
yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
|
|
3050
|
+
const memberRecord = yield* storage.getRecord("_members", pubkey);
|
|
3051
|
+
yield* putMemberRecord({
|
|
3052
|
+
...memberRecord ?? {
|
|
3053
|
+
id: pubkey,
|
|
3054
|
+
addedAt: 0,
|
|
3055
|
+
addedInEpoch: EpochId("epoch-0")
|
|
3056
|
+
},
|
|
3057
|
+
removedAt: Date.now(),
|
|
3058
|
+
removedInEpoch: result.epoch.id
|
|
3059
|
+
});
|
|
3060
|
+
yield* Effect21.forEach(
|
|
3061
|
+
result.wrappedEvents,
|
|
3062
|
+
(wrappedEvent) => relay.publish(wrappedEvent, [...config.relays]).pipe(
|
|
3063
|
+
Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
|
|
3064
|
+
Effect21.ignore
|
|
3065
|
+
),
|
|
3066
|
+
{ discard: true }
|
|
3067
|
+
);
|
|
3068
|
+
yield* Effect21.forEach(
|
|
3069
|
+
result.removalNotices,
|
|
3070
|
+
(notice) => relay.publish(notice, [...config.relays]).pipe(
|
|
3071
|
+
Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
|
|
3072
|
+
Effect21.ignore
|
|
3073
|
+
),
|
|
3074
|
+
{ discard: true }
|
|
3075
|
+
);
|
|
3076
|
+
yield* syncHandle.addEpochSubscription(result.epoch.publicKey);
|
|
3077
|
+
})
|
|
3078
|
+
),
|
|
3079
|
+
getMembers: () => ensureOpen(
|
|
3080
|
+
Effect21.gen(function* () {
|
|
3081
|
+
const allRecords = yield* storage.getAllRecords("_members");
|
|
3082
|
+
return allRecords.filter((record) => !record._d).map(mapMemberRecord);
|
|
3083
|
+
})
|
|
3084
|
+
),
|
|
3085
|
+
getProfile: () => ensureOpen(
|
|
3086
|
+
Effect21.gen(function* () {
|
|
3087
|
+
const record = yield* storage.getRecord("_members", identity.publicKey);
|
|
3088
|
+
if (!record) return {};
|
|
3089
|
+
const profile = {};
|
|
3090
|
+
if (record.name !== void 0) profile.name = record.name;
|
|
3091
|
+
if (record.picture !== void 0) profile.picture = record.picture;
|
|
3092
|
+
if (record.about !== void 0) profile.about = record.about;
|
|
3093
|
+
if (record.nip05 !== void 0) profile.nip05 = record.nip05;
|
|
3094
|
+
return profile;
|
|
3095
|
+
})
|
|
3096
|
+
),
|
|
3097
|
+
setProfile: (profile) => ensureOpen(
|
|
3098
|
+
Effect21.gen(function* () {
|
|
3099
|
+
const existing = yield* storage.getRecord("_members", identity.publicKey);
|
|
3100
|
+
if (!existing) {
|
|
3101
|
+
return yield* new ValidationError({ message: "Current user is not a member" });
|
|
3102
|
+
}
|
|
3103
|
+
const { _d, _u, _a, _e, ...memberFields } = existing;
|
|
3104
|
+
yield* putMemberRecord({ ...memberFields, ...profile });
|
|
3105
|
+
})
|
|
3106
|
+
)
|
|
3107
|
+
};
|
|
3108
|
+
return dbHandle;
|
|
3109
|
+
}).pipe(Effect21.withLogSpan("tablinum.init"))
|
|
3110
|
+
).pipe(Layer9.provide(AllServicesLive));
|
|
2690
3111
|
|
|
2691
3112
|
// src/db/create-tablinum.ts
|
|
2692
3113
|
function resolveLogLevel(input) {
|
|
2693
|
-
if (input ===
|
|
2694
|
-
return "None";
|
|
3114
|
+
if (input === void 0 || input === "none") return "None";
|
|
2695
3115
|
switch (input) {
|
|
2696
3116
|
case "debug":
|
|
2697
3117
|
return "Debug";
|
|
@@ -2772,20 +3192,20 @@ function decodeInvite(encoded) {
|
|
|
2772
3192
|
}
|
|
2773
3193
|
}
|
|
2774
3194
|
export {
|
|
2775
|
-
|
|
2776
|
-
encodeInvite,
|
|
2777
|
-
decodeInvite,
|
|
2778
|
-
createTablinum,
|
|
2779
|
-
collection,
|
|
2780
|
-
ValidationError,
|
|
2781
|
-
TablinumLive,
|
|
2782
|
-
SyncError,
|
|
2783
|
-
StorageError,
|
|
2784
|
-
RelayError,
|
|
2785
|
-
NotFoundError,
|
|
2786
|
-
LogLevel,
|
|
2787
|
-
EpochId,
|
|
2788
|
-
DatabaseName,
|
|
3195
|
+
ClosedError,
|
|
2789
3196
|
CryptoError,
|
|
2790
|
-
|
|
3197
|
+
DatabaseName,
|
|
3198
|
+
EpochId,
|
|
3199
|
+
LogLevel,
|
|
3200
|
+
NotFoundError,
|
|
3201
|
+
RelayError,
|
|
3202
|
+
StorageError,
|
|
3203
|
+
SyncError,
|
|
3204
|
+
TablinumLive,
|
|
3205
|
+
ValidationError,
|
|
3206
|
+
collection,
|
|
3207
|
+
createTablinum,
|
|
3208
|
+
decodeInvite,
|
|
3209
|
+
encodeInvite,
|
|
3210
|
+
field
|
|
2791
3211
|
};
|