@rool-dev/sdk 0.10.2-dev.47747e3 → 0.10.2-dev.550002
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 +36 -163
- package/dist/channel.d.ts +41 -129
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +233 -394
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +3 -55
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -93
- package/dist/client.js.map +1 -1
- package/dist/graphql.d.ts +4 -46
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +13 -223
- package/dist/graphql.js.map +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/path.d.ts +6 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +47 -0
- package/dist/path.js.map +1 -0
- package/dist/rest.d.ts +9 -0
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +48 -1
- package/dist/rest.js.map +1 -1
- package/dist/space.d.ts +4 -14
- package/dist/space.d.ts.map +1 -1
- package/dist/space.js +30 -50
- package/dist/space.js.map +1 -1
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +18 -26
- package/dist/subscription.js.map +1 -1
- package/dist/types.d.ts +36 -212
- package/dist/types.d.ts.map +1 -1
- package/dist/webdav.d.ts +31 -21
- package/dist/webdav.d.ts.map +1 -1
- package/dist/webdav.js +70 -58
- package/dist/webdav.js.map +1 -1
- package/package.json +2 -1
- package/dist/apps.d.ts +0 -30
- package/dist/apps.d.ts.map +0 -1
- package/dist/apps.js +0 -81
- package/dist/apps.js.map +0 -1
- package/dist/locations.d.ts +0 -34
- package/dist/locations.d.ts.map +0 -1
- package/dist/locations.js +0 -90
- package/dist/locations.js.map +0 -1
- package/dist/machine.d.ts +0 -16
- package/dist/machine.d.ts.map +0 -1
- package/dist/machine.js +0 -51
- package/dist/machine.js.map +0 -1
package/dist/channel.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from './event-emitter.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
// 6-character alphanumeric ID — used for interactionIds, conversationIds, etc.
|
|
2
|
+
import { WebDAVError } from './webdav.js';
|
|
3
|
+
import { isObjectPath, machinePath, machineUri } from './path.js';
|
|
4
|
+
// 6-character alphanumeric ID — used for object names, interactionIds, conversationIds, etc.
|
|
5
5
|
const ENTITY_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
6
|
+
const GET_OBJECTS_CHUNK_SIZE = 500;
|
|
6
7
|
export function generateEntityId() {
|
|
7
8
|
let result = '';
|
|
8
9
|
for (let i = 0; i < 6; i++) {
|
|
@@ -42,6 +43,59 @@ function findDefaultLeaf(interactions) {
|
|
|
42
43
|
}
|
|
43
44
|
return best?.id;
|
|
44
45
|
}
|
|
46
|
+
function objectPath(input) {
|
|
47
|
+
const path = machinePath(input);
|
|
48
|
+
if (!isObjectPath(path)) {
|
|
49
|
+
throw new Error(`Object path must be /space/<collection>/<name>.json without dotfiles: ${input}`);
|
|
50
|
+
}
|
|
51
|
+
return path;
|
|
52
|
+
}
|
|
53
|
+
function collectionPath(name) {
|
|
54
|
+
return machinePath(`/space/${name}`);
|
|
55
|
+
}
|
|
56
|
+
function schemaPath(name) {
|
|
57
|
+
return `${collectionPath(name)}/.schema.json`;
|
|
58
|
+
}
|
|
59
|
+
function objectFromBody(path, body) {
|
|
60
|
+
return { path, body };
|
|
61
|
+
}
|
|
62
|
+
function jsonObject(value, label) {
|
|
63
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
64
|
+
throw new Error(`${label} must be a JSON object`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function patchBody(current, patch) {
|
|
69
|
+
const next = { ...current };
|
|
70
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
71
|
+
if (value === null || value === undefined)
|
|
72
|
+
delete next[key];
|
|
73
|
+
else
|
|
74
|
+
next[key] = value;
|
|
75
|
+
}
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
78
|
+
function collectionDef(input, options) {
|
|
79
|
+
const base = Array.isArray(input)
|
|
80
|
+
? { fields: input }
|
|
81
|
+
: { fields: input.fields, schemaOrgType: input.schemaOrgType };
|
|
82
|
+
const schemaOrgType = options?.schemaOrgType ?? base.schemaOrgType;
|
|
83
|
+
return schemaOrgType ? { fields: base.fields, schemaOrgType } : { fields: base.fields };
|
|
84
|
+
}
|
|
85
|
+
function normalizeChannel(channel) {
|
|
86
|
+
for (const conversation of Object.values(channel.conversations)) {
|
|
87
|
+
const interactions = conversation.interactions;
|
|
88
|
+
const list = Array.isArray(interactions) ? interactions : Object.values(interactions);
|
|
89
|
+
for (const interaction of list) {
|
|
90
|
+
const wire = interaction;
|
|
91
|
+
if (!wire.modifiedObjectPaths && wire.modifiedObjectLocations) {
|
|
92
|
+
wire.modifiedObjectPaths = wire.modifiedObjectLocations;
|
|
93
|
+
delete wire.modifiedObjectLocations;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return channel;
|
|
98
|
+
}
|
|
45
99
|
function attachmentBody(file) {
|
|
46
100
|
if (isFile(file)) {
|
|
47
101
|
return {
|
|
@@ -115,8 +169,6 @@ function base64Body(data) {
|
|
|
115
169
|
bytes[i] = binary.charCodeAt(i);
|
|
116
170
|
return bytes.buffer;
|
|
117
171
|
}
|
|
118
|
-
// Default timeout for waiting on SSE object events (30 seconds)
|
|
119
|
-
const OBJECT_COLLECT_TIMEOUT = 30000;
|
|
120
172
|
/**
|
|
121
173
|
* A channel is a space + channelId pair.
|
|
122
174
|
*
|
|
@@ -124,10 +176,10 @@ const OBJECT_COLLECT_TIMEOUT = 30000;
|
|
|
124
176
|
* at open time and cannot be changed. To use a different channel,
|
|
125
177
|
* open a second one.
|
|
126
178
|
*
|
|
127
|
-
* Objects are addressed by
|
|
128
|
-
* Only schema, metadata,
|
|
129
|
-
*
|
|
130
|
-
*
|
|
179
|
+
* Objects are addressed by machine path (`/space/.../*.json`).
|
|
180
|
+
* Only schema, metadata, object stats, and the channel's own history are cached
|
|
181
|
+
* locally. Object bodies are fetched on demand. Object/file reactivity is
|
|
182
|
+
* exposed at the space level via WebDAV sync notifications.
|
|
131
183
|
*/
|
|
132
184
|
export class RoolChannel extends EventEmitter {
|
|
133
185
|
_id;
|
|
@@ -143,21 +195,13 @@ export class RoolChannel extends EventEmitter {
|
|
|
143
195
|
webdav;
|
|
144
196
|
onCloseCallback;
|
|
145
197
|
logger;
|
|
146
|
-
// Local cache for bounded data (schema, metadata, own channel, object
|
|
198
|
+
// Local cache for bounded data (schema, metadata, own channel, object stats)
|
|
147
199
|
_meta;
|
|
148
200
|
_schema;
|
|
149
201
|
_channel;
|
|
150
|
-
_objectLocations;
|
|
151
202
|
_objectStats;
|
|
152
203
|
// Active leaf per conversation (client-side tree cursor)
|
|
153
204
|
_activeLeaves = new Map();
|
|
154
|
-
// Object collection: tracks pending local mutations (by location) for dedup
|
|
155
|
-
// Maps location → optimistic object (for create/update) or null (for delete)
|
|
156
|
-
_pendingMutations = new Map();
|
|
157
|
-
// Resolvers waiting for object data from SSE events, keyed by location
|
|
158
|
-
_objectResolvers = new Map();
|
|
159
|
-
// Buffer for object data that arrived before a collector was registered, keyed by location
|
|
160
|
-
_objectBuffer = new Map();
|
|
161
205
|
constructor(config) {
|
|
162
206
|
super();
|
|
163
207
|
this._id = config.id;
|
|
@@ -176,8 +220,7 @@ export class RoolChannel extends EventEmitter {
|
|
|
176
220
|
// Initialize local cache from server data
|
|
177
221
|
this._meta = config.meta;
|
|
178
222
|
this._schema = config.schema;
|
|
179
|
-
this._channel = config.channel;
|
|
180
|
-
this._objectLocations = config.objectLocations;
|
|
223
|
+
this._channel = config.channel ? normalizeChannel(config.channel) : undefined;
|
|
181
224
|
this._objectStats = new Map(Object.entries(config.objectStats));
|
|
182
225
|
}
|
|
183
226
|
/**
|
|
@@ -198,10 +241,9 @@ export class RoolChannel extends EventEmitter {
|
|
|
198
241
|
return;
|
|
199
242
|
this._meta = data.meta;
|
|
200
243
|
this._schema = data.schema;
|
|
201
|
-
this._objectLocations = data.objectLocations;
|
|
202
244
|
this._objectStats = new Map(Object.entries(data.objectStats));
|
|
203
245
|
if (data.channel)
|
|
204
|
-
this._channel = data.channel;
|
|
246
|
+
this._channel = normalizeChannel(data.channel);
|
|
205
247
|
this._activeLeaves.clear();
|
|
206
248
|
this.emit('reset', { source: 'system' });
|
|
207
249
|
}
|
|
@@ -244,24 +286,6 @@ export class RoolChannel extends EventEmitter {
|
|
|
244
286
|
get isReadOnly() {
|
|
245
287
|
return this._role === 'viewer';
|
|
246
288
|
}
|
|
247
|
-
/**
|
|
248
|
-
* Get the extension URL if this channel was created via installExtension, or null.
|
|
249
|
-
*/
|
|
250
|
-
get extensionUrl() {
|
|
251
|
-
return this._channel?.extensionUrl ?? null;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Get the extension ID if this channel has an installed extension, or null.
|
|
255
|
-
*/
|
|
256
|
-
get extensionId() {
|
|
257
|
-
return this._channel?.extensionId ?? null;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Get the extension manifest if this channel has an installed extension, or null.
|
|
261
|
-
*/
|
|
262
|
-
get manifest() {
|
|
263
|
-
return this._channel?.manifest ?? null;
|
|
264
|
-
}
|
|
265
289
|
/**
|
|
266
290
|
* Get the active branch of the current conversation as a flat array (root → leaf).
|
|
267
291
|
* Walks from the active leaf up through parentId pointers.
|
|
@@ -376,10 +400,6 @@ export class RoolChannel extends EventEmitter {
|
|
|
376
400
|
close() {
|
|
377
401
|
this._closed = true;
|
|
378
402
|
this.onCloseCallback();
|
|
379
|
-
// Clean up pending object collectors
|
|
380
|
-
this._objectResolvers.clear();
|
|
381
|
-
this._objectBuffer.clear();
|
|
382
|
-
this._pendingMutations.clear();
|
|
383
403
|
this.removeAllListeners();
|
|
384
404
|
}
|
|
385
405
|
/**
|
|
@@ -413,208 +433,165 @@ export class RoolChannel extends EventEmitter {
|
|
|
413
433
|
async clearHistory() {
|
|
414
434
|
await this.graphqlClient.clearCheckpointHistory(this._id, this._channelId);
|
|
415
435
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Get an object's stat (audit information).
|
|
427
|
-
* Returns the cached stat or undefined if not known.
|
|
428
|
-
*/
|
|
429
|
-
stat(location) {
|
|
430
|
-
return this._objectStats.get(normalizeLocation(location));
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Find objects using structured filters and/or natural language.
|
|
434
|
-
*/
|
|
435
|
-
async findObjects(options) {
|
|
436
|
-
return this._findObjectsImpl(options, this._conversationId);
|
|
436
|
+
davHeaders(conversationId, interactionId) {
|
|
437
|
+
const headers = new Headers({
|
|
438
|
+
'X-Rool-Channel-Id': this._channelId,
|
|
439
|
+
'X-Rool-Conversation-Id': conversationId,
|
|
440
|
+
});
|
|
441
|
+
if (interactionId)
|
|
442
|
+
headers.set('X-Rool-Interaction-Id', interactionId);
|
|
443
|
+
return headers;
|
|
437
444
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
+
async readObject(path) {
|
|
446
|
+
const canonical = objectPath(path);
|
|
447
|
+
try {
|
|
448
|
+
const response = await this.webdav.get(canonical);
|
|
449
|
+
const body = jsonObject(await response.json(), `Object ${canonical}`);
|
|
450
|
+
return { object: objectFromBody(canonical, body), etag: response.headers.get('ETag') };
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
if (error instanceof WebDAVError && error.status === 404)
|
|
454
|
+
return undefined;
|
|
455
|
+
if (error instanceof SyntaxError)
|
|
456
|
+
throw new Error(`Object ${canonical} did not contain valid JSON`);
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
445
459
|
}
|
|
446
|
-
/**
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
460
|
+
/** Get an object JSON file by machine path. Fetches from the server on each call. */
|
|
461
|
+
async getObject(path) {
|
|
462
|
+
return (await this.readObject(path))?.object;
|
|
463
|
+
}
|
|
464
|
+
/** Get object JSON files by machine path in bulk. Duplicate paths are fetched once. */
|
|
465
|
+
async getObjects(paths) {
|
|
466
|
+
const canonical = [];
|
|
467
|
+
const seen = new Set();
|
|
468
|
+
for (const path of paths) {
|
|
469
|
+
const normalized = objectPath(path);
|
|
470
|
+
if (seen.has(normalized))
|
|
471
|
+
continue;
|
|
472
|
+
seen.add(normalized);
|
|
473
|
+
canonical.push(normalized);
|
|
454
474
|
}
|
|
455
|
-
|
|
456
|
-
|
|
475
|
+
const result = { objects: [], missing: [] };
|
|
476
|
+
for (let i = 0; i < canonical.length; i += GET_OBJECTS_CHUNK_SIZE) {
|
|
477
|
+
const chunk = canonical.slice(i, i + GET_OBJECTS_CHUNK_SIZE);
|
|
478
|
+
const partial = await this.restClient.getObjects(this._id, chunk);
|
|
479
|
+
result.objects.push(...partial.objects);
|
|
480
|
+
result.missing.push(...partial.missing);
|
|
457
481
|
}
|
|
458
|
-
return
|
|
482
|
+
return result;
|
|
459
483
|
}
|
|
460
|
-
/**
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
* @param options.ephemeral - If true, the operation won't be recorded in interaction history.
|
|
468
|
-
* @returns The created object and a status message.
|
|
469
|
-
*/
|
|
470
|
-
async createObject(collection, body, options) {
|
|
471
|
-
return this._createObjectImpl(collection, body, options, this._conversationId);
|
|
484
|
+
/** Get an object's cached audit information. */
|
|
485
|
+
stat(path) {
|
|
486
|
+
return this._objectStats.get(objectPath(path));
|
|
487
|
+
}
|
|
488
|
+
/** Create or replace an object JSON file at an exact machine path. */
|
|
489
|
+
async putObject(path, body) {
|
|
490
|
+
return this._putObjectImpl(path, body, this._conversationId);
|
|
472
491
|
}
|
|
473
492
|
/** @internal */
|
|
474
|
-
async
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const optimistic = { location, collection, basename, body };
|
|
478
|
-
this._pendingMutations.set(location, optimistic);
|
|
479
|
-
this.emit('objectCreated', { location, object: optimistic, source: 'local_user' });
|
|
493
|
+
async _putObjectImpl(path, body, conversationId) {
|
|
494
|
+
const canonical = objectPath(path);
|
|
495
|
+
const optimistic = objectFromBody(canonical, body);
|
|
480
496
|
try {
|
|
481
497
|
const interactionId = generateEntityId();
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
498
|
+
await this.webdav.put(canonical, JSON.stringify(body), {
|
|
499
|
+
contentType: 'application/json',
|
|
500
|
+
headers: this.davHeaders(conversationId, interactionId),
|
|
501
|
+
});
|
|
502
|
+
const fresh = await this.getObject(canonical) ?? optimistic;
|
|
503
|
+
return { object: fresh, message: `Put ${canonical}` };
|
|
485
504
|
}
|
|
486
505
|
catch (error) {
|
|
487
|
-
this.logger.error('[RoolChannel] Failed to
|
|
488
|
-
this._pendingMutations.delete(location);
|
|
489
|
-
this._cancelCollector(location);
|
|
506
|
+
this.logger.error('[RoolChannel] Failed to put object:', error);
|
|
490
507
|
this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
|
|
491
|
-
this.emit('reset', { source: 'system' });
|
|
492
508
|
throw error;
|
|
493
509
|
}
|
|
494
510
|
}
|
|
495
|
-
/**
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* @param location - The object's location (canonical or short form)
|
|
499
|
-
* @param options.data - Fields to add or update. Pass `null` to delete a field. Use `{{placeholder}}` for AI-generated content.
|
|
500
|
-
* @param options.prompt - AI prompt to drive the update.
|
|
501
|
-
* @param options.ephemeral - If true, the operation won't be recorded in interaction history.
|
|
502
|
-
*/
|
|
503
|
-
async updateObject(location, options) {
|
|
504
|
-
return this._updateObjectImpl(location, options, this._conversationId);
|
|
511
|
+
/** Patch an existing object. Null or undefined deletes a field. */
|
|
512
|
+
async patchObject(path, options) {
|
|
513
|
+
return this._patchObjectImpl(path, options, this._conversationId);
|
|
505
514
|
}
|
|
506
515
|
/** @internal */
|
|
507
|
-
async
|
|
508
|
-
const canonical =
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
serverPatch[key] = value === undefined ? null : value;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
// Emit optimistic event if we have data changes
|
|
519
|
-
if (data) {
|
|
520
|
-
const { collection, basename } = parseLocation(canonical);
|
|
521
|
-
const optimistic = { location: canonical, collection, basename, body: data };
|
|
522
|
-
this._pendingMutations.set(canonical, optimistic);
|
|
523
|
-
this.emit('objectUpdated', { location: canonical, object: optimistic, source: 'local_user' });
|
|
524
|
-
}
|
|
516
|
+
async _patchObjectImpl(path, options, conversationId) {
|
|
517
|
+
const canonical = objectPath(path);
|
|
518
|
+
const data = options.data ?? {};
|
|
519
|
+
const current = await this.readObject(canonical);
|
|
520
|
+
if (!current)
|
|
521
|
+
throw new Error(`Object ${canonical} not found`);
|
|
522
|
+
const body = patchBody(current.object.body, data);
|
|
523
|
+
const optimistic = objectFromBody(canonical, body);
|
|
525
524
|
try {
|
|
526
525
|
const interactionId = generateEntityId();
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
parentInteractionId: options.parentInteractionId,
|
|
526
|
+
await this.webdav.put(canonical, JSON.stringify(body), {
|
|
527
|
+
contentType: 'application/json',
|
|
528
|
+
ifMatch: current.etag ?? undefined,
|
|
529
|
+
headers: this.davHeaders(conversationId, interactionId),
|
|
532
530
|
});
|
|
533
|
-
const fresh =
|
|
534
|
-
return { object: fresh, message };
|
|
531
|
+
const fresh = await this.getObject(canonical) ?? optimistic;
|
|
532
|
+
return { object: fresh, message: `Patched ${canonical}` };
|
|
535
533
|
}
|
|
536
534
|
catch (error) {
|
|
537
|
-
this.logger.error('[RoolChannel] Failed to
|
|
538
|
-
this._pendingMutations.delete(canonical);
|
|
539
|
-
this._cancelCollector(canonical);
|
|
535
|
+
this.logger.error('[RoolChannel] Failed to patch object:', error);
|
|
540
536
|
this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
|
|
541
|
-
this.emit('reset', { source: 'system' });
|
|
542
537
|
throw error;
|
|
543
538
|
}
|
|
544
539
|
}
|
|
545
|
-
/**
|
|
546
|
-
* Move (rename or relocate) an object to a new location.
|
|
547
|
-
* Use this to rename, change collection, or atomically rewrite the body.
|
|
548
|
-
*
|
|
549
|
-
* @param from - Current location
|
|
550
|
-
* @param to - New location
|
|
551
|
-
* @param options.body - Replace the body atomically as part of the move.
|
|
552
|
-
* @param options.ephemeral - If true, the operation won't be recorded in interaction history.
|
|
553
|
-
*/
|
|
540
|
+
/** Move an object JSON file to a new machine path, optionally replacing its body. */
|
|
554
541
|
async moveObject(from, to, options) {
|
|
555
542
|
return this._moveObjectImpl(from, to, options, this._conversationId);
|
|
556
543
|
}
|
|
557
544
|
/** @internal */
|
|
558
545
|
async _moveObjectImpl(from, to, options, conversationId) {
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
const { collection, basename } = parseLocation(toLoc);
|
|
563
|
-
const optimistic = {
|
|
564
|
-
location: toLoc,
|
|
565
|
-
collection,
|
|
566
|
-
basename,
|
|
567
|
-
body: options?.body ?? {},
|
|
568
|
-
};
|
|
569
|
-
this._pendingMutations.set(toLoc, optimistic);
|
|
570
|
-
this.emit('objectMoved', { from: fromLoc, to: toLoc, object: optimistic, source: 'local_user' });
|
|
546
|
+
const fromPath = objectPath(from);
|
|
547
|
+
const toPath = objectPath(to);
|
|
548
|
+
const optimistic = objectFromBody(toPath, options?.body ?? {});
|
|
571
549
|
try {
|
|
572
550
|
const interactionId = generateEntityId();
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
ephemeral: options?.ephemeral,
|
|
576
|
-
parentInteractionId: options?.parentInteractionId,
|
|
551
|
+
await this.webdav.move(fromPath, toPath, {
|
|
552
|
+
headers: this.davHeaders(conversationId, interactionId),
|
|
577
553
|
});
|
|
578
|
-
|
|
579
|
-
|
|
554
|
+
if (options?.body) {
|
|
555
|
+
await this.webdav.put(toPath, JSON.stringify(options.body), {
|
|
556
|
+
contentType: 'application/json',
|
|
557
|
+
headers: this.davHeaders(conversationId, interactionId),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
this._objectStats.delete(fromPath);
|
|
561
|
+
const fresh = await this.getObject(toPath) ?? optimistic;
|
|
562
|
+
return { object: fresh, message: `Moved ${fromPath} to ${toPath}` };
|
|
580
563
|
}
|
|
581
564
|
catch (error) {
|
|
582
565
|
this.logger.error('[RoolChannel] Failed to move object:', error);
|
|
583
|
-
this._pendingMutations.delete(toLoc);
|
|
584
|
-
this._cancelCollector(toLoc);
|
|
585
566
|
this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
|
|
586
|
-
this.emit('reset', { source: 'system' });
|
|
587
567
|
throw error;
|
|
588
568
|
}
|
|
589
569
|
}
|
|
590
|
-
/**
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
570
|
+
/** Delete object JSON files by machine path. */
|
|
571
|
+
async deleteObjects(paths) {
|
|
572
|
+
return this._deleteObjectsImpl(paths, this._conversationId);
|
|
573
|
+
}
|
|
574
|
+
/** @deprecated Use deleteObjects instead. */
|
|
575
|
+
async deletePaths(paths) {
|
|
576
|
+
return this.deleteObjects(paths);
|
|
596
577
|
}
|
|
597
578
|
/** @internal */
|
|
598
|
-
async _deleteObjectsImpl(
|
|
599
|
-
if (
|
|
579
|
+
async _deleteObjectsImpl(paths, conversationId) {
|
|
580
|
+
if (paths.length === 0)
|
|
600
581
|
return;
|
|
601
|
-
const canonical =
|
|
602
|
-
// Track for dedup and emit optimistic events
|
|
603
|
-
for (const location of canonical) {
|
|
604
|
-
this._pendingMutations.set(location, null);
|
|
605
|
-
this.emit('objectDeleted', { location, source: 'local_user' });
|
|
606
|
-
}
|
|
582
|
+
const canonical = paths.map(objectPath);
|
|
607
583
|
try {
|
|
608
584
|
const interactionId = generateEntityId();
|
|
609
|
-
|
|
585
|
+
for (const path of canonical) {
|
|
586
|
+
await this.webdav.delete(path, {
|
|
587
|
+
headers: this.davHeaders(conversationId, interactionId),
|
|
588
|
+
});
|
|
589
|
+
this._objectStats.delete(path);
|
|
590
|
+
}
|
|
610
591
|
}
|
|
611
592
|
catch (error) {
|
|
612
|
-
this.logger.error('[RoolChannel] Failed to delete
|
|
613
|
-
for (const location of canonical) {
|
|
614
|
-
this._pendingMutations.delete(location);
|
|
615
|
-
}
|
|
593
|
+
this.logger.error('[RoolChannel] Failed to delete paths:', error);
|
|
616
594
|
this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
|
|
617
|
-
this.emit('reset', { source: 'system' });
|
|
618
595
|
throw error;
|
|
619
596
|
}
|
|
620
597
|
}
|
|
@@ -623,19 +600,25 @@ export class RoolChannel extends EventEmitter {
|
|
|
623
600
|
return this._schema;
|
|
624
601
|
}
|
|
625
602
|
/** Create a new collection schema. */
|
|
626
|
-
async createCollection(name, fields) {
|
|
627
|
-
return this._createCollectionImpl(name, fields, this._conversationId);
|
|
603
|
+
async createCollection(name, fields, options) {
|
|
604
|
+
return this._createCollectionImpl(name, fields, options, this._conversationId);
|
|
628
605
|
}
|
|
629
606
|
/** @internal */
|
|
630
|
-
async _createCollectionImpl(name, fields, conversationId) {
|
|
607
|
+
async _createCollectionImpl(name, fields, options, conversationId) {
|
|
631
608
|
if (this._schema[name]) {
|
|
632
609
|
throw new Error(`Collection "${name}" already exists`);
|
|
633
610
|
}
|
|
634
611
|
// Optimistic local update
|
|
635
|
-
const optimisticDef =
|
|
612
|
+
const optimisticDef = collectionDef(fields, options);
|
|
636
613
|
this._schema[name] = optimisticDef;
|
|
637
614
|
try {
|
|
638
|
-
|
|
615
|
+
await this.webdav.mkcol(collectionPath(name), { headers: this.davHeaders(conversationId, generateEntityId()) });
|
|
616
|
+
await this.webdav.put(schemaPath(name), JSON.stringify(optimisticDef), {
|
|
617
|
+
contentType: 'application/json',
|
|
618
|
+
headers: this.davHeaders(conversationId, generateEntityId()),
|
|
619
|
+
});
|
|
620
|
+
this.emit('schemaUpdated', { schema: this._schema, source: 'local_user' });
|
|
621
|
+
return optimisticDef;
|
|
639
622
|
}
|
|
640
623
|
catch (error) {
|
|
641
624
|
this.logger.error('[RoolChannel] Failed to create collection:', error);
|
|
@@ -644,18 +627,24 @@ export class RoolChannel extends EventEmitter {
|
|
|
644
627
|
}
|
|
645
628
|
}
|
|
646
629
|
/** Alter an existing collection schema, replacing its field definitions. */
|
|
647
|
-
async alterCollection(name, fields) {
|
|
648
|
-
return this._alterCollectionImpl(name, fields, this._conversationId);
|
|
630
|
+
async alterCollection(name, fields, options) {
|
|
631
|
+
return this._alterCollectionImpl(name, fields, options, this._conversationId);
|
|
649
632
|
}
|
|
650
633
|
/** @internal */
|
|
651
|
-
async _alterCollectionImpl(name, fields, conversationId) {
|
|
634
|
+
async _alterCollectionImpl(name, fields, options, conversationId) {
|
|
652
635
|
if (!this._schema[name]) {
|
|
653
636
|
throw new Error(`Collection "${name}" not found`);
|
|
654
637
|
}
|
|
655
638
|
const previous = this._schema[name];
|
|
656
|
-
this._schema[name] =
|
|
639
|
+
this._schema[name] = collectionDef(fields, options);
|
|
657
640
|
try {
|
|
658
|
-
|
|
641
|
+
const updated = this._schema[name];
|
|
642
|
+
await this.webdav.put(schemaPath(name), JSON.stringify(updated), {
|
|
643
|
+
contentType: 'application/json',
|
|
644
|
+
headers: this.davHeaders(conversationId, generateEntityId()),
|
|
645
|
+
});
|
|
646
|
+
this.emit('schemaUpdated', { schema: this._schema, source: 'local_user' });
|
|
647
|
+
return updated;
|
|
659
648
|
}
|
|
660
649
|
catch (error) {
|
|
661
650
|
this.logger.error('[RoolChannel] Failed to alter collection:', error);
|
|
@@ -675,7 +664,8 @@ export class RoolChannel extends EventEmitter {
|
|
|
675
664
|
const previous = this._schema[name];
|
|
676
665
|
delete this._schema[name];
|
|
677
666
|
try {
|
|
678
|
-
await this.
|
|
667
|
+
await this.webdav.delete(collectionPath(name), { collection: true, headers: this.davHeaders(conversationId, generateEntityId()) });
|
|
668
|
+
this.emit('schemaUpdated', { schema: this._schema, source: 'local_user' });
|
|
679
669
|
}
|
|
680
670
|
catch (error) {
|
|
681
671
|
this.logger.error('[RoolChannel] Failed to drop collection:', error);
|
|
@@ -816,8 +806,8 @@ export class RoolChannel extends EventEmitter {
|
|
|
816
806
|
let attachmentRefs;
|
|
817
807
|
if (attachments?.length) {
|
|
818
808
|
attachmentRefs = await Promise.all(attachments.map(async (attachment) => {
|
|
819
|
-
const
|
|
820
|
-
return
|
|
809
|
+
const path = typeof attachment === 'string' ? machinePath(attachment) : await this.uploadAttachment(attachment, conversationId);
|
|
810
|
+
return machineUri(path);
|
|
821
811
|
}));
|
|
822
812
|
}
|
|
823
813
|
// Auto-continue from active leaf if no explicit parent provided
|
|
@@ -851,25 +841,11 @@ export class RoolChannel extends EventEmitter {
|
|
|
851
841
|
if (onAbort)
|
|
852
842
|
signal.removeEventListener('abort', onAbort);
|
|
853
843
|
}
|
|
854
|
-
// Collect modified objects — they arrive via SSE events during/after the mutation.
|
|
855
844
|
const objects = [];
|
|
856
|
-
const
|
|
857
|
-
for (const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
this._objectBuffer.delete(location);
|
|
861
|
-
objects.push(buffered);
|
|
862
|
-
}
|
|
863
|
-
else {
|
|
864
|
-
missing.push(location);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
if (missing.length > 0) {
|
|
868
|
-
const fetched = await Promise.all(missing.map(location => this.graphqlClient.getObject(this._id, location)));
|
|
869
|
-
for (const obj of fetched) {
|
|
870
|
-
if (obj)
|
|
871
|
-
objects.push(obj);
|
|
872
|
-
}
|
|
845
|
+
const fetched = await Promise.all(result.modifiedObjectPaths.map((path) => this.getObject(path)));
|
|
846
|
+
for (const object of fetched) {
|
|
847
|
+
if (object)
|
|
848
|
+
objects.push(object);
|
|
873
849
|
}
|
|
874
850
|
return {
|
|
875
851
|
message: result.message,
|
|
@@ -901,70 +877,20 @@ export class RoolChannel extends EventEmitter {
|
|
|
901
877
|
return this.restClient.proxyFetch(this._id, url, init);
|
|
902
878
|
}
|
|
903
879
|
async uploadAttachment(file, conversationId) {
|
|
904
|
-
await this.ensureCollection('attachments');
|
|
905
|
-
const directory =
|
|
880
|
+
await this.ensureCollection('/rool-drive/attachments');
|
|
881
|
+
const directory = `/rool-drive/attachments/${conversationId}`;
|
|
906
882
|
await this.ensureCollection(directory);
|
|
907
883
|
const attachment = attachmentBody(file);
|
|
908
884
|
const path = `${directory}/${attachment.filename}`;
|
|
909
885
|
await this.webdav.put(path, attachment.body, { contentType: attachment.contentType });
|
|
910
|
-
|
|
911
|
-
if (!resource)
|
|
912
|
-
throw new Error('Failed to resolve uploaded attachment');
|
|
913
|
-
return resource;
|
|
886
|
+
return path;
|
|
914
887
|
}
|
|
915
888
|
async ensureCollection(path) {
|
|
916
|
-
// Note: not an object collection, a folder, which is "collection" in webdav land
|
|
917
889
|
const response = await this.webdav.request('MKCOL', path, { collection: true });
|
|
918
890
|
if (response.status === 201 || response.status === 405)
|
|
919
891
|
return;
|
|
920
892
|
throw new Error(`Failed to create collection ${path}: ${response.status} ${await response.text()}`);
|
|
921
893
|
}
|
|
922
|
-
/**
|
|
923
|
-
* Register a collector that resolves when the object arrives via SSE.
|
|
924
|
-
* @internal
|
|
925
|
-
*/
|
|
926
|
-
_collectObject(location) {
|
|
927
|
-
return new Promise((resolve, reject) => {
|
|
928
|
-
const buffered = this._objectBuffer.get(location);
|
|
929
|
-
if (buffered) {
|
|
930
|
-
this._objectBuffer.delete(location);
|
|
931
|
-
resolve(buffered);
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
const timer = setTimeout(() => {
|
|
935
|
-
this._objectResolvers.delete(location);
|
|
936
|
-
// Fallback: try to fetch from server
|
|
937
|
-
this.graphqlClient.getObject(this._id, location).then(obj => {
|
|
938
|
-
if (obj) {
|
|
939
|
-
resolve(obj);
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
reject(new Error(`Timeout waiting for object ${location} from SSE`));
|
|
943
|
-
}
|
|
944
|
-
}).catch(reject);
|
|
945
|
-
}, OBJECT_COLLECT_TIMEOUT);
|
|
946
|
-
this._objectResolvers.set(location, (obj) => {
|
|
947
|
-
clearTimeout(timer);
|
|
948
|
-
resolve(obj);
|
|
949
|
-
});
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
/** @internal */
|
|
953
|
-
_cancelCollector(location) {
|
|
954
|
-
this._objectResolvers.delete(location);
|
|
955
|
-
this._objectBuffer.delete(location);
|
|
956
|
-
}
|
|
957
|
-
/** @internal */
|
|
958
|
-
_deliverObject(location, object) {
|
|
959
|
-
const resolver = this._objectResolvers.get(location);
|
|
960
|
-
if (resolver) {
|
|
961
|
-
resolver(object);
|
|
962
|
-
this._objectResolvers.delete(location);
|
|
963
|
-
}
|
|
964
|
-
else {
|
|
965
|
-
this._objectBuffer.set(location, object);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
894
|
/**
|
|
969
895
|
* Handle a channel event from the subscription.
|
|
970
896
|
* @internal
|
|
@@ -977,34 +903,6 @@ export class RoolChannel extends EventEmitter {
|
|
|
977
903
|
case 'connected':
|
|
978
904
|
// Resync is handled by the client via _applyResyncData.
|
|
979
905
|
break;
|
|
980
|
-
case 'object_created':
|
|
981
|
-
if (event.location && event.object) {
|
|
982
|
-
if (event.objectStat)
|
|
983
|
-
this._objectStats.set(event.location, event.objectStat);
|
|
984
|
-
this._handleObjectCreated(event.location, event.object, changeSource);
|
|
985
|
-
}
|
|
986
|
-
break;
|
|
987
|
-
case 'object_updated':
|
|
988
|
-
if (event.location && event.object) {
|
|
989
|
-
if (event.objectStat)
|
|
990
|
-
this._objectStats.set(event.location, event.objectStat);
|
|
991
|
-
this._handleObjectUpdated(event.location, event.object, changeSource);
|
|
992
|
-
}
|
|
993
|
-
break;
|
|
994
|
-
case 'object_deleted':
|
|
995
|
-
if (event.location) {
|
|
996
|
-
this._objectStats.delete(event.location);
|
|
997
|
-
this._handleObjectDeleted(event.location, changeSource);
|
|
998
|
-
}
|
|
999
|
-
break;
|
|
1000
|
-
case 'object_moved':
|
|
1001
|
-
if (event.from && event.to && event.object) {
|
|
1002
|
-
this._objectStats.delete(event.from);
|
|
1003
|
-
if (event.objectStat)
|
|
1004
|
-
this._objectStats.set(event.to, event.objectStat);
|
|
1005
|
-
this._handleObjectMoved(event.from, event.to, event.object, changeSource);
|
|
1006
|
-
}
|
|
1007
|
-
break;
|
|
1008
906
|
case 'schema_updated':
|
|
1009
907
|
if (event.schema) {
|
|
1010
908
|
this._schema = event.schema;
|
|
@@ -1020,12 +918,19 @@ export class RoolChannel extends EventEmitter {
|
|
|
1020
918
|
case 'channel_updated':
|
|
1021
919
|
if (event.channelId === this._channelId && event.channel) {
|
|
1022
920
|
const changed = JSON.stringify(this._channel) !== JSON.stringify(event.channel);
|
|
1023
|
-
this._channel = event.channel;
|
|
921
|
+
this._channel = normalizeChannel(event.channel);
|
|
1024
922
|
if (changed) {
|
|
1025
923
|
this.emit('channelUpdated', { channelId: event.channelId, source: changeSource });
|
|
1026
924
|
}
|
|
1027
925
|
}
|
|
1028
926
|
break;
|
|
927
|
+
case 'channel_deleted':
|
|
928
|
+
if (event.channelId === this._channelId) {
|
|
929
|
+
this._channel = undefined;
|
|
930
|
+
this._activeLeaves.clear();
|
|
931
|
+
this.emit('reset', { source: changeSource });
|
|
932
|
+
}
|
|
933
|
+
break;
|
|
1029
934
|
case 'conversation_updated':
|
|
1030
935
|
if (event.channelId === this._channelId && event.conversationId) {
|
|
1031
936
|
if (!this._channel) {
|
|
@@ -1037,7 +942,7 @@ export class RoolChannel extends EventEmitter {
|
|
|
1037
942
|
}
|
|
1038
943
|
const prev = this._channel.conversations[event.conversationId];
|
|
1039
944
|
if (event.conversation) {
|
|
1040
|
-
this._channel.conversations[event.conversationId] = event.conversation;
|
|
945
|
+
this._channel.conversations[event.conversationId] = normalizeChannel({ createdAt: 0, createdBy: '', conversations: { [event.conversationId]: event.conversation } }).conversations[event.conversationId];
|
|
1041
946
|
}
|
|
1042
947
|
else {
|
|
1043
948
|
delete this._channel.conversations[event.conversationId];
|
|
@@ -1070,72 +975,6 @@ export class RoolChannel extends EventEmitter {
|
|
|
1070
975
|
break;
|
|
1071
976
|
}
|
|
1072
977
|
}
|
|
1073
|
-
/** @internal */
|
|
1074
|
-
_handleObjectCreated(location, object, source) {
|
|
1075
|
-
this._deliverObject(location, object);
|
|
1076
|
-
// Maintain local location list — prepend (most recently modified first)
|
|
1077
|
-
this._objectLocations = [location, ...this._objectLocations.filter(l => l !== location)];
|
|
1078
|
-
const pending = this._pendingMutations.get(location);
|
|
1079
|
-
if (pending !== undefined) {
|
|
1080
|
-
this._pendingMutations.delete(location);
|
|
1081
|
-
if (pending !== null) {
|
|
1082
|
-
// Already emitted objectCreated optimistically.
|
|
1083
|
-
// Emit objectUpdated only if AI resolved placeholders (data changed).
|
|
1084
|
-
if (JSON.stringify(pending) !== JSON.stringify(object)) {
|
|
1085
|
-
this.emit('objectUpdated', { location, object, source });
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
else {
|
|
1090
|
-
this.emit('objectCreated', { location, object, source });
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
/** @internal */
|
|
1094
|
-
_handleObjectUpdated(location, object, source) {
|
|
1095
|
-
this._deliverObject(location, object);
|
|
1096
|
-
this._objectLocations = [location, ...this._objectLocations.filter(l => l !== location)];
|
|
1097
|
-
const pending = this._pendingMutations.get(location);
|
|
1098
|
-
if (pending !== undefined) {
|
|
1099
|
-
this._pendingMutations.delete(location);
|
|
1100
|
-
if (pending !== null) {
|
|
1101
|
-
if (JSON.stringify(pending) !== JSON.stringify(object)) {
|
|
1102
|
-
this.emit('objectUpdated', { location, object, source });
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
else {
|
|
1107
|
-
this.emit('objectUpdated', { location, object, source });
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
/** @internal */
|
|
1111
|
-
_handleObjectDeleted(location, source) {
|
|
1112
|
-
this._objectLocations = this._objectLocations.filter(l => l !== location);
|
|
1113
|
-
const pending = this._pendingMutations.get(location);
|
|
1114
|
-
if (pending !== undefined) {
|
|
1115
|
-
this._pendingMutations.delete(location);
|
|
1116
|
-
}
|
|
1117
|
-
else {
|
|
1118
|
-
this.emit('objectDeleted', { location, source });
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
/** @internal */
|
|
1122
|
-
_handleObjectMoved(from, to, object, source) {
|
|
1123
|
-
this._deliverObject(to, object);
|
|
1124
|
-
// Drop old location, insert new one at the front.
|
|
1125
|
-
this._objectLocations = [to, ...this._objectLocations.filter(l => l !== from && l !== to)];
|
|
1126
|
-
const pending = this._pendingMutations.get(to);
|
|
1127
|
-
if (pending !== undefined) {
|
|
1128
|
-
this._pendingMutations.delete(to);
|
|
1129
|
-
if (pending !== null) {
|
|
1130
|
-
if (JSON.stringify(pending) !== JSON.stringify(object)) {
|
|
1131
|
-
this.emit('objectUpdated', { location: to, object, source });
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
else {
|
|
1136
|
-
this.emit('objectMoved', { from, to, object, source });
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
978
|
}
|
|
1140
979
|
/**
|
|
1141
980
|
* A lightweight handle for a specific conversation within a channel.
|
|
@@ -1179,37 +1018,37 @@ export class ConversationHandle {
|
|
|
1179
1018
|
async rename(name) {
|
|
1180
1019
|
return this._channel._renameConversationImpl(name, this._conversationId);
|
|
1181
1020
|
}
|
|
1182
|
-
/**
|
|
1183
|
-
async
|
|
1184
|
-
return this._channel.
|
|
1185
|
-
}
|
|
1186
|
-
/** Create a new object. */
|
|
1187
|
-
async createObject(collection, body, options) {
|
|
1188
|
-
return this._channel._createObjectImpl(collection, body, options, this._conversationId);
|
|
1021
|
+
/** Create or replace an object JSON file. */
|
|
1022
|
+
async putObject(path, body) {
|
|
1023
|
+
return this._channel._putObjectImpl(path, body, this._conversationId);
|
|
1189
1024
|
}
|
|
1190
|
-
/**
|
|
1191
|
-
async
|
|
1192
|
-
return this._channel.
|
|
1025
|
+
/** Patch an existing object JSON file. */
|
|
1026
|
+
async patchObject(path, options) {
|
|
1027
|
+
return this._channel._patchObjectImpl(path, options, this._conversationId);
|
|
1193
1028
|
}
|
|
1194
1029
|
/** Move (rename/relocate) an object. */
|
|
1195
1030
|
async moveObject(from, to, options) {
|
|
1196
1031
|
return this._channel._moveObjectImpl(from, to, options, this._conversationId);
|
|
1197
1032
|
}
|
|
1198
|
-
/** Delete
|
|
1199
|
-
async deleteObjects(
|
|
1200
|
-
return this._channel._deleteObjectsImpl(
|
|
1033
|
+
/** Delete object JSON files by path. */
|
|
1034
|
+
async deleteObjects(paths) {
|
|
1035
|
+
return this._channel._deleteObjectsImpl(paths, this._conversationId);
|
|
1036
|
+
}
|
|
1037
|
+
/** @deprecated Use deleteObjects instead. */
|
|
1038
|
+
async deletePaths(paths) {
|
|
1039
|
+
return this.deleteObjects(paths);
|
|
1201
1040
|
}
|
|
1202
1041
|
/** Send a prompt to the AI agent, scoped to this conversation's history. */
|
|
1203
1042
|
async prompt(text, options) {
|
|
1204
1043
|
return this._channel._promptImpl(text, options, this._conversationId);
|
|
1205
1044
|
}
|
|
1206
1045
|
/** Create a new collection schema. */
|
|
1207
|
-
async createCollection(name, fields) {
|
|
1208
|
-
return this._channel._createCollectionImpl(name, fields, this._conversationId);
|
|
1046
|
+
async createCollection(name, fields, options) {
|
|
1047
|
+
return this._channel._createCollectionImpl(name, fields, options, this._conversationId);
|
|
1209
1048
|
}
|
|
1210
1049
|
/** Alter an existing collection schema. */
|
|
1211
|
-
async alterCollection(name, fields) {
|
|
1212
|
-
return this._channel._alterCollectionImpl(name, fields, this._conversationId);
|
|
1050
|
+
async alterCollection(name, fields, options) {
|
|
1051
|
+
return this._channel._alterCollectionImpl(name, fields, options, this._conversationId);
|
|
1213
1052
|
}
|
|
1214
1053
|
/** Drop a collection schema. */
|
|
1215
1054
|
async dropCollection(name) {
|