@wavehouse/sdk 0.0.0-dev.h825a5d7fa405 → 0.0.0-dev.hab0797ca5a74

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/dist/index.d.cts CHANGED
@@ -59,7 +59,19 @@ interface ClientOptions {
59
59
  maxRetries?: number;
60
60
  }
61
61
  interface StructuredQuery {
62
+ /**
63
+ * Explicit columns to project. A literal "*" is a column named "*", not a
64
+ * wildcard. Omitting columns (with no aggregations and no select_all) selects
65
+ * nothing — use select_all for a full-row read. Mutually exclusive with
66
+ * select_all.
67
+ */
62
68
  columns?: string[];
69
+ /**
70
+ * Request every column the caller's role may read (the all-columns wildcard,
71
+ * expanded server-side to the allowed/denied set). Mutually exclusive with a
72
+ * non-empty columns list.
73
+ */
74
+ select_all?: boolean;
63
75
  aggregations?: Aggregation[];
64
76
  filters?: QueryFilter[];
65
77
  group_by?: string[];
@@ -97,9 +109,36 @@ interface TableSchema {
97
109
  columns: Column[];
98
110
  }
99
111
  type Schemas = Record<string, TableSchema>;
112
+ /**
113
+ * A per-record outcome from a batch (array / NDJSON) insert. Mirrors the
114
+ * single-object response shape plus the record's position. Exactly one of
115
+ * `ok` / `duplicate` / `error` is set.
116
+ */
117
+ interface InsertRecordResult {
118
+ /** 1-based index of the record within the submitted batch. */
119
+ index: number;
120
+ /** Set when the record was validated and published. */
121
+ ok?: boolean;
122
+ /** Set when dedup skipped the record. */
123
+ duplicate?: boolean;
124
+ /** Set (with `ok`/`duplicate` absent) when the record was rejected. */
125
+ error?: string;
126
+ }
100
127
  interface InsertResult {
128
+ /** True for a fully successful insert: a single row, or a batch with no rejected records (`failed === 0`). */
101
129
  ok: boolean;
130
+ /** Single insert only: set when dedup skipped the row. */
102
131
  duplicate?: boolean;
132
+ /** Batch insert (array / NDJSON): number of records submitted. */
133
+ total?: number;
134
+ /** Batch insert: records validated and published. */
135
+ succeeded?: number;
136
+ /** Batch insert: records rejected — see `results`. */
137
+ failed?: number;
138
+ /** Batch insert: records skipped by dedup. */
139
+ duplicates?: number;
140
+ /** Batch insert: per-record outcomes, each `{index, ok|duplicate|error}` (may be truncated for very large batches; the counts stay authoritative). */
141
+ results?: InsertRecordResult[];
103
142
  }
104
143
  interface DLQStats {
105
144
  tables: Record<string, number>;
@@ -339,6 +378,7 @@ declare class LiveQuery<T = Record<string, unknown>> {
339
378
  interface QueryState {
340
379
  table: string;
341
380
  columns: string[];
381
+ selectAll?: boolean;
342
382
  aggregations: Aggregation[];
343
383
  filters: QueryFilter[];
344
384
  groupBy: string[];
@@ -361,6 +401,13 @@ declare class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike
361
401
  private readonly _createStream;
362
402
  constructor(ctx: HttpContext, state: QueryState, createStream: CreateStreamFn$1<Row>);
363
403
  select(...columns: string[]): QueryBuilder<Row>;
404
+ /**
405
+ * Select every column the caller's role is allowed to read (the all-columns
406
+ * wildcard). Use this instead of `.select(...)` when you want a full-row read;
407
+ * a bare `.fetch()` with no `.select()` does this implicitly. Mutually
408
+ * exclusive with `.select(...)`.
409
+ */
410
+ selectAll(): QueryBuilder<Row>;
364
411
  where(column: string, op: FilterOp, value: unknown): QueryBuilder<Row>;
365
412
  count(column?: string, alias?: string): QueryBuilder<Row>;
366
413
  sum(column: string, alias?: string): QueryBuilder<Row>;
@@ -396,6 +443,12 @@ declare class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike
396
443
  }
397
444
 
398
445
  type CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;
446
+ /**
447
+ * Accepted sources for {@link TableRef.insertNDJSON}: a string, raw bytes, a
448
+ * Blob/File (browser file inputs), or a byte stream. Non-string sources are
449
+ * read fully before sending.
450
+ */
451
+ type NDJSONSource = string | Uint8Array | Blob | ReadableStream<Uint8Array>;
399
452
  /**
400
453
  * Reference to a table. NOT thenable — safe to pass around without triggering requests.
401
454
  * Use `.fetch()`, `.select()`, `.insert()`, `.schema()`, or `.stream()` to act on it.
@@ -409,10 +462,48 @@ declare class TableRef<Row = Record<string, unknown>> {
409
462
  fetch(opts?: FetchOptions): Promise<Result<Row[]>>;
410
463
  /** Start building a typed query. Returns an immutable, PromiseLike QueryBuilder. */
411
464
  select(...columns: string[]): QueryBuilder<Row>;
412
- /** Insert one or more rows into this table. */
465
+ /**
466
+ * Start a query that selects every column the caller's role is allowed to read
467
+ * (the all-columns wildcard). Use instead of `.select(...)` for an explicit
468
+ * full-row read; `.fetch()` does this implicitly.
469
+ */
470
+ selectAll(): QueryBuilder<Row>;
471
+ /**
472
+ * Insert one or more rows into this table.
473
+ *
474
+ * A single object is sent as a JSON `POST /v1/ingest`. An **array** is
475
+ * serialized to NDJSON (one record per line) and sent as a single
476
+ * `application/x-ndjson` request: a bad record no longer fails or hides the
477
+ * rest of the batch — per-record outcomes come back in the result
478
+ * (`failed` / `results`), and `ok` is true only when every record succeeded.
479
+ *
480
+ * The array path sends one request regardless of size; bounded-concurrency
481
+ * chunking of very large arrays is tracked separately (#196). For NDJSON you
482
+ * already have (a file or stream), use {@link insertNDJSON}.
483
+ */
413
484
  insert(data: Partial<Row> | Partial<Row>[], opts?: {
414
485
  signal?: AbortSignal;
415
486
  }): Promise<Result<InsertResult>>;
487
+ /**
488
+ * Insert pre-formatted NDJSON (newline-delimited JSON, one record per line)
489
+ * from a string, raw bytes, a Blob/File, or a byte stream — e.g. a `.ndjson`
490
+ * file or a stream produced by another system. For in-memory rows, prefer
491
+ * {@link insert}.
492
+ *
493
+ * Non-string sources are read fully into memory before sending (the server
494
+ * streams the parse). Returns the same per-record batch summary as an array
495
+ * `insert`.
496
+ */
497
+ insertNDJSON(source: NDJSONSource, opts?: {
498
+ signal?: AbortSignal;
499
+ }): Promise<Result<InsertResult>>;
500
+ /**
501
+ * @internal Send an NDJSON body and map the server's batch response onto an
502
+ * {@link InsertResult}. A transport / whole-request failure surfaces as the
503
+ * Result error arm; a processed batch (even with rejected records) surfaces
504
+ * as the success arm with `ok = failed === 0`.
505
+ */
506
+ private _sendNDJSON;
416
507
  /** Fetch the schema for this table. */
417
508
  schema(opts?: {
418
509
  signal?: AbortSignal;
@@ -458,4 +549,4 @@ declare class WaveHouseClient<DB extends Database = Database> {
458
549
  /** Create a new WaveHouse client instance. */
459
550
  declare function createClient<DB extends Database = Database>(config: ClientConfig<DB>): WaveHouseClient<DB>;
460
551
 
461
- export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type FetchOptions, type FilterOp, type InsertResult, LiveQuery, type OrderClause, type ParamDef, type Pipe, PipeRef, PipesNamespace, type Policy, type PolicyFilter, PolicyNamespace, QueryBuilder, type QueryFilter, type Result, type RolePermissions, SchemaNamespace, type Schemas, StreamController, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscriber, type StructuredQuery, SysNamespace, type TablePolicy, TableRef, type TableSchema, type TimeRange, type ValidationResult, WaveHouseClient, type WaveHouseError, createClient };
552
+ export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type FetchOptions, type FilterOp, type InsertRecordResult, type InsertResult, LiveQuery, type NDJSONSource, type OrderClause, type ParamDef, type Pipe, PipeRef, PipesNamespace, type Policy, type PolicyFilter, PolicyNamespace, QueryBuilder, type QueryFilter, type Result, type RolePermissions, SchemaNamespace, type Schemas, StreamController, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscriber, type StructuredQuery, SysNamespace, type TablePolicy, TableRef, type TableSchema, type TimeRange, type ValidationResult, WaveHouseClient, type WaveHouseError, createClient };
package/dist/index.d.ts CHANGED
@@ -59,7 +59,19 @@ interface ClientOptions {
59
59
  maxRetries?: number;
60
60
  }
61
61
  interface StructuredQuery {
62
+ /**
63
+ * Explicit columns to project. A literal "*" is a column named "*", not a
64
+ * wildcard. Omitting columns (with no aggregations and no select_all) selects
65
+ * nothing — use select_all for a full-row read. Mutually exclusive with
66
+ * select_all.
67
+ */
62
68
  columns?: string[];
69
+ /**
70
+ * Request every column the caller's role may read (the all-columns wildcard,
71
+ * expanded server-side to the allowed/denied set). Mutually exclusive with a
72
+ * non-empty columns list.
73
+ */
74
+ select_all?: boolean;
63
75
  aggregations?: Aggregation[];
64
76
  filters?: QueryFilter[];
65
77
  group_by?: string[];
@@ -97,9 +109,36 @@ interface TableSchema {
97
109
  columns: Column[];
98
110
  }
99
111
  type Schemas = Record<string, TableSchema>;
112
+ /**
113
+ * A per-record outcome from a batch (array / NDJSON) insert. Mirrors the
114
+ * single-object response shape plus the record's position. Exactly one of
115
+ * `ok` / `duplicate` / `error` is set.
116
+ */
117
+ interface InsertRecordResult {
118
+ /** 1-based index of the record within the submitted batch. */
119
+ index: number;
120
+ /** Set when the record was validated and published. */
121
+ ok?: boolean;
122
+ /** Set when dedup skipped the record. */
123
+ duplicate?: boolean;
124
+ /** Set (with `ok`/`duplicate` absent) when the record was rejected. */
125
+ error?: string;
126
+ }
100
127
  interface InsertResult {
128
+ /** True for a fully successful insert: a single row, or a batch with no rejected records (`failed === 0`). */
101
129
  ok: boolean;
130
+ /** Single insert only: set when dedup skipped the row. */
102
131
  duplicate?: boolean;
132
+ /** Batch insert (array / NDJSON): number of records submitted. */
133
+ total?: number;
134
+ /** Batch insert: records validated and published. */
135
+ succeeded?: number;
136
+ /** Batch insert: records rejected — see `results`. */
137
+ failed?: number;
138
+ /** Batch insert: records skipped by dedup. */
139
+ duplicates?: number;
140
+ /** Batch insert: per-record outcomes, each `{index, ok|duplicate|error}` (may be truncated for very large batches; the counts stay authoritative). */
141
+ results?: InsertRecordResult[];
103
142
  }
104
143
  interface DLQStats {
105
144
  tables: Record<string, number>;
@@ -339,6 +378,7 @@ declare class LiveQuery<T = Record<string, unknown>> {
339
378
  interface QueryState {
340
379
  table: string;
341
380
  columns: string[];
381
+ selectAll?: boolean;
342
382
  aggregations: Aggregation[];
343
383
  filters: QueryFilter[];
344
384
  groupBy: string[];
@@ -361,6 +401,13 @@ declare class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike
361
401
  private readonly _createStream;
362
402
  constructor(ctx: HttpContext, state: QueryState, createStream: CreateStreamFn$1<Row>);
363
403
  select(...columns: string[]): QueryBuilder<Row>;
404
+ /**
405
+ * Select every column the caller's role is allowed to read (the all-columns
406
+ * wildcard). Use this instead of `.select(...)` when you want a full-row read;
407
+ * a bare `.fetch()` with no `.select()` does this implicitly. Mutually
408
+ * exclusive with `.select(...)`.
409
+ */
410
+ selectAll(): QueryBuilder<Row>;
364
411
  where(column: string, op: FilterOp, value: unknown): QueryBuilder<Row>;
365
412
  count(column?: string, alias?: string): QueryBuilder<Row>;
366
413
  sum(column: string, alias?: string): QueryBuilder<Row>;
@@ -396,6 +443,12 @@ declare class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike
396
443
  }
397
444
 
398
445
  type CreateStreamFn<Row> = (table: string, opts?: StreamOptions) => StreamController<Row>;
446
+ /**
447
+ * Accepted sources for {@link TableRef.insertNDJSON}: a string, raw bytes, a
448
+ * Blob/File (browser file inputs), or a byte stream. Non-string sources are
449
+ * read fully before sending.
450
+ */
451
+ type NDJSONSource = string | Uint8Array | Blob | ReadableStream<Uint8Array>;
399
452
  /**
400
453
  * Reference to a table. NOT thenable — safe to pass around without triggering requests.
401
454
  * Use `.fetch()`, `.select()`, `.insert()`, `.schema()`, or `.stream()` to act on it.
@@ -409,10 +462,48 @@ declare class TableRef<Row = Record<string, unknown>> {
409
462
  fetch(opts?: FetchOptions): Promise<Result<Row[]>>;
410
463
  /** Start building a typed query. Returns an immutable, PromiseLike QueryBuilder. */
411
464
  select(...columns: string[]): QueryBuilder<Row>;
412
- /** Insert one or more rows into this table. */
465
+ /**
466
+ * Start a query that selects every column the caller's role is allowed to read
467
+ * (the all-columns wildcard). Use instead of `.select(...)` for an explicit
468
+ * full-row read; `.fetch()` does this implicitly.
469
+ */
470
+ selectAll(): QueryBuilder<Row>;
471
+ /**
472
+ * Insert one or more rows into this table.
473
+ *
474
+ * A single object is sent as a JSON `POST /v1/ingest`. An **array** is
475
+ * serialized to NDJSON (one record per line) and sent as a single
476
+ * `application/x-ndjson` request: a bad record no longer fails or hides the
477
+ * rest of the batch — per-record outcomes come back in the result
478
+ * (`failed` / `results`), and `ok` is true only when every record succeeded.
479
+ *
480
+ * The array path sends one request regardless of size; bounded-concurrency
481
+ * chunking of very large arrays is tracked separately (#196). For NDJSON you
482
+ * already have (a file or stream), use {@link insertNDJSON}.
483
+ */
413
484
  insert(data: Partial<Row> | Partial<Row>[], opts?: {
414
485
  signal?: AbortSignal;
415
486
  }): Promise<Result<InsertResult>>;
487
+ /**
488
+ * Insert pre-formatted NDJSON (newline-delimited JSON, one record per line)
489
+ * from a string, raw bytes, a Blob/File, or a byte stream — e.g. a `.ndjson`
490
+ * file or a stream produced by another system. For in-memory rows, prefer
491
+ * {@link insert}.
492
+ *
493
+ * Non-string sources are read fully into memory before sending (the server
494
+ * streams the parse). Returns the same per-record batch summary as an array
495
+ * `insert`.
496
+ */
497
+ insertNDJSON(source: NDJSONSource, opts?: {
498
+ signal?: AbortSignal;
499
+ }): Promise<Result<InsertResult>>;
500
+ /**
501
+ * @internal Send an NDJSON body and map the server's batch response onto an
502
+ * {@link InsertResult}. A transport / whole-request failure surfaces as the
503
+ * Result error arm; a processed batch (even with rejected records) surfaces
504
+ * as the success arm with `ok = failed === 0`.
505
+ */
506
+ private _sendNDJSON;
416
507
  /** Fetch the schema for this table. */
417
508
  schema(opts?: {
418
509
  signal?: AbortSignal;
@@ -458,4 +549,4 @@ declare class WaveHouseClient<DB extends Database = Database> {
458
549
  /** Create a new WaveHouse client instance. */
459
550
  declare function createClient<DB extends Database = Database>(config: ClientConfig<DB>): WaveHouseClient<DB>;
460
551
 
461
- export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type FetchOptions, type FilterOp, type InsertResult, LiveQuery, type OrderClause, type ParamDef, type Pipe, PipeRef, PipesNamespace, type Policy, type PolicyFilter, PolicyNamespace, QueryBuilder, type QueryFilter, type Result, type RolePermissions, SchemaNamespace, type Schemas, StreamController, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscriber, type StructuredQuery, SysNamespace, type TablePolicy, TableRef, type TableSchema, type TimeRange, type ValidationResult, WaveHouseClient, type WaveHouseError, createClient };
552
+ export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type FetchOptions, type FilterOp, type InsertRecordResult, type InsertResult, LiveQuery, type NDJSONSource, type OrderClause, type ParamDef, type Pipe, PipeRef, PipesNamespace, type Policy, type PolicyFilter, PolicyNamespace, QueryBuilder, type QueryFilter, type Result, type RolePermissions, SchemaNamespace, type Schemas, StreamController, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscriber, type StructuredQuery, SysNamespace, type TablePolicy, TableRef, type TableSchema, type TimeRange, type ValidationResult, WaveHouseClient, type WaveHouseError, createClient };
package/dist/index.js CHANGED
@@ -39,9 +39,10 @@ function err(error) {
39
39
  async function request(ctx, opts) {
40
40
  const url = buildURL(ctx.baseURL, opts.path, opts.params);
41
41
  const headers = {
42
- "Content-Type": "application/json",
42
+ "Content-Type": opts.contentType ?? "application/json",
43
43
  Accept: "application/json"
44
44
  };
45
+ const requestBody = opts.rawBody !== void 0 ? opts.rawBody : opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
45
46
  if (ctx.auth) {
46
47
  const token = await ctx.auth();
47
48
  if (token) {
@@ -55,7 +56,7 @@ async function request(ctx, opts) {
55
56
  const res = await fetch(url, {
56
57
  method: opts.method,
57
58
  headers,
58
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
59
+ body: requestBody,
59
60
  signal: opts.signal
60
61
  });
61
62
  if (res.ok) {
@@ -690,6 +691,15 @@ var QueryBuilder = class _QueryBuilder {
690
691
  select(...columns) {
691
692
  return this._clone({ columns: [...this._state.columns, ...columns] });
692
693
  }
694
+ /**
695
+ * Select every column the caller's role is allowed to read (the all-columns
696
+ * wildcard). Use this instead of `.select(...)` when you want a full-row read;
697
+ * a bare `.fetch()` with no `.select()` does this implicitly. Mutually
698
+ * exclusive with `.select(...)`.
699
+ */
700
+ selectAll() {
701
+ return this._clone({ selectAll: true });
702
+ }
693
703
  where(column, op, value) {
694
704
  const filter = { column, op: OP_MAP[op], value };
695
705
  return this._clone({ filters: [...this._state.filters, filter] });
@@ -788,8 +798,16 @@ var QueryBuilder = class _QueryBuilder {
788
798
  }
789
799
  _buildAST(effectiveLimit) {
790
800
  const ast = {};
791
- if (this._state.columns.length > 0) ast.columns = this._state.columns;
792
- if (this._state.aggregations.length > 0) ast.aggregations = this._state.aggregations;
801
+ const hasColumns = this._state.columns.length > 0;
802
+ const hasAggs = this._state.aggregations.length > 0;
803
+ if (this._state.selectAll) {
804
+ ast.select_all = true;
805
+ } else if (hasColumns) {
806
+ ast.columns = this._state.columns;
807
+ } else if (!hasAggs) {
808
+ ast.select_all = true;
809
+ }
810
+ if (hasAggs) ast.aggregations = this._state.aggregations;
793
811
  if (this._state.filters.length > 0) ast.filters = this._state.filters;
794
812
  if (this._state.groupBy.length > 0) ast.group_by = this._state.groupBy;
795
813
  if (this._state.orderBy.length > 0) ast.order_by = this._state.orderBy;
@@ -899,6 +917,12 @@ function projectColumns(row, columns) {
899
917
  }
900
918
 
901
919
  // src/table.ts
920
+ var NDJSON_CONTENT_TYPE = "application/x-ndjson";
921
+ async function ndjsonSourceToString(source) {
922
+ if (typeof source === "string") return source;
923
+ if (source instanceof Uint8Array) return new TextDecoder().decode(source);
924
+ return new Response(source).text();
925
+ }
902
926
  var TableRef = class {
903
927
  _ctx;
904
928
  _table;
@@ -927,22 +951,34 @@ var TableRef = class {
927
951
  this._createStream
928
952
  );
929
953
  }
930
- /** Insert one or more rows into this table. */
954
+ /**
955
+ * Start a query that selects every column the caller's role is allowed to read
956
+ * (the all-columns wildcard). Use instead of `.select(...)` for an explicit
957
+ * full-row read; `.fetch()` does this implicitly.
958
+ */
959
+ selectAll() {
960
+ return this.select().selectAll();
961
+ }
962
+ /**
963
+ * Insert one or more rows into this table.
964
+ *
965
+ * A single object is sent as a JSON `POST /v1/ingest`. An **array** is
966
+ * serialized to NDJSON (one record per line) and sent as a single
967
+ * `application/x-ndjson` request: a bad record no longer fails or hides the
968
+ * rest of the batch — per-record outcomes come back in the result
969
+ * (`failed` / `results`), and `ok` is true only when every record succeeded.
970
+ *
971
+ * The array path sends one request regardless of size; bounded-concurrency
972
+ * chunking of very large arrays is tracked separately (#196). For NDJSON you
973
+ * already have (a file or stream), use {@link insertNDJSON}.
974
+ */
931
975
  async insert(data, opts) {
932
976
  if (Array.isArray(data)) {
933
- const promises = data.map(
934
- (row) => request(this._ctx, {
935
- method: "POST",
936
- path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,
937
- body: row,
938
- signal: opts?.signal
939
- })
940
- );
941
- const results = await Promise.all(promises);
942
- for (const res2 of results) {
943
- if (res2.error) return err(res2.error);
977
+ if (data.length === 0) {
978
+ return ok({ ok: true, total: 0, succeeded: 0, failed: 0, duplicates: 0, results: [] });
944
979
  }
945
- return ok({ ok: true });
980
+ const ndjson = data.map((row) => JSON.stringify(row)).join("\n");
981
+ return this._sendNDJSON(ndjson, opts);
946
982
  }
947
983
  const { data: res, error } = await request(this._ctx, {
948
984
  method: "POST",
@@ -955,6 +991,46 @@ var TableRef = class {
955
991
  if (res?.duplicate != null) result.duplicate = res.duplicate;
956
992
  return ok(result);
957
993
  }
994
+ /**
995
+ * Insert pre-formatted NDJSON (newline-delimited JSON, one record per line)
996
+ * from a string, raw bytes, a Blob/File, or a byte stream — e.g. a `.ndjson`
997
+ * file or a stream produced by another system. For in-memory rows, prefer
998
+ * {@link insert}.
999
+ *
1000
+ * Non-string sources are read fully into memory before sending (the server
1001
+ * streams the parse). Returns the same per-record batch summary as an array
1002
+ * `insert`.
1003
+ */
1004
+ async insertNDJSON(source, opts) {
1005
+ const ndjson = await ndjsonSourceToString(source);
1006
+ return this._sendNDJSON(ndjson, opts);
1007
+ }
1008
+ /**
1009
+ * @internal Send an NDJSON body and map the server's batch response onto an
1010
+ * {@link InsertResult}. A transport / whole-request failure surfaces as the
1011
+ * Result error arm; a processed batch (even with rejected records) surfaces
1012
+ * as the success arm with `ok = failed === 0`.
1013
+ */
1014
+ async _sendNDJSON(ndjson, opts) {
1015
+ const { data, error } = await request(this._ctx, {
1016
+ method: "POST",
1017
+ path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,
1018
+ rawBody: ndjson,
1019
+ contentType: NDJSON_CONTENT_TYPE,
1020
+ signal: opts?.signal
1021
+ });
1022
+ if (error) return err(error);
1023
+ const r = data ?? { total: 0, succeeded: 0, failed: 0, duplicates: 0 };
1024
+ const result = {
1025
+ ok: r.failed === 0,
1026
+ total: r.total,
1027
+ succeeded: r.succeeded,
1028
+ failed: r.failed,
1029
+ duplicates: r.duplicates
1030
+ };
1031
+ if (r.results && r.results.length > 0) result.results = r.results;
1032
+ return ok(result);
1033
+ }
958
1034
  /** Fetch the schema for this table. */
959
1035
  async schema(opts) {
960
1036
  const { data, error } = await request(this._ctx, {