@flytedesk/app-kit 3.2.3 → 5.0.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.
Files changed (76) hide show
  1. package/README.md +130 -10
  2. package/dist/auth/client/httpClient.d.ts +79 -1
  3. package/dist/auth/client/httpClient.js +185 -16
  4. package/dist/auth/client/httpClient.js.map +1 -1
  5. package/dist/auth/client/index.d.ts +4 -4
  6. package/dist/auth/client/index.js +3 -3
  7. package/dist/auth/client/index.js.map +1 -1
  8. package/dist/auth/client/useAuthSession.d.ts +61 -13
  9. package/dist/auth/client/useAuthSession.js +185 -42
  10. package/dist/auth/client/useAuthSession.js.map +1 -1
  11. package/dist/auth/errors.d.ts +30 -7
  12. package/dist/auth/errors.js +38 -8
  13. package/dist/auth/errors.js.map +1 -1
  14. package/dist/auth/guards.d.ts +12 -0
  15. package/dist/auth/guards.js +24 -6
  16. package/dist/auth/guards.js.map +1 -1
  17. package/dist/auth/index.d.ts +4 -4
  18. package/dist/auth/index.js +2 -2
  19. package/dist/auth/index.js.map +1 -1
  20. package/dist/auth/oidc-client.js +12 -2
  21. package/dist/auth/oidc-client.js.map +1 -1
  22. package/dist/auth/plugin.d.ts +6 -0
  23. package/dist/auth/plugin.js +532 -304
  24. package/dist/auth/plugin.js.map +1 -1
  25. package/dist/auth/shared-types.d.ts +28 -1
  26. package/dist/auth/shared-types.js +35 -0
  27. package/dist/auth/shared-types.js.map +1 -1
  28. package/dist/auth/silentAuthPage.d.ts +20 -0
  29. package/dist/auth/silentAuthPage.js +75 -0
  30. package/dist/auth/silentAuthPage.js.map +1 -0
  31. package/dist/auth/testing/fake-idp.d.ts +35 -0
  32. package/dist/auth/testing/fake-idp.js +86 -2
  33. package/dist/auth/testing/fake-idp.js.map +1 -1
  34. package/dist/auth/testing/index.d.ts +3 -1
  35. package/dist/auth/testing/index.js +3 -1
  36. package/dist/auth/testing/index.js.map +1 -1
  37. package/dist/auth/testing/memory-store.d.ts +44 -0
  38. package/dist/auth/testing/memory-store.js +120 -0
  39. package/dist/auth/testing/memory-store.js.map +1 -0
  40. package/dist/auth/tokens.d.ts +24 -0
  41. package/dist/auth/tokens.js +31 -1
  42. package/dist/auth/tokens.js.map +1 -1
  43. package/dist/auth/types.d.ts +231 -96
  44. package/dist/auth/unavailable.d.ts +10 -0
  45. package/dist/auth/unavailable.js +11 -0
  46. package/dist/auth/unavailable.js.map +1 -0
  47. package/dist/bigquery/client.d.ts +4 -4
  48. package/dist/bigquery/client.js +2 -2
  49. package/dist/bigquery/client.js.map +1 -1
  50. package/dist/bigquery/errors.d.ts +14 -3
  51. package/dist/bigquery/errors.js +54 -13
  52. package/dist/bigquery/errors.js.map +1 -1
  53. package/dist/bigquery/index.d.ts +6 -4
  54. package/dist/bigquery/index.js +4 -2
  55. package/dist/bigquery/index.js.map +1 -1
  56. package/dist/bigquery/load.d.ts +25 -7
  57. package/dist/bigquery/load.js +61 -31
  58. package/dist/bigquery/load.js.map +1 -1
  59. package/dist/bigquery/query.d.ts +9 -2
  60. package/dist/bigquery/query.js +38 -16
  61. package/dist/bigquery/query.js.map +1 -1
  62. package/dist/bigquery/types.d.ts +55 -16
  63. package/dist/flags/plugin.js +4 -20
  64. package/dist/flags/plugin.js.map +1 -1
  65. package/dist/postgres/advisoryLock.d.ts +58 -0
  66. package/dist/postgres/advisoryLock.js +71 -0
  67. package/dist/postgres/advisoryLock.js.map +1 -0
  68. package/dist/postgres/index.d.ts +6 -0
  69. package/dist/postgres/index.js +7 -0
  70. package/dist/postgres/index.js.map +1 -0
  71. package/dist/profile/plugin.js +3 -20
  72. package/dist/profile/plugin.js.map +1 -1
  73. package/package.json +7 -2
  74. package/scripts/pending-release-count.mjs +0 -37
  75. package/scripts/release.sh +0 -126
  76. package/scripts/release.test.ts +0 -205
@@ -42,6 +42,20 @@ export class DryRunBudgetExceededError extends BigQueryReadError {
42
42
  this.name = "DryRunBudgetExceededError";
43
43
  }
44
44
  }
