EdgeAIG/opencode / .opencode /node_modules /effect /dist /unstable /eventlog /EventLogServerEncrypted.js
EdgeAIG's picture
download
raw
8.18 kB
/**
* Serves encrypted event-log replication.
*
* Encrypted `EventLogRemote` clients use this module when they need a remote
* synchronization endpoint that never sees plaintext events. The server stores
* encrypted entries and replication metadata keyed by client public key and
* store id, then streams encrypted changes back to clients for local
* decryption. This module defines the RPC handlers, server layer, storage
* contract, and in-memory storage layer for that encrypted server path.
*
* @since 4.0.0
*/
import * as Uuid from "uuid";
import * as Arr from "../../Array.js";
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Layer from "../../Layer.js";
import * as PubSub from "../../PubSub.js";
import * as RcMap from "../../RcMap.js";
import * as Schema from "../../Schema.js";
import * as Stream from "../../Stream.js";
import * as RpcServer from "../rpc/RpcServer.js";
import * as Transferable from "../workers/Transferable.js";
import { EntryId, makeRemoteIdUnsafe } from "./EventJournal.js";
import { ChangesRpc, EventLogProtocolError, EventLogRemoteRpcs, WriteEntries } from "./EventLogMessage.js";
import * as EventLogServer from "./EventLogServer.js";
/**
* Provides RPC handlers for the encrypted event-log server.
*
* **Details**
*
* Incoming encrypted write payloads are decoded and persisted through `Storage`;
* change streams read encrypted entries from storage and encode them for the
* remote protocol.
*
* @category layers
* @since 4.0.0
*/
export const layerRpcHandlers = /*#__PURE__*/Layer.unwrap(/*#__PURE__*/Effect.gen(function* () {
const storage = yield* Storage;
const remoteId = yield* storage.getId;
return EventLogServer.layerRpcHandlers({
remoteId,
getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) => storage.getOrCreateSessionAuthBinding(publicKey, signingPublicKey),
onWrite: Effect.fnUntraced(function* (data) {
const request = yield* WriteEntries.decode(data).pipe(Effect.mapError(_ => new EventLogProtocolError({
requestTag: "WriteEntries",
publicKey: undefined,
code: "InternalServerError",
message: "Decoding failure"
})));
if (request.encryptedEntries.length === 0) return;
const entries = request.encryptedEntries.map(({
encryptedEntry,
entryId
}) => new PersistedEntry({
entryId,
iv: request.iv,
encryptedEntry
}));
return yield* storage.write(request.publicKey, request.storeId, entries).pipe(Effect.catchCause(_ => Effect.fail(new EventLogProtocolError({
requestTag: "WriteEntries",
publicKey: request.publicKey,
code: "InternalServerError",
message: "Persistence failure"
}))));
}),
changes: ({
publicKey,
storeId,
startSequence
}) => storage.changes(publicKey, storeId, startSequence).pipe(Stream.mapArrayEffect(entries => Effect.map(ChangesRpc.encodeEncrypted(entries), Arr.of)))
});
}));
/**
* Provides an encrypted event-log RPC server using `EventLogRemoteRpcs` and the
* encrypted server RPC handlers.
*
* **When to use**
*
* Use when you need an encrypted event-log RPC server for encrypted
* `EventLogRemote` replication over an existing `RpcServer.Protocol`.
*
* **Details**
*
* This layer installs `EventLogRemoteRpcs` on the provided RPC server protocol
* and wires those RPCs to `layerRpcHandlers`. Encrypted entries, session
* authentication bindings, remote ids, and change streams are delegated to
* `Storage`.
*
* @see {@link layerRpcHandlers} for the encrypted handler layer without installing an RPC server protocol
* @see {@link Storage} for the storage service required by this layer
* @see {@link layerStorageMemory} for the process-local in-memory storage layer
*
* @category layers
* @since 4.0.0
*/
export const layer = /*#__PURE__*/RpcServer.layer(EventLogRemoteRpcs).pipe(/*#__PURE__*/Layer.provide(layerRpcHandlers));
/**
* Schema for encrypted entries persisted by the encrypted event-log server.
*
* @category storage
* @since 4.0.0
*/
export class PersistedEntry extends /*#__PURE__*/Schema.Class("effect/eventlog/EventLogServerEncrypted/PersistedEntry")({
entryId: EntryId,
iv: Transferable.Uint8Array,
encryptedEntry: Transferable.Uint8Array
}) {
/**
* String representation of the encrypted entry id.
*
* @since 4.0.0
*/
get entryIdString() {
return Uuid.stringify(this.entryId);
}
}
/**
* Defines the backing store service used by the encrypted event-log server.
*
* **When to use**
*
* Use to provide durable encrypted event-log persistence for an encrypted
* event-log server layer.
*
* **Details**
*
* It provides the server remote id, stores session authentication bindings,
* persists encrypted entries, and streams encrypted changes for a public key and
* store id.
*
* @category storage
* @since 4.0.0
*/
export class Storage extends /*#__PURE__*/Context.Service()("effect/eventlog/EventLogServer/Storage") {}
/**
* Creates an in-memory encrypted server `Storage`.
*
* **Details**
*
* Data, session authentication bindings, and streams are process-local and are
* released with the surrounding scope.
*
* @category storage
* @since 4.0.0
*/
export const makeStorageMemory = /*#__PURE__*/Effect.gen(function* () {
const knownIds = new Map();
const journals = new Map();
const sessionAuthBindings = new Map();
const remoteId = makeRemoteIdUnsafe();
const ensureKnownIds = scopeKey => {
let storeKnownIds = knownIds.get(scopeKey);
if (storeKnownIds) return storeKnownIds;
storeKnownIds = new Map();
knownIds.set(scopeKey, storeKnownIds);
return storeKnownIds;
};
const ensureJournal = scopeKey => {
let journal = journals.get(scopeKey);
if (journal) return journal;
journal = [];
journals.set(scopeKey, journal);
return journal;
};
const pubsubs = yield* RcMap.make({
lookup: _scopeKey => Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown),
idleTimeToLive: 60000
});
return Storage.of({
getId: Effect.succeed(remoteId),
getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) => Effect.sync(() => {
let existing = sessionAuthBindings.get(publicKey);
if (existing) return existing;
sessionAuthBindings.set(publicKey, signingPublicKey);
return signingPublicKey;
}),
write: Effect.fnUntraced(function* (publicKey, storeId, entries) {
const scopeKey = makeEncryptedScopeKey({
publicKey,
storeId
});
const pubsub = yield* RcMap.get(pubsubs, scopeKey);
const storeKnownIds = ensureKnownIds(scopeKey);
const journal = ensureJournal(scopeKey);
const encryptedEntries = [];
for (const entry of entries) {
const idString = entry.entryIdString;
if (storeKnownIds.has(idString)) continue;
const encrypted = {
sequence: journal.length,
entryId: entry.entryId,
iv: entry.iv,
encryptedEntry: entry.encryptedEntry
};
encryptedEntries.push(encrypted);
storeKnownIds.set(idString, encrypted.sequence);
journal.push(encrypted);
PubSub.publishUnsafe(pubsub, encrypted);
}
return encryptedEntries;
}, Effect.scoped),
changes: Effect.fnUntraced(function* (publicKey, storeId, startSequence) {
const scopeKey = makeEncryptedScopeKey({
publicKey,
storeId
});
const pubsub = yield* RcMap.get(pubsubs, scopeKey);
const subscription = yield* PubSub.subscribe(pubsub);
return Stream.fromArray(ensureJournal(scopeKey).slice(startSequence)).pipe(Stream.concat(Stream.fromSubscription(subscription)));
}, Stream.unwrap)
});
});
/**
* Provides encrypted server `Storage` using the in-memory implementation.
*
* @category storage
* @since 4.0.0
*/
export const layerStorageMemory = /*#__PURE__*/Layer.effect(Storage)(makeStorageMemory);
const makeEncryptedScopeKey = ({
publicKey,
storeId
}) => {
return `${publicKey}/${storeId}`;
};
//# sourceMappingURL=EventLogServerEncrypted.js.map

Xet Storage Details

Size:
8.18 kB
·
Xet hash:
a3d051a551e24809d0ecd3d6eb74e551b6d320502281c23db5b917e8617fc1c1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.