@loopbacktools/server 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +26 -2
- package/dist/package.json +57 -0
- package/dist/src/domain/ask.d.ts +78 -0
- package/dist/src/domain/ask.js +161 -0
- package/dist/src/domain/errors.d.ts +70 -0
- package/dist/src/domain/errors.js +57 -0
- package/dist/src/generated/app.gen.d.ts +2311 -0
- package/dist/src/generated/app.gen.js +400 -0
- package/dist/src/generated/runtime.gen.d.ts +357 -0
- package/dist/src/generated/runtime.gen.js +298 -0
- package/dist/src/generated/schemas.gen.d.ts +6416 -0
- package/dist/src/generated/schemas.gen.js +306 -0
- package/dist/src/generated/wire-contract.gen.d.ts +77 -0
- package/dist/src/generated/wire-contract.gen.js +3 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +8 -0
- package/dist/src/loopback.d.ts +27 -0
- package/dist/src/loopback.js +16 -0
- package/dist/src/space/arguments.d.ts +8 -0
- package/dist/src/space/arguments.js +52 -0
- package/dist/src/space/migrations.d.ts +10 -0
- package/dist/src/space/migrations.js +106 -0
- package/dist/src/space/object.d.ts +30 -0
- package/dist/src/space/object.js +169 -0
- package/dist/src/space/operations.d.ts +149 -0
- package/dist/src/space/operations.js +568 -0
- package/dist/src/space/read.d.ts +8 -0
- package/dist/src/space/read.js +33 -0
- package/dist/src/space/rpc.d.ts +61 -0
- package/dist/src/space/rpc.js +96 -0
- package/dist/src/space/services.d.ts +41 -0
- package/dist/src/space/services.js +58 -0
- package/dist/src/space/store.d.ts +87 -0
- package/dist/src/space/store.js +276 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +3 -0
- package/dist/src/worker/env.d.ts +19 -0
- package/dist/src/worker/env.js +3 -0
- package/dist/src/worker/files.d.ts +21 -0
- package/dist/src/worker/files.js +42 -0
- package/dist/src/worker/gate.d.ts +55 -0
- package/dist/src/worker/gate.js +83 -0
- package/dist/src/worker/http.d.ts +2092 -0
- package/dist/src/worker/http.js +291 -0
- package/dist/src/worker/jwt.d.ts +49 -0
- package/dist/src/worker/jwt.js +142 -0
- package/dist/src/worker/mcp.d.ts +15 -0
- package/dist/src/worker/mcp.js +401 -0
- package/dist/src/worker/run.d.ts +45 -0
- package/dist/src/worker/run.js +71 -0
- package/dist/src/worker/spaces.d.ts +24 -0
- package/dist/src/worker/spaces.js +64 -0
- package/package.json +51 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DurableObject } from "cloudflare:workers";
|
|
2
|
+
import { type Caller, type ExitJson, type OperationName } from "./rpc.ts";
|
|
3
|
+
/** The subprotocol an inbox speaks on the event socket. */
|
|
4
|
+
export declare const EVENTS_PROTOCOL = "loopback.v1";
|
|
5
|
+
/**
|
|
6
|
+
* One space: its asks, their threads, upload tickets, and the inboxes watching it. Everything it
|
|
7
|
+
* does is a space operation, served through `call`; `fetch` only accepts an inbox's WebSocket.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Space extends DurableObject {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(ctx: DurableObjectState, env: unknown);
|
|
12
|
+
/**
|
|
13
|
+
* Deletes everything the space holds and closes the inboxes watching it. It carries on as an
|
|
14
|
+
* empty space: its schema is applied afresh before it takes another request. An agent waiting
|
|
15
|
+
* on an ask it held finds the ask gone at the end of its current lap.
|
|
16
|
+
*/
|
|
17
|
+
erase(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Runs one space operation on the arguments it decodes them to, and answers with its Exit, encoded
|
|
20
|
+
* for the trip back.
|
|
21
|
+
*/
|
|
22
|
+
call(method: OperationName, args: readonly unknown[], caller?: Caller): Promise<ExitJson>;
|
|
23
|
+
/** Accepts an inbox's event socket and sends it the space as it stands. */
|
|
24
|
+
fetch(request: Request): Promise<Response>;
|
|
25
|
+
/**
|
|
26
|
+
* Inboxes only send keep-alive pings, which the auto-response answers without waking us. Closing
|
|
27
|
+
* needs no handler: the runtime completes the close handshake itself.
|
|
28
|
+
*/
|
|
29
|
+
webSocketMessage(): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
2
|
+
import { BUILD_HEADER, REVISION_HEADER } from "@loopbacktools/protocol";
|
|
3
|
+
import { REVISION } from "@loopbacktools/protocol/schema";
|
|
4
|
+
import { DurableObject } from "cloudflare:workers";
|
|
5
|
+
import { Effect, Exit, Layer, Logger, ManagedRuntime } from "effect";
|
|
6
|
+
import { readArguments } from "./arguments.js";
|
|
7
|
+
import { migrations } from "./migrations.js";
|
|
8
|
+
import { operations, snapshot } from "./operations.js";
|
|
9
|
+
import { buildOf, encodeExit, isLater, Outdated, } from "./rpc.js";
|
|
10
|
+
import { Host, Presence, Replies } from "./services.js";
|
|
11
|
+
import { AskStore, TicketStore, UploadStore } from "./store.js";
|
|
12
|
+
/** The subprotocol an inbox speaks on the event socket. */
|
|
13
|
+
export const EVENTS_PROTOCOL = "loopback.v1";
|
|
14
|
+
/** How long a space that has refused newer code waits before restarting: long enough to answer. */
|
|
15
|
+
const RESTART_DELAY_MS = 100;
|
|
16
|
+
/** The close code an inbox reconnects after when the space is now on other code: Service Restart. */
|
|
17
|
+
const RECONNECT_ONTO_NEW_CODE = 1012;
|
|
18
|
+
/**
|
|
19
|
+
* One space: its asks, their threads, upload tickets, and the inboxes watching it. Everything it
|
|
20
|
+
* does is a space operation, served through `call`; `fetch` only accepts an inbox's WebSocket.
|
|
21
|
+
*/
|
|
22
|
+
export class Space extends DurableObject {
|
|
23
|
+
#runtime;
|
|
24
|
+
/** The deploy this object's code is from. */
|
|
25
|
+
#ownBuild;
|
|
26
|
+
/** Set once newer code has called: from then on everything is refused until the restart. */
|
|
27
|
+
#restarting = false;
|
|
28
|
+
constructor(ctx, env) {
|
|
29
|
+
super(ctx, env);
|
|
30
|
+
this.#ownBuild = buildOf(env);
|
|
31
|
+
ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair("ping", "pong"));
|
|
32
|
+
this.#runtime = this.#build();
|
|
33
|
+
// Build the runtime, migrations included, before the object takes any request.
|
|
34
|
+
void ctx.blockConcurrencyWhile(() => this.#runtime.runPromise(Effect.void));
|
|
35
|
+
}
|
|
36
|
+
#build() {
|
|
37
|
+
const sql = SqliteClient.layer({ storage: this.ctx.storage });
|
|
38
|
+
const host = Layer.succeed(Host, Host.of({
|
|
39
|
+
broadcast: (event) => this.#broadcast(event),
|
|
40
|
+
spaceId: this.ctx.id.toString(),
|
|
41
|
+
}));
|
|
42
|
+
return ManagedRuntime.make(Layer.mergeAll(AskStore.layer, UploadStore.layer, TicketStore.layer, migrations).pipe(Layer.provide(sql), Layer.merge(Layer.mergeAll(host, Replies.layer, Presence.layer)), Layer.merge(Logger.layer([Logger.consoleJson])), Layer.orDie));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Deletes everything the space holds and closes the inboxes watching it. It carries on as an
|
|
46
|
+
* empty space: its schema is applied afresh before it takes another request. An agent waiting
|
|
47
|
+
* on an ask it held finds the ask gone at the end of its current lap.
|
|
48
|
+
*/
|
|
49
|
+
async erase() {
|
|
50
|
+
await this.ctx.blockConcurrencyWhile(async () => {
|
|
51
|
+
for (const socket of this.ctx.getWebSockets()) {
|
|
52
|
+
try {
|
|
53
|
+
socket.close(1000, "The space was erased.");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Already closing.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const erased = this.#runtime;
|
|
60
|
+
await this.ctx.storage.deleteAll();
|
|
61
|
+
this.#runtime = this.#build();
|
|
62
|
+
await this.#runtime.runPromise(Effect.void);
|
|
63
|
+
await erased.dispose();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* For a while after a deploy, an active space can go on running the code it started with while a
|
|
68
|
+
* newer Worker calls it. Called by newer code, from a later build or writing a later revision of
|
|
69
|
+
* the contract, it refuses, running nothing, and restarts onto that code a moment later, once the
|
|
70
|
+
* refusal is on its way; whatever reaches it until then is refused the same way. A caller retries
|
|
71
|
+
* what was refused. Older code calling a newer space carries on, as does code from before callers
|
|
72
|
+
* named their version, which passes nothing.
|
|
73
|
+
*/
|
|
74
|
+
#outdatedFor(caller) {
|
|
75
|
+
const newer = (caller?.revision ?? REVISION) > REVISION || isLater(caller?.build, this.#ownBuild);
|
|
76
|
+
if (!this.#restarting && !newer)
|
|
77
|
+
return false;
|
|
78
|
+
if (!this.#restarting) {
|
|
79
|
+
this.#restarting = true;
|
|
80
|
+
setTimeout(() => this.ctx.abort("Newer code called this space: restarting onto it."), RESTART_DELAY_MS);
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Runs one space operation on the arguments it decodes them to, and answers with its Exit, encoded
|
|
86
|
+
* for the trip back.
|
|
87
|
+
*/
|
|
88
|
+
async call(method, args, caller) {
|
|
89
|
+
if (this.#outdatedFor(caller))
|
|
90
|
+
return encodeExit(Exit.fail(new Outdated()));
|
|
91
|
+
if (!Object.hasOwn(operations, method)) {
|
|
92
|
+
return encodeExit(await Effect.runPromiseExit(Effect.die(new Error(`No operation ${method}`))));
|
|
93
|
+
}
|
|
94
|
+
const operation = operations[method];
|
|
95
|
+
return encodeExit(await this.#runtime.runPromiseExit(Effect.flatMap(readArguments(method, args), (read) => operation(...read))));
|
|
96
|
+
}
|
|
97
|
+
/** Accepts an inbox's event socket and sends it the space as it stands. */
|
|
98
|
+
async fetch(request) {
|
|
99
|
+
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") {
|
|
100
|
+
return new Response("Expected a WebSocket upgrade.", { status: 426 });
|
|
101
|
+
}
|
|
102
|
+
// The inbox reconnects, by then to the space on the new code.
|
|
103
|
+
const revision = request.headers.get(REVISION_HEADER);
|
|
104
|
+
const handedBy = {
|
|
105
|
+
revision: revision === null ? undefined : Number(revision),
|
|
106
|
+
build: request.headers.get(BUILD_HEADER) ?? undefined,
|
|
107
|
+
};
|
|
108
|
+
if (this.#outdatedFor(handedBy)) {
|
|
109
|
+
return new Response("This space is restarting onto newer code.", {
|
|
110
|
+
status: 503,
|
|
111
|
+
headers: { "Retry-After": "1" },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Read the space first: a socket is only accepted once there is something to send it.
|
|
115
|
+
const current = await this.#runtime.runPromise(snapshot);
|
|
116
|
+
const { 0: client, 1: server } = new WebSocketPair();
|
|
117
|
+
this.ctx.acceptWebSocket(server);
|
|
118
|
+
const acceptedBy = { revision: REVISION, build: this.#ownBuild ?? null };
|
|
119
|
+
server.serializeAttachment(acceptedBy);
|
|
120
|
+
const first = { type: "snapshot", snapshot: current };
|
|
121
|
+
server.send(JSON.stringify(first));
|
|
122
|
+
const offered = (request.headers.get("Sec-WebSocket-Protocol") ?? "")
|
|
123
|
+
.split(",")
|
|
124
|
+
.map((protocol) => protocol.trim());
|
|
125
|
+
return new Response(null, {
|
|
126
|
+
status: 101,
|
|
127
|
+
webSocket: client,
|
|
128
|
+
...(offered.includes(EVENTS_PROTOCOL)
|
|
129
|
+
? { headers: { "Sec-WebSocket-Protocol": EVENTS_PROTOCOL } }
|
|
130
|
+
: {}),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Inboxes only send keep-alive pings, which the auto-response answers without waking us. Closing
|
|
135
|
+
* needs no handler: the runtime completes the close handshake itself.
|
|
136
|
+
*/
|
|
137
|
+
async webSocketMessage() { }
|
|
138
|
+
/**
|
|
139
|
+
* Whether a socket was accepted by other code than this. A hibernated socket stays open while its
|
|
140
|
+
* space moves onto new code, so an inbox could go on hearing frames its page can't read, and never
|
|
141
|
+
* learn that Loopback was updated.
|
|
142
|
+
*/
|
|
143
|
+
#acceptedByOther(socket) {
|
|
144
|
+
const by = socket.deserializeAttachment();
|
|
145
|
+
return by?.revision !== REVISION || by.build !== (this.#ownBuild ?? null);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Sends every inbox the event. One whose socket other code accepted is closed instead: it
|
|
149
|
+
* reconnects onto this code, and hears from the server that it is newer.
|
|
150
|
+
*/
|
|
151
|
+
#broadcast(event) {
|
|
152
|
+
return Effect.sync(() => {
|
|
153
|
+
const frame = JSON.stringify(event);
|
|
154
|
+
for (const socket of this.ctx.getWebSockets()) {
|
|
155
|
+
try {
|
|
156
|
+
if (this.#acceptedByOther(socket)) {
|
|
157
|
+
socket.close(RECONNECT_ONTO_NEW_CODE, "Loopback was updated.");
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
socket.send(frame);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// A socket that is closing misses the frame; it gets a fresh snapshot on reconnect.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { type Actor, type Ask, type AskPage, type BriefingAnswerInput, type BriefingInput, type FollowUpInput, type ImageType, type QuestionInput, type Reply, type ReviewInput, type Snapshot, type ToolReply, type UploadReceipt } from "@loopbacktools/protocol";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { AskClosed, AskNotFound, ContentInvalid, NotYourTurn, TicketInvalid, UploadExpired, UploadNotFound, UploadRejected } from "../domain/errors.ts";
|
|
4
|
+
import { Host, Presence, Replies } from "./services.ts";
|
|
5
|
+
import { AskStore, TicketStore, UploadStore } from "./store.ts";
|
|
6
|
+
/** How many closed asks a snapshot carries. */
|
|
7
|
+
export declare const RECENT_LIMIT = 50;
|
|
8
|
+
/** How long an upload ticket stays usable. */
|
|
9
|
+
export declare const UPLOAD_TTL_MS: number;
|
|
10
|
+
/** How long an event ticket stays usable: long enough to open a socket, too short to be worth stealing. */
|
|
11
|
+
export declare const TICKET_TTL_MS: number;
|
|
12
|
+
/** The largest image an upload ticket accepts. */
|
|
13
|
+
export declare const UPLOAD_MAX_BYTES: number;
|
|
14
|
+
/** A question, review or briefing an agent is opening, with the MCP client that sent it when known. */
|
|
15
|
+
export type OpenRequest = ({
|
|
16
|
+
readonly kind: "question";
|
|
17
|
+
readonly input: QuestionInput;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "review";
|
|
20
|
+
readonly input: ReviewInput;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: "briefing";
|
|
23
|
+
readonly input: BriefingInput;
|
|
24
|
+
}) & {
|
|
25
|
+
readonly client?: string;
|
|
26
|
+
/** Whose agent is asking. A retried call is only recognised as the same person's. */
|
|
27
|
+
readonly actor: Actor;
|
|
28
|
+
};
|
|
29
|
+
/** An upload ticket as the space issues it. The worker turns `token` into a URL. */
|
|
30
|
+
export interface IssuedUpload {
|
|
31
|
+
readonly uploadId: string;
|
|
32
|
+
readonly spaceId: string;
|
|
33
|
+
readonly token: string;
|
|
34
|
+
readonly contentType: ImageType;
|
|
35
|
+
readonly expiresAt: string;
|
|
36
|
+
readonly maxBytes: number;
|
|
37
|
+
}
|
|
38
|
+
/** A reply the space holds for an agent, with the thread entry it is, so the agent can take it. */
|
|
39
|
+
export interface Delivery extends ToolReply {
|
|
40
|
+
readonly seq: number;
|
|
41
|
+
}
|
|
42
|
+
/** An upload ticket that has been claimed for one upload. */
|
|
43
|
+
export interface ClaimedUpload {
|
|
44
|
+
readonly uploadId: string;
|
|
45
|
+
readonly contentType: ImageType;
|
|
46
|
+
readonly maxBytes: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Opens an ask, or rejoins the one a retried call opened. A retry is the same question or review,
|
|
50
|
+
* from the same client and project, while the ask is still open or holds a reply that never
|
|
51
|
+
* reached the agent, and only if an agent waited on it within the reattach window.
|
|
52
|
+
*/
|
|
53
|
+
export declare const open: (request: OpenRequest) => Effect.Effect<Ask, ContentInvalid, AskStore | Host | Presence | UploadStore>;
|
|
54
|
+
/**
|
|
55
|
+
* Records the agent's answer to a question the person asked back, and hands the move back to them.
|
|
56
|
+
* Sending the same answer again while it is already their move is a retry, and changes nothing.
|
|
57
|
+
*/
|
|
58
|
+
export declare const answerFollowUp: (input: FollowUpInput) => Effect.Effect<Ask, AskClosed | AskNotFound | ContentInvalid | NotYourTurn, AskStore | Host | Presence | UploadStore>;
|
|
59
|
+
/** The agent's move on a briefing as recorded, and its turn: the thread entry the move ended with. */
|
|
60
|
+
export interface BriefingMove {
|
|
61
|
+
readonly ask: Ask;
|
|
62
|
+
readonly turn: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Records the agent's answers on a briefing, one thread entry per point, numbering any sub-points,
|
|
66
|
+
* and hands the move back to the person. A move sent again before its reply reached the agent is a
|
|
67
|
+
* retry: it rejoins the move it repeats, and changes nothing.
|
|
68
|
+
*/
|
|
69
|
+
export declare const answerBriefing: (input: BriefingAnswerInput) => Effect.Effect<BriefingMove, AskClosed | AskNotFound | ContentInvalid | NotYourTurn, AskStore | Host | Presence | UploadStore>;
|
|
70
|
+
/**
|
|
71
|
+
* Waits up to `waitMs` for a reply the agent has not yet taken, and shows it. `null` means none
|
|
72
|
+
* came: the caller waits again. The reply stays the agent's to take until `markDelivered` says it
|
|
73
|
+
* reached them, so a wait whose caller has gone loses nothing. While a wait is held the ask shows
|
|
74
|
+
* as waiting.
|
|
75
|
+
*/
|
|
76
|
+
export declare const awaitReply: (askId: string, waitMs: number) => Effect.Effect<Delivery | null, AskNotFound, AskStore | Host | Presence | Replies>;
|
|
77
|
+
/** Records that the agent was handed the reply at `seq`: a retry no longer collects it. */
|
|
78
|
+
export declare const markDelivered: (askId: string, seq: number) => Effect.Effect<null, never, AskStore>;
|
|
79
|
+
/**
|
|
80
|
+
* Where an agent's background task stands. A task follows one of the agent's turns, numbered by
|
|
81
|
+
* the thread entry it ended with (0 for opening the ask), and completes with the first reply the
|
|
82
|
+
* person gives after it.
|
|
83
|
+
*/
|
|
84
|
+
export interface TaskState {
|
|
85
|
+
readonly status: "working" | "completed" | "cancelled";
|
|
86
|
+
readonly createdAt: string;
|
|
87
|
+
readonly lastUpdatedAt: string;
|
|
88
|
+
readonly reply: Delivery | null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* One poll of an agent's background task. While it is working, each poll keeps the ask showing an
|
|
92
|
+
* agent following it; once the person replies, it completes with that reply, every time it is asked.
|
|
93
|
+
*/
|
|
94
|
+
export declare const followTask: (askId: string, afterSeq: number) => Effect.Effect<TaskState, AskNotFound, AskStore | Host | Presence>;
|
|
95
|
+
/**
|
|
96
|
+
* The agent stops following a background task. The ask stays open, and the person can still
|
|
97
|
+
* answer it; the inbox stops showing anyone waiting. A task that already completed stays completed.
|
|
98
|
+
*/
|
|
99
|
+
export declare const cancelTask: (askId: string, afterSeq: number) => Effect.Effect<TaskState, AskNotFound, AskStore | Host | Presence>;
|
|
100
|
+
/** Records a person's reply, wakes the agent waiting for it, and tells every inbox. */
|
|
101
|
+
export declare const reply: (askId: string, answer: Reply, by: Actor) => Effect.Effect<Ask, AskClosed | AskNotFound | NotYourTurn | import("../domain/errors.ts").ReplyInvalid, AskStore | Host | Presence | Replies>;
|
|
102
|
+
export declare const get: (askId: string) => Effect.Effect<Ask, AskNotFound, AskStore | Presence>;
|
|
103
|
+
/** Every open ask and the most recently closed. */
|
|
104
|
+
export declare const snapshot: Effect.Effect<Snapshot, never, AskStore | Presence>;
|
|
105
|
+
/** A page of closed asks, newest first. */
|
|
106
|
+
export declare const history: (before: string | undefined, limit: number) => Effect.Effect<AskPage, never, AskStore | Presence>;
|
|
107
|
+
/** Issues a single-use ticket to upload one image. */
|
|
108
|
+
export declare const createUpload: (contentType: ImageType) => Effect.Effect<IssuedUpload, never, Host | UploadStore>;
|
|
109
|
+
/** Uses up an upload ticket. Once claimed it is spent, whether or not the upload then succeeds. */
|
|
110
|
+
export declare const claimUpload: (token: string, contentType: string) => Effect.Effect<ClaimedUpload, UploadExpired | UploadNotFound | UploadRejected, UploadStore>;
|
|
111
|
+
/** Records that a claimed upload's bytes are stored. */
|
|
112
|
+
export declare const completeUpload: (uploadId: string, size: number) => Effect.Effect<UploadReceipt, UploadNotFound, UploadStore>;
|
|
113
|
+
/** A stored upload's type and size. */
|
|
114
|
+
export declare const findUpload: (uploadId: string) => Effect.Effect<UploadReceipt, UploadNotFound, UploadStore>;
|
|
115
|
+
/** A one-time pass onto the space's event socket, for the actor named. */
|
|
116
|
+
export interface IssuedTicket {
|
|
117
|
+
readonly spaceId: string;
|
|
118
|
+
readonly secret: string;
|
|
119
|
+
readonly expiresAt: string;
|
|
120
|
+
}
|
|
121
|
+
/** Issues a one-time pass onto the event socket, held only as its hash. */
|
|
122
|
+
export declare const issueTicket: (actorId: string) => Effect.Effect<IssuedTicket, never, Host | TicketStore>;
|
|
123
|
+
/** Spends an event ticket: who it was issued to, or `TicketInvalid` when it is unknown, used or expired. */
|
|
124
|
+
export declare const redeemTicket: (ticketSecret: string) => Effect.Effect<{
|
|
125
|
+
actorId: string;
|
|
126
|
+
}, TicketInvalid, TicketStore>;
|
|
127
|
+
/** Everything a space does, by name: what its object serves over RPC. */
|
|
128
|
+
export declare const operations: {
|
|
129
|
+
open: (request: OpenRequest) => Effect.Effect<Ask, ContentInvalid, AskStore | Host | Presence | UploadStore>;
|
|
130
|
+
answerFollowUp: (input: FollowUpInput) => Effect.Effect<Ask, AskClosed | AskNotFound | ContentInvalid | NotYourTurn, AskStore | Host | Presence | UploadStore>;
|
|
131
|
+
answerBriefing: (input: BriefingAnswerInput) => Effect.Effect<BriefingMove, AskClosed | AskNotFound | ContentInvalid | NotYourTurn, AskStore | Host | Presence | UploadStore>;
|
|
132
|
+
awaitReply: (askId: string, waitMs: number) => Effect.Effect<Delivery | null, AskNotFound, AskStore | Host | Presence | Replies>;
|
|
133
|
+
markDelivered: (askId: string, seq: number) => Effect.Effect<null, never, AskStore>;
|
|
134
|
+
followTask: (askId: string, afterSeq: number) => Effect.Effect<TaskState, AskNotFound, AskStore | Host | Presence>;
|
|
135
|
+
cancelTask: (askId: string, afterSeq: number) => Effect.Effect<TaskState, AskNotFound, AskStore | Host | Presence>;
|
|
136
|
+
reply: (askId: string, answer: Reply, by: Actor) => Effect.Effect<Ask, AskClosed | AskNotFound | NotYourTurn | import("../domain/errors.ts").ReplyInvalid, AskStore | Host | Presence | Replies>;
|
|
137
|
+
get: typeof get;
|
|
138
|
+
snapshot: () => Effect.Effect<Snapshot, never, AskStore | Presence>;
|
|
139
|
+
history: (before: string | undefined, limit: number) => Effect.Effect<AskPage, never, AskStore | Presence>;
|
|
140
|
+
createUpload: (contentType: ImageType) => Effect.Effect<IssuedUpload, never, Host | UploadStore>;
|
|
141
|
+
claimUpload: (token: string, contentType: string) => Effect.Effect<ClaimedUpload, UploadExpired | UploadNotFound | UploadRejected, UploadStore>;
|
|
142
|
+
completeUpload: (uploadId: string, size: number) => Effect.Effect<UploadReceipt, UploadNotFound, UploadStore>;
|
|
143
|
+
findUpload: (uploadId: string) => Effect.Effect<UploadReceipt, UploadNotFound, UploadStore>;
|
|
144
|
+
issueTicket: (actorId: string) => Effect.Effect<IssuedTicket, never, Host | TicketStore>;
|
|
145
|
+
redeemTicket: (ticketSecret: string) => Effect.Effect<{
|
|
146
|
+
actorId: string;
|
|
147
|
+
}, TicketInvalid, TicketStore>;
|
|
148
|
+
};
|
|
149
|
+
export type Operations = typeof operations;
|