45
+ /** BigQuery itself refused to run a real job because it would bill more than
46
+ * the `maximumBytesBilled` passed to `runQuery` — distinct from
47
+ * `DryRunBudgetExceededError`, which is this module's own client-side check
48
+ * against a *dry-run* estimate. BigQuery reports this as an `invalidQuery`-
49
+ * reasoned error whose message names "bytes billed" (see
50
+ * `MAXIMUM_BYTES_BILLED_REASON_TEXT` below); this class exists so a caller
51
+ * can branch on the billing cap specifically instead of catching a generic
52
+ * BadQueryError. */
53
+ export class MaximumBytesBilledExceededError extends BigQueryReadError {
54
+ constructor(message, cause) {
55
+ super(message, cause);
56
+ this.name = "MaximumBytesBilledExceededError";
57
+ }
58
+ }
45
59
  /** Reason codes the Google APIs client library attaches to a rejected job
46
60
  * promise's `.errors[]` — see
47
61
  * https://cloud.google.com/bigquery/docs/error-messages for the full list.
@@ -63,6 +77,35 @@ const BAD_QUERY_REASONS = new Set([
63
77
  * almost always succeeds seconds later, so callers (see `loadRowsWithRetry`
64
78
  * in ./load.js) retry on this and only this. */
65
79
  const RATE_LIMIT_REASONS = new Set(["rateLimitExceeded", "quotaExceeded"]);
80
+ const RATE_LIMIT_REASON_TEXT = new RegExp([...RATE_LIMIT_REASONS].join("|"));
81
+ /** BigQuery reports a `maximumBytesBilled` refusal as an `invalidQuery`-
82
+ * reasoned error whose message reads e.g. "Query exceeded limit for bytes
83
+ * billed: 123456789. Limit: 100000000." — text-matched (case-insensitively)
84
+ * since it shares its `reason` with every other bad-query case. */
85
+ const MAXIMUM_BYTES_BILLED_REASON_TEXT = /bytes billed/i;
86
+ /** The structured `errors[]` entry that carries a rate-limit reason, wherever it
87
+ * sits in the list — so a rate limit reported as a later entry is both detected
88
+ * and described by its own message, not an unrelated sibling's. */
89
+ function rateLimitCause(apiErr) {
90
+ return apiErr?.errors?.find((cause) => cause.reason !== undefined && RATE_LIMIT_REASONS.has(cause.reason));
91
+ }
92
+ /**
93
+ * The one definition of "BigQuery rate-limited this call", shared by
94
+ * `classifyBigQueryError` and `isRetryableBigQueryError` so that what gets
95
+ * retried and how a retry-exhausted failure is reported can never disagree.
96
+ * True for any structured `errors[].reason` in RATE_LIMIT_REASONS or, for a raw
97
+ * REST error with no `errors[]`, that reason text in its `message`.
98
+ */
99
+ function isRateLimited(err) {
100
+ if (err instanceof RateLimitExceededError)
101
+ return true;
102
+ if (!err || typeof err !== "object")
103
+ return false;
104
+ const apiErr = err;
105
+ if (rateLimitCause(apiErr))
106
+ return true;
107
+ return typeof apiErr.message === "string" && RATE_LIMIT_REASON_TEXT.test(apiErr.message);
108
+ }
66
109
  /**
67
110
  * Normalizes anything a BigQueryLike call can throw/reject with (a Google API
68
111
  * client error, a job's `status.errorResult`/`status.errors`, or an already-
@@ -73,12 +116,14 @@ export function classifyBigQueryError(err) {
73
116
  if (err instanceof BigQueryReadError)
74
117
  return err;
75
118
  const apiErr = err;
76
- const detail = apiErr?.errors?.[0];
119
+ // The entry that explains the classification: the rate-limit cause when there
120
+ // is one (it decides the class below), otherwise the first reported error.
121
+ const detail = rateLimitCause(apiErr) ?? apiErr?.errors?.[0];
77
122
  const reason = detail?.reason;
78
123
  const message = detail?.message ??
79
124
  apiErr?.message ??
80
125
  (err instanceof Error ? err.message : String(err));
81
- if (reason && RATE_LIMIT_REASONS.has(reason)) {
126
+ if (isRateLimited(err)) {
82
127
  return new RateLimitExceededError(message, err);
83
128
  }
84
129
  if (reason && PERMISSION_DENIED_REASONS.has(reason)) {
@@ -87,6 +132,9 @@ export function classifyBigQueryError(err) {
87
132
  if (apiErr?.code === 403) {
88
133
  return new PermissionDeniedError(message, err);
89
134
  }
135
+ if (MAXIMUM_BYTES_BILLED_REASON_TEXT.test(message)) {
136
+ return new MaximumBytesBilledExceededError(message, err);
137
+ }
90
138
  if (reason && BAD_QUERY_REASONS.has(reason)) {
91
139
  return new BadQueryError(message, err);
92
140
  }
@@ -98,18 +146,11 @@ export function classifyBigQueryError(err) {
98
146
  /**
99
147
  * True only for BigQuery's own rate-limit/quota class of error. Deliberately
100
148
  * narrow: a bad schema, a permissions/auth failure, or any other error must
101
- * keep failing immediately, not get masked behind a retry loop. Checks both
102
- * the structured `errors[].reason` the client library attaches and, as a
103
- * fallback, the reason text embedded in a raw REST error's `message`.
149
+ * keep failing immediately, not get masked behind a retry loop. Retryable
150
+ * exactly when `classifyBigQueryError` would report a RateLimitExceededError
151
+ * (see `isRateLimited`).
104
152
  */
