mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-06 23:44:36 +00:00
Conversation.submit(tx, draft) and ConversationHandle.submit(tx, draft) run the same admission inside the caller's commit and return the submission ID, so a host's own checks and writes and the admission are stored together or not at all. Admission must come before the commit's first table write, once per commit, and throws ReadAfterWrite otherwise on every path. A first argument that is not a draft is taken as a transaction; one of another Session, or a wrapped one, is refused. The handle form also rejects once its invocation is aborted. The pi.durable.submit span covers the admission only. closes #11149
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import type { ConversationId } from "./types.ts";
|
|
|
|
/**
|
|
* The message of a thrown value, without throwing itself: `String()` throws for values such as `Object.create(null)`,
|
|
* and code that formats an error must not fail for the value it formats.
|
|
*/
|
|
export function errorMessage(error: unknown): string {
|
|
if (error instanceof Error) return error.message;
|
|
try {
|
|
return String(error);
|
|
} catch {
|
|
return Object.prototype.toString.call(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A transaction read a table after its first table write, or admitted a submission (`submit(tx, draft)`) after it. Read
|
|
* every required row, and admit, before writing.
|
|
*/
|
|
export class ReadAfterWrite extends Error {
|
|
/** `operation` names the call, such as `Tx.task()`. */
|
|
constructor(operation: string) {
|
|
super(`${operation} cannot read tables after the first table write`);
|
|
this.name = "ReadAfterWrite";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Storage read rejected an invalid request, such as an unknown conversation or a cursor from another scan, with no
|
|
* durable effect. Unlike any other error a Storage throws, it fails only that read, not the Session; from `commit()` it is
|
|
* fatal like any other.
|
|
*/
|
|
export class StorageRequestError extends Error {
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = "StorageRequestError";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The Session failed, so nothing runs or commits any more. It fails when a Storage method throws, a commit cannot be
|
|
* adopted, or the Harness's own bookkeeping throws (a scheduler commit, an internal commit listener, a registry whose
|
|
* `snapshot()` throws). `cause` is that first error. Reopen the Session; reopening recovers from what was committed.
|
|
*/
|
|
export class SessionFailed extends Error {
|
|
constructor(cause: unknown) {
|
|
super("Session failed after a storage error; close and reopen it", { cause });
|
|
this.name = "SessionFailed";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The Harness was opened read-only (`HarnessOptions.readOnly`): a commit with writes, or a call that would run tasks, was
|
|
* refused. Nothing was written, and the Harness stays usable for reads.
|
|
*/
|
|
export class ReadOnlySession extends Error {
|
|
constructor(what: string) {
|
|
super(`Read-only Harness: ${what} refused`);
|
|
this.name = "ReadOnlySession";
|
|
}
|
|
}
|
|
|
|
/** A submission reached a busy conversation and was not admitted. */
|
|
export class ConversationBusy extends Error {
|
|
readonly conversationId: ConversationId;
|
|
|
|
constructor(conversationId: ConversationId) {
|
|
super(`Conversation ${conversationId} is busy`);
|
|
this.name = "ConversationBusy";
|
|
this.conversationId = conversationId;
|
|
}
|
|
}
|
|
|
|
/** An input with `whenIdle: "reject"` reached an idle conversation and was not admitted. */
|
|
export class ConversationIdle extends Error {
|
|
readonly conversationId: ConversationId;
|
|
|
|
constructor(conversationId: ConversationId) {
|
|
super(`Conversation ${conversationId} is idle`);
|
|
this.name = "ConversationIdle";
|
|
this.conversationId = conversationId;
|
|
}
|
|
}
|