xmemory 2.1.2 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -183,6 +183,109 @@ const tools = desc.asOpenaiTools(); // OpenAI function-calling format
183
183
 
184
184
  Results are cached for 5 minutes. Call `inst.clearDescribeCache()` to force a refresh.
185
185
 
186
+ ## Schema evolution
187
+
188
+ Schemas can change after creation. xmemory supports **safe, data-preserving
189
+ migrations** (rename / remove / type change) driven by structured migration
190
+ ops, plus a **suggestion engine** that proposes improvements from real read
191
+ traffic. This is purely additive — existing methods are unchanged.
192
+
193
+ See the [Schema evolution section of the API reference](https://xmemory.ai/api/#schema-evolution)
194
+ for the conceptual model, and the [TypeScript guide](https://xmemory.ai/typescript/)
195
+ for full walkthroughs.
196
+
197
+ ### Suggestion-engine flow (review → decide → apply)
198
+
199
+ The engine surfaces a single rolling proposal per instance. The minimum flow is
200
+ three calls — review, decide (in bulk), apply:
201
+
202
+ ```typescript
203
+ import { XmemoryClient, type DecisionInput } from "xmemory";
204
+
205
+ const xm = new XmemoryClient({ apiKey: "..." });
206
+ const inst = xm.instance("<instance-id>");
207
+
208
+ // 1. Review — get the proposal + its concurrency token.
209
+ const review = await inst.reviewSuggestions();
210
+ if (review.status === "evolution_in_progress") {
211
+ console.log(`A migration is in flight; retry in ${review.retry_after_seconds}s`);
212
+ } else if (review.proposal) {
213
+ const proposal = review.proposal;
214
+ for (const item of proposal.items) {
215
+ console.log(item.item_fingerprint, item.rationale, item.op);
216
+ }
217
+
218
+ // 2. Decide — accept / reject / defer per item, in one batch.
219
+ const decisions: DecisionInput[] = proposal.items.map((item) => ({
220
+ item_fingerprint: item.item_fingerprint,
221
+ decision: "accept",
222
+ }));
223
+ const decided = await inst.decideSuggestions(proposal.proposal_version, decisions);
224
+
225
+ // 3. Apply — commit accepted decisions as one migration.
226
+ const applied = await inst.applyPendingDecisions(decided.next_proposal_version);
227
+ console.log(applied.status, applied.summary); // e.g. "ok" "added 1 field"
228
+ }
229
+ ```
230
+
231
+ When `status === "evolution_in_progress"`, back off for `retry_after_seconds`
232
+ and retry instead of blocking.
233
+
234
+ ### Direct migration flow (enhance → dry-run → update)
235
+
236
+ Drive a migration yourself — ask the server to *enhance* the current schema,
237
+ preview the DDL, then apply it:
238
+
239
+ ```typescript
240
+ import { XmemoryClient, SchemaType } from "xmemory";
241
+ import yaml from "js-yaml";
242
+
243
+ const xm = new XmemoryClient({ apiKey: "..." });
244
+ const current = (await xm.admin.getInstanceSchema("<instance-id>")).data_schema;
245
+
246
+ // 1. Enhance — new schema + an executor-ready migration plan.
247
+ const enhanced = await xm.admin.enhanceSchema(
248
+ "<cluster-id>",
249
+ "Rename Person.mail to Person.email.",
250
+ yaml.dump(current),
251
+ );
252
+ console.log(enhanced.summary, enhanced.migration_plan?.ops);
253
+
254
+ const newYaml = yaml.dump(enhanced.data_schema);
255
+
256
+ // 2. Dry-run — preview the DDL without applying anything.
257
+ const preview = await xm.admin.dryRunMigration("<instance-id>", newYaml, SchemaType.YML, {
258
+ migrationPlan: enhanced.migration_plan ?? undefined,
259
+ });
260
+ console.log(preview.statements);
261
+
262
+ // 3. Update — apply. confirmDestructive is required for ops that drop data.
263
+ const info = await xm.admin.updateInstanceSchema("<instance-id>", newYaml, SchemaType.YML, {
264
+ migrationPlan: enhanced.migration_plan ?? undefined,
265
+ confirmDestructive: false,
266
+ });
267
+ console.log(info.migration_id, info.prior_version, "->", info.new_version);
268
+ ```
269
+
270
+ ### Migration history
271
+
272
+ ```typescript
273
+ const page = await xm.admin.listMigrations("<instance-id>", { limit: 20 });
274
+ for (const record of page.items) {
275
+ console.log(record.id, record.source, record.prior_version, "->", record.new_version);
276
+ }
277
+
278
+ const detail = await xm.admin.getMigration("<instance-id>", "<migration-id>", { includeYaml: true });
279
+ console.log(detail.yaml_before, detail.yaml_after);
280
+ ```
281
+
282
+ Migration ops are exported as discriminated-union types (`MigrationPlan`,
283
+ `MigrationOp`, `AddField`, `RenameField`, `RemoveObject`, …) keyed on `op_type`.
284
+ `ProposalItem.op` and `MigrationRecord.ops` are raw dicts for forward
285
+ compatibility — narrow them to `MigrationOp` when needed.
286
+
287
+ Runnable end-to-end examples live in [`examples/`](examples/).
288
+
186
289
  ## Error handling
187
290
 
188
291
  All errors throw `XmemoryAPIError`. Health check failures throw `XmemoryHealthCheckError` (a subclass).
@@ -207,6 +310,23 @@ try {
207
310
  }
208
311
  ```
209
312
 
313
+ `XmemoryAPIError` carries `status` (HTTP status), `code` (structured error code,
314
+ when the server returned one), and `details`. The schema-evolution endpoints
315
+ return codes you can pattern match on via `.code` — for example
316
+ `stale_proposal_version`, `dependency_closure_failed`,
317
+ `destructive_confirmation_required`, `non_additive_change_requires_plan`,
318
+ `stale_schema_version`, `migration_not_found`, `instance_not_initialised`:
319
+
320
+ ```typescript
321
+ try {
322
+ await inst.applyPendingDecisions(token);
323
+ } catch (e) {
324
+ if (e instanceof XmemoryAPIError && e.code === "stale_proposal_version") {
325
+ const review = await inst.reviewSuggestions(); // re-review and retry
326
+ }
327
+ }
328
+ ```
329
+
210
330
  ## All timeouts are per-request
211
331
 
212
332
  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,8 @@ 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 { XmemoryClientOptions, RequestOptions, ReadOptions, WriteOptions, ExtractOptions, CreateInstanceOptions, GenerateSchemaOptions, UpdateInstanceSchemaOptions, DryRunMigrationOptions, ListMigrationsOptions, GetMigrationOptions, SuggestionRequestOptions, } from "./types.js";
11
11
  export type { ClusterInfo, InstanceInfo, InstanceSchemaInfo, ReadResult, WriteResult, AsyncWriteResult, WriteStatusResult, ExtractResult, GenerateSchemaResult, ToolDescription, ToolParameterDescription, RawDescribeResult, } from "./types.js";
12
+ export type { MigrationPlan, MigrationOp, FieldSpec, AddObject, RemoveObject, RenameObject, ChangeObject, AddField, RemoveField, RenameField, ChangeField, AddRelation, RemoveRelation, RenameRelation, ChangeRelation, DefaultValue, EnumValues, } from "./types.js";
13
+ 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
@@ -161,6 +161,57 @@ export class InstanceHandle {
161
161
  timeoutMs: options?.timeoutMs,
162
162
  });
163
163
  }
164
+ // -- Schema evolution (suggestion engine) ---------------------------------
165
+ /**
166
+ * Return the current consolidated schema-improvement proposal (step 1).
167
+ *
168
+ * The result's `proposal.proposal_version` is the optimistic-concurrency
169
+ * token you pass to `decideSuggestions` / `applyPendingDecisions`. When
170
+ * `status === "evolution_in_progress"`, back off for `retry_after_seconds`
171
+ * and retry instead of blocking on the in-flight migration.
172
+ */
173
+ async reviewSuggestions(options) {
174
+ const body = {};
175
+ if (options?.sessionId != null)
176
+ body.session_id = options.sessionId;
177
+ return this._requestOne("POST", `/instances/${this.id}/suggestions/review`, {
178
+ body,
179
+ timeoutMs: options?.timeoutMs,
180
+ });
181
+ }
182
+ /**
183
+ * Record accept/reject/defer decisions for proposal items, in bulk (step 2).
184
+ *
185
+ * `proposalVersion` must be the token from the latest `reviewSuggestions`; a
186
+ * stale token throws `XmemoryAPIError` with `code === "stale_proposal_version"`.
187
+ * The result's `next_proposal_version` can be passed straight to
188
+ * `applyPendingDecisions`.
189
+ */
190
+ async decideSuggestions(proposalVersion, decisions, options) {
191
+ const body = { proposal_version: proposalVersion, decisions };
192
+ if (options?.sessionId != null)
193
+ body.session_id = options.sessionId;
194
+ return this._requestOne("POST", `/instances/${this.id}/suggestions/decide`, {
195
+ body,
196
+ timeoutMs: options?.timeoutMs,
197
+ });
198
+ }
199
+ /**
200
+ * Commit accepted decisions as a single migration (step 3).
201
+ *
202
+ * `status === "nothing_to_apply"` means no accepted items were left. A stale
203
+ * `proposalVersion` or an unmet dependency throws `XmemoryAPIError`
204
+ * (`code === "stale_proposal_version"` / `"dependency_closure_failed"`).
205
+ */
206
+ async applyPendingDecisions(proposalVersion, options) {
207
+ const body = { proposal_version: proposalVersion };
208
+ if (options?.sessionId != null)
209
+ body.session_id = options.sessionId;
210
+ return this._requestOne("POST", `/instances/${this.id}/suggestions/apply`, {
211
+ body,
212
+ timeoutMs: options?.timeoutMs,
213
+ });
214
+ }
164
215
  /**
165
216
  * Return agent-facing tool descriptions enriched with the instance schema.
166
217
  *
package/dist/types.d.ts CHANGED
@@ -8,10 +8,28 @@ 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
+ export type WriteQueueStatus = "queued" | "processing" | "extracting" | "extracted" | "applying" | "completed" | "failed" | "not_found";
12
12
  export declare class XmemoryAPIError extends Error {
13
13
  readonly status?: number | undefined;
14
- constructor(message: string, status?: number | undefined);
14
+ /**
15
+ * Structured error code when the server returned one. For the
16
+ * schema-evolution endpoints this is the `error_type` discriminator
17
+ * (e.g. `"stale_proposal_version"`, `"destructive_confirmation_required"`).
18
+ * Pattern match on this instead of parsing the message.
19
+ */
20
+ readonly code?: string | undefined;
21
+ /** Optional structured `details` payload attached to some errors. */
22
+ readonly details?: (Record<string, unknown> | null) | undefined;
23
+ constructor(message: string, status?: number | undefined,
24
+ /**
25
+ * Structured error code when the server returned one. For the
26
+ * schema-evolution endpoints this is the `error_type` discriminator
27
+ * (e.g. `"stale_proposal_version"`, `"destructive_confirmation_required"`).
28
+ * Pattern match on this instead of parsing the message.
29
+ */
30
+ code?: string | undefined,
31
+ /** Optional structured `details` payload attached to some errors. */
32
+ details?: (Record<string, unknown> | null) | undefined);
15
33
  }
16
34
  export declare class XmemoryHealthCheckError extends XmemoryAPIError {
17
35
  constructor(message: string, status?: number);
@@ -28,6 +46,10 @@ export interface InstanceInfo {
28
46
  readonly name: string;
29
47
  readonly description: string | null;
30
48
  readonly data_schema: Record<string, unknown> | null;
49
+ readonly migration_id?: string | null;
50
+ readonly prior_version?: number | null;
51
+ readonly new_version?: number | null;
52
+ readonly migration_warnings?: string[] | null;
31
53
  }
32
54
  export interface InstanceSchemaInfo {
33
55
  readonly data_schema: Record<string, unknown>;
@@ -40,7 +62,6 @@ export interface WriteResult {
40
62
  readonly write_id: string;
41
63
  readonly trace_id: string | null;
42
64
  readonly cleaned_objects: unknown;
43
- readonly diff_plan: unknown;
44
65
  }
45
66
  export interface AsyncWriteResult {
46
67
  readonly write_id: string;
@@ -58,6 +79,214 @@ export interface ExtractResult {
58
79
  export interface GenerateSchemaResult {
59
80
  readonly data_schema: Record<string, unknown>;
60
81
  }
82
+ export type FieldType = "str" | "string" | "int" | "integer" | "float" | "bool" | "boolean";
83
+ export type OnDelete = "nullify" | "cascade";
84
+ export type CastStrategy = "safe_implicit" | "explicit_using" | "lossy";
85
+ export type DecisionKind = "accept" | "reject" | "defer";
86
+ export type MigrationSource = "direct" | "suggestion_engine";
87
+ export type DefaultValue = string | number | boolean | null;
88
+ export type EnumValues = Array<string | number | boolean> | null;
89
+ export interface FieldSpec {
90
+ name: string;
91
+ type: FieldType;
92
+ required?: boolean;
93
+ description?: string | null;
94
+ default?: DefaultValue;
95
+ enum?: EnumValues;
96
+ }
97
+ export interface AddObject {
98
+ op_type: "add_object";
99
+ name: string;
100
+ description?: string | null;
101
+ primary_key: string[];
102
+ fields: FieldSpec[];
103
+ should_backfill?: boolean;
104
+ }
105
+ export interface RemoveObject {
106
+ op_type: "remove_object";
107
+ name: string;
108
+ }
109
+ export interface RenameObject {
110
+ op_type: "rename_object";
111
+ old_name: string;
112
+ new_name: string;
113
+ }
114
+ export interface ChangeObject {
115
+ op_type: "change_object";
116
+ name: string;
117
+ new_primary_key?: string[] | null;
118
+ new_description?: string | null;
119
+ }
120
+ export interface AddField {
121
+ op_type: "add_field";
122
+ object_name: string;
123
+ field_name: string;
124
+ field_type: FieldType;
125
+ required?: boolean;
126
+ description?: string | null;
127
+ default?: DefaultValue;
128
+ enum?: EnumValues;
129
+ should_backfill?: boolean;
130
+ }
131
+ export interface RemoveField {
132
+ op_type: "remove_field";
133
+ object_name: string;
134
+ field_name: string;
135
+ }
136
+ export interface RenameField {
137
+ op_type: "rename_field";
138
+ object_name: string;
139
+ old_name: string;
140
+ new_name: string;
141
+ }
142
+ export interface ChangeField {
143
+ op_type: "change_field";
144
+ object_name: string;
145
+ field_name: string;
146
+ new_type?: FieldType | null;
147
+ new_required?: boolean | null;
148
+ new_description?: string | null;
149
+ new_default?: DefaultValue;
150
+ new_enum?: EnumValues;
151
+ clear_default?: boolean;
152
+ clear_enum?: boolean;
153
+ cast_strategy?: CastStrategy | null;
154
+ using_expression?: string | null;
155
+ confirm_data_loss?: boolean;
156
+ }
157
+ export interface AddRelation {
158
+ op_type: "add_relation";
159
+ name: string;
160
+ description?: string | null;
161
+ objects: Record<string, string>;
162
+ keys?: Record<string, string[]> | null;
163
+ on_delete?: Record<string, OnDelete> | null;
164
+ should_backfill?: boolean;
165
+ }
166
+ export interface RemoveRelation {
167
+ op_type: "remove_relation";
168
+ name: string;
169
+ }
170
+ export interface RenameRelation {
171
+ op_type: "rename_relation";
172
+ old_name: string;
173
+ new_name: string;
174
+ }
175
+ export interface ChangeRelation {
176
+ op_type: "change_relation";
177
+ name: string;
178
+ new_keys?: Record<string, string[]> | null;
179
+ new_on_delete?: Record<string, OnDelete> | null;
180
+ new_description?: string | null;
181
+ }
182
+ export type MigrationOp = AddObject | RemoveObject | RenameObject | ChangeObject | AddField | RemoveField | RenameField | ChangeField | AddRelation | RemoveRelation | RenameRelation | ChangeRelation;
183
+ export interface MigrationPlan {
184
+ ops: MigrationOp[];
185
+ }
186
+ export interface EnhanceSchemaResult {
187
+ readonly data_schema: Record<string, unknown>;
188
+ readonly migration_plan: MigrationPlan | null;
189
+ readonly summary: string | null;
190
+ readonly warnings: Record<string, unknown>[];
191
+ readonly repair_log: Record<string, unknown>[];
192
+ }
193
+ export interface PlanSummary {
194
+ readonly count_by_op_type: Record<string, number>;
195
+ readonly total: number;
196
+ }
197
+ export interface DryRunResult {
198
+ readonly status: "ok";
199
+ readonly instance_id: string;
200
+ readonly current_version: number;
201
+ readonly statements: string[];
202
+ readonly warnings: string[];
203
+ readonly plan_summary: PlanSummary;
204
+ readonly requires_metadata_sync: boolean;
205
+ }
206
+ export interface MigrationRecord {
207
+ readonly id: string;
208
+ readonly applied_at: string;
209
+ readonly source: MigrationSource;
210
+ readonly decided_by: string | null;
211
+ readonly prior_version: number;
212
+ readonly new_version: number;
213
+ readonly ops: Record<string, unknown>[];
214
+ readonly ops_summary: PlanSummary;
215
+ readonly notes: string | null;
216
+ readonly yaml_before: string | null;
217
+ readonly yaml_after: string | null;
218
+ }
219
+ export interface ListMigrationsResult {
220
+ readonly status: "ok";
221
+ readonly instance_id: string;
222
+ readonly items: MigrationRecord[];
223
+ readonly next_before_id: string | null;
224
+ readonly has_more: boolean;
225
+ }
226
+ export interface GetMigrationResult {
227
+ readonly status: "ok";
228
+ readonly instance_id: string;
229
+ readonly record: MigrationRecord;
230
+ }
231
+ export interface ProposalItem {
232
+ readonly item_fingerprint: string;
233
+ /** Raw op dict (forward-compatible). Cast to `MigrationOp` when needed. */
234
+ readonly op: Record<string, unknown>;
235
+ readonly evidence_feedback_ids: string[];
236
+ readonly evidence_query_samples: string[];
237
+ readonly frequency: number;
238
+ readonly depends_on: string[];
239
+ readonly current_decision: string | null;
240
+ readonly rationale: string;
241
+ }
242
+ export interface ConsolidatedProposal {
243
+ readonly instance_id: string;
244
+ readonly proposal_version: string;
245
+ readonly schema_version: number;
246
+ readonly items: ProposalItem[];
247
+ readonly generated_at: string;
248
+ readonly notes: string[];
249
+ }
250
+ export interface ReviewSuggestionsResult {
251
+ readonly status: "ok" | "evolution_in_progress";
252
+ readonly instance_id: string;
253
+ readonly proposal: ConsolidatedProposal | null;
254
+ readonly retry_after_seconds: number | null;
255
+ }
256
+ export interface DecisionInput {
257
+ item_fingerprint: string;
258
+ decision: DecisionKind;
259
+ edits?: Record<string, unknown> | null;
260
+ }
261
+ export interface RecordedDecision {
262
+ readonly item_fingerprint: string;
263
+ readonly decision_id: string;
264
+ }
265
+ export interface DependencyWarning {
266
+ readonly kind: string;
267
+ readonly item_fingerprint: string;
268
+ readonly related_fingerprints: string[];
269
+ readonly related_summaries: string[];
270
+ readonly guidance: string;
271
+ }
272
+ export interface DecideSuggestionsResult {
273
+ readonly status: "ok";
274
+ readonly instance_id: string;
275
+ readonly decisions_recorded: RecordedDecision[];
276
+ readonly warnings: DependencyWarning[];
277
+ readonly next_proposal_version: string;
278
+ }
279
+ export interface ApplyPendingDecisionsResult {
280
+ readonly status: "ok" | "nothing_to_apply";
281
+ readonly instance_id: string;
282
+ readonly migration_id: string | null;
283
+ readonly prior_version: number;
284
+ readonly new_version: number;
285
+ readonly applied_items: string[];
286
+ readonly summary: string;
287
+ readonly warnings: string[];
288
+ readonly notes: string[];
289
+ }
61
290
  export interface ToolParameterDescription {
62
291
  readonly name: string;
63
292
  readonly type: string;
@@ -113,6 +342,33 @@ export interface GenerateSchemaOptions {
113
342
  currentYmlSchema?: string;
114
343
  timeoutMs?: number;
115
344
  }
345
+ export interface UpdateInstanceSchemaOptions {
346
+ /** Serialized migration plan from `enhanceSchema`; required for non-additive changes. */
347
+ migrationPlan?: MigrationPlan | Record<string, unknown>;
348
+ /** Authorise ops that drop data (remove object/field, lossy type cast). */
349
+ confirmDestructive?: boolean;
350
+ timeoutMs?: number;
351
+ }
352
+ export interface DryRunMigrationOptions {
353
+ migrationPlan?: MigrationPlan | Record<string, unknown>;
354
+ confirmDestructive?: boolean;
355
+ timeoutMs?: number;
356
+ }
357
+ export interface ListMigrationsOptions {
358
+ limit?: number;
359
+ beforeId?: string;
360
+ includeYaml?: boolean;
361
+ timeoutMs?: number;
362
+ }
363
+ export interface GetMigrationOptions {
364
+ includeYaml?: boolean;
365
+ timeoutMs?: number;
366
+ }
367
+ export interface SuggestionRequestOptions {
368
+ /** Optional free-form session ID for end-to-end tracing. */
369
+ sessionId?: string;
370
+ timeoutMs?: number;
371
+ }
116
372
  export interface ApiError {
117
373
  readonly code: string;
118
374
  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.2.1",
4
4
  "description": "xmemory",
5
5
  "keywords": [
6
6
  "xmemory"