@orkestrel/workspace 0.0.6 → 0.0.8
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/README.md +8 -8
- package/dist/src/core/index.cjs +151 -107
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +362 -114
- package/dist/src/core/index.d.ts +362 -114
- package/dist/src/core/index.js +151 -107
- package/dist/src/core/index.js.map +1 -1
- package/package.json +22 -19
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# @orkestrel/workspace
|
|
2
2
|
|
|
3
|
-
The virtual file workspace for the `@orkestrel` line
|
|
3
|
+
> The virtual file workspace for the `@orkestrel` line: a path-keyed map of immutable files with
|
|
4
|
+
> an editing surface over it, a registry that holds those maps by id under one active selection,
|
|
5
|
+
> and a snapshot store seam that persists them.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Around that map sit a registry of named workspaces with one active selection, and pluggable stores
|
|
9
|
-
that persist plain snapshots.
|
|
7
|
+
Reach a file by its path — `write`, `read`, `search`, `replace`, `move`, `remove` — and every edit
|
|
8
|
+
mints a new file value and puts it back, so a file is a value you can hold and compare rather than
|
|
9
|
+
a handle that changes underneath you.
|
|
10
10
|
|
|
11
11
|
It is not a filesystem. There is no disk, no `node:fs`, no watcher, no synchronization lifecycle,
|
|
12
12
|
and no dirty-state tracking — a path is a key, not a location. Durability is a separate seam: a
|
|
13
13
|
snapshot is a plain serializable value, and a store is the thing that keeps one.
|
|
14
14
|
|
|
15
15
|
Nothing here is model-specific. An agent loop, a tool handler, and plain application code are all
|
|
16
|
-
|
|
16
|
+
callers.
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@ Ranged edits clamp to the text they address, search is literal until you ask for
|
|
|
41
41
|
absent path answers with `undefined` instead of throwing. Give the manager a store and `save` and
|
|
42
42
|
`open` round-trip a workspace through it.
|
|
43
43
|
|
|
44
|
-
See the [workspace guide](guides/
|
|
44
|
+
See the [workspace guide](guides/workspace.md) for the complete surface, edit semantics,
|
|
45
45
|
events, and stores.
|
|
46
46
|
|
|
47
47
|
## Requirements
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -4,9 +4,9 @@ let _orkestrel_database = require("@orkestrel/database");
|
|
|
4
4
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
5
5
|
//#region src/core/constants.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Maps file extensions to language tags for text content.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* The table is frozen, and an extension it does not list falls back to `text` in
|
|
10
10
|
* {@link import('./helpers.js').inferLanguage}.
|
|
11
11
|
*/
|
|
12
12
|
var EXTENSION_LANGUAGES = Object.freeze({
|
|
@@ -53,12 +53,15 @@ var EXTENSION_LANGUAGES = Object.freeze({
|
|
|
53
53
|
});
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/core/errors.ts
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Reports an invalid workspace edit or search operation, carrying a {@link WorkspaceErrorCode}
|
|
58
|
+
* and, when the operation had one, the context it ran under.
|
|
59
|
+
*/
|
|
57
60
|
var WorkspaceError = class extends Error {
|
|
58
61
|
code;
|
|
59
62
|
context;
|
|
60
63
|
/**
|
|
61
|
-
*
|
|
64
|
+
* Creates a workspace error.
|
|
62
65
|
*
|
|
63
66
|
* @param code - The machine-readable failure code
|
|
64
67
|
* @param message - The human-readable failure message
|
|
@@ -72,10 +75,14 @@ var WorkspaceError = class extends Error {
|
|
|
72
75
|
}
|
|
73
76
|
};
|
|
74
77
|
/**
|
|
75
|
-
*
|
|
78
|
+
* Narrows a caught value to a {@link WorkspaceError}.
|
|
79
|
+
*
|
|
80
|
+
* @remarks
|
|
81
|
+
* The check is one `instanceof` test, so it stays total: every input answers `true` or `false`
|
|
82
|
+
* and none throws.
|
|
76
83
|
*
|
|
77
84
|
* @param value - The caught value
|
|
78
|
-
* @returns
|
|
85
|
+
* @returns True if the value is a workspace error; false otherwise
|
|
79
86
|
*
|
|
80
87
|
* @example
|
|
81
88
|
* ```ts
|
|
@@ -88,7 +95,7 @@ function isWorkspaceError(value) {
|
|
|
88
95
|
//#endregion
|
|
89
96
|
//#region src/core/helpers.ts
|
|
90
97
|
/**
|
|
91
|
-
*
|
|
98
|
+
* Infers a language tag from the final file extension.
|
|
92
99
|
*
|
|
93
100
|
* @param path - The file path
|
|
94
101
|
* @returns The mapped language, or `text` when the extension is absent or unknown
|
|
@@ -104,10 +111,10 @@ function inferLanguage(path) {
|
|
|
104
111
|
return EXTENSION_LANGUAGES[path.slice(dot + 1).toLowerCase()] ?? "text";
|
|
105
112
|
}
|
|
106
113
|
/**
|
|
107
|
-
*
|
|
114
|
+
* Determines whether content is the text arm.
|
|
108
115
|
*
|
|
109
116
|
* @param content - The file content
|
|
110
|
-
* @returns
|
|
117
|
+
* @returns True if the content carries text and a language; false otherwise
|
|
111
118
|
*
|
|
112
119
|
* @example
|
|
113
120
|
* ```ts
|
|
@@ -118,57 +125,21 @@ function isText(content) {
|
|
|
118
125
|
return "text" in content;
|
|
119
126
|
}
|
|
120
127
|
/**
|
|
121
|
-
*
|
|
128
|
+
* Determines whether content is the binary arm.
|
|
122
129
|
*
|
|
123
130
|
* @param content - The file content
|
|
124
|
-
* @returns
|
|
131
|
+
* @returns True if the content carries a base64 string and a MIME; false otherwise
|
|
125
132
|
*
|
|
126
133
|
* @example
|
|
127
134
|
* ```ts
|
|
128
|
-
* isBinary({
|
|
135
|
+
* isBinary({ base64: 'AAAA', mime: 'image/png' }) // true
|
|
129
136
|
* ```
|
|
130
137
|
*/
|
|
131
138
|
function isBinary(content) {
|
|
132
|
-
return "
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Narrow an unknown value to an immutable file record.
|
|
136
|
-
*
|
|
137
|
-
* @param value - The value to inspect
|
|
138
|
-
* @returns Whether the value has the file shape
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```ts
|
|
142
|
-
* isFile({ path: 'a.txt', content: { text: 'a', language: 'text' }, state: 'created', size: 1, lines: 1 })
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
function isFile(value) {
|
|
146
|
-
return (0, _orkestrel_contract.holds)(() => {
|
|
147
|
-
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
148
|
-
if (!(0, _orkestrel_contract.isString)(value.path) || !(0, _orkestrel_contract.literalOf)("created", "modified")(value.state)) return false;
|
|
149
|
-
if (!(0, _orkestrel_contract.isNumber)(value.size) || !(0, _orkestrel_contract.isNumber)(value.lines)) return false;
|
|
150
|
-
if (!(0, _orkestrel_contract.isRecord)(value.content)) return false;
|
|
151
|
-
const text = (0, _orkestrel_contract.isString)(value.content.text) && (0, _orkestrel_contract.isString)(value.content.language);
|
|
152
|
-
const binary = (0, _orkestrel_contract.isString)(value.content.data) && (0, _orkestrel_contract.literalOf)("image/png", "image/jpeg", "image/gif", "image/webp")(value.content.mime);
|
|
153
|
-
return text || binary;
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Narrow an unknown value to a workspace snapshot.
|
|
158
|
-
*
|
|
159
|
-
* @param value - The value read from a persistence boundary
|
|
160
|
-
* @returns Whether the value has the workspace snapshot shape
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```ts
|
|
164
|
-
* isWorkspaceSnapshot({ id: 'work', files: [] }) // true
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
function isWorkspaceSnapshot(value) {
|
|
168
|
-
return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _orkestrel_contract.isString)(value.id) && (0, _orkestrel_contract.arrayOf)(isFile)(value.files));
|
|
139
|
+
return "base64" in content;
|
|
169
140
|
}
|
|
170
141
|
/**
|
|
171
|
-
*
|
|
142
|
+
* Computes the byte size of file content.
|
|
172
143
|
*
|
|
173
144
|
* @param content - The content to measure
|
|
174
145
|
* @returns UTF-8 bytes for text or decoded bytes for binary content
|
|
@@ -180,10 +151,10 @@ function isWorkspaceSnapshot(value) {
|
|
|
180
151
|
*/
|
|
181
152
|
function computeSize(content) {
|
|
182
153
|
if (isText(content)) return new TextEncoder().encode(content.text).length;
|
|
183
|
-
return
|
|
154
|
+
return computeDecodedSize(content.base64);
|
|
184
155
|
}
|
|
185
156
|
/**
|
|
186
|
-
*
|
|
157
|
+
* Counts the lines in file content.
|
|
187
158
|
*
|
|
188
159
|
* @param content - The content to inspect
|
|
189
160
|
* @returns The text line count, or zero for empty text and binary content
|
|
@@ -201,27 +172,27 @@ function countLines(content) {
|
|
|
201
172
|
return count;
|
|
202
173
|
}
|
|
203
174
|
/**
|
|
204
|
-
*
|
|
175
|
+
* Computes the decoded byte length of a base64 string, arithmetically rather than by decoding it.
|
|
205
176
|
*
|
|
206
|
-
* @param base64 - The base64
|
|
177
|
+
* @param base64 - The base64 string
|
|
207
178
|
* @returns The decoded byte count
|
|
208
179
|
*
|
|
209
180
|
* @example
|
|
210
181
|
* ```ts
|
|
211
|
-
*
|
|
182
|
+
* computeDecodedSize('AAAA') // 3
|
|
212
183
|
* ```
|
|
213
184
|
*/
|
|
214
|
-
function
|
|
185
|
+
function computeDecodedSize(base64) {
|
|
215
186
|
let padding = 0;
|
|
216
187
|
if (base64.endsWith("==")) padding = 2;
|
|
217
188
|
else if (base64.endsWith("=")) padding = 1;
|
|
218
189
|
return Math.floor(base64.length * 3 / 4) - padding;
|
|
219
190
|
}
|
|
220
191
|
/**
|
|
221
|
-
*
|
|
192
|
+
* Determines whether a 1-based range is structurally valid.
|
|
222
193
|
*
|
|
223
194
|
* @param range - The range to inspect
|
|
224
|
-
* @returns
|
|
195
|
+
* @returns True if both positions are positive and ordered; false otherwise
|
|
225
196
|
*
|
|
226
197
|
* @example
|
|
227
198
|
* ```ts
|
|
@@ -235,7 +206,7 @@ function isValidRange(range) {
|
|
|
235
206
|
return !(range.start.line === range.end.line && range.start.column > range.end.column);
|
|
236
207
|
}
|
|
237
208
|
/**
|
|
238
|
-
*
|
|
209
|
+
* Clamps a position to text bounds.
|
|
239
210
|
*
|
|
240
211
|
* @param text - The addressed text
|
|
241
212
|
* @param position - The requested position
|
|
@@ -256,7 +227,7 @@ function clampPosition(text, position) {
|
|
|
256
227
|
};
|
|
257
228
|
}
|
|
258
229
|
/**
|
|
259
|
-
*
|
|
230
|
+
* Clamps both positions in a range to text bounds.
|
|
260
231
|
*
|
|
261
232
|
* @param text - The addressed text
|
|
262
233
|
* @param range - The requested range
|
|
@@ -274,7 +245,7 @@ function clampRange(text, range) {
|
|
|
274
245
|
};
|
|
275
246
|
}
|
|
276
247
|
/**
|
|
277
|
-
*
|
|
248
|
+
* Converts a 1-based position to a zero-based string offset.
|
|
278
249
|
*
|
|
279
250
|
* @param text - The addressed text
|
|
280
251
|
* @param position - The requested position
|
|
@@ -293,7 +264,7 @@ function offsetAt(text, position) {
|
|
|
293
264
|
return Math.min(offset, text.length);
|
|
294
265
|
}
|
|
295
266
|
/**
|
|
296
|
-
*
|
|
267
|
+
* Slices a clamped half-open text range.
|
|
297
268
|
*
|
|
298
269
|
* @param text - The source text
|
|
299
270
|
* @param range - The requested range
|
|
@@ -309,7 +280,7 @@ function sliceRange(text, range) {
|
|
|
309
280
|
return text.slice(offsetAt(text, clamped.start), offsetAt(text, clamped.end));
|
|
310
281
|
}
|
|
311
282
|
/**
|
|
312
|
-
*
|
|
283
|
+
* Replaces a clamped half-open text range.
|
|
313
284
|
*
|
|
314
285
|
* @param text - The source text
|
|
315
286
|
* @param range - The requested range
|
|
@@ -328,7 +299,7 @@ function spliceRange(text, range, replacement) {
|
|
|
328
299
|
return text.slice(0, start) + replacement + text.slice(end);
|
|
329
300
|
}
|
|
330
301
|
/**
|
|
331
|
-
*
|
|
302
|
+
* Assembles a nested range from four 1-based coordinates.
|
|
332
303
|
*
|
|
333
304
|
* @param fromLine - The start line
|
|
334
305
|
* @param fromColumn - The start column
|
|
@@ -354,7 +325,7 @@ function rangeOf(fromLine, fromColumn, toLine, toColumn) {
|
|
|
354
325
|
};
|
|
355
326
|
}
|
|
356
327
|
/**
|
|
357
|
-
*
|
|
328
|
+
* Escapes regular-expression metacharacters for literal matching.
|
|
358
329
|
*
|
|
359
330
|
* @param value - The literal text
|
|
360
331
|
* @returns The escaped regular-expression source
|
|
@@ -368,13 +339,52 @@ function escapeRegExp(value) {
|
|
|
368
339
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
369
340
|
}
|
|
370
341
|
//#endregion
|
|
342
|
+
//#region src/core/validators.ts
|
|
343
|
+
/**
|
|
344
|
+
* Narrows an unknown value to an immutable file record.
|
|
345
|
+
*
|
|
346
|
+
* @param value - The value to inspect
|
|
347
|
+
* @returns True if the value has the file shape; false otherwise
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* isFile({ path: 'a.txt', content: { text: 'a', language: 'text' }, state: 'created', size: 1, lines: 1 })
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
function isFile(value) {
|
|
355
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
356
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
357
|
+
if (!(0, _orkestrel_contract.isString)(value.path) || !(0, _orkestrel_contract.literalOf)("created", "modified")(value.state)) return false;
|
|
358
|
+
if (!(0, _orkestrel_contract.isNumber)(value.size) || !(0, _orkestrel_contract.isNumber)(value.lines)) return false;
|
|
359
|
+
if (!(0, _orkestrel_contract.isRecord)(value.content)) return false;
|
|
360
|
+
const text = (0, _orkestrel_contract.isString)(value.content.text) && (0, _orkestrel_contract.isString)(value.content.language);
|
|
361
|
+
const binary = (0, _orkestrel_contract.isString)(value.content.base64) && (0, _orkestrel_contract.literalOf)("image/png", "image/jpeg", "image/gif", "image/webp")(value.content.mime);
|
|
362
|
+
return text || binary;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Narrows an unknown value to a workspace snapshot.
|
|
367
|
+
*
|
|
368
|
+
* @param value - The value read from a persistence boundary
|
|
369
|
+
* @returns True if the value has the workspace snapshot shape; false otherwise
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* isWorkspaceSnapshot({ id: 'work', files: [] }) // true
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
function isWorkspaceSnapshot(value) {
|
|
377
|
+
return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _orkestrel_contract.isString)(value.id) && (0, _orkestrel_contract.arrayOf)(isFile)(value.files));
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
371
380
|
//#region src/core/workspaces/Workspace.ts
|
|
372
381
|
/**
|
|
373
|
-
*
|
|
382
|
+
* Implements `WorkspaceInterface` over one insertion-ordered path map the instance owns,
|
|
383
|
+
* projecting fresh arrays on every read.
|
|
374
384
|
*
|
|
375
385
|
* Whole-file edits create text files, ranged edits operate only on existing text files, and
|
|
376
386
|
* binary files remain available through construction-time hydration. Mutations emit after the
|
|
377
|
-
*
|
|
387
|
+
* file map has changed.
|
|
378
388
|
*
|
|
379
389
|
* @example
|
|
380
390
|
* ```ts
|
|
@@ -391,7 +401,7 @@ var Workspace = class {
|
|
|
391
401
|
#files = /* @__PURE__ */ new Map();
|
|
392
402
|
#emitter;
|
|
393
403
|
/**
|
|
394
|
-
*
|
|
404
|
+
* Creates a workspace.
|
|
395
405
|
*
|
|
396
406
|
* @param options - Optional identity, emitter configuration, and initial files
|
|
397
407
|
*/
|
|
@@ -437,7 +447,7 @@ var Workspace = class {
|
|
|
437
447
|
};
|
|
438
448
|
}
|
|
439
449
|
has(path) {
|
|
440
|
-
if ((0, _orkestrel_contract.isArray)(path)) return path.
|
|
450
|
+
if ((0, _orkestrel_contract.isArray)(path)) return path.every((one) => this.#files.has(one));
|
|
441
451
|
return this.#files.has(path);
|
|
442
452
|
}
|
|
443
453
|
search(query, options) {
|
|
@@ -447,7 +457,7 @@ var Workspace = class {
|
|
|
447
457
|
for (const file of this.#files.values()) {
|
|
448
458
|
if (limit !== void 0 && matches.length >= limit) break;
|
|
449
459
|
if (!isText(file.content)) continue;
|
|
450
|
-
const lines = file.content.text.split(
|
|
460
|
+
const lines = file.content.text.split(/\r\n|\n/);
|
|
451
461
|
for (let index = 0; index < lines.length; index += 1) {
|
|
452
462
|
if (limit !== void 0 && matches.length >= limit) break;
|
|
453
463
|
const lineText = lines[index] ?? "";
|
|
@@ -501,39 +511,42 @@ var Workspace = class {
|
|
|
501
511
|
for (const [one, text] of Object.entries(path)) this.#write(one, text);
|
|
502
512
|
return;
|
|
503
513
|
}
|
|
504
|
-
|
|
514
|
+
if (content === void 0) return;
|
|
505
515
|
if (!range) {
|
|
506
|
-
this.#write(path,
|
|
516
|
+
this.#write(path, content);
|
|
507
517
|
return;
|
|
508
518
|
}
|
|
509
|
-
this.#splice(path,
|
|
519
|
+
this.#splice(path, content, range);
|
|
510
520
|
}
|
|
511
521
|
prepend(path, content) {
|
|
512
522
|
if ((0, _orkestrel_contract.isRecord)(path)) {
|
|
513
523
|
for (const [one, text] of Object.entries(path)) this.#prepend(one, text);
|
|
514
524
|
return;
|
|
515
525
|
}
|
|
516
|
-
|
|
526
|
+
if (content === void 0) return;
|
|
527
|
+
this.#prepend(path, content);
|
|
517
528
|
}
|
|
518
529
|
append(path, content) {
|
|
519
530
|
if ((0, _orkestrel_contract.isRecord)(path)) {
|
|
520
531
|
for (const [one, text] of Object.entries(path)) this.#append(one, text);
|
|
521
532
|
return;
|
|
522
533
|
}
|
|
523
|
-
|
|
534
|
+
if (content === void 0) return;
|
|
535
|
+
this.#append(path, content);
|
|
524
536
|
}
|
|
525
537
|
move(from, to) {
|
|
526
538
|
if ((0, _orkestrel_contract.isRecord)(from)) {
|
|
527
|
-
let moved =
|
|
528
|
-
for (const [one, target] of Object.entries(from)) if (this.#move(one, target)) moved =
|
|
539
|
+
let moved = true;
|
|
540
|
+
for (const [one, target] of Object.entries(from)) if (!this.#move(one, target)) moved = false;
|
|
529
541
|
return moved;
|
|
530
542
|
}
|
|
531
|
-
|
|
543
|
+
if (to === void 0) return false;
|
|
544
|
+
return this.#move(from, to);
|
|
532
545
|
}
|
|
533
546
|
remove(path) {
|
|
534
547
|
if ((0, _orkestrel_contract.isArray)(path)) {
|
|
535
|
-
let removed =
|
|
536
|
-
for (const one of path) if (this.#remove(one)) removed =
|
|
548
|
+
let removed = true;
|
|
549
|
+
for (const one of path) if (!this.#remove(one)) removed = false;
|
|
537
550
|
return removed;
|
|
538
551
|
}
|
|
539
552
|
return this.#remove(path);
|
|
@@ -566,7 +579,7 @@ var Workspace = class {
|
|
|
566
579
|
}
|
|
567
580
|
#splice(path, content, range) {
|
|
568
581
|
const existing = this.#files.get(path);
|
|
569
|
-
if (!existing) throw new WorkspaceError("
|
|
582
|
+
if (!existing) throw new WorkspaceError("MISSING", `Cannot splice a range of a missing file: ${path}`, { path });
|
|
570
583
|
if (!isText(existing.content)) throw new WorkspaceError("MODALITY", `Cannot splice a range of a binary file: ${path}`, { path });
|
|
571
584
|
if (!isValidRange(range)) throw new WorkspaceError("RANGE", `Invalid range for file: ${path}`, {
|
|
572
585
|
path,
|
|
@@ -635,7 +648,8 @@ var Workspace = class {
|
|
|
635
648
|
//#endregion
|
|
636
649
|
//#region src/core/workspaces/WorkspaceManager.ts
|
|
637
650
|
/**
|
|
638
|
-
*
|
|
651
|
+
* Implements `WorkspaceManagerInterface` over an insertion-ordered id map and one active id
|
|
652
|
+
* the instance owns, resolving `active` through that map on every read and holding no emitter.
|
|
639
653
|
*
|
|
640
654
|
* A supplied store adds lenient snapshot `open` and `save` operations. Event defaults flow into
|
|
641
655
|
* workspaces created through the registry, while observability remains owned by each workspace.
|
|
@@ -656,7 +670,7 @@ var WorkspaceManager = class {
|
|
|
656
670
|
#error;
|
|
657
671
|
#store;
|
|
658
672
|
/**
|
|
659
|
-
*
|
|
673
|
+
* Creates a registry.
|
|
660
674
|
*
|
|
661
675
|
* @param options - Default event hooks and optional snapshot store
|
|
662
676
|
*/
|
|
@@ -720,8 +734,8 @@ var WorkspaceManager = class {
|
|
|
720
734
|
}
|
|
721
735
|
remove(ids) {
|
|
722
736
|
if ((0, _orkestrel_contract.isArray)(ids)) {
|
|
723
|
-
let removed =
|
|
724
|
-
for (const id of ids) if (this.#drop(id)) removed =
|
|
737
|
+
let removed = true;
|
|
738
|
+
for (const id of ids) if (!this.#drop(id)) removed = false;
|
|
725
739
|
return removed;
|
|
726
740
|
}
|
|
727
741
|
return this.#drop(ids);
|
|
@@ -743,21 +757,27 @@ var WorkspaceManager = class {
|
|
|
743
757
|
//#endregion
|
|
744
758
|
//#region src/core/workspaces/stores/DatabaseWorkspaceStore.ts
|
|
745
759
|
/**
|
|
746
|
-
*
|
|
760
|
+
* Persists workspace snapshots in a database table.
|
|
747
761
|
*
|
|
748
762
|
* Snapshots occupy one opaque column and are narrowed when read back from the storage boundary.
|
|
749
763
|
*
|
|
750
764
|
* @example
|
|
751
765
|
* ```ts
|
|
752
|
-
* import {
|
|
753
|
-
*
|
|
754
|
-
*
|
|
766
|
+
* import { rawShape, stringShape } from '@orkestrel/contract'
|
|
767
|
+
* import { createDatabase, createMemoryDriver } from '@orkestrel/database'
|
|
768
|
+
* import { DatabaseWorkspaceStore } from '@orkestrel/workspace'
|
|
769
|
+
*
|
|
770
|
+
* const database = createDatabase({
|
|
771
|
+
* driver: createMemoryDriver(),
|
|
772
|
+
* tables: { workspaces: { id: stringShape(), snapshot: rawShape({}) } },
|
|
773
|
+
* })
|
|
774
|
+
* const store = new DatabaseWorkspaceStore(database.table('workspaces'))
|
|
755
775
|
* ```
|
|
756
776
|
*/
|
|
757
777
|
var DatabaseWorkspaceStore = class {
|
|
758
778
|
#table;
|
|
759
779
|
/**
|
|
760
|
-
*
|
|
780
|
+
* Wraps a workspace snapshot table.
|
|
761
781
|
*
|
|
762
782
|
* @param table - The table keyed by workspace identifier
|
|
763
783
|
*/
|
|
@@ -782,7 +802,7 @@ var DatabaseWorkspaceStore = class {
|
|
|
782
802
|
//#endregion
|
|
783
803
|
//#region src/core/workspaces/stores/MemoryWorkspaceStore.ts
|
|
784
804
|
/**
|
|
785
|
-
*
|
|
805
|
+
* Holds workspace snapshots in the current process.
|
|
786
806
|
*
|
|
787
807
|
* @example
|
|
788
808
|
* ```ts
|
|
@@ -808,16 +828,37 @@ var MemoryWorkspaceStore = class {
|
|
|
808
828
|
//#endregion
|
|
809
829
|
//#region src/core/factories.ts
|
|
810
830
|
/**
|
|
811
|
-
*
|
|
831
|
+
* Creates an immutable file with derived size and line counts.
|
|
812
832
|
*
|
|
813
833
|
* @param input - The file path, content, and optional state
|
|
814
834
|
* @returns A frozen file record
|
|
815
835
|
*
|
|
816
|
-
* @example
|
|
836
|
+
* @example Files and content
|
|
817
837
|
* ```ts
|
|
818
|
-
* import {
|
|
819
|
-
*
|
|
820
|
-
*
|
|
838
|
+
* import {
|
|
839
|
+
* computeSize,
|
|
840
|
+
* countLines,
|
|
841
|
+
* createBinaryContent,
|
|
842
|
+
* createFile,
|
|
843
|
+
* createTextContent,
|
|
844
|
+
* inferLanguage,
|
|
845
|
+
* isBinary,
|
|
846
|
+
* isText,
|
|
847
|
+
* } from '@orkestrel/workspace'
|
|
848
|
+
*
|
|
849
|
+
* const note = createFile({
|
|
850
|
+
* path: 'notes.md',
|
|
851
|
+
* content: createTextContent('# Title\nBody', inferLanguage('notes.md')), // 'markdown'
|
|
852
|
+
* })
|
|
853
|
+
*
|
|
854
|
+
* note.size // 12 — UTF-8 bytes, through computeSize
|
|
855
|
+
* note.lines // 2 — through countLines
|
|
856
|
+
* note.state // 'created'
|
|
857
|
+
* isText(note.content) // true
|
|
858
|
+
*
|
|
859
|
+
* const icon = createFile({ path: 'icon.png', content: createBinaryContent('AAAA', 'image/png') })
|
|
860
|
+
* isBinary(icon.content) // true
|
|
861
|
+
* icon.size // 3 — decoded base64 bytes, through computeDecodedSize
|
|
821
862
|
* ```
|
|
822
863
|
*/
|
|
823
864
|
function createFile(input) {
|
|
@@ -830,7 +871,8 @@ function createFile(input) {
|
|
|
830
871
|
});
|
|
831
872
|
}
|
|
832
873
|
/**
|
|
833
|
-
*
|
|
874
|
+
* Creates the text arm of {@link FileContent}, returned as {@link TextContent} rather than as
|
|
875
|
+
* the whole union.
|
|
834
876
|
*
|
|
835
877
|
* @param text - The text body
|
|
836
878
|
* @param language - The language tag
|
|
@@ -850,9 +892,10 @@ function createTextContent(text, language) {
|
|
|
850
892
|
};
|
|
851
893
|
}
|
|
852
894
|
/**
|
|
853
|
-
*
|
|
895
|
+
* Creates the binary arm of {@link FileContent}, returned as {@link BinaryContent} rather than
|
|
896
|
+
* as the whole union.
|
|
854
897
|
*
|
|
855
|
-
* @param
|
|
898
|
+
* @param base64 - The base64 payload
|
|
856
899
|
* @param mime - The binary MIME
|
|
857
900
|
* @returns Binary file content
|
|
858
901
|
*
|
|
@@ -863,14 +906,14 @@ function createTextContent(text, language) {
|
|
|
863
906
|
* createBinaryContent('AAAA', 'image/png')
|
|
864
907
|
* ```
|
|
865
908
|
*/
|
|
866
|
-
function createBinaryContent(
|
|
909
|
+
function createBinaryContent(base64, mime) {
|
|
867
910
|
return {
|
|
868
|
-
|
|
911
|
+
base64,
|
|
869
912
|
mime
|
|
870
913
|
};
|
|
871
914
|
}
|
|
872
915
|
/**
|
|
873
|
-
*
|
|
916
|
+
* Creates a workspace with the same identity, emitter, and seed options the constructor takes.
|
|
874
917
|
*
|
|
875
918
|
* @param options - Optional identity, emitter configuration, and initial files
|
|
876
919
|
* @returns A working workspace
|
|
@@ -887,7 +930,7 @@ function createWorkspace(options) {
|
|
|
887
930
|
return new Workspace(options);
|
|
888
931
|
}
|
|
889
932
|
/**
|
|
890
|
-
*
|
|
933
|
+
* Creates an in-memory workspace snapshot store.
|
|
891
934
|
*
|
|
892
935
|
* @returns A process-local workspace store
|
|
893
936
|
*
|
|
@@ -902,9 +945,10 @@ function createMemoryWorkspaceStore() {
|
|
|
902
945
|
return new MemoryWorkspaceStore();
|
|
903
946
|
}
|
|
904
947
|
/**
|
|
905
|
-
*
|
|
948
|
+
* Creates a database-backed workspace snapshot store, over an in-memory driver when the caller
|
|
949
|
+
* supplies none.
|
|
906
950
|
*
|
|
907
|
-
* @param driver - The database driver
|
|
951
|
+
* @param driver - The database driver. Default: an in-memory driver.
|
|
908
952
|
* @returns A workspace store backed by the supplied driver
|
|
909
953
|
*
|
|
910
954
|
* @example
|
|
@@ -925,7 +969,7 @@ function createDatabaseWorkspaceStore(driver = (0, _orkestrel_database.createMem
|
|
|
925
969
|
}).table("workspaces"));
|
|
926
970
|
}
|
|
927
971
|
/**
|
|
928
|
-
*
|
|
972
|
+
* Creates an empty workspace registry.
|
|
929
973
|
*
|
|
930
974
|
* @param options - Default event hooks and optional durability
|
|
931
975
|
* @returns A workspace manager
|
|
@@ -950,6 +994,7 @@ exports.WorkspaceError = WorkspaceError;
|
|
|
950
994
|
exports.WorkspaceManager = WorkspaceManager;
|
|
951
995
|
exports.clampPosition = clampPosition;
|
|
952
996
|
exports.clampRange = clampRange;
|
|
997
|
+
exports.computeDecodedSize = computeDecodedSize;
|
|
953
998
|
exports.computeSize = computeSize;
|
|
954
999
|
exports.countLines = countLines;
|
|
955
1000
|
exports.createBinaryContent = createBinaryContent;
|
|
@@ -959,7 +1004,6 @@ exports.createMemoryWorkspaceStore = createMemoryWorkspaceStore;
|
|
|
959
1004
|
exports.createTextContent = createTextContent;
|
|
960
1005
|
exports.createWorkspace = createWorkspace;
|
|
961
1006
|
exports.createWorkspaceManager = createWorkspaceManager;
|
|
962
|
-
exports.decodedSize = decodedSize;
|
|
963
1007
|
exports.escapeRegExp = escapeRegExp;
|
|
964
1008
|
exports.inferLanguage = inferLanguage;
|
|
965
1009
|
exports.isBinary = isBinary;
|