@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.
- package/README.md +130 -10
- package/dist/auth/client/httpClient.d.ts +79 -1
- package/dist/auth/client/httpClient.js +185 -16
- package/dist/auth/client/httpClient.js.map +1 -1
- package/dist/auth/client/index.d.ts +4 -4
- package/dist/auth/client/index.js +3 -3
- package/dist/auth/client/index.js.map +1 -1
- package/dist/auth/client/useAuthSession.d.ts +61 -13
- package/dist/auth/client/useAuthSession.js +185 -42
- package/dist/auth/client/useAuthSession.js.map +1 -1
- package/dist/auth/errors.d.ts +30 -7
- package/dist/auth/errors.js +38 -8
- package/dist/auth/errors.js.map +1 -1
- package/dist/auth/guards.d.ts +12 -0
- package/dist/auth/guards.js +24 -6
- package/dist/auth/guards.js.map +1 -1
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/index.js +2 -2
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oidc-client.js +12 -2
- package/dist/auth/oidc-client.js.map +1 -1
- package/dist/auth/plugin.d.ts +6 -0
- package/dist/auth/plugin.js +532 -304
- package/dist/auth/plugin.js.map +1 -1
- package/dist/auth/shared-types.d.ts +28 -1
- package/dist/auth/shared-types.js +35 -0
- package/dist/auth/shared-types.js.map +1 -1
- package/dist/auth/silentAuthPage.d.ts +20 -0
- package/dist/auth/silentAuthPage.js +75 -0
- package/dist/auth/silentAuthPage.js.map +1 -0
- package/dist/auth/testing/fake-idp.d.ts +35 -0
- package/dist/auth/testing/fake-idp.js +86 -2
- package/dist/auth/testing/fake-idp.js.map +1 -1
- package/dist/auth/testing/index.d.ts +3 -1
- package/dist/auth/testing/index.js +3 -1
- package/dist/auth/testing/index.js.map +1 -1
- package/dist/auth/testing/memory-store.d.ts +44 -0
- package/dist/auth/testing/memory-store.js +120 -0
- package/dist/auth/testing/memory-store.js.map +1 -0
- package/dist/auth/tokens.d.ts +24 -0
- package/dist/auth/tokens.js +31 -1
- package/dist/auth/tokens.js.map +1 -1
- package/dist/auth/types.d.ts +231 -96
- package/dist/auth/unavailable.d.ts +10 -0
- package/dist/auth/unavailable.js +11 -0
- package/dist/auth/unavailable.js.map +1 -0
- package/dist/bigquery/client.d.ts +4 -4
- package/dist/bigquery/client.js +2 -2
- package/dist/bigquery/client.js.map +1 -1
- package/dist/bigquery/errors.d.ts +14 -3
- package/dist/bigquery/errors.js +54 -13
- package/dist/bigquery/errors.js.map +1 -1
- package/dist/bigquery/index.d.ts +6 -4
- package/dist/bigquery/index.js +4 -2
- package/dist/bigquery/index.js.map +1 -1
- package/dist/bigquery/load.d.ts +25 -7
- package/dist/bigquery/load.js +61 -31
- package/dist/bigquery/load.js.map +1 -1
- package/dist/bigquery/query.d.ts +9 -2
- package/dist/bigquery/query.js +38 -16
- package/dist/bigquery/query.js.map +1 -1
- package/dist/bigquery/types.d.ts +55 -16
- package/dist/flags/plugin.js +4 -20
- package/dist/flags/plugin.js.map +1 -1
- package/dist/postgres/advisoryLock.d.ts +58 -0
- package/dist/postgres/advisoryLock.js +71 -0
- package/dist/postgres/advisoryLock.js.map +1 -0
- package/dist/postgres/index.d.ts +6 -0
- package/dist/postgres/index.js +7 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/profile/plugin.js +3 -20
- package/dist/profile/plugin.js.map +1 -1
- package/package.json +7 -2
- package/scripts/pending-release-count.mjs +0 -37
- package/scripts/release.sh +0 -126
- package/scripts/release.test.ts +0 -205
package/dist/bigquery/errors.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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.
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
-
|
|
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":"AAQA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAGxB;IAFlB,YACE,OAAe,EACC,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAU;QAG/B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,OAAO,aAAc,SAAQ,iBAAiB;IAClD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,OAAO,qBAAsB,SAAQ,iBAAiB;IAC1D,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED;;;yEAGyE;AACzE,MAAM,OAAO,sBAAuB,SAAQ,iBAAiB;IAC3D,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED;+CAC+C;AAC/C,MAAM,OAAO,yBAA0B,SAAQ,iBAAiB;IAE5C;IACA;IAFlB,YACkB,mBAA2B,EAC3B,cAAsB;QAEtC,KAAK,CACH,uBAAuB,mBAAmB,sBAAsB,cAAc,cAAc,CAC7F,CAAC;QALc,wBAAmB,GAAnB,mBAAmB,CAAQ;QAC3B,mBAAc,GAAd,cAAc,CAAQ;QAKtC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAQD;;;;0DAI0D;AAC1D,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACxC,cAAc;IACd,WAAW;IACX,yBAAyB;CAC1B,CAAC,CAAC;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,cAAc;IACd,SAAS;IACT,YAAY;IACZ,uBAAuB;CACxB,CAAC,CAAC;AACH;;;iDAGiD;AACjD,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/bigquery/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/bigquery/index.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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";
|
package/dist/bigquery/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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"}
|
package/dist/bigquery/load.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
20
|
-
*
|
|
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,
|
|
40
|
+
export declare function loadRowsWithRetry<T extends object>(bq: BigQueryLike, options: LoadRowsOptions, source: LoadRowSource<T>): Promise<LoadRowsResult>;
|
package/dist/bigquery/load.js
CHANGED
|
@@ -1,61 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Load-job capability (AK-14 follow-on, MP-200/PLN-2):
|
|
3
|
-
* BigQuery table
|
|
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.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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 {
|
|
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
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
49
|
-
*
|
|
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,
|
|
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,
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/bigquery/load.ts"],"names":[],"mappings":"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"}
|
package/dist/bigquery/query.d.ts
CHANGED
|
@@ -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
|
package/dist/bigquery/query.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/bigquery/query.ts"],"names":[],"mappings":"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"}
|
package/dist/bigquery/types.d.ts
CHANGED
|
@@ -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 —
|
|
128
|
-
*
|
|
129
|
-
* "
|
|
130
|
-
*
|
|
131
|
-
* `
|
|
132
|
-
|
|
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;
|