xmemory 2.1.2 → 2.3.0

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 CHANGED
@@ -152,7 +152,34 @@ const result = await inst.read("Who is on the team?");
152
152
  console.log(result.reader_result);
153
153
  ```
154
154
 
155
- Options: `{ readMode?, traceId?, timeoutMs? }` — `readMode` defaults to `"single-answer"`.
155
+ Options: `{ readMode?, scope?, traceId?, timeoutMs? }` — `readMode` defaults to `"single-answer"`.
156
+
157
+ #### Scoped reads
158
+
159
+ By default a read may draw on the whole instance. Pass a `scope` to restrict it
160
+ to a set of concrete objects — useful for grounding an answer in exactly the
161
+ records you care about, or for keeping a per-user / per-entity read from leaking
162
+ into unrelated data.
163
+
164
+ Each object in the scope is identified by its `type` (the PascalCase class name
165
+ or snake_case table name) plus its user-defined primary `key` (a mapping of
166
+ primary-key field name to value):
167
+
168
+ ```typescript
169
+ const result = await inst.read("What do we know about these people?", {
170
+ scope: {
171
+ objects: [
172
+ { type: "Person", key: { full_name: "Alice Smith" } },
173
+ { type: "Person", key: { full_name: "Bob Jones" } },
174
+ ],
175
+ relationsScope: "all_relations", // default: "no_relations"
176
+ },
177
+ });
178
+ ```
179
+
180
+ `relationsScope` controls relation traversal: `"no_relations"` (the default)
181
+ restricts the read to the listed objects only, while `"all_relations"` also
182
+ exposes the relations among the in-scope objects.
156
183
 
157
184
  ### `inst.extract(text, options?)` → `ExtractResult`
158
185
 
@@ -183,6 +210,109 @@ const tools = desc.asOpenaiTools(); // OpenAI function-calling format
183
210
 
184
211
  Results are cached for 5 minutes. Call `inst.clearDescribeCache()` to force a refresh.
185
212
 
213
+ ## Schema evolution
214
+
215
+ Schemas can change after creation. xmemory supports **safe, data-preserving
216
+ migrations** (rename / remove / type change) driven by structured migration
217
+ ops, plus a **suggestion engine** that proposes improvements from real read
218
+ traffic. This is purely additive — existing methods are unchanged.
219
+
220
+ See the [Schema evolution section of the API reference](https://xmemory.ai/api/#schema-evolution)
221
+ for the conceptual model, and the [TypeScript guide](https://xmemory.ai/typescript/)
222
+ for full walkthroughs.
223
+
224
+ ### Suggestion-engine flow (review → decide → apply)
225
+
226
+ The engine surfaces a single rolling proposal per instance. The minimum flow is
227
+ three calls — review, decide (in bulk), apply:
228
+
229
+ ```typescript
230
+ import { XmemoryClient, type DecisionInput } from "xmemory";
231
+
232
+ const xm = new XmemoryClient({ apiKey: "..." });
233
+ const inst = xm.instance("<instance-id>");
234
+
235
+ // 1. Review — get the proposal + its concurrency token.
236
+ const review = await inst.reviewSuggestions();
237
+ if (review.status === "evolution_in_progress") {
238
+ console.log(`A migration is in flight; retry in ${review.retry_after_seconds}s`);
239
+ } else if (review.proposal) {
240
+ const proposal = review.proposal;
241
+ for (const item of proposal.items) {
242
+ console.log(item.item_fingerprint, item.rationale, item.op);
243
+ }
244
+
245
+ // 2. Decide — accept / reject / defer per item, in one batch.
246
+ const decisions: DecisionInput[] = proposal.items.map((item) => ({
247
+ item_fingerprint: item.item_fingerprint,
248
+ decision: "accept",
249
+ }));
250
+ const decided = await inst.decideSuggestions(proposal.proposal_version, decisions);
251
+
252
+ // 3. Apply — commit accepted decisions as one migration.
253
+ const applied = await inst.applyPendingDecisions(decided.next_proposal_version);
254
+ console.log(applied.status, applied.summary); // e.g. "ok" "added 1 field"
255
+ }
256
+ ```
257
+
258
+ When `status === "evolution_in_progress"`, back off for `retry_after_seconds`
259
+ and retry instead of blocking.
260
+
261
+ ### Direct migration flow (enhance → dry-run → update)
262
+
263
+ Drive a migration yourself — ask the server to *enhance* the current schema,
264
+ preview the DDL, then apply it:
265
+
266
+ ```typescript
267
+ import { XmemoryClient, SchemaType } from "xmemory";
268
+ import yaml from "js-yaml";
269
+
270
+ const xm = new XmemoryClient({ apiKey: "..." });
271
+ const current = (await xm.admin.getInstanceSchema("<instance-id>")).data_schema;
272
+
273
+ // 1. Enhance — new schema + an executor-ready migration plan.
274
+ const enhanced = await xm.admin.enhanceSchema(
275
+ "<cluster-id>",
276
+ "Rename Person.mail to Person.email.",
277
+ yaml.dump(current),
278
+ );
279
+ console.log(enhanced.summary, enhanced.migration_plan?.ops);
280
+
281
+ const newYaml = yaml.dump(enhanced.data_schema);
282
+
283
+ // 2. Dry-run — preview the DDL without applying anything.
284
+ const preview = await xm.admin.dryRunMigration("<instance-id>", newYaml, SchemaType.YML, {
285
+ migrationPlan: enhanced.migration_plan ?? undefined,
286
+ });
287
+ console.log(preview.statements);
288
+
289
+ // 3. Update — apply. confirmDestructive is required for ops that drop data.
290
+ const info = await xm.admin.updateInstanceSchema("<instance-id>", newYaml, SchemaType.YML, {
291
+ migrationPlan: enhanced.migration_plan ?? undefined,
292
+ confirmDestructive: false,
293
+ });
294
+ console.log(info.migration_id, info.prior_version, "->", info.new_version);
295
+ ```
296
+
297
+ ### Migration history
298
+
299
+ ```typescript
300
+ const page = await xm.admin.listMigrations("<instance-id>", { limit: 20 });
301
+ for (const record of page.items) {
302
+ console.log(record.id, record.source, record.prior_version, "->", record.new_version);
303
+ }
304
+
305
+ const detail = await xm.admin.getMigration("<instance-id>", "<migration-id>", { includeYaml: true });
306
+ console.log(detail.yaml_before, detail.yaml_after);
307
+ ```
308
+
309
+ Migration ops are exported as discriminated-union types (`MigrationPlan`,
310
+ `MigrationOp`, `AddField`, `RenameField`, `RemoveObject`, …) keyed on `op_type`.
311
+ `ProposalItem.op` and `MigrationRecord.ops` are raw dicts for forward
312
+ compatibility — narrow them to `MigrationOp` when needed.
313
+
314
+ Runnable end-to-end examples live in [`examples/`](examples/).
315
+
186
316
  ## Error handling
187
317
 
188
318
  All errors throw `XmemoryAPIError`. Health check failures throw `XmemoryHealthCheckError` (a subclass).
@@ -207,6 +337,23 @@ try {
207
337
  }
208
338
  ```