105
153
  export function isRetryableBigQueryError(err) {
106
- if (!err || typeof err !== "object")
107
- return false;
108
- const apiErr = err;
109
- if (apiErr.errors?.some((cause) => cause.reason !== undefined && RATE_LIMIT_REASONS.has(cause.reason))) {
110
- return true;
111
- }
112
- return (typeof apiErr.message === "string" &&
113
- /rateLimitExceeded|quotaExceeded/.test(apiErr.message));
154
+ return isRateLimited(err);
114
155
  }
115
156
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/bigquery/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/bigquery/errors.ts"],"names":[],"mappings":"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"}
@@ -28,7 +28,9 @@
28
28
  * labels: client.buildJobLabels({ actor: userId, purpose: "audience-export" }),
29
29
  * pageSize: 500,
30
30
  * });
31
- * // page.nextPageToken, if present, resumes with { ...same options, pageToken }
31
+ * // page.nextPageToken, if present, resumes on the SAME job with
32
+ * // { ...same options, pageToken: page.nextPageToken, jobId: page.jobId }
33
+ * // (a page token is only valid against the job that produced it)
32
34
  *
33
35
  * const { jobId } = await client.extractTableToGCS({
34
36
  * datasetId: "dataset",
@@ -43,8 +45,8 @@ export type { BigQueryReadClient, CreateBigQueryReadClientOptions, } from "./cli
43
45
  export { runQuery, runQueryAllPages, estimateQueryBytes } from "./query.js";
44
46
  export { extractTableToGCS, getExtractJobStatus, waitForExtractJob, } from "./extract.js";
45
47
  export { loadRowsWithRetry } from "./load.js";
46
- export type { LoadRowsOptions } from "./load.js";
48
+ export type { LoadRowSource, LoadRowsOptions, LoadRowsResult } from "./load.js";
47
49
  export { buildJobLabels, sanitizeLabelValue } from "./labels.js";
48
50
  export { unwrapBigQueryCount, unwrapBigQueryValue } from "./values.js";
49
- export { BadQueryError, BigQueryReadError, DryRunBudgetExceededError, PermissionDeniedError, RateLimitExceededError, classifyBigQueryError, isRetryableBigQueryError, } from "./errors.js";
50
- export type { BigQueryApiErrorDetail, BigQueryCreateQueryJobOptions, BigQueryDatasetLike, BigQueryExtractOptions, BigQueryJobLike, BigQueryJobMetadata, BigQueryLike, BigQueryLoadOptions, BigQueryNextQuery, BigQueryQueryResultsOptions, BigQuerySchemaField, BigQueryTableLike, BigQueryWriteStreamLike, DryRunEstimate, EstimateQueryBytesOptions, ExtractJobHandle, ExtractJobState, ExtractJobStatus, ExtractTableToGCSInput, GcsFileLike, JobLabelInput, QueryPage, RunQueryOptions, WaitForExtractJobOptions, } from "./types.js";
51
+ export { BadQueryError, BigQueryReadError, DryRunBudgetExceededError, MaximumBytesBilledExceededError, PermissionDeniedError, RateLimitExceededError, classifyBigQueryError, isRetryableBigQueryError, } from "./errors.js";
52
+ export type { BigQueryApiErrorDetail, BigQueryCreateQueryJobOptions, BigQueryDatasetLike, BigQueryExtractOptions, BigQueryJobLike, BigQueryJobMetadata, BigQueryLike, BigQueryLoadOptions, BigQueryNextQuery, BigQueryQueryResultsOptions, BigQuerySchemaField, BigQueryTableLike, BigQueryTableReference, DryRunEstimate, EstimateQueryBytesOptions, ExtractJobHandle, ExtractJobState, ExtractJobStatus, ExtractTableToGCSInput, GcsFileLike, JobLabelInput, QueryPage, RunQueryOptions, WaitForExtractJobOptions, } from "./types.js";
@@ -28,7 +28,9 @@
28
28
  * labels: client.buildJobLabels({ actor: userId, purpose: "audience-export" }),
29
29
  * pageSize: 500,
30
30
  * });
31
- * // page.nextPageToken, if present, resumes with { ...same options, pageToken }
31
+ * // page.nextPageToken, if present, resumes on the SAME job with
32
+ * // { ...same options, pageToken: page.nextPageToken, jobId: page.jobId }
33
+ * // (a page token is only valid against the job that produced it)
32
34
  *
