@dalgo/bigquery 0.0.0-stage → 0.3.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +472 -2
  3. package/dist/analytical/canonical.d.ts +10 -0
  4. package/dist/analytical/canonical.d.ts.map +1 -0
  5. package/dist/analytical/canonical.js +50 -0
  6. package/dist/analytical/client.d.ts +84 -0
  7. package/dist/analytical/client.d.ts.map +1 -0
  8. package/dist/analytical/client.js +730 -0
  9. package/dist/analytical/deadline.d.ts +13 -0
  10. package/dist/analytical/deadline.d.ts.map +1 -0
  11. package/dist/analytical/deadline.js +23 -0
  12. package/dist/analytical/google-identity.d.ts +40 -0
  13. package/dist/analytical/google-identity.d.ts.map +1 -0
  14. package/dist/analytical/google-identity.js +158 -0
  15. package/dist/analytical/ledger.d.ts +80 -0
  16. package/dist/analytical/ledger.d.ts.map +1 -0
  17. package/dist/analytical/ledger.js +89 -0
  18. package/dist/analytical/metadata-client.d.ts +66 -0
  19. package/dist/analytical/metadata-client.d.ts.map +1 -0
  20. package/dist/analytical/metadata-client.js +247 -0
  21. package/dist/analytical/metadata-harness.d.ts +28 -0
  22. package/dist/analytical/metadata-harness.d.ts.map +1 -0
  23. package/dist/analytical/metadata-harness.js +127 -0
  24. package/dist/analytical/metadata.d.ts +8 -0
  25. package/dist/analytical/metadata.d.ts.map +1 -0
  26. package/dist/analytical/metadata.js +155 -0
  27. package/dist/analytical/protocol.d.ts +157 -0
  28. package/dist/analytical/protocol.d.ts.map +1 -0
  29. package/dist/analytical/protocol.js +320 -0
  30. package/dist/analytical/public-metadata.d.ts +29 -0
  31. package/dist/analytical/public-metadata.d.ts.map +1 -0
  32. package/dist/analytical/public-metadata.js +70 -0
  33. package/dist/analytical/transport.d.ts +39 -0
  34. package/dist/analytical/transport.d.ts.map +1 -0
  35. package/dist/analytical/transport.js +224 -0
  36. package/dist/analytical/values.d.ts +18 -0
  37. package/dist/analytical/values.d.ts.map +1 -0
  38. package/dist/analytical/values.js +157 -0
  39. package/dist/analytical/wire.d.ts +26 -0
  40. package/dist/analytical/wire.d.ts.map +1 -0
  41. package/dist/analytical/wire.js +141 -0
  42. package/dist/analytical.d.ts +27 -0
  43. package/dist/analytical.d.ts.map +1 -0
  44. package/dist/analytical.js +14 -0
  45. package/dist/database.d.ts +35 -0
  46. package/dist/database.d.ts.map +1 -0
  47. package/dist/database.js +437 -0
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +2 -0
  51. package/dist/sql.d.ts +29 -0
  52. package/dist/sql.d.ts.map +1 -0
  53. package/dist/sql.js +198 -0
  54. package/dist/types.d.ts +49 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +1 -0
  57. package/package.json +58 -4