209
339
 
340
+ `XmemoryAPIError` carries `status` (HTTP status), `code` (structured error code,
341
+ when the server returned one), and `details`. The schema-evolution endpoints
342
+ return codes you can pattern match on via `.code` — for example
343
+ `stale_proposal_version`, `dependency_closure_failed`,
344
+ `destructive_confirmation_required`, `non_additive_change_requires_plan`,
345
+ `stale_schema_version`, `migration_not_found`, `instance_not_initialised`:
346
+
347
+ ```typescript
348
+ try {
349
+ await inst.applyPendingDecisions(token);
350
+ } catch (e) {
351
+ if (e instanceof XmemoryAPIError && e.code === "stale_proposal_version") {
352
+ const review = await inst.reviewSuggestions(); // re-review and retry
353
+ }
354
+ }
355
+ ```
356
+
210
357
  ## All timeouts are per-request
211
358
 
212
359
  Every method accepts an optional `timeoutMs` in its options bag, overriding the client default.
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * XmemoryClient — main entry point for the xmemory API.
3
3
  */
4
4
  import { InstanceHandle } from "./instance.js";
5
- import { type ClusterInfo, type CreateInstanceOptions, type GenerateSchemaOptions, type GenerateSchemaResult, type InstanceInfo, type InstanceSchemaInfo, type RequestOptions, type SchemaTypeValue, type XmemoryClientOptions } from "./types.js";
5
+ import { type ClusterInfo, type CreateInstanceOptions, type DryRunMigrationOptions, type DryRunResult, type EnhanceSchemaResult, type GenerateSchemaOptions, type GenerateSchemaResult, type GetMigrationOptions, type InstanceInfo, type InstanceSchemaInfo, type ListMigrationsOptions, type ListMigrationsResult, type MigrationRecord, type RequestOptions, type SchemaTypeValue, type UpdateInstanceSchemaOptions, type XmemoryClientOptions } from "./types.js";
6
6
  export interface AdminNamespace {
7
7
  listClusters(options?: RequestOptions & {
8
8
  ids?: string[];
@@ -15,9 +15,13 @@ export interface AdminNamespace {
15
15
  getInstance(instanceId: string, options?: RequestOptions): Promise<InstanceInfo>;
16
16
  deleteInstance(instanceId: string, options?: RequestOptions): Promise<string[]>;
17
17
  getInstanceSchema(instanceId: string, options?: RequestOptions): Promise<InstanceSchemaInfo>;
18
- updateInstanceSchema(instanceId: string, schemaText: string, schemaType: SchemaTypeValue, options?: RequestOptions): Promise<InstanceInfo>;
18
+ updateInstanceSchema(instanceId: string, schemaText: string, schemaType: SchemaTypeValue, options?: UpdateInstanceSchemaOptions): Promise<InstanceInfo>;
19
19
  updateInstanceMetadata(instanceId: string, name: string, description: string, options?: RequestOptions): Promise<InstanceInfo>;
20
20
  generateSchema(clusterId: string, schemaDescription: string, options?: GenerateSchemaOptions): Promise<GenerateSchemaResult>;
21
+ enhanceSchema(clusterId: string, schemaDescription: string, currentYmlSchema: string, options?: RequestOptions): Promise<EnhanceSchemaResult>;
22
+ dryRunMigration(instanceId: string, schemaText: string, schemaType: SchemaTypeValue, options?: DryRunMigrationOptions): Promise<DryRunResult>;
23
+ listMigrations(instanceId: string, options?: ListMigrationsOptions): Promise<ListMigrationsResult>;
24
+ getMigration(instanceId: string, migrationId: string, options?: GetMigrationOptions): Promise<MigrationRecord>;
21
25
  }
22
26
  export declare class XmemoryClient {
23
27
  private readonly _baseUrl;
package/dist/client.js CHANGED
@@ -10,6 +10,33 @@ const RESET = "\x1b[0m";
10
10
  function deprecationWarning(message) {
11
11
  console.warn(`${ORANGE}[xmemory] DEPRECATION: ${message}${RESET}`);
12
12
  }
13
+ /**
14
+ * Pull a structured error out of an error body. Handles the schema-evolution
15
+ * shape (`{status: "error", error_type, error_message, details}`, where
16
+ * `error_type` is the code) and the standard `{errors: [{code, message}]}`
17
+ * envelope.
18
+ */
19
+ function extractStructuredError(payload) {
20
+ if (typeof payload !== "object" || payload === null)
21
+ return {};
22
+ const p = payload;
23
+ if (p.status === "error" && typeof p.error_type === "string") {
24
+ return {
25
+ code: p.error_type,
26
+ message: typeof p.error_message === "string" ? p.error_message : undefined,
27
+ details: (p.details ?? null),
28
+ };
29
+ }
30
+ const errors = p.errors;
31
+ if (Array.isArray(errors) && errors.length > 0 && typeof errors[0] === "object" && errors[0] !== null) {
32
+ const first = errors[0];
33
+ return {
34
+ code: typeof first.code === "string" ? first.code : undefined,
35
+ message: typeof first.message === "string" ? first.message : undefined,
36
+ };
37
+ }
38
+ return {};
39
+ }
13
40
  // ---------------------------------------------------------------------------
14
41
  // Client
15
42
  // ---------------------------------------------------------------------------
@@ -122,15 +149,17 @@ export class XmemoryClient {
122
149
  throw new XmemoryAPIError(`Invalid JSON from server (${res.status}): ${raw.slice(0, 200)}`, res.status);
123
150
  }
124
151
  if (!res.ok) {
125
- const msg = typeof payload === "object" && payload !== null && "message" in payload
126
- ? String(payload.message)
127
- : raw.slice(0, 200);
128
- throw new XmemoryAPIError(`HTTP ${res.status}: ${msg}`, res.status);
152
+ const structured = extractStructuredError(payload);
153
+ const msg = structured.message ??
154
+ (typeof payload === "object" && payload !== null && "message" in payload
155
+ ? String(payload.message)
156
+ : raw.slice(0, 200));
157
+ throw new XmemoryAPIError(`HTTP ${res.status}: ${msg}`, res.status, structured.code, structured.details);
129
158
  }
130
159
  const response = payload;
131
160
  if (response.errors?.length) {
132
161
  const first = response.errors[0];
133
- throw new XmemoryAPIError(`API error: ${first.message} (${first.code})`, res.status);
162
+ throw new XmemoryAPIError(`API error: ${first.message} (${first.code})`, res.status, first.code);
134
163
  }
135
164
  return response;
136
165
  }
@@ -202,8 +231,15 @@ export class XmemoryClient {
202
231
  });
203
232
  },
204
233
  updateInstanceSchema: async (instanceId, schemaText, schemaType, options) => {
234
+ const body = {
235
+ instance_schema: buildInstanceSchema(schemaText, schemaType),
236
+ };
237
+ if (options?.migrationPlan != null)
238
+ body.migration_plan = options.migrationPlan;
239
+ if (options?.confirmDestructive != null)
240
+ body.confirm_destructive = options.confirmDestructive;
205
241
  return this._requestOne("PUT", `/instances/${instanceId}/schema`, {
206
- body: { instance_schema: buildInstanceSchema(schemaText, schemaType) },
242
+ body,
207
243
  timeoutMs: options?.timeoutMs,
208
244
  });
209
245
  },
@@ -219,6 +255,41 @@ export class XmemoryClient {
219
255
  body.current_yml_schema = options.currentYmlSchema;
220
256
  return this._requestOne("POST", `/clusters/${clusterId}/instances/generate_schema`, { body, timeoutMs: options?.timeoutMs });
221
257
  },
258
+ enhanceSchema: async (clusterId, schemaDescription, currentYmlSchema, options) => {
259
+ return this._requestOne("POST", `/clusters/${clusterId}/instances/generate_schema`, {
260
+ body: { schema_description: schemaDescription, current_yml_schema: currentYmlSchema },
261
+ timeoutMs: options?.timeoutMs,
262
+ });
263
+ },
264
+ dryRunMigration: async (instanceId, schemaText, schemaType, options) => {
265
+ const body = {
266
+ instance_schema: buildInstanceSchema(schemaText, schemaType),
267
+ };
268
+ if (options?.migrationPlan != null)
269
+ body.migration_plan = options.migrationPlan;
270
+ if (options?.confirmDestructive != null)
271
+ body.confirm_destructive = options.confirmDestructive;
272
+ return this._requestOne("POST", `/instances/${instanceId}/migrations/dry_run`, {
273
+ body,
274
+ timeoutMs: options?.timeoutMs,
275
+ });
276
+ },
277
+ listMigrations: async (instanceId, options) => {
278
+ const params = {
279
+ limit: String(options?.limit ?? 50),
280
+ include_yaml: String(options?.includeYaml ?? false),
281
+ };
282
+ if (options?.beforeId != null)
283
+ params.before_id = options.beforeId;
284
+ return this._requestOne("GET", `/instances/${instanceId}/migrations`, {
285
+ params,
286
+ timeoutMs: options?.timeoutMs,
287
+ });
288
+ },
289
+ getMigration: async (instanceId, migrationId, options) => {
290
+ const result = await this._requestOne("GET", `/instances/${instanceId}/migrations/${migrationId}`, { params: { include_yaml: String(options?.includeYaml ?? false) }, timeoutMs: options?.timeoutMs });
291
+ return result.record;
292
+ },
222
293
  };