33
35
  * const { jobId } = await client.extractTableToGCS({
34
36
  * datasetId: "dataset",
@@ -44,5 +46,5 @@ export { extractTableToGCS, getExtractJobStatus, waitForExtractJob, } from "./ex
44
46
  export { loadRowsWithRetry } from "./load.js";
45
47
  export { buildJobLabels, sanitizeLabelValue } from "./labels.js";
46
48
  export { unwrapBigQueryCount, unwrapBigQueryValue } from "./values.js";
47
- export { BadQueryError, BigQueryReadError, DryRunBudgetExceededError, PermissionDeniedError, RateLimitExceededError, classifyBigQueryError, isRetryableBigQueryError, } from "./errors.js";
49
+ export { BadQueryError, BigQueryReadError, DryRunBudgetExceededError, MaximumBytesBilledExceededError, PermissionDeniedError, RateLimitExceededError, classifyBigQueryError, isRetryableBigQueryError, } from "./errors.js";
48
50
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bigquery/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAMvD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bigquery/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAMvD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,+BAA+B,EAC/B,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,aAAa,CAAC"}
@@ -1,4 +1,12 @@
1
1
  import type { BigQueryLike, BigQueryLoadOptions } from "./types.js";
2
+ /**
3
+ * Opens the rows for one load attempt. Called once per attempt: a retry after
4
+ * a rate-limit error re-uploads everything, so it calls this again and must
5
+ * get the same rows. That is why the source is a function and not an
6
+ * iterable — a generator object can only be consumed once, so a retry handed
7
+ * the same one would silently load nothing.
8
+ */
9
+ export type LoadRowSource<T extends object> = () => AsyncIterable<T> | Iterable<T>;
2
10
  export interface LoadRowsOptions extends BigQueryLoadOptions {
3
11
  datasetId: string;
4
12
  tableId: string;
@@ -6,17 +14,27 @@ export interface LoadRowsOptions extends BigQueryLoadOptions {
6
14
  attempts?: number;
7
15
  /** Delay before the first retry; doubles every subsequent retry. Default 1000ms. */
8
16
  initialDelayMs?: number;
17
+ /** Target size of each NDJSON chunk written to the load job, in bytes. The
18
+ * helper holds at most about one chunk being built plus what the write
19
+ * stream itself buffers. A single row larger than this is written as its
20
+ * own chunk. Default 1 MiB. */
21
+ chunkBytes?: number;
9
22
  /** Injectable for tests — real code always uses a real timer. */
10
23
  sleep?: (ms: number) => Promise<void>;
11
24
  }
25
+ export interface LoadRowsResult {
26
+ /** Rows the successful attempt uploaded. */
27
+ rowCount: number;
28
+ }
12
29
  /**
13
- * Loads `rows` into `datasetId.tableId` in one load job, retrying with
14
- * exponential backoff ONLY when the failure is BigQuery's
30
+ * Streams the rows `source` yields into `datasetId.tableId` in one load job,
31
+ * retrying with exponential backoff ONLY when the failure is BigQuery's
15
32
  * rateLimitExceeded/quotaExceeded class of error (see
16
- * `isRetryableBigQueryError`). Any other error (bad schema, auth failure,
17
- * etc.) is classified and rethrown on the first attempt.
33
+ * `isRetryableBigQueryError`). Any other error (bad schema, auth failure, a
34
+ * throw from the source, etc.) is classified and rethrown on the first
35
+ * attempt. Each attempt calls `source` afresh — see `LoadRowSource`.
18
36
  *
19
- * Pass `writeDisposition: "WRITE_TRUNCATE"` for a full-refresh reload (the
20
- * IPEDS institutions loader's use case); omit it for an append load.
37
+ * Pass `writeDisposition: "WRITE_TRUNCATE"` for a full-refresh reload; omit it
38
+ * for an append load.
21
39
  */
22
- export declare function loadRowsWithRetry<T extends object>(bq: BigQueryLike, options: LoadRowsOptions, rows: T[]): Promise<void>;
40
+ export declare function loadRowsWithRetry<T extends object>(bq: BigQueryLike, options: LoadRowsOptions, source: LoadRowSource<T>): Promise<LoadRowsResult>;
@@ -1,61 +1,91 @@
1
1
  /**
2
- * Load-job capability (AK-14 follow-on, MP-200/PLN-2): truncate-and-reload a
3
- * BigQuery table from an array of rows, with retry-with-backoff limited to
2
+ * Load-job capability (AK-14 follow-on, MP-200/PLN-2): loads a stream of rows
3
+ * into a BigQuery table in one load job, with retry-with-backoff limited to
4
4
  * BigQuery's own rate-limit/quota class of error — generalized out of
5
5
  * media-planner's `apps/api/src/lib/bigqueryLoad.ts` (via flytedesk-id's
6
6
  * verbatim port at `src/lib/bigqueryLoad.ts`, MP-200), which this module
7
- * replaces. Callers needing a periodic full-refresh load (e.g. the IPEDS
8
- * institutions loader) use this instead of keeping their own copy of the
9
- * write-stream + retry plumbing.
7
+ * replaces.
8
+ *
9
+ * Memory is bounded by `chunkBytes`, never by the row count (AK-28): rows are
10
+ * pulled from the caller's source one at a time, serialized into NDJSON chunks
11
+ * of about `chunkBytes`, and piped into the load job's write stream with
12
+ * `stream.pipeline`, which stops pulling from the source whenever the write
13
+ * stream's buffer is full and resumes on its `drain`. A caller that reads its
14
+ * rows page by page (a keyset-paged or cursor read) can therefore load any
15
+ * number of rows in a fixed amount of memory.
10
16
  */
11
- import { BigQueryReadError, classifyBigQueryError, isRetryableBigQueryError } from "./errors.js";
17
+ import { once } from "node:events";
18
+ import { pipeline } from "node:stream/promises";
19
+ import { BigQueryReadError, classifyBigQueryError, isRetryableBigQueryError, } from "./errors.js";
12
20
  const DEFAULT_ATTEMPTS = 5;
13
21
  const DEFAULT_INITIAL_DELAY_MS = 1_000;
22
+ const DEFAULT_CHUNK_BYTES = 1024 * 1024;
14
23
  const DEFAULT_SOURCE_FORMAT = "NEWLINE_DELIMITED_JSON";
15
24
  function defaultSleep(ms) {
16
25
  return new Promise((resolve) => setTimeout(resolve, ms));
17
26
  }
18
- async function runLoadJob(bq, options, rows) {
27
+ /** Serializes `rows` into NDJSON, one `Buffer` of about `chunkBytes` at a
28
+ * time, counting rows into `counter` as they are consumed. */
29
+ async function* ndjsonChunks(rows, chunkBytes, counter) {
30
+ let lines = [];
31
+ let size = 0;
32
+ for await (const row of rows) {
33
+ const line = `${JSON.stringify(row)}\n`;
34
+ lines.push(line);
35
+ size += Buffer.byteLength(line);
36
+ counter.rows += 1;
37
+ if (size >= chunkBytes) {
38
+ yield Buffer.from(lines.join(""));
39
+ lines = [];
40
+ size = 0;
41
+ }
42
+ }
43
+ if (lines.length > 0)
44
+ yield Buffer.from(lines.join(""));
45
+ }
46
+ async function runLoadJob(bq, options, source) {
19
47
  const table = bq.dataset(options.datasetId).table(options.tableId);
20
48
  const createWriteStream = table.createWriteStream;
21
49
  if (!createWriteStream) {
22
50
  throw new BigQueryReadError(`dataset(${options.datasetId}).table(${options.tableId}) does not implement createWriteStream — this BigQueryLike was not constructed for load jobs`);
23
51
  }
24
- const ndjson = rows.map((row) => `${JSON.stringify(row)}\n`);
25
- await new Promise((resolve, reject) => {
26
- const stream = createWriteStream.call(table, {
27
- sourceFormat: options.sourceFormat ?? DEFAULT_SOURCE_FORMAT,
28
- schema: options.schema,
29
- writeDisposition: options.writeDisposition,
30
- createDisposition: options.createDisposition,
31
- labels: options.labels,
32
- jobId: options.jobId,
33
- });
34
- stream.on("error", reject);
35
- stream.on("complete", () => resolve());
36
- for (const line of ndjson)
37
- stream.write(line);
38
- stream.end();
52
+ const stream = createWriteStream.call(table, {
53
+ sourceFormat: options.sourceFormat ?? DEFAULT_SOURCE_FORMAT,
54
+ schema: options.schema,
55
+ writeDisposition: options.writeDisposition,
56
+ createDisposition: options.createDisposition,
57
+ labels: options.labels,
58
+ jobId: options.jobId,
39
59
  });
60
+ const counter = { rows: 0 };
61
+ // The load is done only when BigQuery reports the job "complete" AND the
62
+ // pipeline has flushed every chunk. `once` rejects on the stream's "error",
63
+ // which is how a failed job surfaces; `pipeline` rejects (and destroys the
64
+ // stream, aborting the upload) when the source itself throws.
65
+ await Promise.all([
66
+ once(stream, "complete"),
67
+ pipeline(ndjsonChunks(source(), options.chunkBytes ?? DEFAULT_CHUNK_BYTES, counter), stream),
68
+ ]);
69
+ return { rowCount: counter.rows };
40
70
  }
41
71
  /**
42
- * Loads `rows` into `datasetId.tableId` in one load job, retrying with
43
- * exponential backoff ONLY when the failure is BigQuery's
72
+ * Streams the rows `source` yields into `datasetId.tableId` in one load job,
73
+ * retrying with exponential backoff ONLY when the failure is BigQuery's
44
74
  * rateLimitExceeded/quotaExceeded class of error (see
45
- * `isRetryableBigQueryError`). Any other error (bad schema, auth failure,
46
- * etc.) is classified and rethrown on the first attempt.
75
+ * `isRetryableBigQueryError`). Any other error (bad schema, auth failure, a
76
+ * throw from the source, etc.) is classified and rethrown on the first
77
+ * attempt. Each attempt calls `source` afresh — see `LoadRowSource`.
47
78
  *
48
- * Pass `writeDisposition: "WRITE_TRUNCATE"` for a full-refresh reload (the
49
- * IPEDS institutions loader's use case); omit it for an append load.
79
+ * Pass `writeDisposition: "WRITE_TRUNCATE"` for a full-refresh reload; omit it
80
+ * for an append load.
50
81
  */
51
- export async function loadRowsWithRetry(bq, options, rows) {
82
+ export async function loadRowsWithRetry(bq, options, source) {
52
83
  const attempts = options.attempts ?? DEFAULT_ATTEMPTS;
53
84
  const initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
54
85
  const sleep = options.sleep ?? defaultSleep;
55
86
  for (let attempt = 1;; attempt += 1) {
56
87
  try {
57
- await runLoadJob(bq, options, rows);
58
- return;
88
+ return await runLoadJob(bq, options, source);
59
89
  }
60
90
  catch (err) {
61
91
  if (attempt >= attempts || !isRetryableBigQueryError(err)) {
@@ -1 +1 @@
1
- {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/bigquery/load.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/bigquery/load.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,17 @@
1
1
  import type { BigQueryLike, DryRunEstimate, EstimateQueryBytesOptions, QueryPage, RunQueryOptions } from "./types.js";
2
2
  /** Runs one page of a parameterised, read-only query. Always bind
3
3
  * user-supplied values through `options.params`/`options.types` — never
4
- * string-interpolate them into `options.sql`. */
4
+ * string-interpolate them into `options.sql`.
5
+ *
6
+ * A BigQuery page token is only valid against the job that produced it
7
+ * (AK-26) — passing `pageToken` therefore REQUIRES `jobId` (the `jobId` a
8
+ * prior `QueryPage` returned) so this fetches the *same* job's next page
9
+ * instead of creating a fresh job and handing it a token it never issued. */
5
10
  export declare function runQuery<T = Record<string, unknown>>(bq: BigQueryLike, options: RunQueryOptions): Promise<QueryPage<T>>;
6
11
  /** Pages through every result of a query, yielding one row-array per page.
7
- * Stops once BigQuery reports no further `nextPageToken`. */
12
+ * Stops once BigQuery reports no further `nextPageToken`. Carries the
13
+ * producing job's id forward across pages (AK-26) so every page after the
14
+ * first resumes the *same* job instead of starting a new one. */
8
15
  export declare function runQueryAllPages<T = Record<string, unknown>>(bq: BigQueryLike, options: RunQueryOptions): AsyncGenerator<T[], void, void>;
9
16
  /**
10
17
  * Dry-runs a query to get its billed-byte estimate without actually running
@@ -4,25 +4,41 @@
4
4
  * (AK-11). Inventory-specific SQL stays in media-planner; this module only
5
5
  * ever takes SQL + params from the caller and runs it.
6
6
  */
7
- import { classifyBigQueryError } from "./errors.js";
7
+ import { BadQueryError, classifyBigQueryError } from "./errors.js";
8
8
  import { DryRunBudgetExceededError } from "./errors.js";
9
9
  /** Runs one page of a parameterised, read-only query. Always bind
10
10
  * user-supplied values through `options.params`/`options.types` — never
11
- * string-interpolate them into `options.sql`. */
11
+ * string-interpolate them into `options.sql`.
12
+ *
13
+ * A BigQuery page token is only valid against the job that produced it
14
+ * (AK-26) — passing `pageToken` therefore REQUIRES `jobId` (the `jobId` a
15
+ * prior `QueryPage` returned) so this fetches the *same* job's next page
16
+ * instead of creating a fresh job and handing it a token it never issued. */
12
17
  export async function runQuery(bq, options) {
13
18
  let job;
14
- try {
15
- [job] = await bq.createQueryJob({
16
- query: options.sql,
17
- params: options.params,
18
- types: options.types,
19
- labels: options.labels,
20
- location: options.location,
21
- maxResults: options.pageSize,
22
- });
19
+ if (options.pageToken !== undefined) {
20
+ if (!options.jobId) {
21
+ throw new BadQueryError("runQuery: pageToken requires the jobId of the job that produced it (QueryPage.jobId)");
22
+ }
23
+ job = bq.job(options.jobId);
23
24
  }
24
- catch (err) {
25
- throw classifyBigQueryError(err);
25
+ else {
26
+ try {
27
+ [job] = await bq.createQueryJob({
28
+ query: options.sql,
29
+ params: options.params,
30
+ types: options.types,
31
+ labels: options.labels,
32
+ location: options.location,
33
+ maxResults: options.pageSize,
34
+ maximumBytesBilled: options.maximumBytesBilled !== undefined
35
+ ? String(options.maximumBytesBilled)
36
+ : undefined,
37
+ });
38
+ }
39
+ catch (err) {
40
+ throw classifyBigQueryError(err);
41
+ }
26
42
  }
27
43
  try {
28
44
  const [rows, next] = await job.getQueryResults({
@@ -31,7 +47,7 @@ export async function runQuery(bq, options) {
31
47
  });
32
48
  return {
33
49
  rows,
34
- jobId: job.id ?? "",
50
+ jobId: job.id ?? options.jobId ?? "",
35
51
  nextPageToken: next?.pageToken,
36
52
  };
37
53
  }
@@ -40,15 +56,19 @@ export async function runQuery(bq, options) {
40
56
  }
41
57
  }
42
58
  /** Pages through every result of a query, yielding one row-array per page.
43
- * Stops once BigQuery reports no further `nextPageToken`. */
59
+ * Stops once BigQuery reports no further `nextPageToken`. Carries the
60
+ * producing job's id forward across pages (AK-26) so every page after the
61
+ * first resumes the *same* job instead of starting a new one. */
44
62
  export async function* runQueryAllPages(bq, options) {
45
63
  let pageToken = options.pageToken;
64
+ let jobId = options.jobId;
46
65
  for (;;) {
47
- const page = await runQuery(bq, { ...options, pageToken });
66
+ const page = await runQuery(bq, { ...options, pageToken, jobId });
48
67
  yield page.rows;
49
68
  if (!page.nextPageToken)
50
69
  return;
51
70
  pageToken = page.nextPageToken;
71
+ jobId = page.jobId;
52
72
  }
53
73
  }
54
74
  /**
@@ -76,6 +96,8 @@ export async function estimateQueryBytes(bq, options) {
76
96
  const estimate = {
77
97
  totalBytesProcessed: Number(stats?.totalBytesProcessed ?? 0),
78
98
  cacheHit: stats?.cacheHit ?? false,
99
+ statementType: stats?.statementType,
100
+ referencedTables: stats?.referencedTables,
79
101
  };
80
102
  if (options.maxBytesBilled !== undefined &&
81
103
  estimate.totalBytesProcessed > options.maxBytesBilled) {
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/bigquery/query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/bigquery/query.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@
11
11
  * `@google-cloud/bigquery` calls it made; inventory-specific SQL stays behind
12
12
  * in media-planner.
13
13
  */
14
+ import type { Writable } from "node:stream";
14
15
  /** One structured error entry, same shape the Google APIs client library
15
16
  * attaches to a rejected job promise's `.errors` array. */
16
17
  export interface BigQueryApiErrorDetail {
@@ -23,11 +24,24 @@ export interface BigQueryJobStatus {
23
24
  errorResult?: BigQueryApiErrorDetail | null;
24
25
  errors?: BigQueryApiErrorDetail[];
25
26
  }
27
+ /** A table a dry-run job reports the query as touching — the SDK's
28
+ * `ITableReference` (bigquery.d.ts), all fields optional per the REST API
29
+ * even though BigQuery always populates them in practice. */
30
+ export interface BigQueryTableReference {
31
+ projectId?: string;
32
+ datasetId?: string;
33
+ tableId?: string;
34
+ }
26
35
  export interface BigQueryJobStatistics {
27
36
  query?: {
28
37
  totalBytesProcessed?: string | number;
29
38
  totalBytesBilled?: string | number;
30
39
  cacheHit?: boolean;
40
+ /** The dry-run job's classified statement kind, e.g. `"SELECT"` —
41
+ * `IJobStatistics2.statementType` in the real SDK. */
42
+ statementType?: string;
43
+ /** Every table the query touches — `IJobStatistics2.referencedTables`. */
44
+ referencedTables?: BigQueryTableReference[];
31
45
  };
32
46
  }
33
47
  export interface BigQueryJobMetadata {
@@ -72,6 +86,12 @@ export interface BigQueryCreateQueryJobOptions {
72
86
  location?: string;
73
87
  maxResults?: number;
74
88
  jobId?: string;
89
+ /** BigQuery's own hard billing cap on the real job — the SDK's
90
+ * `IJobConfigurationQuery.maximumBytesBilled` is a decimal string; a job
91
+ * that would bill more than this is refused by BigQuery itself rather
92
+ * than run. Distinct from `EstimateQueryBytesOptions.maxBytesBilled`
93
+ * (a pre-flight dry-run check this module enforces client-side). */
94
+ maximumBytesBilled?: string;
75
95
  }
76
96
  export interface BigQueryExtractOptions {
77
97
  /** `@google-cloud/bigquery`'s own extract-job vocabulary — note this is
@@ -90,15 +110,6 @@ export interface GcsFileLike {
90
110
  readonly name: string;
91
111
  };
92
112
  }
93
- /** Structural subset of Node's own Writable that `Table#createWriteStream`
94
- * needs to expose — the "error"/"complete" events a load-job caller pipes
95
- * rows into and waits on. */
96
- export interface BigQueryWriteStreamLike {
97
- on(event: "error", listener: (err: unknown) => void): this;
98
- on(event: "complete", listener: () => void): this;
99
- write(chunk: string): boolean;
100
- end(): void;
101
- }
102
113
  /** One BigQuery table-schema field, as `createWriteStream`'s `schema` option
103
114
  * (and the REST API's `tables.insert`) accepts it. `fields` recurses for
104
115
  * RECORD/STRUCT columns. */
@@ -124,12 +135,15 @@ export interface BigQueryLoadOptions {
124
135
  }
125
136
  export interface BigQueryTableLike {
126
137
  createExtractJob(destination: GcsFileLike, options?: BigQueryExtractOptions): Promise<[BigQueryJobLike, BigQueryJobMetadata]>;
127
- /** Opens a load-job write stream — the caller pipes newline-delimited rows
128
- * (or other `sourceFormat`) into it, then awaits the stream's "complete"/
129
- * "error" event. Mirrors `@google-cloud/bigquery`'s `Table#createWriteStream`.
130
- * Optional: read-only callers (query/extract) never need it — only
131
- * `loadRowsWithRetry` (./load.js) does, and it fails loudly if missing. */
132
- createWriteStream?(options: BigQueryLoadOptions): BigQueryWriteStreamLike;
138
+ /** Opens a load-job write stream — a real Node `Writable`, since the loader
139
+ * pipes NDJSON into it with `stream.pipeline` and relies on its
140
+ * backpressure (`write()` returning false, then "drain"). The stream emits
141
+ * "complete" once BigQuery finishes the job, or "error" if the job fails.
142
+ * Mirrors `@google-cloud/bigquery`'s `Table#createWriteStream`, which
143
+ * returns a `Writable`. Optional: read-only callers (query/extract) never
144
+ * need it — only `loadRowsWithRetry` (./load.js) does, and it fails loudly
145
+ * if missing. */
146
+ createWriteStream?(options: BigQueryLoadOptions): Writable;
133
147
  }
134
148
  export interface BigQueryDatasetLike {
135
149
  table(id: string): BigQueryTableLike;
@@ -151,8 +165,23 @@ export interface RunQueryOptions {
151
165
  location?: string;
152
166
  /** Page size. Omit to let BigQuery pick a default. */
153
167
  pageSize?: number;
154
- /** Resume a prior page — from `QueryPage.nextPageToken`. */
168
+ /** Resume a prior page — from `QueryPage.nextPageToken`. A page token is
169
+ * only valid against the job that produced it, so `jobId` (from that same
170
+ * `QueryPage.jobId`) is REQUIRED whenever this is set — `runQuery` throws
171
+ * a `BadQueryError` otherwise rather than creating a new job and handing
172
+ * it a token it never issued (AK-26). */
155
173
  pageToken?: string;
174
+ /** The job that produced `pageToken` — required together with it to fetch
175
+ * the next page of that same job instead of starting a new one. */
176
+ jobId?: string;
177
+ /** Hard cap BigQuery itself enforces on the real job — a job that would
178
+ * bill more than this many bytes is refused by BigQuery before running,
179
+ * classified as `MaximumBytesBilledExceededError` (see errors.ts).
180
+ * Distinct from `estimateQueryBytes`'s `maxBytesBilled`, which is a
181
+ * client-side pre-flight check on a *dry-run* estimate — this is the
182
+ * belt to that check's suspenders, since data can grow between a dry
183
+ * run and the real job, or a caller can skip the dry run entirely. */
184
+ maximumBytesBilled?: number;
156
185
  }
157
186
  export interface QueryPage<T = Record<string, unknown>> {
158
187
  rows: T[];
@@ -172,6 +201,16 @@ export interface EstimateQueryBytesOptions {
172
201
  export interface DryRunEstimate {
173
202
  totalBytesProcessed: number;
174
203
  cacheHit: boolean;
204
+ /** The dry run's classified statement kind, e.g. `"SELECT"` — `undefined`
205
+ * when BigQuery's dry-run statistics omitted it. A caller enforcing
206
+ * read-only access should reject anything other than `"SELECT"`. */
207
+ statementType?: string;
208
+ /** Every table the query touches, per BigQuery's own dry-run analysis —
209
+ * `undefined` when the statistics omitted it, `[]` when the query
210
+ * genuinely touches none. The allowlist-enforcement surface: a caller
211
+ * checks every entry against its own read-only table allowlist before
212
+ * ever submitting the real job. */
213
+ referencedTables?: BigQueryTableReference[];
175
214
  }
176
215
  export interface ExtractTableToGCSInput {
177
216
  datasetId: string;