@infino-ai/infino 0.4.0 → 0.4.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/infino/index.d.ts CHANGED
@@ -152,8 +152,9 @@ export interface QueryOptions {
152
152
  /** A table handle. Obtain one from {@link Connection.createTable} or {@link Connection.openTable}. */
153
153
  export declare class Table {
154
154
  private inner;
155
+ private remote;
155
156
  /** @hidden */
156
- constructor(inner: any);
157
+ constructor(inner: any, remote?: boolean);
157
158
  /** The table's Arrow schema. */
158
159
  schema(): arrow.Schema;
159
160
  /**
@@ -224,8 +225,9 @@ export declare class Table {
224
225
  /** A catalog connection. Create one with {@link connect}. */
225
226
  export declare class Connection {
226
227
  private inner;
228
+ private remote;
227
229
  /** @hidden */
228
- constructor(inner: any);
230
+ constructor(inner: any, remote?: boolean);
229
231
  /**
230
232
  * Provision the database this connection targets. On the hosted service this
231
233
  * registers the database (throws if it already exists); on a local backend the
@@ -254,7 +256,10 @@ export declare class Connection {
254
256
  * For a hosted (`https://`) target, authenticate with an API key: pass
255
257
  * {@link ConnectOptions.apiKey}, or set the `INFINO_API_KEY` environment
256
258
  * variable. Storage and cache tuning the URI can't carry also goes in
257
- * `options` (see {@link ConnectOptions}).
259
+ * `options` (see {@link ConnectOptions}). An operation failure the hosted
260
+ * API reported carries the HTTP status it returned as `status` on the
261
+ * thrown Error (`409` create conflict, `404` missing, `503` transient —
262
+ * retry with backoff); errors on local connections have no `status`.
258
263
  *
259
264
  * ```ts
260
265
  * // local
package/infino/index.js CHANGED
@@ -121,7 +121,10 @@ function schemaToIpc(schema) {
121
121
  // built by hand. The schema here is ours (from the addon), so its types
122
122
  // are same-instance.
123
123
  function buildColumn(field, rows) {
124
- const values = rows.map((r) => r[field.name]);
124
+ // An omitted key reads as `undefined`, which `vectorFromArray` would
125
+ // coerce to the type's zero value (0, "", NaN, false) rather than null —
126
+ // its null set is `[null]` only. Normalize so omitted == explicit null.
127
+ const values = rows.map((r) => r[field.name] ?? null);
125
128
  const t = field.type;
126
129
  if (t && typeof t.listSize === "number") {
127
130
  const flat = Float32Array.from(values.flat());
@@ -189,17 +192,54 @@ function decode(buf, asArrow) {
189
192
  return obj;
190
193
  });
191
194
  }
195
+ // --- hosted-API error status ---
196
+ // The hosted transport types a few statuses before the number itself is
197
+ // lost (the message keeps a stable prefix); every other HTTP failure it
198
+ // reports as "<op>: server returned NNN: <body>". Both let the status be
199
+ // recovered here. This table goes away once the transport carries the
200
+ // status structurally.
201
+ const PREFIX_STATUS = [
202
+ ["NotFound: ", 404],
203
+ ["AlreadyExists: ", 409],
204
+ ["ConflictError: ", 412],
205
+ ];
206
+ const SERVER_STATUS = /\bserver returned (\d{3}): /;
207
+ /**
208
+ * Run a native call. On a hosted (Infino Cloud) connection, a failure is
209
+ * rethrown as-is but tagged with `status` — the HTTP status the API
210
+ * actually returned — so callers can branch (e.g. `409` = already exists,
211
+ * `503` = transient, retry) without parsing message text. Local
212
+ * connections rethrow untouched.
213
+ */
214
+ function guard(remote, fn) {
215
+ if (!remote)
216
+ return fn();
217
+ try {
218
+ return fn();
219
+ }
220
+ catch (e) {
221
+ if (e instanceof Error && e.status === undefined) {
222
+ const prefixed = PREFIX_STATUS.find(([prefix]) => e.message.startsWith(prefix));
223
+ const status = prefixed ? prefixed[1] : Number(SERVER_STATUS.exec(e.message)?.[1]);
224
+ if (Number.isFinite(status))
225
+ e.status = status;
226
+ }
227
+ throw e;
228
+ }
229
+ }
192
230
  // --- friendly handles ---
193
231
  /** A table handle. Obtain one from {@link Connection.createTable} or {@link Connection.openTable}. */
194
232
  class Table {
195
233
  inner;
234
+ remote;
196
235
  /** @hidden */
197
- constructor(inner) {
236
+ constructor(inner, remote = false) {
198
237
  this.inner = inner;
238
+ this.remote = remote;
199
239
  }
200
240
  /** The table's Arrow schema. */
201
241
  schema() {
202
- return arrow.tableFromIPC(this.inner.schema()).schema;
242
+ return arrow.tableFromIPC(guard(this.remote, () => this.inner.schema())).schema;
203
243
  }
204
244
  /**
205
245
  * Append rows. Accepts an array of objects, an apache-arrow
@@ -207,45 +247,47 @@ class Table {
207
247
  * append == one commit.
208
248
  */
209
249
  append(data) {
210
- this.inner.append(dataToIpc(data, () => this.schema()));
250
+ const ipc = dataToIpc(data, () => this.schema());
251
+ guard(this.remote, () => this.inner.append(ipc));
211
252
  }
212
253
  bm25Search(column, query, k, opts = {}) {
213
- const buf = this.inner.bm25Search(column, query, k, opts.mode, opts.stats, opts.projection);
254
+ const buf = guard(this.remote, () => this.inner.bm25Search(column, query, k, opts.mode, opts.stats, opts.projection));
214
255
  return decode(buf, opts.arrow);
215
256
  }
216
257
  vectorSearch(column, query, k, opts = {}) {
217
258
  const q = query instanceof Float32Array ? query : Float32Array.from(query);
218
- const buf = this.inner.vectorSearch(column, q, k, opts.nprobe, opts.rerankMult, opts.projection, opts.filter);
259
+ const buf = guard(this.remote, () => this.inner.vectorSearch(column, q, k, opts.nprobe, opts.rerankMult, opts.projection, opts.filter));
219
260
  return decode(buf, opts.arrow);
220
261
  }
221
262
  hybridSearch(textColumn, textQuery, vectorColumn, vectorQuery, k, opts = {}) {
222
263
  const q = vectorQuery instanceof Float32Array ? vectorQuery : Float32Array.from(vectorQuery);
223
- const buf = this.inner.hybridSearch(textColumn, textQuery, vectorColumn, q, k, opts.mode, opts.nprobe, opts.projection);
264
+ const buf = guard(this.remote, () => this.inner.hybridSearch(textColumn, textQuery, vectorColumn, q, k, opts.mode, opts.nprobe, opts.projection));
224
265
  return decode(buf, opts.arrow);
225
266
  }
226
267
  tokenMatch(column, query, opts = {}) {
227
- const buf = this.inner.tokenMatch(column, query, opts.mode, opts.projection);
268
+ const buf = guard(this.remote, () => this.inner.tokenMatch(column, query, opts.mode, opts.projection));
228
269
  return decode(buf, opts.arrow);
229
270
  }
230
271
  exactMatch(column, value, opts = {}) {
231
- const buf = this.inner.exactMatch(column, value, opts.projection);
272
+ const buf = guard(this.remote, () => this.inner.exactMatch(column, value, opts.projection));
232
273
  return decode(buf, opts.arrow);
233
274
  }
234
275
  /** Count rows matching a BM25 keyword `query` over `column`, without
235
276
  * fetching them. `mode` is `"or"` (default) or `"and"`. */
236
277
  count(column, query, opts = {}) {
237
- return this.inner.count(column, query, opts.mode);
278
+ return guard(this.remote, () => this.inner.count(column, query, opts.mode));
238
279
  }
239
280
  /** Replace rows matching a SQL predicate (e.g. `"status = 'spam'"`) with
240
281
  * `data` (same shapes as `append`), 1:1 — the matched count must equal the
241
282
  * replacement-row count. Requires durable storage (not `memory://`). */
242
283
  update(predicate, data) {
243
- return this.inner.update(predicate, dataToIpc(data, () => this.schema()));
284
+ const ipc = dataToIpc(data, () => this.schema());
285
+ return guard(this.remote, () => this.inner.update(predicate, ipc));
244
286
  }
245
287
  /** Delete rows matching a SQL predicate (e.g. `"status = 'spam'"`).
246
288
  * Requires durable storage (not `memory://`). */
247
289
  delete(predicate) {
248
- return this.inner.delete(predicate);
290
+ return guard(this.remote, () => this.inner.delete(predicate));
249
291
  }
250
292
  /**
251
293
  * Merge small / underfilled superfiles into larger ones (omit `settings`
@@ -255,7 +297,7 @@ class Table {
255
297
  * so calling this on a hosted (`https://…`) connection throws.
256
298
  */
257
299
  optimize(settings) {
258
- this.inner.optimize(settings);
300
+ guard(this.remote, () => this.inner.optimize(settings));
259
301
  }
260
302
  /**
261
303
  * Delete orphaned storage objects left by compaction or interrupted writes.
@@ -266,16 +308,18 @@ class Table {
266
308
  * calling this on a hosted connection throws.
267
309
  */
268
310
  gc(graceSecs) {
269
- return this.inner.gc(graceSecs);
311
+ return guard(this.remote, () => this.inner.gc(graceSecs));
270
312
  }
271
313
  }
272
314
  exports.Table = Table;
273
315
  /** A catalog connection. Create one with {@link connect}. */
274
316
  class Connection {
275
317
  inner;
318
+ remote;
276
319
  /** @hidden */
277
- constructor(inner) {
320
+ constructor(inner, remote = false) {
278
321
  this.inner = inner;
322
+ this.remote = remote;
279
323
  }
280
324
  /**
281
325
  * Provision the database this connection targets. On the hosted service this
@@ -283,23 +327,24 @@ class Connection {
283
327
  * catalog root is the database, so it is a no-op success.
284
328
  */
285
329
  createDatabase() {
286
- this.inner.createDatabase();
330
+ guard(this.remote, () => this.inner.createDatabase());
287
331
  }
288
332
  /** Create a table from an apache-arrow `Schema` or `{ column: type }`. */
289
333
  createTable(name, schema, indexes) {
290
- return new Table(this.inner.createTable(name, schemaToIpc(schema), indexes));
334
+ const ipc = schemaToIpc(schema);
335
+ return new Table(guard(this.remote, () => this.inner.createTable(name, ipc, indexes)), this.remote);
291
336
  }
292
337
  openTable(name) {
293
- return new Table(this.inner.openTable(name));
338
+ return new Table(guard(this.remote, () => this.inner.openTable(name)), this.remote);
294
339
  }
295
340
  dropTable(name, purge) {
296
- this.inner.dropTable(name, purge);
341
+ guard(this.remote, () => this.inner.dropTable(name, purge));
297
342
  }
298
343
  listTables() {
299
- return this.inner.listTables();
344
+ return guard(this.remote, () => this.inner.listTables());
300
345
  }
301
346
  querySql(sql, opts = {}) {
302
- return decode(this.inner.querySql(sql), opts.arrow);
347
+ return decode(guard(this.remote, () => this.inner.querySql(sql)), opts.arrow);
303
348
  }
304
349
  }
305
350
  exports.Connection = Connection;
@@ -314,7 +359,10 @@ exports.Connection = Connection;
314
359
  * For a hosted (`https://`) target, authenticate with an API key: pass
315
360
  * {@link ConnectOptions.apiKey}, or set the `INFINO_API_KEY` environment
316
361
  * variable. Storage and cache tuning the URI can't carry also goes in
317
- * `options` (see {@link ConnectOptions}).
362
+ * `options` (see {@link ConnectOptions}). An operation failure the hosted
363
+ * API reported carries the HTTP status it returned as `status` on the
364
+ * thrown Error (`409` create conflict, `404` missing, `503` transient —
365
+ * retry with backoff); errors on local connections have no `status`.
318
366
  *
319
367
  * ```ts
320
368
  * // local
@@ -324,5 +372,6 @@ exports.Connection = Connection;
324
372
  * ```
325
373
  */
326
374
  function connect(uri, options) {
327
- return new Connection((0, native_js_1.connect)(uri, options));
375
+ const remote = /^https?:\/\//.test(uri);
376
+ return new Connection(guard(remote, () => (0, native_js_1.connect)(uri, options)), remote);
328
377
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infino-ai/infino",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Fast search on object storage — SQL, full-text, and vector search.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -82,12 +82,12 @@
82
82
  "apache-arrow": "^17"
83
83
  },
84
84
  "optionalDependencies": {
85
- "infx-darwin-x64": "0.4.0",
86
- "infx-darwin-arm64": "0.4.0",
87
- "infx-linux-x64-gnu": "0.4.0",
88
- "infx-linux-arm64-gnu": "0.4.0",
89
- "infx-linux-x64-musl": "0.4.0",
90
- "infx-linux-arm64-musl": "0.4.0"
85
+ "infx-darwin-x64": "0.4.1",
86
+ "infx-darwin-arm64": "0.4.1",
87
+ "infx-linux-x64-gnu": "0.4.1",
88
+ "infx-linux-arm64-gnu": "0.4.1",
89
+ "infx-linux-x64-musl": "0.4.1",
90
+ "infx-linux-arm64-musl": "0.4.1"
91
91
  },
92
92
  "devDependencies": {
93
93
  "@napi-rs/cli": "^2.18.0",