223
294
  }
224
295
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,9 @@ export type { AdminNamespace } from "./client.js";
6
6
  export { DescribeResult, InstanceHandle } from "./instance.js";
7
7
  export { XmemoryAPIError, XmemoryHealthCheckError } from "./types.js";
8
8
  export { SchemaType } from "./types.js";
9
- export type { SchemaTypeValue, ExtractionLogic, ReadMode, WriteQueueStatus } from "./types.js";
10
- export type { XmemoryClientOptions, RequestOptions, ReadOptions, WriteOptions, ExtractOptions, CreateInstanceOptions, GenerateSchemaOptions, } from "./types.js";
9
+ export type { SchemaTypeValue, ExtractionLogic, ReadMode, WriteQueueStatus, FieldType, OnDelete, CastStrategy, DecisionKind, MigrationSource, } from "./types.js";
10
+ export type { ScopeObject, ReadScope, RelationsScope } from "./types.js";
11
+ export type { XmemoryClientOptions, RequestOptions, ReadOptions, WriteOptions, ExtractOptions, CreateInstanceOptions, GenerateSchemaOptions, UpdateInstanceSchemaOptions, DryRunMigrationOptions, ListMigrationsOptions, GetMigrationOptions, SuggestionRequestOptions, } from "./types.js";
11
12
  export type { ClusterInfo, InstanceInfo, InstanceSchemaInfo, ReadResult, WriteResult, AsyncWriteResult, WriteStatusResult, ExtractResult, GenerateSchemaResult, ToolDescription, ToolParameterDescription, RawDescribeResult, } from "./types.js";