@@ -0,0 +1,13 @@
1
+ /** Millisecond timestamps from the trusted run ledger. This never starts a run.
2
+ * Only explicit status/cancel use control=true; cumulative bytes stay charged.
3
+ */
4
+ export interface DeadlineInput {
5
+ readonly now: number;
6
+ readonly executionDeadline: number;
7
+ readonly callerDeadline?: number;
8
+ readonly httpLimitMs: number;
9
+ readonly control: boolean;
10
+ readonly bytesRemaining: bigint;
11
+ }
12
+ export declare function operationDeadline(input: DeadlineInput): number;
13
+ //# sourceMappingURL=deadline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../../src/analytical/deadline.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAe9D"}
@@ -0,0 +1,23 @@
1
+ import { fail } from "./wire.js";
2
+ export function operationDeadline(input) {
3
+ const { now, executionDeadline, callerDeadline, httpLimitMs, control, bytesRemaining } = input;
4
+ if (typeof bytesRemaining !== "bigint")
5
+ fail("invalid_input");
6
+ if (bytesRemaining <= 0n)
7
+ fail("response_limit");
8
+ if (![now, executionDeadline, httpLimitMs, ...(callerDeadline === undefined ? [] : [callerDeadline])].every(Number.isSafeInteger)
9
+ || httpLimitMs <= 0 || httpLimitMs > 15000 || typeof control !== "boolean"
10
+ || !Number.isSafeInteger(now + httpLimitMs))
11
+ fail("invalid_input");
12
+ let deadline = now + httpLimitMs;
13
+ if (!control) {
14
+ if (now >= executionDeadline)
15
+ fail("local_stopped");
16
+ deadline = Math.min(deadline, executionDeadline);
17
+ }
18
+ if (callerDeadline !== undefined)
19
+ deadline = Math.min(deadline, callerDeadline);
20
+ if (now >= deadline)
21
+ fail("local_stopped");
22
+ return deadline;
23
+ }
@@ -0,0 +1,40 @@
1
+ import { type Principal } from "./protocol.js";
2
+ import { type IdentityProvider, type SafeFetch, type TrustedIdentity } from "./transport.js";
3
+ /** Pass to GIS initTokenClient; consent is triggered by the consumer's user gesture. */
4
+ export declare function googleAuthorizationScopes(options?: {
5
+ email?: boolean;
6
+ cancellation?: boolean;
7
+ }): string;
8
+ /** Structural subset of GIS TokenResponse. Never store or log this object. */
9
+ export interface GoogleTokenResponse {
10
+ readonly access_token?: string;
11
+ readonly token_type?: string;
12
+ readonly expires_in?: number;
13
+ readonly scope?: string;
14
+ readonly error?: string;
15
+ }
16
+ export interface GoogleIdentitySummary {
17
+ readonly principal: Principal;
18
+ readonly expiresAt: number;
19
+ readonly read: true;
20
+ readonly cancel: boolean;
21
+ /** Optional verified display label; never an identity or account-linkage key. */
22
+ readonly email?: string;
23
+ }
24
+ export interface GoogleIdentityConfig {
25
+ readonly fetch?: SafeFetch;
26
+ readonly now?: () => number;
27
+ readonly timeoutMs?: number;
28
+ }
29
+ /** Memory-only GIS token provider. It never prompts, refreshes, links Firebase
30
+ * accounts or chooses a project. Connect only from the real GIS callback; call
31
+ * disconnect on app sign-out, local disconnect and execution-account change.
32
+ */
33
+ export declare class GoogleTokenIdentityProvider implements IdentityProvider {
34
+ #private;
35
+ constructor(config?: GoogleIdentityConfig);
36
+ disconnect(): void;
37
+ connect(response: GoogleTokenResponse, signal?: AbortSignal): Promise<GoogleIdentitySummary>;
38
+ authorize(signal: AbortSignal): Promise<TrustedIdentity>;
39
+ }
40
+ //# sourceMappingURL=google-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-identity.d.ts","sourceRoot":"","sources":["../../src/analytical/google-identity.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEtE,OAAO,EAAa,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAQxG,wFAAwF;AACxF,wBAAgB,yBAAyB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAE3G;AAED,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,iFAAiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,qBAAa,2BAA4B,YAAW,gBAAgB;;gBAO/C,MAAM,GAAE,oBAAyB;IAM7C,UAAU,IAAI,IAAI;IAMZ,OAAO,CAAC,QAAQ,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAkF5F,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;CAOtE"}
@@ -0,0 +1,158 @@
1
+ import { AnalyticalError, fail, isObject, parseJSON } from "./wire.js";
2
+ import { cloneFrozen, nonempty } from "./protocol.js";
3
+ import { opaqueID } from "./ledger.js";
4
+ import { abortable } from "./transport.js";
5
+ const readonlyScope = "https://www.googleapis.com/auth/bigquery.readonly";
6
+ const cancelScope = "https://www.googleapis.com/auth/bigquery";
7
+ const discoveryURL = "https://accounts.google.com/.well-known/openid-configuration";
8
+ const userInfoURL = "https://openidconnect.googleapis.com/v1/userinfo";
9
+ const maxBodyBytes = 64 * 1024;
10
+ /** Pass to GIS initTokenClient; consent is triggered by the consumer's user gesture. */
11
+ export function googleAuthorizationScopes(options = {}) {
12
+ return ["openid", options.cancellation ? cancelScope : readonlyScope, ...(options.email ? ["email"] : [])].join(" ");
13
+ }
14
+ /** Memory-only GIS token provider. It never prompts, refreshes, links Firebase
15
+ * accounts or chooses a project. Connect only from the real GIS callback; call
16
+ * disconnect on app sign-out, local disconnect and execution-account change.
17
+ */
18
+ export class GoogleTokenIdentityProvider {
19
+ #fetch;
20
+ #now;
21
+ #timeout;
22
+ #revision = 0;
23
+ #identity;
24
+ #pending;
25
+ constructor(config = {}) {
26
+ this.#fetch = config.fetch ?? ((url, init) => globalThis.fetch(url, init));
27
+ this.#now = config.now ?? Date.now;
28
+ this.#timeout = config.timeoutMs ?? 15000;
29
+ if (!Number.isSafeInteger(this.#timeout) || this.#timeout < 1 || this.#timeout > 15000)
30
+ fail("invalid_input");
31
+ }
32
+ disconnect() {
33
+ this.#revision += 1;
34
+ this.#identity = undefined;
35
+ this.#pending?.abort();
36
+ this.#pending = undefined;
37
+ }
38
+ async connect(response, signal) {
39
+ // A denied/failed token rotation must invalidate the previous approval too.
40
+ this.disconnect();
41
+ const revision = this.#revision;
42
+ const now = this.#now();
43
+ if (signal?.aborted)
44
+ fail("local_stopped");
45
+ if (!response || response.error !== undefined || typeof response.access_token !== "string" ||
46
+ response.access_token.length === 0 || response.access_token.length > 16384 || !/^[A-Za-z0-9._~+/-]+=*$/u.test(response.access_token) ||
47
+ typeof response.token_type !== "string" || response.token_type.toLowerCase() !== "bearer")
48
+ fail("auth_required");
49
+ const token = response.access_token;
50
+ if (!Number.isSafeInteger(now) || !Number.isSafeInteger(response.expires_in) ||
51
+ (response.expires_in ?? 0) < 1 || (response.expires_in ?? 0) > 86400)
52
+ fail("auth_expired");
53
+ const expiresAt = now + response.expires_in * 1000;
54
+ if (!Number.isSafeInteger(expiresAt))
55
+ fail("auth_expired");
56
+ if (typeof response.scope !== "string" || response.scope.length > 8192)
57
+ fail("scope_missing");
58
+ const grants = new Set(response.scope.split(/\s+/u));
59
+ if (!grants.has("openid") || !(grants.has(readonlyScope) || grants.has(cancelScope)))
60
+ fail("scope_missing");
61
+ const controller = new AbortController();
62
+ this.#pending = controller;
63
+ const abort = () => controller.abort();
64
+ signal?.addEventListener("abort", abort, { once: true });
65
+ const deadline = Math.min(now + this.#timeout, expiresAt);
66
+ const timer = setTimeout(abort, deadline - now);
67
+ let bytes = 0;
68
+ const get = async (url, authenticated) => {
69
+ if (controller.signal.aborted || this.#now() >= deadline)
70
+ fail("local_stopped");
71
+ const result = await abortable(this.#fetch(url, {
72
+ method: "GET", redirect: "error", credentials: "omit", cache: "no-store", signal: controller.signal,
73
+ headers: { Accept: "application/json", ...(authenticated ? { Authorization: `Bearer ${token}` } : {}) },
74
+ }), controller.signal);
75
+ if (result.redirected || result.status >= 300 && result.status < 400)
76
+ fail("auth_required");
77
+ const reader = result.body?.getReader();
78
+ if (!reader)
79
+ fail("malformed_wire");
80
+ const chunks = [];
81
+ let length = 0;
82
+ try {
83
+ while (true) {
84
+ const chunk = await abortable(reader.read(), controller.signal);
85
+ if (chunk.done)
86
+ break;
87
+ length += chunk.value.byteLength;
88
+ bytes += chunk.value.byteLength;
89
+ if (length > maxBodyBytes || bytes > 2 * maxBodyBytes)
90
+ fail("response_limit");
91
+ chunks.push(chunk.value);
92
+ }
93
+ }
94
+ finally {
95
+ void reader.cancel().catch(() => { });
96
+ reader.releaseLock();
97
+ }
98
+ if (controller.signal.aborted || this.#now() >= deadline)
99
+ fail("local_stopped");
100
+ if (!result.ok)
101
+ fail(result.status === 401 ? "auth_expired" : "auth_required");
102
+ const raw = new Uint8Array(length);
103
+ let offset = 0;
104
+ for (const chunk of chunks) {
105
+ raw.set(chunk, offset);
106
+ offset += chunk.length;
107
+ }
108
+ const body = parseJSON(raw, maxBodyBytes);
109
+ if (!isObject(body))
110
+ fail("malformed_wire");
111
+ return body;
112
+ };
113
+ try {
114
+ // Discovery is public and never receives the access token. Source metadata
115
+ // cannot override either endpoint, even with a Google-looking URL.
116
+ const discovery = await get(discoveryURL, false);
117
+ if (discovery.issuer !== "https://accounts.google.com" || discovery.userinfo_endpoint !== userInfoURL)
118
+ fail("auth_required");
119
+ const user = await get(userInfoURL, true);
120
+ if (typeof user.sub !== "string" || user.sub.trim().length === 0)
121
+ fail("auth_required");
122
+ nonempty(user.sub);
123
+ if (this.#now() >= expiresAt)
124
+ fail("auth_expired");
125
+ if (controller.signal.aborted || revision !== this.#revision)
126
+ fail("local_stopped");
127
+ const principal = { kind: "google-user", subject: user.sub, generation: opaqueID() };
128
+ const identity = cloneFrozen({ principal, accessToken: token, expiresAt, read: true, cancel: grants.has(cancelScope) });
129
+ this.#identity = identity;
130
+ return cloneFrozen({ principal, expiresAt, read: true, cancel: identity.cancel,
131
+ ...(typeof user.email === "string" && user.email.length <= 320 && user.email_verified === true ? { email: user.email } : {}),
132
+ });
133
+ }
134
+ catch (error) {
135
+ if (error instanceof AnalyticalError)
136
+ throw error;
137
+ return fail(controller.signal.aborted ? "local_stopped" : "auth_required");
138
+ }
139
+ finally {
140
+ clearTimeout(timer);
141
+ signal?.removeEventListener("abort", abort);
142
+ if (this.#pending === controller)
143
+ this.#pending = undefined;
144
+ }
145
+ }
146
+ async authorize(signal) {
147
+ if (signal.aborted)
148
+ fail("local_stopped");
149
+ const identity = this.#identity;
150
+ if (!identity)
151
+ fail("auth_required");
152
+ if (this.#now() >= identity.expiresAt) {
153
+ this.#identity = undefined;
154
+ fail("auth_expired");
155
+ }
156
+ return identity;
157
+ }
158
+ }
@@ -0,0 +1,80 @@
1
+ import type { Bounds, Execution, Observation, ReadPlan, Receipt, SchemaField } from "./protocol.js";
2
+ export interface Preview {
3
+ readonly nonce: string;
4
+ readonly plan: ReadPlan;
5
+ readonly observation: Observation;
6
+ readonly policyDigest: string;
7
+ readonly execution: Execution;
8
+ readonly bounds: Bounds;
9
+ readonly estimatedBytes: string;
10
+ readonly createdAt: string;
11
+ readonly expiresAt: string;
12
+ readonly approvalDigest: string;
13
+ }
14
+ export interface CursorState {
15
+ version: 1;
16
+ runId: string;
17
+ job: NonNullable<Receipt["job"]>;
18
+ planDigest: string;
19
+ schemaDigest: string;
20
+ principal: Execution["principal"];
21
+ pageToken: string | null;
22
+ pageSize: number;
23
+ offset: number;
24
+ pageDigest: string;
25
+ ordinal: number;
26
+ counters: Receipt["counters"];
27
+ ledgerRef: string;
28
+ }
29
+ export interface RunRecord {
30
+ receipt: Receipt;
31
+ preview: Preview;
32
+ activePrincipal: Execution["principal"];
33
+ reservation: string;
34
+ pageToken: string | null;
35
+ nextToken: string | null;
36
+ pageDigest: string;
37
+ offset: number;
38
+ pageOrdinal: number;
39
+ pageDone: boolean;
40
+ schema: readonly SchemaField[];
41
+ totalRows?: string;
42
+ seenTokens: string[];
43
+ cursor: CursorState | null;
44
+ }
45
+ export interface LedgerState {
46
+ version: 1;
47
+ previews: Record<string, {
48
+ preview: Preview;
49
+ used: boolean;
50
+ }>;
51
+ runs: Record<string, RunRecord>;
52
+ }
53
+ /** Atomic synchronous mutators commit only on success; leases exclude concurrent
54
+ * network/delivery operations on one run across every participating tab/client.
55
+ * Implementations must persist the same session across deliberate Resume.
56
+ */
57
+ export interface Ledger {
58
+ update<T>(mutator: (state: LedgerState) => T): Promise<T>;
59
+ withLease<T>(runId: string, signal: AbortSignal | undefined, operation: () => Promise<T>): Promise<T>;
60
+ }
61
+ /** Deterministic test-only ledger. Production callers must inject shared durable
62
+ * storage; the client never creates a fallback or silently renews a session.
63
+ */
64
+ export declare class MemoryLedger implements Ledger {
65
+ #private;
66
+ update<T>(mutator: (state: LedgerState) => T): Promise<T>;
67
+ withLease<T>(runId: string, signal: AbortSignal | undefined, operation: () => Promise<T>): Promise<T>;
68
+ }
69
+ /** Browser durable session storage: one explicitly chosen database/session name
70
+ * shared by all tabs. IndexedDB serializes state updates; Web Locks own run
71
+ * operations and release them automatically when their tab exits.
72
+ */
73
+ export declare class IndexedDBLedger implements Ledger {
74
+ #private;
75
+ constructor(sessionName: string);
76
+ update<T>(mutator: (state: LedgerState) => T): Promise<T>;
77
+ withLease<T>(runId: string, signal: AbortSignal | undefined, operation: () => Promise<T>): Promise<T>;
78
+ }
79
+ export declare function opaqueID(): string;
80
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/analytical/ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { AnalyticalError, fail } from "./wire.js";
2
+ const empty = () => ({
3
+ version: 1, previews: Object.create(null), runs: Object.create(null)
4
+ });
5
+ /** Deterministic test-only ledger. Production callers must inject shared durable
6
+ * storage; the client never creates a fallback or silently renews a session.
7
+ */
8
+ export class MemoryLedger {
9
+ #state = empty();
10
+ #leases = new Set();
11
+ async update(mutator) {
12
+ const next = structuredClone(this.#state);
13
+ const result = mutator(next);
14
+ const returned = structuredClone(result);
15
+ this.#state = next;
16
+ return returned;
17
+ }
18
+ async withLease(runId, signal, operation) {
19
+ if (signal?.aborted || this.#leases.has(runId))
20
+ fail("local_stopped");
21
+ this.#leases.add(runId);
22
+ try {
23
+ return await operation();
24
+ }
25
+ finally {
26
+ this.#leases.delete(runId);
27
+ }
28
+ }
29
+ }
30
+ /** Browser durable session storage: one explicitly chosen database/session name
31
+ * shared by all tabs. IndexedDB serializes state updates; Web Locks own run
32
+ * operations and release them automatically when their tab exits.
33
+ */
34
+ export class IndexedDBLedger {
35
+ #database;
36
+ #lockPrefix;
37
+ constructor(sessionName) {
38
+ if (typeof sessionName !== "string" || !/^[A-Za-z0-9_-]{1,128}$/u.test(sessionName) || !globalThis.indexedDB || !globalThis.navigator?.locks)
39
+ fail("invalid_input");
40
+ this.#lockPrefix = `dalgo-bigquery:${sessionName}:`;
41
+ this.#database = new Promise((resolve, reject) => {
42
+ const request = indexedDB.open(`dalgo-bigquery-${sessionName}`, 1);
43
+ request.onupgradeneeded = () => { request.result.createObjectStore("session"); };
44
+ request.onsuccess = () => { const database = request.result; database.onversionchange = () => database.close(); resolve(database); };
45
+ request.onerror = () => reject(new AnalyticalError("invalid_input"));
46
+ request.onblocked = () => reject(new AnalyticalError("invalid_input"));
47
+ });
48
+ }
49
+ async update(mutator) {
50
+ const database = await this.#database;
51
+ return new Promise((resolve, reject) => {
52
+ const transaction = database.transaction("session", "readwrite", {
53
+ durability: "strict"
54
+ });
55
+ const store = transaction.objectStore("session");
56
+ const request = store.get("state");
57
+ let result;
58
+ let failure;
59
+ request.onsuccess = () => {
60
+ try {
61
+ const state = (request.result ?? empty());
62
+ if (state.version !== 1 || !state.runs || !state.previews)
63
+ fail("invalid_input");
64
+ result = structuredClone(mutator(state));
65
+ store.put(state, "state");
66
+ }
67
+ catch (error) {
68
+ failure = error;
69
+ transaction.abort();
70
+ }
71
+ };
72
+ transaction.oncomplete = () => resolve(result);
73
+ transaction.onabort = () => reject(failure ?? new AnalyticalError("invalid_input"));
74
+ transaction.onerror = () => reject(failure ?? new AnalyticalError("invalid_input"));
75
+ });
76
+ }
77
+ async withLease(runId, signal, operation) {
78
+ if (signal?.aborted || !/^[0-9a-f]{48}$/u.test(runId))
79
+ fail("local_stopped");
80
+ return navigator.locks.request(this.#lockPrefix + runId, {
81
+ ifAvailable: true
82
+ }, async (lock) => {
83
+ if (lock === null || signal?.aborted)
84
+ fail("local_stopped");
85
+ return operation();
86
+ });
87
+ }
88
+ }
89
+ export function opaqueID() { const bytes = crypto.getRandomValues(new Uint8Array(24)); return Array.from(bytes, byte => byte.toString(16).padStart(2, "0")).join(""); }
@@ -0,0 +1,66 @@
1
+ import { type Bounds, type Principal } from "./protocol.js";
2
+ import { type Clock, type IdentityProvider, type SafeFetch } from "./transport.js";
3
+ import { type JsonValue } from "./wire.js";
4
+ /** A trusted consumer allowlist, not a locator accepted from user input. */
5
+ export interface MetadataSource {
6
+ readonly sourceId: string;
7
+ readonly sourceProject: string;
8
+ readonly datasetId: string;
9
+ readonly tableId: string;
10
+ }
11
+ /** The application owner's explicit metadata consent is distinct from Google
12
+ * OAuth grants. The protected callback must read current owner-scoped state. */
13
+ export interface MetadataConsent {
14
+ readonly purpose: "metadata-only";
15
+ readonly ownerId: string;
16
+ readonly consentId: string;
17
+ readonly source: MetadataSource;
18
+ readonly principal: Principal;
19
+ readonly selectedJobProject: string;
20
+ }
21
+ /** One synchronous snapshot of current protected owner consent and verified
22
+ * Google identity. Invalidate it before sign-out/account/token changes begin.
23
+ * Never construct this from caller JSON, cached consent or an unverified token. */
24
+ export interface MetadataCurrentBinding {
25
+ readonly consent: MetadataConsent;
26
+ readonly read: boolean;
27
+ readonly expiresAt: number;
28
+ }
29
+ export type MetadataLimits = Pick<Bounds, "responseBytes" | "totalResponseBytes" | "wallMs" | "httpMs">;
30
+ export interface MetadataClientConfig {
31
+ readonly sources: readonly MetadataSource[];
32
+ readonly provider: IdentityProvider;
33
+ readonly authorizeMetadata: (source: MetadataSource, signal: AbortSignal) => Promise<MetadataConsent>;
34
+ readonly currentMetadataBinding: (source: MetadataSource) => MetadataCurrentBinding | undefined;
35
+ readonly limits?: Partial<MetadataLimits>;
36
+ readonly fetch?: SafeFetch;
37
+ readonly clock?: Clock;
38
+ }
39
+ export interface MetadataOptions {
40
+ readonly signal?: AbortSignal;
41
+ readonly deadline?: number;
42
+ }
43
+ /** Deliberately partial native metadata projection. Nested schema/configuration
44
+ * is preserved as lossless JSON; this is not an execution schema or rights proof. */
45
+ export interface MetadataDiscovery {
46
+ readonly source: MetadataSource;
47
+ readonly consent: MetadataConsent;
48
+ readonly dataset: Readonly<Record<string, JsonValue>>;
49
+ readonly table: Readonly<Record<string, JsonValue>>;
50
+ readonly observedAt: string;
51
+ readonly responseBytes: number;
52
+ readonly status: "inactive";
53
+ readonly queryAdmission: "blocked";
54
+ readonly costAdmission: "not-granted";
55
+ readonly projectPermissions: "unverified";
56
+ readonly billing: "unverified";
57
+ readonly sourceRights: "unreviewed";
58
+ readonly providerRetentionAuthorization: "not-granted";
59
+ }
60
+ /** No query, dry-run, row, ledger, export or cost-admission method exists here. */
61
+ export declare class BigQueryMetadataClient {
62
+ #private;
63
+ constructor(config: MetadataClientConfig);
64
+ discover(sourceId: string, options?: MetadataOptions): Promise<MetadataDiscovery>;
65
+ }
66
+ //# sourceMappingURL=metadata-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-client.d.ts","sourceRoot":"","sources":["../../src/analytical/metadata-client.ts"],"names":[],"mappings":"AACA,OAAO,EAA4G,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AACtK,OAAO,EAAmC,KAAK,KAAK,EAAE,KAAK,gBAAgB,EAAuB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzI,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAElE,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AACD;gFACgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AACD;;mFAEmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;AACxG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACtG,QAAQ,CAAC,sBAAsB,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,sBAAsB,GAAG,SAAS,CAAC;IAChG,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AACD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AACD;qFACqF;AACrF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,kBAAkB,EAAE,YAAY,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,8BAA8B,EAAE,aAAa,CAAC;CACxD;AAoED,mFAAmF;AACnF,qBAAa,sBAAsB;;gBAQd,MAAM,EAAE,oBAAoB;IAclC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA6GnG"}