@prettier-ai/dsh-workspace 0.1.2-alpha.1
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.i18n.yaml +6 -0
- package/README.md +178 -0
- package/README.zh.md +178 -0
- package/lib/index.js +757 -0
- package/lib/invariant.js +114 -0
- package/lib/types/entity.d.ts +88 -0
- package/lib/types/entity.js +156 -0
- package/lib/types/index.d.ts +163 -0
- package/lib/types/index.js +600 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +43 -0
- package/lib/types/paths.d.ts +18 -0
- package/lib/types/paths.js +21 -0
- package/lib/types/spec.d.ts +85 -0
- package/lib/types/spec.js +63 -0
- package/lib/types/types.d.ts +92 -0
- package/lib/types/types.js +8 -0
- package/package.json +60 -0
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import "node:crypto";
|
|
2
|
+
import "node:fs/promises";
|
|
3
|
+
import "node:path";
|
|
4
|
+
import { Service } from "@prettier-ai/cordis";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { SessionId } from "@prettier-ai/dsh-session";
|
|
7
|
+
import { defineDomain, domainTable } from "@prettier-ai/dsh-storage-domain";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/spec.ts
|
|
10
|
+
/**
|
|
11
|
+
* The workspace domain declaration: record schema and the `defineDomain` spec
|
|
12
|
+
* the registry opens. The zod schema validates the shipped format at the
|
|
13
|
+
* durability boundary and is the direct source of a future RPC wire projection.
|
|
14
|
+
* @module @prettier-ai/dsh-workspace/src/spec
|
|
15
|
+
*/
|
|
16
|
+
/** Workspace id schema at the durable boundary; branding has no runtime representation. */
|
|
17
|
+
const workspaceId = z.string().transform((value) => value);
|
|
18
|
+
/**
|
|
19
|
+
* Durable shape of one workspace record. `path` is the `fs.realpath` canon
|
|
20
|
+
* stamped at create; `sessionIds` is the ordered ownership account (array
|
|
21
|
+
* order is display order); timestamps are ISO-8601 strings.
|
|
22
|
+
*/
|
|
23
|
+
const workspaceRecord = z.object({
|
|
24
|
+
path: z.string(),
|
|
25
|
+
title: z.string(),
|
|
26
|
+
sessionIds: z.array(z.string().transform(SessionId)),
|
|
27
|
+
createdAt: z.string(),
|
|
28
|
+
updatedAt: z.string()
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Recoverable two-write mutation marker. The marker is persisted before the
|
|
32
|
+
* record/order pair can diverge, so startup can distinguish an interrupted
|
|
33
|
+
* registry operation from unexplained medium corruption.
|
|
34
|
+
*/
|
|
35
|
+
const workspacePendingMutation = z.discriminatedUnion("operation", [z.object({
|
|
36
|
+
operation: z.literal("create"),
|
|
37
|
+
workspaceId
|
|
38
|
+
}), z.object({
|
|
39
|
+
operation: z.literal("delete"),
|
|
40
|
+
workspaceId
|
|
41
|
+
})]);
|
|
42
|
+
defineDomain({
|
|
43
|
+
name: "workspace",
|
|
44
|
+
version: 2,
|
|
45
|
+
global: {
|
|
46
|
+
schema: z.object({
|
|
47
|
+
initialized: z.boolean(),
|
|
48
|
+
workspaceIds: z.array(workspaceId),
|
|
49
|
+
archivedSessionIds: z.array(z.string().transform(SessionId)).default([]),
|
|
50
|
+
pendingMutation: workspacePendingMutation.optional()
|
|
51
|
+
}),
|
|
52
|
+
initial: {
|
|
53
|
+
initialized: false,
|
|
54
|
+
workspaceIds: [],
|
|
55
|
+
archivedSessionIds: []
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
tables: { workspaces: domainTable(workspaceRecord) }
|
|
59
|
+
});
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/index.ts
|
|
62
|
+
/**
|
|
63
|
+
* Workspace entity registry (`ctx.workspaceRegistry`): durable workspace records,
|
|
64
|
+
* stable registry order, and header-validated session membership over the
|
|
65
|
+
* domain data form.
|
|
66
|
+
* @module @prettier-ai/dsh-workspace
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* Brand a string as a {@link WorkspaceId}.
|
|
70
|
+
* @param id - Raw workspace id string.
|
|
71
|
+
* @returns the same string, branded at compile time.
|
|
72
|
+
*/
|
|
73
|
+
function WorkspaceId(id) {
|
|
74
|
+
return id;
|
|
75
|
+
}
|
|
76
|
+
Service.init;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region lib/types/invariant.js
|
|
79
|
+
/**
|
|
80
|
+
* Package-owned invariant companion for `@prettier-ai/dsh-workspace`.
|
|
81
|
+
* @module @prettier-ai/dsh-workspace/invariant
|
|
82
|
+
*/
|
|
83
|
+
const PACKAGE_NAME = "@prettier-ai/dsh-workspace";
|
|
84
|
+
/** Cordis companion plugin name. */
|
|
85
|
+
const name = "workspace-invariant";
|
|
86
|
+
/** Service required before the companion can reserve package ownership. */
|
|
87
|
+
const inject = ["invariants"];
|
|
88
|
+
/**
|
|
89
|
+
* Owned relationship: the registry's entity cache mirrors the workspace
|
|
90
|
+
* domain's durable table. Every `domain/changed` for the `workspaces` table
|
|
91
|
+
* must name a record the cache already holds an entity for (the registry
|
|
92
|
+
* caches before the durable put and mutates only through cached entities).
|
|
93
|
+
* A delete is valid only after the registry has removed the entity from its
|
|
94
|
+
* cache, whether for create rollback or an explicit registration deletion;
|
|
95
|
+
* deleting while the cache still publishes the entity proves a bypass.
|
|
96
|
+
*/
|
|
97
|
+
const install = Object.assign((ctx, fail) => {
|
|
98
|
+
ctx.on("domain/changed", (change) => {
|
|
99
|
+
if (change.domain !== "workspace" || change.table !== "workspaces") return;
|
|
100
|
+
if (change.operation === "deleted") {
|
|
101
|
+
if (ctx.workspaceRegistry.get(WorkspaceId(change.key)) !== void 0) fail(`workspace record '${change.key}' was deleted while the registry cache still publishes it — some write path bypassed ctx.workspaceRegistry`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (ctx.workspaceRegistry.get(WorkspaceId(change.key)) === void 0) fail(`workspace record '${change.key}' landed durably but the registry cache holds no entity for it — the cache and the domain table have diverged`);
|
|
105
|
+
});
|
|
106
|
+
}, { inject: ["workspaceRegistry"] });
|
|
107
|
+
/**
|
|
108
|
+
* Register this package's invariant companion.
|
|
109
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
110
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
111
|
+
*/
|
|
112
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
113
|
+
//#endregion
|
|
114
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-private workspace entity: the single {@link Workspace}
|
|
3
|
+
* implementation. Holds a record snapshot that is swapped in place after each
|
|
4
|
+
* durable mutation; every write funnels through the private `mutate` so
|
|
5
|
+
* `updatedAt` stamping and invalid-account pruning happen exactly once.
|
|
6
|
+
* Not re-exported from the package entrypoint — consumers see only the
|
|
7
|
+
* `Workspace` interface.
|
|
8
|
+
* @module @prettier-ai/dsh-workspace/src/entity
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionHeader, SessionId } from '@prettier-ai/dsh-session';
|
|
11
|
+
import type { KvTable } from '@prettier-ai/dsh-storage-domain';
|
|
12
|
+
import type { WorkspaceRecord } from './spec.ts';
|
|
13
|
+
import type { Workspace, WorkspaceId } from './types.ts';
|
|
14
|
+
/** An insertSessionBefore request named a session or anchor not on the account (storage failures stay plain errors). */
|
|
15
|
+
export declare class WorkspaceMoveInvalidError extends Error {
|
|
16
|
+
/**
|
|
17
|
+
* @param message - Which id was unaccounted and where.
|
|
18
|
+
*/
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The registry-owned machinery an entity mutates through. Entities never see
|
|
23
|
+
* the registry itself — only the open table, the canonical session-path
|
|
24
|
+
* index backing the `sessionIds` projection, and attach-time header reads.
|
|
25
|
+
*/
|
|
26
|
+
export interface WorkspaceEntityHost {
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the open `workspaces` table.
|
|
29
|
+
* @returns the table; throws while the registry has not started yet.
|
|
30
|
+
*/
|
|
31
|
+
table(): KvTable<WorkspaceId, WorkspaceRecord>;
|
|
32
|
+
/**
|
|
33
|
+
* Read a session's canonical directory from the registry's header index.
|
|
34
|
+
* @param id - Session whose indexed path is requested.
|
|
35
|
+
* @returns the canonical directory, or `undefined` when the header is
|
|
36
|
+
* missing or its cwd cannot identify an existing directory.
|
|
37
|
+
*/
|
|
38
|
+
sessionPath(id: SessionId): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Read one stored session header for attach validation.
|
|
41
|
+
* @param id - The session whose header to read.
|
|
42
|
+
* @returns the header; rejects when session persistence is absent or holds
|
|
43
|
+
* no session with this id.
|
|
44
|
+
*/
|
|
45
|
+
readSessionHeader(id: SessionId): Promise<SessionHeader>;
|
|
46
|
+
/**
|
|
47
|
+
* Publish a successfully validated canonical cwd to the projection index.
|
|
48
|
+
* @param id - Validated session id.
|
|
49
|
+
* @param path - Canonical existing directory from the immutable header cwd.
|
|
50
|
+
*/
|
|
51
|
+
rememberSessionPath(id: SessionId, path: string): void;
|
|
52
|
+
}
|
|
53
|
+
/** The single {@link Workspace} implementation; constructed only by the registry. */
|
|
54
|
+
export declare class WorkspaceEntity implements Workspace {
|
|
55
|
+
private readonly host;
|
|
56
|
+
readonly id: WorkspaceId;
|
|
57
|
+
private record;
|
|
58
|
+
/**
|
|
59
|
+
* @param host - Registry-owned table, session-path index, and header reads.
|
|
60
|
+
* @param id - The record's stable id.
|
|
61
|
+
* @param record - The validated record snapshot loaded or just written.
|
|
62
|
+
*/
|
|
63
|
+
constructor(host: WorkspaceEntityHost, id: WorkspaceId, record: WorkspaceRecord);
|
|
64
|
+
get path(): string;
|
|
65
|
+
get title(): string;
|
|
66
|
+
get createdAt(): string;
|
|
67
|
+
get updatedAt(): string;
|
|
68
|
+
get sessionIds(): readonly SessionId[];
|
|
69
|
+
setTitle(title: string): Promise<void>;
|
|
70
|
+
attachSession(sessionId: SessionId): Promise<void>;
|
|
71
|
+
insertSessionBefore(sessionId: SessionId, beforeSessionId?: SessionId): Promise<void>;
|
|
72
|
+
detachSession(sessionId: SessionId): Promise<void>;
|
|
73
|
+
status(): Promise<'ok' | 'missing-dir'>;
|
|
74
|
+
/**
|
|
75
|
+
* The single write path: run `fn` on the domain write chain via
|
|
76
|
+
* `table.update`, stamping `updatedAt` and pruning candidates that no
|
|
77
|
+
* longer pass the id-plus-canonical-cwd membership check, then swap the
|
|
78
|
+
* snapshot.
|
|
79
|
+
*
|
|
80
|
+
* `fn` sees the value current at its chain slot, so membership decisions
|
|
81
|
+
* (attach/detach idempotence) are race-free against queued writes; a fn
|
|
82
|
+
* signalling no change by returning `current` verbatim aborts the slot
|
|
83
|
+
* through the sentinel when pruning also finds nothing, so a no-op neither
|
|
84
|
+
* rewrites the medium nor emits a change event.
|
|
85
|
+
*/
|
|
86
|
+
private mutate;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=entity.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-private workspace entity: the single {@link Workspace}
|
|
3
|
+
* implementation. Holds a record snapshot that is swapped in place after each
|
|
4
|
+
* durable mutation; every write funnels through the private `mutate` so
|
|
5
|
+
* `updatedAt` stamping and invalid-account pruning happen exactly once.
|
|
6
|
+
* Not re-exported from the package entrypoint — consumers see only the
|
|
7
|
+
* `Workspace` interface.
|
|
8
|
+
* @module @prettier-ai/dsh-workspace/src/entity
|
|
9
|
+
*/
|
|
10
|
+
import { stat } from 'node:fs/promises';
|
|
11
|
+
import { realpathNormalize } from "./paths.js";
|
|
12
|
+
/** An insertSessionBefore request named a session or anchor not on the account (storage failures stay plain errors). */
|
|
13
|
+
export class WorkspaceMoveInvalidError extends Error {
|
|
14
|
+
/**
|
|
15
|
+
* @param message - Which id was unaccounted and where.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'WorkspaceMoveInvalidError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Chain-slot abort sentinel thrown by the update fn when the record needs no change; only `mutate` observes it. */
|
|
23
|
+
const unchangedSentinel = new Error('workspace record unchanged (internal sentinel)');
|
|
24
|
+
/** The single {@link Workspace} implementation; constructed only by the registry. */
|
|
25
|
+
export class WorkspaceEntity {
|
|
26
|
+
host;
|
|
27
|
+
id;
|
|
28
|
+
record;
|
|
29
|
+
/**
|
|
30
|
+
* @param host - Registry-owned table, session-path index, and header reads.
|
|
31
|
+
* @param id - The record's stable id.
|
|
32
|
+
* @param record - The validated record snapshot loaded or just written.
|
|
33
|
+
*/
|
|
34
|
+
constructor(host, id, record) {
|
|
35
|
+
this.host = host;
|
|
36
|
+
this.id = id;
|
|
37
|
+
this.record = record;
|
|
38
|
+
}
|
|
39
|
+
get path() {
|
|
40
|
+
return this.record.path;
|
|
41
|
+
}
|
|
42
|
+
get title() {
|
|
43
|
+
return this.record.title;
|
|
44
|
+
}
|
|
45
|
+
get createdAt() {
|
|
46
|
+
return this.record.createdAt;
|
|
47
|
+
}
|
|
48
|
+
get updatedAt() {
|
|
49
|
+
return this.record.updatedAt;
|
|
50
|
+
}
|
|
51
|
+
get sessionIds() {
|
|
52
|
+
return this.record.sessionIds.filter(id => this.host.sessionPath(id) === this.record.path);
|
|
53
|
+
}
|
|
54
|
+
async setTitle(title) {
|
|
55
|
+
await this.mutate(record => ({ ...record, title }));
|
|
56
|
+
}
|
|
57
|
+
async attachSession(sessionId) {
|
|
58
|
+
// Validation is skipped when the settled snapshot already accounts the
|
|
59
|
+
// id: the cwd fact was checked when it first attached and both inputs
|
|
60
|
+
// (stored header cwd, workspace path) are immutable. Membership itself is
|
|
61
|
+
// decided on the write chain inside `mutate`, never on this snapshot.
|
|
62
|
+
if (!this.record.sessionIds.includes(sessionId)) {
|
|
63
|
+
const header = await this.host.readSessionHeader(sessionId);
|
|
64
|
+
if (header.cwd === undefined) {
|
|
65
|
+
throw new Error(`cannot attach session '${sessionId}' to workspace '${this.record.path}': `
|
|
66
|
+
+ 'its stored header carries no cwd to validate against');
|
|
67
|
+
}
|
|
68
|
+
let cwd;
|
|
69
|
+
try {
|
|
70
|
+
cwd = await realpathNormalize(header.cwd);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`cannot attach session '${sessionId}' to workspace '${this.record.path}': `
|
|
74
|
+
+ `its cwd '${header.cwd}' does not resolve, so it cannot be validated`, { cause: error });
|
|
75
|
+
}
|
|
76
|
+
if (!(await stat(cwd)).isDirectory()) {
|
|
77
|
+
throw new Error(`cannot attach session '${sessionId}' to workspace '${this.record.path}': `
|
|
78
|
+
+ `its cwd '${header.cwd}' is not a directory`);
|
|
79
|
+
}
|
|
80
|
+
if (cwd !== this.record.path) {
|
|
81
|
+
throw new Error(`cannot attach session '${sessionId}' to workspace '${this.record.path}': `
|
|
82
|
+
+ `its cwd resolves to '${cwd}'`);
|
|
83
|
+
}
|
|
84
|
+
this.host.rememberSessionPath(sessionId, cwd);
|
|
85
|
+
}
|
|
86
|
+
await this.mutate(record => record.sessionIds.includes(sessionId)
|
|
87
|
+
? record
|
|
88
|
+
: { ...record, sessionIds: [sessionId, ...record.sessionIds] });
|
|
89
|
+
}
|
|
90
|
+
async insertSessionBefore(sessionId, beforeSessionId) {
|
|
91
|
+
await this.mutate((record) => {
|
|
92
|
+
if (!record.sessionIds.includes(sessionId)) {
|
|
93
|
+
throw new WorkspaceMoveInvalidError(`cannot move session '${sessionId}' in workspace '${record.path}': the session is not accounted`);
|
|
94
|
+
}
|
|
95
|
+
if (beforeSessionId !== undefined && !record.sessionIds.includes(beforeSessionId)) {
|
|
96
|
+
throw new WorkspaceMoveInvalidError(`cannot move session '${sessionId}' before '${beforeSessionId}' in workspace '${record.path}': `
|
|
97
|
+
+ 'the anchor session is not accounted');
|
|
98
|
+
}
|
|
99
|
+
if (beforeSessionId === sessionId)
|
|
100
|
+
return record;
|
|
101
|
+
const without = record.sessionIds.filter(id => id !== sessionId);
|
|
102
|
+
const at = beforeSessionId === undefined ? without.length : without.indexOf(beforeSessionId);
|
|
103
|
+
const sessionIds = [...without.slice(0, at), sessionId, ...without.slice(at)];
|
|
104
|
+
return sessionIds.every((id, index) => id === record.sessionIds[index])
|
|
105
|
+
? record
|
|
106
|
+
: { ...record, sessionIds };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async detachSession(sessionId) {
|
|
110
|
+
await this.mutate(record => record.sessionIds.includes(sessionId)
|
|
111
|
+
? { ...record, sessionIds: record.sessionIds.filter(id => id !== sessionId) }
|
|
112
|
+
: record);
|
|
113
|
+
}
|
|
114
|
+
async status() {
|
|
115
|
+
try {
|
|
116
|
+
return (await stat(this.record.path)).isDirectory() ? 'ok' : 'missing-dir';
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Any stat failure (ENOENT, dangling parent, permission loss) means the
|
|
120
|
+
// directory is not usable right now; the record itself never mutates.
|
|
121
|
+
return 'missing-dir';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The single write path: run `fn` on the domain write chain via
|
|
126
|
+
* `table.update`, stamping `updatedAt` and pruning candidates that no
|
|
127
|
+
* longer pass the id-plus-canonical-cwd membership check, then swap the
|
|
128
|
+
* snapshot.
|
|
129
|
+
*
|
|
130
|
+
* `fn` sees the value current at its chain slot, so membership decisions
|
|
131
|
+
* (attach/detach idempotence) are race-free against queued writes; a fn
|
|
132
|
+
* signalling no change by returning `current` verbatim aborts the slot
|
|
133
|
+
* through the sentinel when pruning also finds nothing, so a no-op neither
|
|
134
|
+
* rewrites the medium nor emits a change event.
|
|
135
|
+
*/
|
|
136
|
+
async mutate(fn) {
|
|
137
|
+
let next;
|
|
138
|
+
try {
|
|
139
|
+
next = await this.host.table().update(this.id, (current) => {
|
|
140
|
+
const changed = fn(current);
|
|
141
|
+
const sessionIds = changed.sessionIds.filter(id => this.host.sessionPath(id) === changed.path);
|
|
142
|
+
if (changed === current && sessionIds.length === current.sessionIds.length) {
|
|
143
|
+
throw unchangedSentinel;
|
|
144
|
+
}
|
|
145
|
+
return { ...changed, sessionIds, updatedAt: new Date().toISOString() };
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error === unchangedSentinel)
|
|
150
|
+
return;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
this.record = next;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=entity.js.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace entity registry (`ctx.workspaceRegistry`): durable workspace records,
|
|
3
|
+
* stable registry order, and header-validated session membership over the
|
|
4
|
+
* domain data form.
|
|
5
|
+
* @module @prettier-ai/dsh-workspace
|
|
6
|
+
*/
|
|
7
|
+
import { Context, Service } from '@prettier-ai/cordis';
|
|
8
|
+
import type { SessionId } from '@prettier-ai/dsh-session';
|
|
9
|
+
export { WorkspaceMoveInvalidError } from './entity.ts';
|
|
10
|
+
import type { Workspace, WorkspaceId as WorkspaceIdBrand } from './types.ts';
|
|
11
|
+
export type { Workspace } from './types.ts';
|
|
12
|
+
export { workspaceDomainState, workspaceRecord, workspaceDomainSpec } from './spec.ts';
|
|
13
|
+
export type { WorkspaceDomainState, WorkspaceRecord } from './spec.ts';
|
|
14
|
+
export { realpathNormalize } from './paths.ts';
|
|
15
|
+
/** Identifies one workspace record (see `src/types.ts` for the brand rationale). */
|
|
16
|
+
export type WorkspaceId = WorkspaceIdBrand;
|
|
17
|
+
/**
|
|
18
|
+
* Brand a string as a {@link WorkspaceId}.
|
|
19
|
+
* @param id - Raw workspace id string.
|
|
20
|
+
* @returns the same string, branded at compile time.
|
|
21
|
+
*/
|
|
22
|
+
export declare function WorkspaceId(id: string): WorkspaceId;
|
|
23
|
+
/**
|
|
24
|
+
* An archiveSession request named a session neither live nor in session
|
|
25
|
+
* persistence — a definite miss only; storage faults propagate as themselves.
|
|
26
|
+
*/
|
|
27
|
+
export declare class WorkspaceUnknownSessionError extends Error {
|
|
28
|
+
readonly sessionId: SessionId;
|
|
29
|
+
/**
|
|
30
|
+
* @param sessionId - The unknown session id.
|
|
31
|
+
*/
|
|
32
|
+
constructor(sessionId: SessionId);
|
|
33
|
+
}
|
|
34
|
+
/** A workspace reorder named a source or anchor absent from the durable registry order. */
|
|
35
|
+
export declare class WorkspaceOrderInvalidError extends Error {
|
|
36
|
+
readonly workspaceId: WorkspaceId;
|
|
37
|
+
/**
|
|
38
|
+
* @param workspaceId - Missing source or anchor id.
|
|
39
|
+
*/
|
|
40
|
+
constructor(workspaceId: WorkspaceId);
|
|
41
|
+
}
|
|
42
|
+
declare module '@prettier-ai/cordis' {
|
|
43
|
+
interface Context {
|
|
44
|
+
workspaceRegistry: WorkspaceRegistry;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Durable workspace registry. Startup waits for `sessionPersistence`, builds
|
|
49
|
+
* one canonical-cwd header index, and completes the one-time history
|
|
50
|
+
* bootstrap before the service becomes active. The persistence dependency is
|
|
51
|
+
* mandatory so an unavailable peer can never be mistaken for an empty
|
|
52
|
+
* history and commit the initialized marker.
|
|
53
|
+
*/
|
|
54
|
+
export declare class WorkspaceRegistry extends Service {
|
|
55
|
+
static inject: string[];
|
|
56
|
+
private table?;
|
|
57
|
+
private global?;
|
|
58
|
+
private state?;
|
|
59
|
+
private readonly entities;
|
|
60
|
+
private readonly headers;
|
|
61
|
+
private readonly sessionPaths;
|
|
62
|
+
private readonly invalidSessionPaths;
|
|
63
|
+
private operationTail;
|
|
64
|
+
private readonly host;
|
|
65
|
+
constructor(ctx: Context);
|
|
66
|
+
/** Open the domain, finish bootstrap when required, and rebuild the ordered cache. */
|
|
67
|
+
protected [Service.init](): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Create or reuse a workspace for an existing directory. The path is
|
|
70
|
+
* canonicalized through `fs.realpath`; a nonexistent path rejects with the
|
|
71
|
+
* original error and a non-directory rejects. Repeated calls for the same
|
|
72
|
+
* canonical path return the existing entity without changing its title.
|
|
73
|
+
* A newly created workspace is prepended to the durable registry order.
|
|
74
|
+
* Different canonical paths may share a display title.
|
|
75
|
+
* @param path - Existing directory to own, in any path spelling.
|
|
76
|
+
* @param title - Display title used only when a new record is created.
|
|
77
|
+
* @returns the existing or newly durable workspace.
|
|
78
|
+
*/
|
|
79
|
+
create(path: string, title?: string): Promise<Workspace>;
|
|
80
|
+
/**
|
|
81
|
+
* Look up a workspace by id.
|
|
82
|
+
* @param id - Workspace id.
|
|
83
|
+
* @returns the workspace, or `undefined` when unknown.
|
|
84
|
+
*/
|
|
85
|
+
get(id: WorkspaceId): Workspace | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Synchronous workspace projection in durable registry order. Every
|
|
88
|
+
* entity's `sessionIds` getter is already filtered by the startup/live
|
|
89
|
+
* canonical-cwd header index; this method performs no persistence reads.
|
|
90
|
+
* @returns a fresh ordered array of workspace entities.
|
|
91
|
+
*/
|
|
92
|
+
list(): Workspace[];
|
|
93
|
+
/**
|
|
94
|
+
* Delete one workspace registration while retaining its directory and every
|
|
95
|
+
* session log. The durable order is updated before the table deletion; a
|
|
96
|
+
* failed table write restores the prior order and keeps the entity
|
|
97
|
+
* published. Unknown ids are an idempotent no-op for domain callers.
|
|
98
|
+
* @param id - Workspace registration to remove.
|
|
99
|
+
* @returns `true` when a record was deleted, `false` when it was unknown.
|
|
100
|
+
*/
|
|
101
|
+
delete(id: WorkspaceId): Promise<boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* Move one workspace within the durable display order, DOM-insertBefore-like.
|
|
104
|
+
* With an anchor it lands before that workspace; without one it appends.
|
|
105
|
+
* @param id - Workspace to move.
|
|
106
|
+
* @param beforeId - Workspace anchor; omitted appends.
|
|
107
|
+
* @returns the complete committed workspace order.
|
|
108
|
+
*/
|
|
109
|
+
insertBefore(id: WorkspaceId, beforeId?: WorkspaceId): Promise<readonly WorkspaceId[]>;
|
|
110
|
+
/**
|
|
111
|
+
* The registry-global archive set: sessions hidden from every grouping
|
|
112
|
+
* surface. Archiving never touches workspace accounting — an archived
|
|
113
|
+
* session keeps its `sessionIds` slot so unarchiving restores its position.
|
|
114
|
+
* @returns the archived session ids in archive order.
|
|
115
|
+
*/
|
|
116
|
+
get archivedSessionIds(): readonly SessionId[];
|
|
117
|
+
/**
|
|
118
|
+
* Archive one session durably. The session must exist (live or in session
|
|
119
|
+
* persistence); its workspace accounting — or lack of one — is irrelevant.
|
|
120
|
+
* An already archived id resolves without writing.
|
|
121
|
+
* @param sessionId - The session to archive.
|
|
122
|
+
* @returns resolution after durability.
|
|
123
|
+
*/
|
|
124
|
+
archiveSession(sessionId: SessionId): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Whether a session is live, header-indexed, or present in a fresh
|
|
127
|
+
* persistence listing. Only a definite miss returns false — a failing
|
|
128
|
+
* `sessionPersistence.list()` propagates so storage faults never
|
|
129
|
+
* masquerade as an unknown session.
|
|
130
|
+
*/
|
|
131
|
+
private sessionKnown;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve by canonical directory path without creating or mutating a
|
|
134
|
+
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
135
|
+
* directory returns `undefined`.
|
|
136
|
+
* @param path - Existing directory path in any spelling.
|
|
137
|
+
* @returns the workspace owning the canonical path, when one exists.
|
|
138
|
+
*/
|
|
139
|
+
resolveByPath(path: string): Promise<Workspace | undefined>;
|
|
140
|
+
private createCanonical;
|
|
141
|
+
private deleteKnown;
|
|
142
|
+
/**
|
|
143
|
+
* Complete the one mutation explicitly named by durable state. Unexplained
|
|
144
|
+
* order/table divergence still reaches {@link validateStoredState} and
|
|
145
|
+
* fails loud; this path never guesses which operation created a row from its shape alone.
|
|
146
|
+
*/
|
|
147
|
+
private recoverPendingMutation;
|
|
148
|
+
private bootstrap;
|
|
149
|
+
private validateStoredState;
|
|
150
|
+
private rebuildEntities;
|
|
151
|
+
private replaceHeaderIndex;
|
|
152
|
+
private indexHeaders;
|
|
153
|
+
private indexHeader;
|
|
154
|
+
private indexLiveSessions;
|
|
155
|
+
private reportFilteredCandidates;
|
|
156
|
+
private readSessionHeader;
|
|
157
|
+
private requireTable;
|
|
158
|
+
private requireState;
|
|
159
|
+
private setState;
|
|
160
|
+
private enqueueOperation;
|
|
161
|
+
}
|
|
162
|
+
export default WorkspaceRegistry;
|
|
163
|
+
//# sourceMappingURL=index.d.ts.map
|