13
+ export type { MigrationPlan, MigrationOp, FieldSpec, AddObject, RemoveObject, RenameObject, ChangeObject, AddField, RemoveField, RenameField, ChangeField, AddRelation, RemoveRelation, RenameRelation, ChangeRelation, DefaultValue, EnumValues, } from "./types.js";
14
+ export type { EnhanceSchemaResult, PlanSummary, DryRunResult, MigrationRecord, ListMigrationsResult, GetMigrationResult, ConsolidatedProposal, ProposalItem, ReviewSuggestionsResult, DecisionInput, RecordedDecision, DependencyWarning, DecideSuggestionsResult, ApplyPendingDecisionsResult, } from "./types.js";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * InstanceHandle — scoped data operations on a single xmemory instance.
3
3
  */
4
- import type { AsyncWriteResult, ExtractOptions, ExtractResult, InstanceSchemaInfo, RawDescribeResult, ReadOptions, ReadResult, RequestOneFn, RequestOptions, ToolDescription, WriteOptions, WriteResult, WriteStatusResult } from "./types.js";
4
+ import type { ApplyPendingDecisionsResult, AsyncWriteResult, DecideSuggestionsResult, DecisionInput, ExtractOptions, ExtractResult, InstanceSchemaInfo, RawDescribeResult, ReadOptions, ReadResult, RequestOneFn, RequestOptions, ReviewSuggestionsResult, SuggestionRequestOptions, ToolDescription, WriteOptions, WriteResult, WriteStatusResult } from "./types.js";
5
5
  export declare class DescribeResult {
6
6
  readonly instanceId: string;
7
7
  readonly instanceName: string;
@@ -36,6 +36,32 @@ export declare class InstanceHandle {
36
36
  writeStatus(writeId: string, options?: RequestOptions): Promise<WriteStatusResult>;
37
37
  extract(text: string, options?: ExtractOptions): Promise<ExtractResult>;
38
38
  getSchema(options?: RequestOptions): Promise<InstanceSchemaInfo>;
39
+ /**
40
+ * Return the current consolidated schema-improvement proposal (step 1).
41
+ *
42
+ * The result's `proposal.proposal_version` is the optimistic-concurrency
43
+ * token you pass to `decideSuggestions` / `applyPendingDecisions`. When
44
+ * `status === "evolution_in_progress"`, back off for `retry_after_seconds`
45
+ * and retry instead of blocking on the in-flight migration.
46
+ */
47
+ reviewSuggestions(options?: SuggestionRequestOptions): Promise<ReviewSuggestionsResult>;
48
+ /**
49
+ * Record accept/reject/defer decisions for proposal items, in bulk (step 2).
50
+ *
51
+ * `proposalVersion` must be the token from the latest `reviewSuggestions`; a
52
+ * stale token throws `XmemoryAPIError` with `code === "stale_proposal_version"`.
53
+ * The result's `next_proposal_version` can be passed straight to
54
+ * `applyPendingDecisions`.
55
+ */
56
+ decideSuggestions(proposalVersion: string, decisions: DecisionInput[], options?: SuggestionRequestOptions): Promise<DecideSuggestionsResult>;
57
+ /**
58
+ * Commit accepted decisions as a single migration (step 3).
59
+ *
60
+ * `status === "nothing_to_apply"` means no accepted items were left. A stale
61
+ * `proposalVersion` or an unmet dependency throws `XmemoryAPIError`
62
+ * (`code === "stale_proposal_version"` / `"dependency_closure_failed"`).
63
+ */
64
+ applyPendingDecisions(proposalVersion: string, options?: SuggestionRequestOptions): Promise<ApplyPendingDecisionsResult>;
39
65
  /**
40
66
  * Return agent-facing tool descriptions enriched with the instance schema.
41
67
  *
package/dist/instance.js CHANGED
@@ -109,6 +109,17 @@ export class InstanceHandle {
109
109
  query,
110
110
  mode: options?.readMode ?? "single-answer",
111
111
  };
112
+ if (options?.scope != null) {
113
+ // Serialize to the API's identity wire shape: each object is
114
+ // `{type, key: {key: {...}}}` (by user-defined primary key), plus `relations_scope`.
115
+ body.scope = {
116
+ objects: options.scope.objects.map((o) => ({
117
+ type: o.type,
118
+ key: { key: o.key },
119
+ })),
120
+ relations_scope: options.scope.relationsScope ?? "no_relations",
121
+ };
122
+ }
112
123
  if (options?.traceId != null)
113
124
  body.trace_id = options.traceId;
114
125
  return this._requestOne("POST", `/instances/${this.id}/read`, {
@@ -161,6 +172,57 @@ export class InstanceHandle {
161
172
  timeoutMs: options?.timeoutMs,
162
173
  });
163
174
  }
175
+ // -- Schema evolution (suggestion engine) ---------------------------------
176
+ /**
177
+ * Return the current consolidated schema-improvement proposal (step 1).
178
+ *
179
+ * The result's `proposal.proposal_version` is the optimistic-concurrency
180
+ * token you pass to `decideSuggestions` / `applyPendingDecisions`. When
181
+ * `status === "evolution_in_progress"`, back off for `retry_after_seconds`
182
+ * and retry instead of blocking on the in-flight migration.
183
+ */
184
+ async reviewSuggestions(options) {
185
+ const body = {};
186
+ if (options?.sessionId != null)
187
+ body.session_id = options.sessionId;
188
+ return this._requestOne("POST", `/instances/${this.id}/suggestions/review`, {
189
+ body,
190
+ timeoutMs: options?.timeoutMs,
191
+ });
192
+ }
193
+ /**
194
+ * Record accept/reject/defer decisions for proposal items, in bulk (step 2).
195
+ *
196
+ * `proposalVersion` must be the token from the latest `reviewSuggestions`; a
197
+ * stale token throws `XmemoryAPIError` with `code === "stale_proposal_version"`.
198
+ * The result's `next_proposal_version` can be passed straight to
199
+ * `applyPendingDecisions`.
200
+ */
201
+ async decideSuggestions(proposalVersion, decisions, options) {
202
+ const body = { proposal_version: proposalVersion, decisions };
203
+ if (options?.sessionId != null)
204
+ body.session_id = options.sessionId;
205
+ return this._requestOne("POST", `/instances/${this.id}/suggestions/decide`, {
206
+ body,
207
+ timeoutMs: options?.timeoutMs,
208
+ });
209
+ }
210
+ /**
211
+ * Commit accepted decisions as a single migration (step 3).
212
+ *
213
+ * `status === "nothing_to_apply"` means no accepted items were left. A stale
214
+ * `proposalVersion` or an unmet dependency throws `XmemoryAPIError`
215
+ * (`code === "stale_proposal_version"` / `"dependency_closure_failed"`).
216
+ */
217
+ async applyPendingDecisions(proposalVersion, options) {
218
+ const body = { proposal_version: proposalVersion };
219
+ if (options?.sessionId != null)
220
+ body.session_id = options.sessionId;
221
+ return this._requestOne("POST", `/instances/${this.id}/suggestions/apply`, {
222
+ body,
223
+ timeoutMs: options?.timeoutMs,
224
+ });
225
+ }
164
226
  /**
165
227
  * Return agent-facing tool descriptions enriched with the instance schema.
166
228
  *
package/dist/types.d.ts CHANGED
@@ -8,10 +8,48 @@ export declare const SchemaType: {
8
8
  export type SchemaTypeValue = (typeof SchemaType)[keyof typeof SchemaType];
9
9
  export type ExtractionLogic = "fast" | "regular" | "deep";
10
10
  export type ReadMode = "single-answer" | "raw-tables" | "xresponse";
11
- export type WriteQueueStatus = "queued" | "processing" | "completed" | "failed" | "not_found";
11
+ /**
12
+ * One concrete object a scoped read may touch. Identify it by `type` (PascalCase
13
+ * class name or snake_case table name) plus its user-defined primary `key`
14
+ * (a mapping of primary-key field name to value).
15
+ */
16
+ export interface ScopeObject {
17
+ type: string;
18
+ key: Record<string, string | number | boolean>;
19
+ }
20
+ /** Which relations a scoped read may traverse. */
21
+ export type RelationsScope = "no_relations" | "all_relations";
22
+ /**
23
+ * A read's scope: the concrete `objects` it may touch, plus relation policy.
24
+ * `relationsScope` is `no_relations` (objects only) by default; `all_relations`
25
+ * also exposes the relations among the in-scope `objects`.
26
+ */
27
+ export interface ReadScope {
28
+ objects: ScopeObject[];
29
+ relationsScope?: RelationsScope;
30
+ }
31
+ export type WriteQueueStatus = "queued" | "processing" | "extracting" | "extracted" | "applying" | "completed" | "failed" | "not_found";
12
32
  export declare class XmemoryAPIError extends Error {
13
33
  readonly status?: number | undefined;
14
- constructor(message: string, status?: number | undefined);
34
+ /**
35
+ * Structured error code when the server returned one. For the
36
+ * schema-evolution endpoints this is the `error_type` discriminator
37
+ * (e.g. `"stale_proposal_version"`, `"destructive_confirmation_required"`).
38
+ * Pattern match on this instead of parsing the message.
39
+ */
40
+ readonly code?: string | undefined;
41
+ /** Optional structured `details` payload attached to some errors. */
42
+ readonly details?: (Record<string, unknown> | null) | undefined;
43
+ constructor(message: string, status?: number | undefined,
44
+ /**
45
+ * Structured error code when the server returned one. For the
46
+ * schema-evolution endpoints this is the `error_type` discriminator
47
+ * (e.g. `"stale_proposal_version"`, `"destructive_confirmation_required"`).
48
+ * Pattern match on this instead of parsing the message.
49
+ */
50
+ code?: string | undefined,
51
+ /** Optional structured `details` payload attached to some errors. */
52
+ details?: (Record<string, unknown> | null) | undefined);
15
53
  }
16
54
  export declare class XmemoryHealthCheckError extends XmemoryAPIError {
17
55
  constructor(message: string, status?: number);
@@ -28,6 +66,10 @@ export interface InstanceInfo {
28
66
  readonly name: string;
29
67
  readonly description: string | null;
30
68
  readonly data_schema: Record<string, unknown> | null;
69
+ readonly migration_id?: string | null;
70
+ readonly prior_version?: number | null;
71
+ readonly new_version?: number | null;
72
+ readonly migration_warnings?: string[] | null;
31
73
  }
32
74
  export interface InstanceSchemaInfo {
33
75
  readonly data_schema: Record<string, unknown>;
@@ -40,7 +82,6 @@ export interface WriteResult {
40
82
  readonly write_id: string;
41
83
  readonly trace_id: string | null;
42
84
  readonly cleaned_objects: unknown;
43
- readonly diff_plan: unknown;
44
85
  }
45
86
  export interface AsyncWriteResult {
46
87
  readonly write_id: string;
@@ -58,6 +99,214 @@ export interface ExtractResult {
58
99
  export interface GenerateSchemaResult {
59
100
  readonly data_schema: Record<string, unknown>;
60
101
  }
102
+ export type FieldType = "str" | "string" | "int" | "integer" | "float" | "bool" | "boolean";
103
+ export type OnDelete = "nullify" | "cascade";
104
+ export type CastStrategy = "safe_implicit" | "explicit_using" | "lossy";
105
+ export type DecisionKind = "accept" | "reject" | "defer";
106
+ export type MigrationSource = "direct" | "suggestion_engine";
107
+ export type DefaultValue = string | number | boolean | null;
108
+ export type EnumValues = Array<string | number | boolean> | null;
109
+ export interface FieldSpec {
110
+ name: string;
111
+ type: FieldType;
112
+ required?: boolean;
113
+ description?: string | null;
114
+ default?: DefaultValue;
115
+ enum?: EnumValues;
116
+ }
117
+ export interface AddObject {
118
+ op_type: "add_object";
119
+ name: string;
120
+ description?: string | null;
121
+ primary_key: string[];
122
+ fields: FieldSpec[];
123
+ should_backfill?: boolean;
124
+ }
125
+ export interface RemoveObject {
126
+ op_type: "remove_object";
127
+ name: string;
128
+ }
129
+ export interface RenameObject {
130
+ op_type: "rename_object";
131
+ old_name: string;
132
+ new_name: string;
133
+ }
134
+ export interface ChangeObject {
135
+ op_type: "change_object";
136
+ name: string;
137
+ new_primary_key?: string[] | null;
138
+ new_description?: string | null;
139
+ }
140
+ export interface AddField {
141
+ op_type: "add_field";
142
+ object_name: string;
143
+ field_name: string;
144
+ field_type: FieldType;
145
+ required?: boolean;
146
+ description?: string | null;
147
+ default?: DefaultValue;
148
+ enum?: EnumValues;
149
+ should_backfill?: boolean;
150
+ }
151
+ export interface RemoveField {
152
+ op_type: "remove_field";
153
+ object_name: string;
154
+ field_name: string;
155
+ }
156
+ export interface RenameField {
157
+ op_type: "rename_field";
158
+ object_name: string;
159
+ old_name: string;
160
+ new_name: string;
161
+ }
162
+ export interface ChangeField {
163
+ op_type: "change_field";
164
+ object_name: string;
165
+ field_name: string;
166
+ new_type?: FieldType | null;
167
+ new_required?: boolean | null;
168
+ new_description?: string | null;
169
+ new_default?: DefaultValue;
170
+ new_enum?: EnumValues;
171
+ clear_default?: boolean;
172
+ clear_enum?: boolean;
173
+ cast_strategy?: CastStrategy | null;
174
+ using_expression?: string | null;
175
+ confirm_data_loss?: boolean;
176
+ }
177
+ export interface AddRelation {
178
+ op_type: "add_relation";
179
+ name: string;
180
+ description?: string | null;
181
+ objects: Record<string, string>;
182
+ keys?: Record<string, string[]> | null;
183
+ on_delete?: Record<string, OnDelete> | null;
184
+ should_backfill?: boolean;
185
+ }
186
+ export interface RemoveRelation {
187
+ op_type: "remove_relation";
188
+ name: string;
189
+ }
190
+ export interface RenameRelation {
191
+ op_type: "rename_relation";
192
+ old_name: string;
193
+ new_name: string;
194
+ }
195
+ export interface ChangeRelation {
196
+ op_type: "change_relation";
197
+ name: string;
198
+ new_keys?: Record<string, string[]> | null;
199
+ new_on_delete?: Record<string, OnDelete> | null;
200
+ new_description?: string | null;
201
+ }
202
+ export type MigrationOp = AddObject | RemoveObject | RenameObject | ChangeObject | AddField | RemoveField | RenameField | ChangeField | AddRelation | RemoveRelation | RenameRelation | ChangeRelation;
203
+ export interface MigrationPlan {
204
+ ops: MigrationOp[];
205
+ }
206
+ export interface EnhanceSchemaResult {
207
+ readonly data_schema: Record<string, unknown>;
208
+ readonly migration_plan: MigrationPlan | null;
209
+ readonly summary: string | null;
210
+ readonly warnings: Record<string, unknown>[];
211
+ readonly repair_log: Record<string, unknown>[];
212
+ }
213
+ export interface PlanSummary {
214
+ readonly count_by_op_type: Record<string, number>;
215
+ readonly total: number;
216
+ }
217
+ export interface DryRunResult {
218
+ readonly status: "ok";
219
+ readonly instance_id: string;
220
+ readonly current_version: number;
221
+ readonly statements: string[];
222
+ readonly warnings: string[];
223
+ readonly plan_summary: PlanSummary;
224
+ readonly requires_metadata_sync: boolean;
225
+ }
226
+ export interface MigrationRecord {
227
+ readonly id: string;
228
+ readonly applied_at: string;
229
+ readonly source: MigrationSource;
230
+ readonly decided_by: string | null;
231
+ readonly prior_version: number;
232
+ readonly new_version: number;
233
+ readonly ops: Record<string, unknown>[];
234
+ readonly ops_summary: PlanSummary;
235
+ readonly notes: string | null;
236
+ readonly yaml_before: string | null;
237
+ readonly yaml_after: string | null;
238
+ }
239
+ export interface ListMigrationsResult {
240
+ readonly status: "ok";
241
+ readonly instance_id: string;
242
+ readonly items: MigrationRecord[];
243
+ readonly next_before_id: string | null;
244
+ readonly has_more: boolean;
245
+ }
246
+ export interface GetMigrationResult {
247
+ readonly status: "ok";
248
+ readonly instance_id: string;
249
+ readonly record: MigrationRecord;
250
+ }
251
+ export interface ProposalItem {
252
+ readonly item_fingerprint: string;
253
+ /** Raw op dict (forward-compatible). Cast to `MigrationOp` when needed. */
254
+ readonly op: Record<string, unknown>;
255
+ readonly evidence_feedback_ids: string[];
256
+ readonly evidence_query_samples: string[];
257
+ readonly frequency: number;
258
+ readonly depends_on: string[];
259
+ readonly current_decision: string | null;
260
+ readonly rationale: string;
261
+ }
262
+ export interface ConsolidatedProposal {
263
+ readonly instance_id: string;
264
+ readonly proposal_version: string;
265
+ readonly schema_version: number;
266
+ readonly items: ProposalItem[];
267
+ readonly generated_at: string;
268
+ readonly notes: string[];
269
+ }
270
+ export interface ReviewSuggestionsResult {
271
+ readonly status: "ok" | "evolution_in_progress";
272
+ readonly instance_id: string;
273
+ readonly proposal: ConsolidatedProposal | null;
274
+ readonly retry_after_seconds: number | null;
275
+ }
276
+ export interface DecisionInput {
277
+ item_fingerprint: string;
278
+ decision: DecisionKind;
279
+ edits?: Record<string, unknown> | null;
280
+ }
281
+ export interface RecordedDecision {
282
+ readonly item_fingerprint: string;
283
+ readonly decision_id: string;
284
+ }
285
+ export interface DependencyWarning {
286
+ readonly kind: string;
287
+ readonly item_fingerprint: string;
288
+ readonly related_fingerprints: string[];
289
+ readonly related_summaries: string[];
290
+ readonly guidance: string;
291
+ }
292
+ export interface DecideSuggestionsResult {
293
+ readonly status: "ok";
294
+ readonly instance_id: string;
295
+ readonly decisions_recorded: RecordedDecision[];
296
+ readonly warnings: DependencyWarning[];
297
+ readonly next_proposal_version: string;
298
+ }
299
+ export interface ApplyPendingDecisionsResult {
300
+ readonly status: "ok" | "nothing_to_apply";
301
+ readonly instance_id: string;
302
+ readonly migration_id: string | null;
303
+ readonly prior_version: number;
304
+ readonly new_version: number;
305
+ readonly applied_items: string[];
306
+ readonly summary: string;
307
+ readonly warnings: string[];
308
+ readonly notes: string[];
309
+ }
61
310
  export interface ToolParameterDescription {
62
311
  readonly name: string;
63
312
  readonly type: string;
@@ -92,6 +341,8 @@ export interface RequestOptions {
92
341
  }
93
342
  export interface ReadOptions {
94
343
  readMode?: ReadMode;
344
+ /** Restrict the read to a set of concrete objects (plus optional relation traversal). */
345
+ scope?: ReadScope;
95
346
  traceId?: string;
96
347
  timeoutMs?: number;
97
348
  }
@@ -113,6 +364,33 @@ export interface GenerateSchemaOptions {
113
364
  currentYmlSchema?: string;
114
365
  timeoutMs?: number;
115
366
  }
367
+ export interface UpdateInstanceSchemaOptions {
368
+ /** Serialized migration plan from `enhanceSchema`; required for non-additive changes. */
369
+ migrationPlan?: MigrationPlan | Record<string, unknown>;
370
+ /** Authorise ops that drop data (remove object/field, lossy type cast). */
371
+ confirmDestructive?: boolean;
372
+ timeoutMs?: number;
373
+ }
374
+ export interface DryRunMigrationOptions {
375
+ migrationPlan?: MigrationPlan | Record<string, unknown>;
376
+ confirmDestructive?: boolean;
377
+ timeoutMs?: number;
378
+ }
379
+ export interface ListMigrationsOptions {
380
+ limit?: number;
381
+ beforeId?: string;
382
+ includeYaml?: boolean;
383
+ timeoutMs?: number;
384
+ }
385
+ export interface GetMigrationOptions {
386
+ includeYaml?: boolean;
387
+ timeoutMs?: number;
388
+ }
389
+ export interface SuggestionRequestOptions {
390
+ /** Optional free-form session ID for end-to-end tracing. */
391
+ sessionId?: string;
392
+ timeoutMs?: number;
393
+ }
116
394
  export interface ApiError {
117
395
  readonly code: string;
118
396
  readonly message: string;
package/dist/types.js CHANGED
@@ -10,9 +10,22 @@ export const SchemaType = { YML: 0, JSON: 1 };
10
10
  // ---------------------------------------------------------------------------
11
11
  export class XmemoryAPIError extends Error {
12
12
  status;
13
- constructor(message, status) {
13
+ code;
14
+ details;
15
+ constructor(message, status,
16
+ /**
17
+ * Structured error code when the server returned one. For the
18
+ * schema-evolution endpoints this is the `error_type` discriminator
19
+ * (e.g. `"stale_proposal_version"`, `"destructive_confirmation_required"`).
20
+ * Pattern match on this instead of parsing the message.
21
+ */
22
+ code,
23
+ /** Optional structured `details` payload attached to some errors. */
24
+ details) {
14
25
  super(message);
15
26
  this.status = status;
27
+ this.code = code;
28
+ this.details = details;
16
29
  this.name = "XmemoryAPIError";
17
30
  }
18
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xmemory",
3
- "version": "2.1.2",
3
+ "version": "2.3.0",
4
4
  "description": "xmemory",
5
5
  "keywords": [
6
6
  "xmemory"