@orkestrel/workspace 0.0.5 → 0.0.7
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 +1 -1
- package/dist/src/core/index.cjs +112 -101
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +312 -103
- package/dist/src/core/index.d.ts +312 -103
- package/dist/src/core/index.js +112 -101
- package/dist/src/core/index.js.map +1 -0
- package/package.json +23 -18
package/dist/src/core/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createDatabase, createMemoryDriver } from "@orkestrel/database";
|
|
|
3
3
|
import { Emitter } from "@orkestrel/emitter";
|
|
4
4
|
//#region src/core/constants.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Maps file extensions to language tags for text content.
|
|
7
7
|
*
|
|
8
8
|
* Unknown extensions intentionally fall back to `text` in
|
|
9
9
|
* {@link import('./helpers.js').inferLanguage}.
|
|
@@ -52,12 +52,12 @@ var EXTENSION_LANGUAGES = Object.freeze({
|
|
|
52
52
|
});
|
|
53
53
|
//#endregion
|
|
54
54
|
//#region src/core/errors.ts
|
|
55
|
-
/**
|
|
55
|
+
/** Reports an invalid workspace edit or search operation. */
|
|
56
56
|
var WorkspaceError = class extends Error {
|
|
57
57
|
code;
|
|
58
58
|
context;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
60
|
+
* Creates a workspace error.
|
|
61
61
|
*
|
|
62
62
|
* @param code - The machine-readable failure code
|
|
63
63
|
* @param message - The human-readable failure message
|
|
@@ -71,10 +71,10 @@ var WorkspaceError = class extends Error {
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
/**
|
|
74
|
-
*
|
|
74
|
+
* Narrows a caught value to a {@link WorkspaceError}.
|
|
75
75
|
*
|
|
76
76
|
* @param value - The caught value
|
|
77
|
-
* @returns
|
|
77
|
+
* @returns True if the value is a workspace error; false otherwise
|
|
78
78
|
*
|
|
79
79
|
* @example
|
|
80
80
|
* ```ts
|
|
@@ -87,7 +87,7 @@ function isWorkspaceError(value) {
|
|
|
87
87
|
//#endregion
|
|
88
88
|
//#region src/core/helpers.ts
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* Infers a language tag from the final file extension.
|
|
91
91
|
*
|
|
92
92
|
* @param path - The file path
|
|
93
93
|
* @returns The mapped language, or `text` when the extension is absent or unknown
|
|
@@ -103,10 +103,10 @@ function inferLanguage(path) {
|
|
|
103
103
|
return EXTENSION_LANGUAGES[path.slice(dot + 1).toLowerCase()] ?? "text";
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
106
|
+
* Determines whether content is the text arm.
|
|
107
107
|
*
|
|
108
108
|
* @param content - The file content
|
|
109
|
-
* @returns
|
|
109
|
+
* @returns True if the content carries text and a language; false otherwise
|
|
110
110
|
*
|
|
111
111
|
* @example
|
|
112
112
|
* ```ts
|
|
@@ -117,57 +117,21 @@ function isText(content) {
|
|
|
117
117
|
return "text" in content;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
120
|
+
* Checks whether content is the binary arm.
|
|
121
121
|
*
|
|
122
122
|
* @param content - The file content
|
|
123
|
-
* @returns
|
|
123
|
+
* @returns True if the content carries a base64 string and a MIME; false otherwise
|
|
124
124
|
*
|
|
125
125
|
* @example
|
|
126
126
|
* ```ts
|
|
127
|
-
* isBinary({
|
|
127
|
+
* isBinary({ base64: 'AAAA', mime: 'image/png' }) // true
|
|
128
128
|
* ```
|
|
129
129
|
*/
|
|
130
130
|
function isBinary(content) {
|
|
131
|
-
return "
|
|
131
|
+
return "base64" in content;
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* @param value - The value to inspect
|
|
137
|
-
* @returns Whether the value has the file shape
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* ```ts
|
|
141
|
-
* isFile({ path: 'a.txt', content: { text: 'a', language: 'text' }, state: 'created', size: 1, lines: 1 })
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
function isFile(value) {
|
|
145
|
-
return holds(() => {
|
|
146
|
-
if (!isRecord(value)) return false;
|
|
147
|
-
if (!isString(value.path) || !literalOf("created", "modified")(value.state)) return false;
|
|
148
|
-
if (!isNumber(value.size) || !isNumber(value.lines)) return false;
|
|
149
|
-
if (!isRecord(value.content)) return false;
|
|
150
|
-
const text = isString(value.content.text) && isString(value.content.language);
|
|
151
|
-
const binary = isString(value.content.data) && literalOf("image/png", "image/jpeg", "image/gif", "image/webp")(value.content.mime);
|
|
152
|
-
return text || binary;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Narrow an unknown value to a workspace snapshot.
|
|
157
|
-
*
|
|
158
|
-
* @param value - The value read from a persistence boundary
|
|
159
|
-
* @returns Whether the value has the workspace snapshot shape
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* ```ts
|
|
163
|
-
* isWorkspaceSnapshot({ id: 'work', files: [] }) // true
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
function isWorkspaceSnapshot(value) {
|
|
167
|
-
return holds(() => isRecord(value) && isString(value.id) && arrayOf(isFile)(value.files));
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Compute the byte size of file content.
|
|
134
|
+
* Computes the byte size of file content.
|
|
171
135
|
*
|
|
172
136
|
* @param content - The content to measure
|
|
173
137
|
* @returns UTF-8 bytes for text or decoded bytes for binary content
|
|
@@ -179,10 +143,10 @@ function isWorkspaceSnapshot(value) {
|
|
|
179
143
|
*/
|
|
180
144
|
function computeSize(content) {
|
|
181
145
|
if (isText(content)) return new TextEncoder().encode(content.text).length;
|
|
182
|
-
return
|
|
146
|
+
return computeDecodedSize(content.base64);
|
|
183
147
|
}
|
|
184
148
|
/**
|
|
185
|
-
*
|
|
149
|
+
* Counts the lines in file content.
|
|
186
150
|
*
|
|
187
151
|
* @param content - The content to inspect
|
|
188
152
|
* @returns The text line count, or zero for empty text and binary content
|
|
@@ -200,27 +164,27 @@ function countLines(content) {
|
|
|
200
164
|
return count;
|
|
201
165
|
}
|
|
202
166
|
/**
|
|
203
|
-
*
|
|
167
|
+
* Computes the decoded byte length of a base64 string.
|
|
204
168
|
*
|
|
205
|
-
* @param base64 - The base64
|
|
169
|
+
* @param base64 - The base64 string
|
|
206
170
|
* @returns The decoded byte count
|
|
207
171
|
*
|
|
208
172
|
* @example
|
|
209
173
|
* ```ts
|
|
210
|
-
*
|
|
174
|
+
* computeDecodedSize('AAAA') // 3
|
|
211
175
|
* ```
|
|
212
176
|
*/
|
|
213
|
-
function
|
|
177
|
+
function computeDecodedSize(base64) {
|
|
214
178
|
let padding = 0;
|
|
215
179
|
if (base64.endsWith("==")) padding = 2;
|
|
216
180
|
else if (base64.endsWith("=")) padding = 1;
|
|
217
181
|
return Math.floor(base64.length * 3 / 4) - padding;
|
|
218
182
|
}
|
|
219
183
|
/**
|
|
220
|
-
*
|
|
184
|
+
* Determines whether a 1-based range is structurally valid.
|
|
221
185
|
*
|
|
222
186
|
* @param range - The range to inspect
|
|
223
|
-
* @returns
|
|
187
|
+
* @returns True if both positions are positive and ordered; false otherwise
|
|
224
188
|
*
|
|
225
189
|
* @example
|
|
226
190
|
* ```ts
|
|
@@ -234,7 +198,7 @@ function isValidRange(range) {
|
|
|
234
198
|
return !(range.start.line === range.end.line && range.start.column > range.end.column);
|
|
235
199
|
}
|
|
236
200
|
/**
|
|
237
|
-
*
|
|
201
|
+
* Clamps a position to text bounds.
|
|
238
202
|
*
|
|
239
203
|
* @param text - The addressed text
|
|
240
204
|
* @param position - The requested position
|
|
@@ -255,7 +219,7 @@ function clampPosition(text, position) {
|
|
|
255
219
|
};
|
|
256
220
|
}
|
|
257
221
|
/**
|
|
258
|
-
*
|
|
222
|
+
* Clamps both positions in a range to text bounds.
|
|
259
223
|
*
|
|
260
224
|
* @param text - The addressed text
|
|
261
225
|
* @param range - The requested range
|
|
@@ -273,7 +237,7 @@ function clampRange(text, range) {
|
|
|
273
237
|
};
|
|
274
238
|
}
|
|
275
239
|
/**
|
|
276
|
-
*
|
|
240
|
+
* Converts a 1-based position to a zero-based string offset.
|
|
277
241
|
*
|
|
278
242
|
* @param text - The addressed text
|
|
279
243
|
* @param position - The requested position
|
|
@@ -292,7 +256,7 @@ function offsetAt(text, position) {
|
|
|
292
256
|
return Math.min(offset, text.length);
|
|
293
257
|
}
|
|
294
258
|
/**
|
|
295
|
-
*
|
|
259
|
+
* Slices a clamped half-open text range.
|
|
296
260
|
*
|
|
297
261
|
* @param text - The source text
|
|
298
262
|
* @param range - The requested range
|
|
@@ -308,7 +272,7 @@ function sliceRange(text, range) {
|
|
|
308
272
|
return text.slice(offsetAt(text, clamped.start), offsetAt(text, clamped.end));
|
|
309
273
|
}
|
|
310
274
|
/**
|
|
311
|
-
*
|
|
275
|
+
* Replaces a clamped half-open text range.
|
|
312
276
|
*
|
|
313
277
|
* @param text - The source text
|
|
314
278
|
* @param range - The requested range
|
|
@@ -327,7 +291,7 @@ function spliceRange(text, range, replacement) {
|
|
|
327
291
|
return text.slice(0, start) + replacement + text.slice(end);
|
|
328
292
|
}
|
|
329
293
|
/**
|
|
330
|
-
*
|
|
294
|
+
* Assembles a nested range from four 1-based coordinates.
|
|
331
295
|
*
|
|
332
296
|
* @param fromLine - The start line
|
|
333
297
|
* @param fromColumn - The start column
|
|
@@ -353,7 +317,7 @@ function rangeOf(fromLine, fromColumn, toLine, toColumn) {
|
|
|
353
317
|
};
|
|
354
318
|
}
|
|
355
319
|
/**
|
|
356
|
-
*
|
|
320
|
+
* Escapes regular-expression metacharacters for literal matching.
|
|
357
321
|
*
|
|
358
322
|
* @param value - The literal text
|
|
359
323
|
* @returns The escaped regular-expression source
|
|
@@ -367,9 +331,47 @@ function escapeRegExp(value) {
|
|
|
367
331
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
368
332
|
}
|
|
369
333
|
//#endregion
|
|
334
|
+
//#region src/core/validators.ts
|
|
335
|
+
/**
|
|
336
|
+
* Narrows an unknown value to an immutable file record.
|
|
337
|
+
*
|
|
338
|
+
* @param value - The value to inspect
|
|
339
|
+
* @returns True if the value has the file shape; false otherwise
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```ts
|
|
343
|
+
* isFile({ path: 'a.txt', content: { text: 'a', language: 'text' }, state: 'created', size: 1, lines: 1 })
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
function isFile(value) {
|
|
347
|
+
return holds(() => {
|
|
348
|
+
if (!isRecord(value)) return false;
|
|
349
|
+
if (!isString(value.path) || !literalOf("created", "modified")(value.state)) return false;
|
|
350
|
+
if (!isNumber(value.size) || !isNumber(value.lines)) return false;
|
|
351
|
+
if (!isRecord(value.content)) return false;
|
|
352
|
+
const text = isString(value.content.text) && isString(value.content.language);
|
|
353
|
+
const binary = isString(value.content.base64) && literalOf("image/png", "image/jpeg", "image/gif", "image/webp")(value.content.mime);
|
|
354
|
+
return text || binary;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Narrows an unknown value to a workspace snapshot.
|
|
359
|
+
*
|
|
360
|
+
* @param value - The value read from a persistence boundary
|
|
361
|
+
* @returns True if the value has the workspace snapshot shape; false otherwise
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* isWorkspaceSnapshot({ id: 'work', files: [] }) // true
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
function isWorkspaceSnapshot(value) {
|
|
369
|
+
return holds(() => isRecord(value) && isString(value.id) && arrayOf(isFile)(value.files));
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
370
372
|
//#region src/core/workspaces/Workspace.ts
|
|
371
373
|
/**
|
|
372
|
-
*
|
|
374
|
+
* Provides a mutable path-keyed editing surface over immutable files.
|
|
373
375
|
*
|
|
374
376
|
* Whole-file edits create text files, ranged edits operate only on existing text files, and
|
|
375
377
|
* binary files remain available through construction-time hydration. Mutations emit after the
|
|
@@ -390,7 +392,7 @@ var Workspace = class {
|
|
|
390
392
|
#files = /* @__PURE__ */ new Map();
|
|
391
393
|
#emitter;
|
|
392
394
|
/**
|
|
393
|
-
*
|
|
395
|
+
* Creates a workspace.
|
|
394
396
|
*
|
|
395
397
|
* @param options - Optional identity, emitter configuration, and initial files
|
|
396
398
|
*/
|
|
@@ -436,7 +438,7 @@ var Workspace = class {
|
|
|
436
438
|
};
|
|
437
439
|
}
|
|
438
440
|
has(path) {
|
|
439
|
-
if (isArray(path)) return path.
|
|
441
|
+
if (isArray(path)) return path.every((one) => this.#files.has(one));
|
|
440
442
|
return this.#files.has(path);
|
|
441
443
|
}
|
|
442
444
|
search(query, options) {
|
|
@@ -446,7 +448,7 @@ var Workspace = class {
|
|
|
446
448
|
for (const file of this.#files.values()) {
|
|
447
449
|
if (limit !== void 0 && matches.length >= limit) break;
|
|
448
450
|
if (!isText(file.content)) continue;
|
|
449
|
-
const lines = file.content.text.split(
|
|
451
|
+
const lines = file.content.text.split(/\r\n|\n/);
|
|
450
452
|
for (let index = 0; index < lines.length; index += 1) {
|
|
451
453
|
if (limit !== void 0 && matches.length >= limit) break;
|
|
452
454
|
const lineText = lines[index] ?? "";
|
|
@@ -500,39 +502,42 @@ var Workspace = class {
|
|
|
500
502
|
for (const [one, text] of Object.entries(path)) this.#write(one, text);
|
|
501
503
|
return;
|
|
502
504
|
}
|
|
503
|
-
|
|
505
|
+
if (content === void 0) return;
|
|
504
506
|
if (!range) {
|
|
505
|
-
this.#write(path,
|
|
507
|
+
this.#write(path, content);
|
|
506
508
|
return;
|
|
507
509
|
}
|
|
508
|
-
this.#splice(path,
|
|
510
|
+
this.#splice(path, content, range);
|
|
509
511
|
}
|
|
510
512
|
prepend(path, content) {
|
|
511
513
|
if (isRecord(path)) {
|
|
512
514
|
for (const [one, text] of Object.entries(path)) this.#prepend(one, text);
|
|
513
515
|
return;
|
|
514
516
|
}
|
|
515
|
-
|
|
517
|
+
if (content === void 0) return;
|
|
518
|
+
this.#prepend(path, content);
|
|
516
519
|
}
|
|
517
520
|
append(path, content) {
|
|
518
521
|
if (isRecord(path)) {
|
|
519
522
|
for (const [one, text] of Object.entries(path)) this.#append(one, text);
|
|
520
523
|
return;
|
|
521
524
|
}
|
|
522
|
-
|
|
525
|
+
if (content === void 0) return;
|
|
526
|
+
this.#append(path, content);
|
|
523
527
|
}
|
|
524
528
|
move(from, to) {
|
|
525
529
|
if (isRecord(from)) {
|
|
526
|
-
let moved =
|
|
527
|
-
for (const [one, target] of Object.entries(from)) if (this.#move(one, target)) moved =
|
|
530
|
+
let moved = true;
|
|
531
|
+
for (const [one, target] of Object.entries(from)) if (!this.#move(one, target)) moved = false;
|
|
528
532
|
return moved;
|
|
529
533
|
}
|
|
530
|
-
|
|
534
|
+
if (to === void 0) return false;
|
|
535
|
+
return this.#move(from, to);
|
|
531
536
|
}
|
|
532
537
|
remove(path) {
|
|
533
538
|
if (isArray(path)) {
|
|
534
|
-
let removed =
|
|
535
|
-
for (const one of path) if (this.#remove(one)) removed =
|
|
539
|
+
let removed = true;
|
|
540
|
+
for (const one of path) if (!this.#remove(one)) removed = false;
|
|
536
541
|
return removed;
|
|
537
542
|
}
|
|
538
543
|
return this.#remove(path);
|
|
@@ -565,7 +570,7 @@ var Workspace = class {
|
|
|
565
570
|
}
|
|
566
571
|
#splice(path, content, range) {
|
|
567
572
|
const existing = this.#files.get(path);
|
|
568
|
-
if (!existing) throw new WorkspaceError("
|
|
573
|
+
if (!existing) throw new WorkspaceError("MISSING", `Cannot splice a range of a missing file: ${path}`, { path });
|
|
569
574
|
if (!isText(existing.content)) throw new WorkspaceError("MODALITY", `Cannot splice a range of a binary file: ${path}`, { path });
|
|
570
575
|
if (!isValidRange(range)) throw new WorkspaceError("RANGE", `Invalid range for file: ${path}`, {
|
|
571
576
|
path,
|
|
@@ -634,7 +639,7 @@ var Workspace = class {
|
|
|
634
639
|
//#endregion
|
|
635
640
|
//#region src/core/workspaces/WorkspaceManager.ts
|
|
636
641
|
/**
|
|
637
|
-
*
|
|
642
|
+
* Provides an insertion-ordered workspace registry with an active selection.
|
|
638
643
|
*
|
|
639
644
|
* A supplied store adds lenient snapshot `open` and `save` operations. Event defaults flow into
|
|
640
645
|
* workspaces created through the registry, while observability remains owned by each workspace.
|
|
@@ -655,7 +660,7 @@ var WorkspaceManager = class {
|
|
|
655
660
|
#error;
|
|
656
661
|
#store;
|
|
657
662
|
/**
|
|
658
|
-
*
|
|
663
|
+
* Creates a registry.
|
|
659
664
|
*
|
|
660
665
|
* @param options - Default event hooks and optional snapshot store
|
|
661
666
|
*/
|
|
@@ -719,8 +724,8 @@ var WorkspaceManager = class {
|
|
|
719
724
|
}
|
|
720
725
|
remove(ids) {
|
|
721
726
|
if (isArray(ids)) {
|
|
722
|
-
let removed =
|
|
723
|
-
for (const id of ids) if (this.#drop(id)) removed =
|
|
727
|
+
let removed = true;
|
|
728
|
+
for (const id of ids) if (!this.#drop(id)) removed = false;
|
|
724
729
|
return removed;
|
|
725
730
|
}
|
|
726
731
|
return this.#drop(ids);
|
|
@@ -742,21 +747,27 @@ var WorkspaceManager = class {
|
|
|
742
747
|
//#endregion
|
|
743
748
|
//#region src/core/workspaces/stores/DatabaseWorkspaceStore.ts
|
|
744
749
|
/**
|
|
745
|
-
*
|
|
750
|
+
* Persists workspace snapshots in a database table.
|
|
746
751
|
*
|
|
747
752
|
* Snapshots occupy one opaque column and are narrowed when read back from the storage boundary.
|
|
748
753
|
*
|
|
749
754
|
* @example
|
|
750
755
|
* ```ts
|
|
751
|
-
* import {
|
|
752
|
-
*
|
|
753
|
-
*
|
|
756
|
+
* import { rawShape, stringShape } from '@orkestrel/contract'
|
|
757
|
+
* import { createDatabase, createMemoryDriver } from '@orkestrel/database'
|
|
758
|
+
* import { DatabaseWorkspaceStore } from '@orkestrel/workspace'
|
|
759
|
+
*
|
|
760
|
+
* const database = createDatabase({
|
|
761
|
+
* driver: createMemoryDriver(),
|
|
762
|
+
* tables: { workspaces: { id: stringShape(), snapshot: rawShape({}) } },
|
|
763
|
+
* })
|
|
764
|
+
* const store = new DatabaseWorkspaceStore(database.table('workspaces'))
|
|
754
765
|
* ```
|
|
755
766
|
*/
|
|
756
767
|
var DatabaseWorkspaceStore = class {
|
|
757
768
|
#table;
|
|
758
769
|
/**
|
|
759
|
-
*
|
|
770
|
+
* Wraps a workspace snapshot table.
|
|
760
771
|
*
|
|
761
772
|
* @param table - The table keyed by workspace identifier
|
|
762
773
|
*/
|
|
@@ -781,7 +792,7 @@ var DatabaseWorkspaceStore = class {
|
|
|
781
792
|
//#endregion
|
|
782
793
|
//#region src/core/workspaces/stores/MemoryWorkspaceStore.ts
|
|
783
794
|
/**
|
|
784
|
-
*
|
|
795
|
+
* Holds workspace snapshots in the current process.
|
|
785
796
|
*
|
|
786
797
|
* @example
|
|
787
798
|
* ```ts
|
|
@@ -807,7 +818,7 @@ var MemoryWorkspaceStore = class {
|
|
|
807
818
|
//#endregion
|
|
808
819
|
//#region src/core/factories.ts
|
|
809
820
|
/**
|
|
810
|
-
*
|
|
821
|
+
* Creates an immutable file with derived size and line counts.
|
|
811
822
|
*
|
|
812
823
|
* @param input - The file path, content, and optional state
|
|
813
824
|
* @returns A frozen file record
|
|
@@ -829,7 +840,7 @@ function createFile(input) {
|
|
|
829
840
|
});
|
|
830
841
|
}
|
|
831
842
|
/**
|
|
832
|
-
*
|
|
843
|
+
* Creates the text arm of {@link FileContent}.
|
|
833
844
|
*
|
|
834
845
|
* @param text - The text body
|
|
835
846
|
* @param language - The language tag
|
|
@@ -849,9 +860,9 @@ function createTextContent(text, language) {
|
|
|
849
860
|
};
|
|
850
861
|
}
|
|
851
862
|
/**
|
|
852
|
-
*
|
|
863
|
+
* Creates the binary arm of {@link FileContent}.
|
|
853
864
|
*
|
|
854
|
-
* @param
|
|
865
|
+
* @param base64 - The base64 payload
|
|
855
866
|
* @param mime - The binary MIME
|
|
856
867
|
* @returns Binary file content
|
|
857
868
|
*
|
|
@@ -862,14 +873,14 @@ function createTextContent(text, language) {
|
|
|
862
873
|
* createBinaryContent('AAAA', 'image/png')
|
|
863
874
|
* ```
|
|
864
875
|
*/
|
|
865
|
-
function createBinaryContent(
|
|
876
|
+
function createBinaryContent(base64, mime) {
|
|
866
877
|
return {
|
|
867
|
-
|
|
878
|
+
base64,
|
|
868
879
|
mime
|
|
869
880
|
};
|
|
870
881
|
}
|
|
871
882
|
/**
|
|
872
|
-
*
|
|
883
|
+
* Creates a workspace.
|
|
873
884
|
*
|
|
874
885
|
* @param options - Optional identity, emitter configuration, and initial files
|
|
875
886
|
* @returns A working workspace
|
|
@@ -886,7 +897,7 @@ function createWorkspace(options) {
|
|
|
886
897
|
return new Workspace(options);
|
|
887
898
|
}
|
|
888
899
|
/**
|
|
889
|
-
*
|
|
900
|
+
* Creates an in-memory workspace snapshot store.
|
|
890
901
|
*
|
|
891
902
|
* @returns A process-local workspace store
|
|
892
903
|
*
|
|
@@ -901,9 +912,9 @@ function createMemoryWorkspaceStore() {
|
|
|
901
912
|
return new MemoryWorkspaceStore();
|
|
902
913
|
}
|
|
903
914
|
/**
|
|
904
|
-
*
|
|
915
|
+
* Creates a database-backed workspace snapshot store.
|
|
905
916
|
*
|
|
906
|
-
* @param driver - The database driver
|
|
917
|
+
* @param driver - The database driver. Default: an in-memory driver.
|
|
907
918
|
* @returns A workspace store backed by the supplied driver
|
|
908
919
|
*
|
|
909
920
|
* @example
|
|
@@ -924,7 +935,7 @@ function createDatabaseWorkspaceStore(driver = createMemoryDriver()) {
|
|
|
924
935
|
}).table("workspaces"));
|
|
925
936
|
}
|
|
926
937
|
/**
|
|
927
|
-
*
|
|
938
|
+
* Creates an empty workspace registry.
|
|
928
939
|
*
|
|
929
940
|
* @param options - Default event hooks and optional durability
|
|
930
941
|
* @returns A workspace manager
|
|
@@ -941,6 +952,6 @@ function createWorkspaceManager(options) {
|
|
|
941
952
|
return new WorkspaceManager(options);
|
|
942
953
|
}
|
|
943
954
|
//#endregion
|
|
944
|
-
export { DatabaseWorkspaceStore, EXTENSION_LANGUAGES, MemoryWorkspaceStore, Workspace, WorkspaceError, WorkspaceManager, clampPosition, clampRange, computeSize, countLines, createBinaryContent, createDatabaseWorkspaceStore, createFile, createMemoryWorkspaceStore, createTextContent, createWorkspace, createWorkspaceManager,
|
|
955
|
+
export { DatabaseWorkspaceStore, EXTENSION_LANGUAGES, MemoryWorkspaceStore, Workspace, WorkspaceError, WorkspaceManager, clampPosition, clampRange, computeDecodedSize, computeSize, countLines, createBinaryContent, createDatabaseWorkspaceStore, createFile, createMemoryWorkspaceStore, createTextContent, createWorkspace, createWorkspaceManager, escapeRegExp, inferLanguage, isBinary, isFile, isText, isValidRange, isWorkspaceError, isWorkspaceSnapshot, offsetAt, rangeOf, sliceRange, spliceRange };
|
|
945
956
|
|
|
946
957
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["#id","#files","#emitter","#pattern","#write","#splice","#prepend","#append","#move","#remove","#workspaces","#on","#error","#store","#active","#drop","#table","#snapshots"],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/helpers.ts","../../../src/core/validators.ts","../../../src/core/workspaces/Workspace.ts","../../../src/core/workspaces/WorkspaceManager.ts","../../../src/core/workspaces/stores/DatabaseWorkspaceStore.ts","../../../src/core/workspaces/stores/MemoryWorkspaceStore.ts","../../../src/core/factories.ts"],"sourcesContent":["/**\n * Maps file extensions to language tags for text content.\n *\n * Unknown extensions intentionally fall back to `text` in\n * {@link import('./helpers.js').inferLanguage}.\n */\nexport const EXTENSION_LANGUAGES: Readonly<Record<string, string>> = Object.freeze({\n\tts: 'typescript',\n\ttsx: 'typescript',\n\tjs: 'javascript',\n\tjsx: 'javascript',\n\tmjs: 'javascript',\n\tcjs: 'javascript',\n\tjson: 'json',\n\tmd: 'markdown',\n\thtml: 'html',\n\thtm: 'html',\n\tcss: 'css',\n\tscss: 'scss',\n\tsass: 'sass',\n\tless: 'less',\n\tvue: 'vue',\n\tsvelte: 'svelte',\n\tpy: 'python',\n\trb: 'ruby',\n\trs: 'rust',\n\tgo: 'go',\n\tjava: 'java',\n\tkt: 'kotlin',\n\tswift: 'swift',\n\tc: 'c',\n\tcpp: 'cpp',\n\th: 'c',\n\thpp: 'cpp',\n\tcs: 'csharp',\n\tphp: 'php',\n\tsql: 'sql',\n\tsh: 'bash',\n\tbash: 'bash',\n\tzsh: 'bash',\n\tps1: 'powershell',\n\tyaml: 'yaml',\n\tyml: 'yaml',\n\ttoml: 'toml',\n\txml: 'xml',\n\tsvg: 'xml',\n\ttxt: 'text',\n})\n","import type { WorkspaceErrorCode } from './types.js'\n\n/** Reports an invalid workspace edit or search operation. */\nexport class WorkspaceError extends Error {\n\treadonly code: WorkspaceErrorCode\n\treadonly context?: Readonly<Record<string, unknown>>\n\n\t/**\n\t * Creates a workspace error.\n\t *\n\t * @param code - The machine-readable failure code\n\t * @param message - The human-readable failure message\n\t * @param context - Optional values associated with the failed operation\n\t */\n\tconstructor(\n\t\tcode: WorkspaceErrorCode,\n\t\tmessage: string,\n\t\tcontext?: Readonly<Record<string, unknown>>,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'WorkspaceError'\n\t\tthis.code = code\n\t\tif (context !== undefined) this.context = context\n\t}\n}\n\n/**\n * Narrows a caught value to a {@link WorkspaceError}.\n *\n * @param value - The caught value\n * @returns True if the value is a workspace error; false otherwise\n *\n * @example\n * ```ts\n * if (isWorkspaceError(error)) error.code\n * ```\n */\nexport function isWorkspaceError(value: unknown): value is WorkspaceError {\n\treturn value instanceof WorkspaceError\n}\n","import type { BinaryContent, FileContent, Position, Range, TextContent } from './types.js'\nimport { EXTENSION_LANGUAGES } from './constants.js'\n\n/**\n * Infers a language tag from the final file extension.\n *\n * @param path - The file path\n * @returns The mapped language, or `text` when the extension is absent or unknown\n *\n * @example\n * ```ts\n * inferLanguage('src/main.ts') // 'typescript'\n * ```\n */\nexport function inferLanguage(path: string): string {\n\tconst dot = path.lastIndexOf('.')\n\tif (dot === -1) return 'text'\n\tconst extension = path.slice(dot + 1).toLowerCase()\n\treturn EXTENSION_LANGUAGES[extension] ?? 'text'\n}\n\n/**\n * Determines whether content is the text arm.\n *\n * @param content - The file content\n * @returns True if the content carries text and a language; false otherwise\n *\n * @example\n * ```ts\n * isText({ text: 'hello', language: 'text' }) // true\n * ```\n */\nexport function isText(content: FileContent): content is TextContent {\n\treturn 'text' in content\n}\n\n/**\n * Checks whether content is the binary arm.\n *\n * @param content - The file content\n * @returns True if the content carries a base64 string and a MIME; false otherwise\n *\n * @example\n * ```ts\n * isBinary({ base64: 'AAAA', mime: 'image/png' }) // true\n * ```\n */\nexport function isBinary(content: FileContent): content is BinaryContent {\n\treturn 'base64' in content\n}\n\n/**\n * Computes the byte size of file content.\n *\n * @param content - The content to measure\n * @returns UTF-8 bytes for text or decoded bytes for binary content\n *\n * @example\n * ```ts\n * computeSize({ text: 'café', language: 'text' }) // 5\n * ```\n */\nexport function computeSize(content: FileContent): number {\n\tif (isText(content)) return new TextEncoder().encode(content.text).length\n\treturn computeDecodedSize(content.base64)\n}\n\n/**\n * Counts the lines in file content.\n *\n * @param content - The content to inspect\n * @returns The text line count, or zero for empty text and binary content\n *\n * @example\n * ```ts\n * countLines({ text: 'a\\nb', language: 'text' }) // 2\n * ```\n */\nexport function countLines(content: FileContent): number {\n\tif (!isText(content)) return 0\n\tif (content.text.length === 0) return 0\n\tlet count = 1\n\tfor (const character of content.text) if (character === '\\n') count += 1\n\treturn count\n}\n\n/**\n * Computes the decoded byte length of a base64 string.\n *\n * @param base64 - The base64 string\n * @returns The decoded byte count\n *\n * @example\n * ```ts\n * computeDecodedSize('AAAA') // 3\n * ```\n */\nexport function computeDecodedSize(base64: string): number {\n\tlet padding = 0\n\tif (base64.endsWith('==')) padding = 2\n\telse if (base64.endsWith('=')) padding = 1\n\treturn Math.floor((base64.length * 3) / 4) - padding\n}\n\n/**\n * Determines whether a 1-based range is structurally valid.\n *\n * @param range - The range to inspect\n * @returns True if both positions are positive and ordered; false otherwise\n *\n * @example\n * ```ts\n * isValidRange({ start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }) // true\n * ```\n */\nexport function isValidRange(range: Range): boolean {\n\tif (range.start.line < 1 || range.start.column < 1) return false\n\tif (range.end.line < 1 || range.end.column < 1) return false\n\tif (range.start.line > range.end.line) return false\n\treturn !(range.start.line === range.end.line && range.start.column > range.end.column)\n}\n\n/**\n * Clamps a position to text bounds.\n *\n * @param text - The addressed text\n * @param position - The requested position\n * @returns The clamped position\n *\n * @example\n * ```ts\n * clampPosition('ab', { line: 9, column: 9 }) // { line: 1, column: 3 }\n * ```\n */\nexport function clampPosition(text: string, position: Position): Position {\n\tconst lines = text.split('\\n')\n\tconst line = Math.max(1, Math.min(position.line, lines.length))\n\tconst lineText = lines[line - 1] ?? ''\n\tconst column = Math.max(1, Math.min(position.column, lineText.length + 1))\n\treturn { line, column }\n}\n\n/**\n * Clamps both positions in a range to text bounds.\n *\n * @param text - The addressed text\n * @param range - The requested range\n * @returns The clamped range\n *\n * @example\n * ```ts\n * clampRange('ab', { start: { line: 1, column: 1 }, end: { line: 9, column: 9 } })\n * ```\n */\nexport function clampRange(text: string, range: Range): Range {\n\treturn { start: clampPosition(text, range.start), end: clampPosition(text, range.end) }\n}\n\n/**\n * Converts a 1-based position to a zero-based string offset.\n *\n * @param text - The addressed text\n * @param position - The requested position\n * @returns The bounded string offset\n *\n * @example\n * ```ts\n * offsetAt('ab\\ncd', { line: 2, column: 1 }) // 3\n * ```\n */\nexport function offsetAt(text: string, position: Position): number {\n\tconst lines = text.split('\\n')\n\tlet offset = 0\n\tfor (let index = 0; index < position.line - 1 && index < lines.length; index += 1) {\n\t\toffset += (lines[index]?.length ?? 0) + 1\n\t}\n\toffset += position.column - 1\n\treturn Math.min(offset, text.length)\n}\n\n/**\n * Slices a clamped half-open text range.\n *\n * @param text - The source text\n * @param range - The requested range\n * @returns The ranged substring\n *\n * @example\n * ```ts\n * sliceRange('hello', { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }) // 'hello'\n * ```\n */\nexport function sliceRange(text: string, range: Range): string {\n\tconst clamped = clampRange(text, range)\n\treturn text.slice(offsetAt(text, clamped.start), offsetAt(text, clamped.end))\n}\n\n/**\n * Replaces a clamped half-open text range.\n *\n * @param text - The source text\n * @param range - The requested range\n * @param replacement - The replacement text\n * @returns The spliced text\n *\n * @example\n * ```ts\n * spliceRange('hello', { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }, 'bye')\n * ```\n */\nexport function spliceRange(text: string, range: Range, replacement: string): string {\n\tconst clamped = clampRange(text, range)\n\tconst start = offsetAt(text, clamped.start)\n\tconst end = offsetAt(text, clamped.end)\n\treturn text.slice(0, start) + replacement + text.slice(end)\n}\n\n/**\n * Assembles a nested range from four 1-based coordinates.\n *\n * @param fromLine - The start line\n * @param fromColumn - The start column\n * @param toLine - The end line\n * @param toColumn - The end column\n * @returns The assembled range without validation\n *\n * @example\n * ```ts\n * rangeOf(1, 1, 1, 2)\n * ```\n */\nexport function rangeOf(\n\tfromLine: number,\n\tfromColumn: number,\n\ttoLine: number,\n\ttoColumn: number,\n): Range {\n\treturn { start: { line: fromLine, column: fromColumn }, end: { line: toLine, column: toColumn } }\n}\n\n/**\n * Escapes regular-expression metacharacters for literal matching.\n *\n * @param value - The literal text\n * @returns The escaped regular-expression source\n *\n * @example\n * ```ts\n * escapeRegExp('a.b') // 'a\\\\.b'\n * ```\n */\nexport function escapeRegExp(value: string): string {\n\treturn value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { FileInterface, WorkspaceSnapshot } from './types.js'\nimport { arrayOf, holds, isNumber, isRecord, isString, literalOf } from '@orkestrel/contract'\n\n/**\n * Narrows an unknown value to an immutable file record.\n *\n * @param value - The value to inspect\n * @returns True if the value has the file shape; false otherwise\n *\n * @example\n * ```ts\n * isFile({ path: 'a.txt', content: { text: 'a', language: 'text' }, state: 'created', size: 1, lines: 1 })\n * ```\n */\nexport function isFile(value: unknown): value is FileInterface {\n\treturn holds(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (!isString(value.path) || !literalOf('created', 'modified')(value.state)) return false\n\t\tif (!isNumber(value.size) || !isNumber(value.lines)) return false\n\t\tif (!isRecord(value.content)) return false\n\t\tconst text = isString(value.content.text) && isString(value.content.language)\n\t\tconst binary =\n\t\t\tisString(value.content.base64) &&\n\t\t\tliteralOf('image/png', 'image/jpeg', 'image/gif', 'image/webp')(value.content.mime)\n\t\treturn text || binary\n\t})\n}\n\n/**\n * Narrows an unknown value to a workspace snapshot.\n *\n * @param value - The value read from a persistence boundary\n * @returns True if the value has the workspace snapshot shape; false otherwise\n *\n * @example\n * ```ts\n * isWorkspaceSnapshot({ id: 'work', files: [] }) // true\n * ```\n */\nexport function isWorkspaceSnapshot(value: unknown): value is WorkspaceSnapshot {\n\treturn holds(() => isRecord(value) && isString(value.id) && arrayOf(isFile)(value.files))\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tFileInterface,\n\tRange,\n\tReadResult,\n\tReplaceResult,\n\tSearchMatch,\n\tSearchOptions,\n\tWorkspaceEventMap,\n\tWorkspaceInterface,\n\tWorkspaceOptions,\n\tWorkspaceSnapshot,\n} from '../types.js'\nimport { isArray, isRecord } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport { WorkspaceError } from '../errors.js'\nimport { createFile } from '../factories.js'\nimport {\n\tclampRange,\n\tescapeRegExp,\n\tinferLanguage,\n\tisText,\n\tisValidRange,\n\tsliceRange,\n\tspliceRange,\n} from '../helpers.js'\n\n/**\n * Provides a mutable path-keyed editing surface over immutable files.\n *\n * Whole-file edits create text files, ranged edits operate only on existing text files, and\n * binary files remain available through construction-time hydration. Mutations emit after the\n * registry has changed.\n *\n * @example\n * ```ts\n * import { Workspace } from '@orkestrel/workspace'\n *\n * const workspace = new Workspace()\n * workspace.write('notes.txt', 'hello')\n * workspace.append('notes.txt', ' world')\n * workspace.read('notes.txt') // 'hello world'\n * ```\n */\nexport class Workspace implements WorkspaceInterface {\n\treadonly #id: string\n\treadonly #files = new Map<string, FileInterface>()\n\treadonly #emitter: Emitter<WorkspaceEventMap>\n\n\t/**\n\t * Creates a workspace.\n\t *\n\t * @param options - Optional identity, emitter configuration, and initial files\n\t */\n\tconstructor(options?: WorkspaceOptions) {\n\t\tthis.#id = options?.id ?? crypto.randomUUID()\n\t\tthis.#emitter = new Emitter<WorkspaceEventMap>({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\tif (options?.seed) for (const file of options.seed) this.#files.set(file.path, file)\n\t}\n\n\tget id(): string {\n\t\treturn this.#id\n\t}\n\n\tget emitter(): EmitterInterface<WorkspaceEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget count(): number {\n\t\treturn this.#files.size\n\t}\n\n\tfile(path: string): FileInterface | undefined {\n\t\treturn this.#files.get(path)\n\t}\n\n\tfiles(): readonly FileInterface[] {\n\t\treturn [...this.#files.values()]\n\t}\n\n\tread(path: string): string | undefined\n\tread(path: string, range: Range): ReadResult | undefined\n\tread(paths: readonly string[]): Readonly<Record<string, string>>\n\tread(\n\t\tpath: string | readonly string[],\n\t\trange?: Range,\n\t): string | ReadResult | undefined | Readonly<Record<string, string>> {\n\t\tif (isArray(path)) {\n\t\t\tconst result: Record<string, string> = {}\n\t\t\tfor (const one of path) {\n\t\t\t\tconst file = this.#files.get(one)\n\t\t\t\tif (file && isText(file.content)) result[one] = file.content.text\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\t\tconst file = this.#files.get(path)\n\t\tif (!file) return undefined\n\t\tif (!range) return isText(file.content) ? file.content.text : undefined\n\t\tif (!isText(file.content)) {\n\t\t\tthrow new WorkspaceError('MODALITY', `Cannot read a range of a binary file: ${path}`, {\n\t\t\t\tpath,\n\t\t\t})\n\t\t}\n\t\treturn {\n\t\t\tcontent: sliceRange(file.content.text, range),\n\t\t\trange: clampRange(file.content.text, range),\n\t\t}\n\t}\n\n\thas(path: string): boolean\n\thas(paths: readonly string[]): boolean\n\thas(path: string | readonly string[]): boolean {\n\t\tif (isArray(path)) return path.every((one) => this.#files.has(one))\n\t\treturn this.#files.has(path)\n\t}\n\n\tsearch(query: string, options?: SearchOptions): readonly SearchMatch[] {\n\t\tconst pattern = this.#pattern(query, options)\n\t\tconst limit = options?.limit\n\t\tconst matches: SearchMatch[] = []\n\t\tfor (const file of this.#files.values()) {\n\t\t\tif (limit !== undefined && matches.length >= limit) break\n\t\t\tif (!isText(file.content)) continue\n\t\t\tconst lines = file.content.text.split(/\\r\\n|\\n/)\n\t\t\tfor (let index = 0; index < lines.length; index += 1) {\n\t\t\t\tif (limit !== undefined && matches.length >= limit) break\n\t\t\t\tconst lineText = lines[index] ?? ''\n\t\t\t\tpattern.lastIndex = 0\n\t\t\t\tlet hit = pattern.exec(lineText)\n\t\t\t\twhile (hit !== null) {\n\t\t\t\t\tif (limit !== undefined && matches.length >= limit) break\n\t\t\t\t\tmatches.push({\n\t\t\t\t\t\tpath: file.path,\n\t\t\t\t\t\tline: index + 1,\n\t\t\t\t\t\tcolumn: hit.index + 1,\n\t\t\t\t\t\tlength: hit[0].length,\n\t\t\t\t\t\tcontent: lineText,\n\t\t\t\t\t})\n\t\t\t\t\tif (hit[0].length === 0) pattern.lastIndex += 1\n\t\t\t\t\thit = pattern.exec(lineText)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn matches\n\t}\n\n\treplace(query: string, replacement: string, options?: SearchOptions): ReplaceResult {\n\t\tconst pattern = this.#pattern(query, options)\n\t\tconst limit = options?.limit\n\t\tlet occurrences = 0\n\t\tlet files = 0\n\t\tfor (const [path, file] of this.#files) {\n\t\t\tif (limit !== undefined && occurrences >= limit) break\n\t\t\tif (!isText(file.content)) continue\n\t\t\tconst remaining = limit === undefined ? undefined : limit - occurrences\n\t\t\tlet count = 0\n\t\t\tpattern.lastIndex = 0\n\t\t\tconst next = file.content.text.replace(pattern, (match) => {\n\t\t\t\tif (remaining !== undefined && count >= remaining) return match\n\t\t\t\tcount += 1\n\t\t\t\treturn replacement\n\t\t\t})\n\t\t\tif (count > 0) {\n\t\t\t\toccurrences += count\n\t\t\t\tfiles += 1\n\t\t\t\tthis.write(path, next)\n\t\t\t}\n\t\t}\n\t\treturn { occurrences, files }\n\t}\n\n\twrite(path: string, content: string): void\n\twrite(path: string, content: string, range: Range): void\n\twrite(files: Readonly<Record<string, string>>): void\n\twrite(path: string | Readonly<Record<string, string>>, content?: string, range?: Range): void {\n\t\tif (isRecord(path)) {\n\t\t\tfor (const [one, text] of Object.entries(path)) this.#write(one, text)\n\t\t\treturn\n\t\t}\n\t\tif (content === undefined) return\n\t\tif (!range) {\n\t\t\tthis.#write(path, content)\n\t\t\treturn\n\t\t}\n\t\tthis.#splice(path, content, range)\n\t}\n\n\tprepend(path: string, content: string): void\n\tprepend(files: Readonly<Record<string, string>>): void\n\tprepend(path: string | Readonly<Record<string, string>>, content?: string): void {\n\t\tif (isRecord(path)) {\n\t\t\tfor (const [one, text] of Object.entries(path)) this.#prepend(one, text)\n\t\t\treturn\n\t\t}\n\t\tif (content === undefined) return\n\t\tthis.#prepend(path, content)\n\t}\n\n\tappend(path: string, content: string): void\n\tappend(files: Readonly<Record<string, string>>): void\n\tappend(path: string | Readonly<Record<string, string>>, content?: string): void {\n\t\tif (isRecord(path)) {\n\t\t\tfor (const [one, text] of Object.entries(path)) this.#append(one, text)\n\t\t\treturn\n\t\t}\n\t\tif (content === undefined) return\n\t\tthis.#append(path, content)\n\t}\n\n\tmove(from: string, to: string): boolean\n\tmove(mapping: Readonly<Record<string, string>>): boolean\n\tmove(from: string | Readonly<Record<string, string>>, to?: string): boolean {\n\t\tif (isRecord(from)) {\n\t\t\tlet moved = true\n\t\t\tfor (const [one, target] of Object.entries(from)) {\n\t\t\t\tif (!this.#move(one, target)) moved = false\n\t\t\t}\n\t\t\treturn moved\n\t\t}\n\t\tif (to === undefined) return false\n\t\treturn this.#move(from, to)\n\t}\n\n\tremove(path: string): boolean\n\tremove(paths: readonly string[]): boolean\n\tremove(path: string | readonly string[]): boolean {\n\t\tif (isArray(path)) {\n\t\t\tlet removed = true\n\t\t\tfor (const one of path) {\n\t\t\t\tif (!this.#remove(one)) removed = false\n\t\t\t}\n\t\t\treturn removed\n\t\t}\n\t\treturn this.#remove(path)\n\t}\n\n\tclear(): void {\n\t\tthis.#files.clear()\n\t\tthis.#emitter.emit('clear')\n\t}\n\n\tsnapshot(): WorkspaceSnapshot {\n\t\treturn { id: this.#id, files: this.files() }\n\t}\n\n\tdestroy(): void {\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#write(path: string, content: string): void {\n\t\tconst existing = this.#files.get(path)\n\t\tconst language =\n\t\t\texisting && isText(existing.content) ? existing.content.language : inferLanguage(path)\n\t\tconst file = createFile({\n\t\t\tpath,\n\t\t\tcontent: { text: content, language },\n\t\t\tstate: existing ? 'modified' : 'created',\n\t\t})\n\t\tthis.#files.set(path, file)\n\t\tthis.#emitter.emit('write', file)\n\t}\n\n\t#splice(path: string, content: string, range: Range): void {\n\t\tconst existing = this.#files.get(path)\n\t\tif (!existing) {\n\t\t\tthrow new WorkspaceError('MISSING', `Cannot splice a range of a missing file: ${path}`, {\n\t\t\t\tpath,\n\t\t\t})\n\t\t}\n\t\tif (!isText(existing.content)) {\n\t\t\tthrow new WorkspaceError('MODALITY', `Cannot splice a range of a binary file: ${path}`, {\n\t\t\t\tpath,\n\t\t\t})\n\t\t}\n\t\tif (!isValidRange(range)) {\n\t\t\tthrow new WorkspaceError('RANGE', `Invalid range for file: ${path}`, { path, range })\n\t\t}\n\t\tconst file = createFile({\n\t\t\tpath,\n\t\t\tcontent: {\n\t\t\t\ttext: spliceRange(existing.content.text, range, content),\n\t\t\t\tlanguage: existing.content.language,\n\t\t\t},\n\t\t\tstate: 'modified',\n\t\t})\n\t\tthis.#files.set(path, file)\n\t\tthis.#emitter.emit('write', file)\n\t}\n\n\t#prepend(path: string, content: string): void {\n\t\tconst existing = this.#files.get(path)\n\t\tlet text = ''\n\t\tif (existing) {\n\t\t\tif (!isText(existing.content)) {\n\t\t\t\tthrow new WorkspaceError('MODALITY', `Cannot prepend text to a binary file: ${path}`, {\n\t\t\t\t\tpath,\n\t\t\t\t})\n\t\t\t}\n\t\t\ttext = existing.content.text\n\t\t}\n\t\tthis.#write(path, content + text)\n\t}\n\n\t#append(path: string, content: string): void {\n\t\tconst existing = this.#files.get(path)\n\t\tlet text = ''\n\t\tif (existing) {\n\t\t\tif (!isText(existing.content)) {\n\t\t\t\tthrow new WorkspaceError('MODALITY', `Cannot append text to a binary file: ${path}`, {\n\t\t\t\t\tpath,\n\t\t\t\t})\n\t\t\t}\n\t\t\ttext = existing.content.text\n\t\t}\n\t\tthis.#write(path, text + content)\n\t}\n\n\t#move(from: string, to: string): boolean {\n\t\tif (from === to) return false\n\t\tconst file = this.#files.get(from)\n\t\tif (!file) return false\n\t\tconst moved = createFile({ path: to, content: file.content, state: 'modified' })\n\t\tconst entries = [...this.#files]\n\t\tthis.#files.clear()\n\t\tfor (const [path, current] of entries) {\n\t\t\tif (path === from) this.#files.set(to, moved)\n\t\t\telse if (path !== to) this.#files.set(path, current)\n\t\t}\n\t\tthis.#emitter.emit('move', from, to)\n\t\treturn true\n\t}\n\n\t#remove(path: string): boolean {\n\t\tconst removed = this.#files.delete(path)\n\t\tif (removed) this.#emitter.emit('remove', path)\n\t\treturn removed\n\t}\n\n\t#pattern(query: string, options?: SearchOptions): RegExp {\n\t\tconst source = options?.regex === true ? query : escapeRegExp(query)\n\t\tconst flags = options?.sensitive === false ? 'gi' : 'g'\n\t\ttry {\n\t\t\treturn new RegExp(source, flags)\n\t\t} catch {\n\t\t\tthrow new WorkspaceError('PATTERN', `Invalid search pattern: ${query}`, { query })\n\t\t}\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type {\n\tWorkspaceEventMap,\n\tWorkspaceInterface,\n\tWorkspaceManagerInterface,\n\tWorkspaceManagerOptions,\n\tWorkspaceOptions,\n\tWorkspaceStoreInterface,\n} from '../types.js'\nimport { isArray } from '@orkestrel/contract'\nimport { Workspace } from './Workspace.js'\n\n/**\n * Provides an insertion-ordered workspace registry with an active selection.\n *\n * A supplied store adds lenient snapshot `open` and `save` operations. Event defaults flow into\n * workspaces created through the registry, while observability remains owned by each workspace.\n *\n * @example\n * ```ts\n * import { WorkspaceManager } from '@orkestrel/workspace'\n *\n * const manager = new WorkspaceManager()\n * const workspace = manager.add({ id: 'scratch' })\n * manager.active === workspace\n * ```\n */\nexport class WorkspaceManager implements WorkspaceManagerInterface {\n\treadonly #workspaces = new Map<string, WorkspaceInterface>()\n\t#active: string | undefined\n\treadonly #on: EmitterHooks<WorkspaceEventMap> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\n\treadonly #store: WorkspaceStoreInterface | undefined\n\n\t/**\n\t * Creates a registry.\n\t *\n\t * @param options - Default event hooks and optional snapshot store\n\t */\n\tconstructor(options?: WorkspaceManagerOptions) {\n\t\tthis.#on = options?.on\n\t\tthis.#error = options?.error\n\t\tthis.#store = options?.store\n\t}\n\n\tget count(): number {\n\t\treturn this.#workspaces.size\n\t}\n\n\tget active(): WorkspaceInterface | undefined {\n\t\treturn this.#active === undefined ? undefined : this.#workspaces.get(this.#active)\n\t}\n\n\tworkspace(id: string): WorkspaceInterface | undefined {\n\t\treturn this.#workspaces.get(id)\n\t}\n\n\tworkspaces(): readonly WorkspaceInterface[] {\n\t\treturn [...this.#workspaces.values()]\n\t}\n\n\tadd(options?: WorkspaceOptions): WorkspaceInterface {\n\t\tconst on = options?.on ?? this.#on\n\t\tconst error = options?.error ?? this.#error\n\t\tconst workspace = new Workspace({\n\t\t\t...(options?.id === undefined ? {} : { id: options.id }),\n\t\t\t...(on === undefined ? {} : { on }),\n\t\t\t...(error === undefined ? {} : { error }),\n\t\t\t...(options?.seed === undefined ? {} : { seed: options.seed }),\n\t\t})\n\t\tthis.#workspaces.set(workspace.id, workspace)\n\t\tif (this.#active === undefined) this.#active = workspace.id\n\t\treturn workspace\n\t}\n\n\tswitch(id: string): WorkspaceInterface | undefined {\n\t\tconst workspace = this.#workspaces.get(id)\n\t\tif (workspace === undefined) return undefined\n\t\tthis.#active = id\n\t\treturn workspace\n\t}\n\n\tasync open(id: string): Promise<WorkspaceInterface | undefined> {\n\t\tconst existing = this.#workspaces.get(id)\n\t\tif (existing !== undefined) {\n\t\t\tthis.#active = id\n\t\t\treturn existing\n\t\t}\n\t\tif (this.#store === undefined) return undefined\n\t\tconst snapshot = await this.#store.get(id)\n\t\tif (snapshot === undefined) return undefined\n\t\tconst workspace = this.add({ id, seed: snapshot.files })\n\t\tthis.#active = workspace.id\n\t\treturn workspace\n\t}\n\n\tasync save(id: string): Promise<boolean> {\n\t\tconst workspace = this.#workspaces.get(id)\n\t\tif (this.#store === undefined || workspace === undefined) return false\n\t\tawait this.#store.set(workspace.snapshot())\n\t\treturn true\n\t}\n\n\tremove(id: string): boolean\n\tremove(ids: readonly string[]): boolean\n\tremove(ids: string | readonly string[]): boolean {\n\t\tif (isArray(ids)) {\n\t\t\tlet removed = true\n\t\t\tfor (const id of ids) {\n\t\t\t\tif (!this.#drop(id)) removed = false\n\t\t\t}\n\t\t\treturn removed\n\t\t}\n\t\treturn this.#drop(ids)\n\t}\n\n\tclear(): void {\n\t\tfor (const workspace of this.#workspaces.values()) workspace.destroy()\n\t\tthis.#workspaces.clear()\n\t\tthis.#active = undefined\n\t}\n\n\t#drop(id: string): boolean {\n\t\tconst workspace = this.#workspaces.get(id)\n\t\tif (workspace === undefined) return false\n\t\tthis.#workspaces.delete(id)\n\t\tworkspace.destroy()\n\t\tif (this.#active === id) this.#active = undefined\n\t\treturn true\n\t}\n}\n","import type {\n\tWorkspaceSnapshot,\n\tWorkspaceSnapshotRow,\n\tWorkspaceStoreInterface,\n} from '../../types.js'\nimport type { TableInterface } from '@orkestrel/database'\nimport { isWorkspaceSnapshot } from '../../validators.js'\n\n/**\n * Persists workspace snapshots in a database table.\n *\n * Snapshots occupy one opaque column and are narrowed when read back from the storage boundary.\n *\n * @example\n * ```ts\n * import { rawShape, stringShape } from '@orkestrel/contract'\n * import { createDatabase, createMemoryDriver } from '@orkestrel/database'\n * import { DatabaseWorkspaceStore } from '@orkestrel/workspace'\n *\n * const database = createDatabase({\n * \tdriver: createMemoryDriver(),\n * \ttables: { workspaces: { id: stringShape(), snapshot: rawShape({}) } },\n * })\n * const store = new DatabaseWorkspaceStore(database.table('workspaces'))\n * ```\n */\nexport class DatabaseWorkspaceStore implements WorkspaceStoreInterface {\n\treadonly #table: TableInterface<WorkspaceSnapshotRow>\n\n\t/**\n\t * Wraps a workspace snapshot table.\n\t *\n\t * @param table - The table keyed by workspace identifier\n\t */\n\tconstructor(table: TableInterface<WorkspaceSnapshotRow>) {\n\t\tthis.#table = table\n\t}\n\n\tasync get(id: string): Promise<WorkspaceSnapshot | undefined> {\n\t\tconst row = await this.#table.get(id)\n\t\tif (row === undefined) return undefined\n\t\treturn isWorkspaceSnapshot(row.snapshot) ? row.snapshot : undefined\n\t}\n\n\tasync set(snapshot: WorkspaceSnapshot): Promise<void> {\n\t\tawait this.#table.set({ id: snapshot.id, snapshot })\n\t}\n\n\tasync delete(id: string): Promise<void> {\n\t\tawait this.#table.remove(id)\n\t}\n}\n","import type { WorkspaceSnapshot, WorkspaceStoreInterface } from '../../types.js'\n\n/**\n * Holds workspace snapshots in the current process.\n *\n * @example\n * ```ts\n * import { MemoryWorkspaceStore } from '@orkestrel/workspace'\n *\n * const store = new MemoryWorkspaceStore()\n * ```\n */\nexport class MemoryWorkspaceStore implements WorkspaceStoreInterface {\n\treadonly #snapshots = new Map<string, WorkspaceSnapshot>()\n\n\tget(id: string): Promise<WorkspaceSnapshot | undefined> {\n\t\treturn Promise.resolve(this.#snapshots.get(id))\n\t}\n\n\tset(snapshot: WorkspaceSnapshot): Promise<void> {\n\t\tthis.#snapshots.set(snapshot.id, snapshot)\n\t\treturn Promise.resolve()\n\t}\n\n\tdelete(id: string): Promise<void> {\n\t\tthis.#snapshots.delete(id)\n\t\treturn Promise.resolve()\n\t}\n}\n","import type {\n\tBinaryContent,\n\tBinaryMIME,\n\tFileInput,\n\tFileInterface,\n\tTextContent,\n\tWorkspaceInterface,\n\tWorkspaceManagerInterface,\n\tWorkspaceManagerOptions,\n\tWorkspaceOptions,\n\tWorkspaceSnapshotRow,\n\tWorkspaceStoreInterface,\n} from './types.js'\nimport type { DriverInterface, TableInterface } from '@orkestrel/database'\nimport { rawShape, stringShape } from '@orkestrel/contract'\nimport { createDatabase, createMemoryDriver } from '@orkestrel/database'\nimport { computeSize, countLines } from './helpers.js'\nimport { Workspace } from './workspaces/Workspace.js'\nimport { WorkspaceManager } from './workspaces/WorkspaceManager.js'\nimport { DatabaseWorkspaceStore } from './workspaces/stores/DatabaseWorkspaceStore.js'\nimport { MemoryWorkspaceStore } from './workspaces/stores/MemoryWorkspaceStore.js'\n\n/**\n * Creates an immutable file with derived size and line counts.\n *\n * @param input - The file path, content, and optional state\n * @returns A frozen file record\n *\n * @example\n * ```ts\n * import { createFile, createTextContent } from '@orkestrel/workspace'\n *\n * createFile({ path: 'a.txt', content: createTextContent('hello', 'text') })\n * ```\n */\nexport function createFile(input: FileInput): FileInterface {\n\treturn Object.freeze({\n\t\tpath: input.path,\n\t\tcontent: input.content,\n\t\tstate: input.state ?? 'created',\n\t\tsize: computeSize(input.content),\n\t\tlines: countLines(input.content),\n\t})\n}\n\n/**\n * Creates the text arm of {@link FileContent}.\n *\n * @param text - The text body\n * @param language - The language tag\n * @returns Text file content\n *\n * @example\n * ```ts\n * import { createTextContent } from '@orkestrel/workspace'\n *\n * createTextContent('hello', 'text')\n * ```\n */\nexport function createTextContent(text: string, language: string): TextContent {\n\treturn { text, language }\n}\n\n/**\n * Creates the binary arm of {@link FileContent}.\n *\n * @param base64 - The base64 payload\n * @param mime - The binary MIME\n * @returns Binary file content\n *\n * @example\n * ```ts\n * import { createBinaryContent } from '@orkestrel/workspace'\n *\n * createBinaryContent('AAAA', 'image/png')\n * ```\n */\nexport function createBinaryContent(base64: string, mime: BinaryMIME): BinaryContent {\n\treturn { base64, mime }\n}\n\n/**\n * Creates a workspace.\n *\n * @param options - Optional identity, emitter configuration, and initial files\n * @returns A working workspace\n *\n * @example\n * ```ts\n * import { createWorkspace } from '@orkestrel/workspace'\n *\n * const workspace = createWorkspace()\n * workspace.write('a.txt', 'hello')\n * ```\n */\nexport function createWorkspace(options?: WorkspaceOptions): WorkspaceInterface {\n\treturn new Workspace(options)\n}\n\n/**\n * Creates an in-memory workspace snapshot store.\n *\n * @returns A process-local workspace store\n *\n * @example\n * ```ts\n * import { createMemoryWorkspaceStore } from '@orkestrel/workspace'\n *\n * const store = createMemoryWorkspaceStore()\n * ```\n */\nexport function createMemoryWorkspaceStore(): WorkspaceStoreInterface {\n\treturn new MemoryWorkspaceStore()\n}\n\n/**\n * Creates a database-backed workspace snapshot store.\n *\n * @param driver - The database driver. Default: an in-memory driver.\n * @returns A workspace store backed by the supplied driver\n *\n * @example\n * ```ts\n * import { createDatabaseWorkspaceStore } from '@orkestrel/workspace'\n *\n * const store = createDatabaseWorkspaceStore()\n * ```\n */\nexport function createDatabaseWorkspaceStore(\n\tdriver: DriverInterface = createMemoryDriver(),\n): WorkspaceStoreInterface {\n\tconst columns = { id: stringShape(), snapshot: rawShape({}) }\n\tconst database = createDatabase({ driver, tables: { workspaces: columns } })\n\tconst table: TableInterface<WorkspaceSnapshotRow> = database.table('workspaces')\n\treturn new DatabaseWorkspaceStore(table)\n}\n\n/**\n * Creates an empty workspace registry.\n *\n * @param options - Default event hooks and optional durability\n * @returns A workspace manager\n *\n * @example\n * ```ts\n * import { createWorkspaceManager } from '@orkestrel/workspace'\n *\n * const manager = createWorkspaceManager()\n * manager.add()\n * ```\n */\nexport function createWorkspaceManager(\n\toptions?: WorkspaceManagerOptions,\n): WorkspaceManagerInterface {\n\treturn new WorkspaceManager(options)\n}\n"],"mappings":"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"}
|