@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DALgo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,473 @@
1
- # Temporary Holding Version
1
+ # DALgo BigQuery package
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The maintained package is `@dalgo/bigquery`. Its new `/analytical` export is a
4
+ browser-compatible analytical protocol for the accepted dual-runtime A0 contract.
5
+ The ordinary package export retains the legacy DALgo record adapter described
6
+ below. The analytical entry does not import a DALgo runtime; consumer integration
7
+ and release acceptance remain separate required gates.
8
+
9
+ ## Metadata-only browser discovery
10
+
11
+ `BigQueryMetadataClient` in `/analytical` makes only bounded `datasets.get` and
12
+ `tables.get` requests against a trusted consumer's closed source allowlist. It
13
+ has no job, dry-run, row, query approval, cost-admission or persistence method.
14
+ Every successful result remains `inactive`, with `queryAdmission: "blocked"`
15
+ and `costAdmission: "not-granted"`. Billing, execution-project permissions and
16
+ source rights remain unverified; provider retention is not authorized.
17
+
18
+ ```ts
19
+ import { BigQueryMetadataClient } from "@dalgo/bigquery/analytical";
20
+
21
+ const metadata = new BigQueryMetadataClient({
22
+ sources: reviewedMetadataLocators,
23
+ provider: verifiedGoogleIdentity,
24
+ authorizeMetadata: prepareCurrentOwnerMetadataConsent,
25
+ currentMetadataBinding: readCurrentJointMetadataBinding,
26
+ });
27
+ const observed = await metadata.discover(selectedAllowlistedSourceId, { signal });
28
+ // Metadata evidence only. Do not activate the source or submit a query.
29
+ ```
30
+
31
+ The protected `authorizeMetadata(source, signal)` integration must return the
32
+ current application owner's explicit metadata consent, its stable consent ID,
33
+ exact allowlisted source, verified Google principal including token generation,
34
+ and selected future job project. This is trusted owner-scoped application state,
35
+ not caller JSON or an OAuth token. Google consent and application-owner consent
36
+ are separate requirements. The mandatory synchronous
37
+ `currentMetadataBinding(source)` callback returns one **current joint snapshot**
38
+ `{ consent, read, expiresAt }`, or `undefined` when revoked. It must read protected
39
+ current application-owner/consent/source/project state together with the current
40
+ verified Google subject/generation/grant/expiry; cached preparation results, caller
41
+ JSON and an async callback do not satisfy this contract. Invalidate this binding
42
+ **before** owner/sign-out/consent/project/source/account/grant changes or token
43
+ connect/disconnect/rotation begin; publish a new binding only after verification
44
+ and explicit current-owner metadata consent. No token belongs in this snapshot.
45
+
46
+ Async preparation/identity checks alone leave a race. The client compares the
47
+ joint synchronous snapshot immediately at every physical GET/retry dispatch,
48
+ after all async checks, and at public result delivery after async work and cleanup.
49
+ No await separates the guard from these boundaries. Current read grant and expiry
50
+ are checked too. Missing, malformed, asynchronous or changed bindings refuse
51
+ requests/delivery with sanitized errors. Reordering separate async checks cannot
52
+ substitute for this guard. Workload identities are excluded from this browser
53
+ slice. Use the existing GIS identity provider and user-triggered read-only consent
54
+ described below; no silent refresh is added. Actual consumer implementation of
55
+ this protected joint state is a required integration/review gate, not provided by
56
+ this metadata-only package.
57
+
58
+ The selected job project is recorded only as unverified future context; it is
59
+ never substituted for the source project or sent as a quota/billing project.
60
+ Only fixed Google HTTPS metadata URLs are constructed. Tokens stay in transient
61
+ Authorization headers; URLs, results and sanitized errors exclude them. The
62
+ consumer must not log tokens/headers or persist metadata/source bodies. Requests
63
+ omit credentials, refuse redirects, and use `cache: "no-store"`. One discovery
64
+ per client runs at a time, with finite wall/HTTP deadlines and shared response
65
+ byte limits (including failed/retried reads); injected integrations that ignore
66
+ abort are still bounded. No background reads or automatic storage are created.
67
+
68
+ Dataset requests use the `METADATA` view, excluding ACL information. Table
69
+ requests use `STORAGE_STATS` because `BASIC` omits `lastModifiedTime`. The result
70
+ is explicitly a partial metadata projection: exact resource references,
71
+ location, etag and last-modified time when supplied, native schema (including
72
+ nested/repeated fields and native descriptor properties), and partition/clustering
73
+ configuration. Unknown execution types/configuration may be observed but are
74
+ never admitted. JSON number lexemes remain `JsonNumber.text` values. Schema
75
+ shape/reference conflicts and malformed wire evidence are refused. Observation
76
+ time and last-modified time do not establish row coverage, freshness, uniqueness,
77
+ semantic compatibility, rights or execution eligibility.
78
+
79
+ Google documents [GIS REST/CORS access](https://developers.google.com/identity/oauth2/web/guides/use-token-model),
80
+ [metadata-only tables.get](https://docs.cloud.google.com/bigquery/docs/reference/rest/v2/tables/get)
81
+ and [datasets.get permissions/views](https://docs.cloud.google.com/bigquery/docs/reference/rest/v2/datasets/get).
82
+ This supports the direct-browser design; deployed OAuth client/origin, CORS,
83
+ owner-consent and live metadata acceptance remain required and unverified here.
84
+ WDI remains inactive: no observation table, live schema or location is invented.
85
+
86
+ [Public datasets](https://docs.cloud.google.com/bigquery/public-data) distinguish
87
+ source storage from execution-project query charges. Free quota is not cost
88
+ admission. Future queries need separate source-rights and provider-retention
89
+ authorization plus reviewed project/location, dry-run/cap approval and same-job
90
+ receipts. [Cost controls](https://docs.cloud.google.com/bigquery/docs/best-practices-costs)
91
+ and [cached results](https://docs.cloud.google.com/bigquery/docs/cached-results)
92
+ confirm that `LIMIT` is not a general scan cap and `useQueryCache: false` does not
93
+ prevent provider result-table materialization. Client no-store/RAM limits cannot
94
+ clear that retention gate.
95
+
96
+ The ordinary export requires `@dalgo/core` with peer range `^0.1.0` and uses
97
+ the exact published `0.1.0` development baseline. A package-specific workspace
98
+ override preserves this registry dependency while other adapters retain their
99
+ separate Git pins. The analytical entry has no DALgo runtime import. Package
100
+ publication and application consumer adoption remain separate gates; this
101
+ contract does not claim compatibility with newer unpublished core source versions.
102
+
103
+ ## Analytical execution
104
+
105
+ `BigQueryAnalyticalClient` requires reviewed source profiles, a protected `prepare`
106
+ callback, a trusted identity provider and durable session storage. The callback
107
+ must re-run the consumer's ordinary read-policy preparation and return its
108
+ canonical query and policy-context digest on every operation. Caller-edited
109
+ configuration, a raw SQL string or an OAuth token decoded by the caller cannot
110
+ substitute for these trusted integrations.
111
+
112
+ ```ts
113
+ import { BigQueryAnalyticalClient, IndexedDBLedger } from "@dalgo/bigquery/analytical";
114
+
115
+ const client = await BigQueryAnalyticalClient.create({
116
+ profiles: reviewedSourceProfiles,
117
+ prepare: prepareProtectedRead,
118
+ provider: verifiedExecutionIdentity,
119
+ ledger: new IndexedDBLedger("explicit-shared-budget-session"),
120
+ });
121
+ const preview = await client.preview({
122
+ jobProject: selectedJobProject,
123
+ principal: verifiedPrincipal,
124
+ maximumBytesBilled: "10000000",
125
+ sessionBudgetBytes: "30000000",
126
+ });
127
+ // Present the exact estimate, source/job project, principal, cap and bounds.
128
+ const approval = await client.approve(preview, explicitlyApprovedDigest);
129
+ const run = await client.execute(approval);
130
+ const page = await run.nextPage();
131
+ ```
132
+
133
+ The compiler accepts only explicit scalar projections, bounded AND/OR predicates,
134
+ comparisons, null checks, IN arrays, scalar order and an explicit limit. Native
135
+ TABLE metadata and reviewed execution-affecting configuration are checked before
136
+ Preview and again before dispatch. An approved run repeats policy, metadata and
137
+ dry-run checks and makes one capped `jobs.query` submission without POST retries.
138
+ Missing job identity after an ambiguous attempt retains the full cap reservation.
139
+ A known job is persisted before cell validation can fail.
140
+
141
+ The fixed Google HTTPS transport rejects redirects and bounds decompressed body
142
+ chunks before the lossless parser runs. It charges retries, malformed responses
143
+ and control responses to the same cumulative byte counter. Injected providers,
144
+ policy preparation and transports are bounded even if they ignore abort signals.
145
+ The identity provider must attest a Google subject verified with the exact
146
+ short-lived access token, or an explicitly configured operator workload subject.
147
+ It must report expiry and current read/cancel grants. Tokens stay in memory and
148
+ are excluded from persisted previews, receipts, cursors and ledger state.
149
+
150
+ `IndexedDBLedger` serializes durable updates and uses Web Locks for per-run
151
+ exclusion across clients/tabs sharing the explicitly chosen session name. The
152
+ session budget is shared by stable subject and job project; changing identity
153
+ generation cannot renew it. `MemoryLedger` is for deterministic tests only;
154
+ there is no automatic memory fallback. Consumers must preserve the durable
155
+ session instead of choosing another database name to continue a stopped run.
156
+
157
+ The ledger retains source/schema, approved user query parameters, job receipts,
158
+ page tokens, counters and page-content hashes for protected same-job Resume.
159
+ It never stores returned result cells, rows, raw response bodies or OAuth tokens.
160
+ Closing a run also clears its internal in-memory row buffer; the consumer owns
161
+ any cells it has already received. No source snapshots or retained result cache
162
+ are created by this module.
163
+
164
+ `GoogleTokenIdentityProvider` verifies a real GIS callback's grants and token
165
+ expiry, fetches fixed Google discovery metadata, then calls its pinned UserInfo
166
+ endpoint with the same access token used by the BigQuery transport. It requires
167
+ `openid` and BigQuery read-only (or explicitly consented cancellation) scope,
168
+ binds the returned stable `sub`, and permits missing email. Every connect attempt
169
+ clears the old authorization and every successful token change gets a new
170
+ principal generation. `authorize` never prompts or silently refreshes.
171
+
172
+ Use `googleAuthorizationScopes()` in a separate GIS `initTokenClient`, then call
173
+ `provider.connect(response)` from its callback. Trigger `requestAccessToken()`
174
+ from a user gesture. The returned connection summary contains no token and can
175
+ be shown separately from the Firebase/DataTug identity. Call `disconnect()` on
176
+ app sign-out, execution-account change and local disconnect; it clears the token
177
+ and aborts a pending identity lookup without revoking other Google grants.
178
+ Cancellation consent uses `googleAuthorizationScopes({ cancellation: true })`;
179
+ it grants the broader BigQuery scope and requires an explicit product action.
180
+ OAuth client setup and deployed-origin/CORS acceptance remain required.
181
+
182
+ Google's [token model](https://developers.google.com/identity/oauth2/web/guides/use-token-model)
183
+ defines user-triggered consent and expiry recovery; its [discovery document](https://accounts.google.com/.well-known/openid-configuration)
184
+ pins the UserInfo endpoint used here.
185
+
186
+ A run supports either `nextPage()` or `nextRow()`. Returned immutable pages carry
187
+ schema, exact typed cells, a receipt and an opaque same-job cursor. `close()`
188
+ stops local delivery without claiming remote cancellation. Resume requires the
189
+ trusted persisted cursor, refetches the same partial page, verifies its digest
190
+ and skips the delivered offset; a boundary cursor fetches the next token. The
191
+ original deadline, response/row/page counters and reservation persist.
192
+
193
+ `rebind(receipt, cursor)` is an explicit reconnect action for a known job. It
194
+ verifies the same stable subject with a new access generation and unchanged
195
+ protected read policy, then atomically replaces the trusted cursor reference.
196
+ It preserves original approval/principal provenance and makes no BigQuery
197
+ request. It never renews a deadline, counter or budget. Expired runs can regain
198
+ bounded `status(receipt)` and `cancel(receipt)` access; Resume still rejects
199
+ before result dispatch. `cancel` requires the broader explicitly granted scope;
200
+ its acknowledgement remains `cancel_requested` until authoritative status.
201
+ Warnings are distinct from terminal `errorResult`, and provider reason `stopped`
202
+ does not establish confirmed cancellation. Billing reconciles a reservation
203
+ only from authoritative terminal billed bytes; absent billing retains the cap.
204
+
205
+ Metadata rechecks cannot remove the residual race in which the named source is
206
+ replaced between observation and submission. Receipts expose that limitation.
207
+
208
+ ## Lossless values and digest foundation
209
+
210
+ ```ts
211
+ import {
212
+ decodeRows, hashPayload, normalizeScalar, operationDeadline,
213
+ } from "@dalgo/bigquery/analytical";
214
+
215
+ const cell = normalizeScalar({ type: "INT64" }, "9223372036854775807");
216
+ // { type: "INT64", value: "9223372036854775807" }
217
+
218
+ const rows = decodeRows(
219
+ new TextEncoder().encode('[{"f":[{"v":null}]}]'),
220
+ [{ type: "STRING", mode: "NULLABLE" }],
221
+ );
222
+ ```
223
+
224
+ `parseJSON` accepts bounded UTF-8 bytes, preserves JSON number lexemes in
225
+ `JsonNumber`, rejects duplicate properties, trailing content, invalid UTF-8,
226
+ unpaired surrogates and depth above 32. The maximum input is 10 MiB; callers
227
+ must separately bound decompressed reads before constructing that buffer.
228
+ Direct scalar strings also reject unpaired UTF-16 surrogates. Warehouse
229
+ integer/decimal values remain strings, BOOL becomes boolean, finite FLOAT64
230
+ uses ECMAScript NumberToString, and SQL NULL remains distinct from JSON text
231
+ `"null"`. TIMESTAMP uses signed epoch microseconds in this foundation contract.
232
+ Cells are limited to 1 MiB and decoded pages to 1,000 rows and 128 fields.
233
+
234
+ Canonical TIMESTAMP values remain signed epoch microseconds. REST scalar and
235
+ IN-array parameters convert at serialization to exact UTC calendar text with all
236
+ six fractional digits using integer arithmetic. Negative instants and the full
237
+ year 0001–9999 range preserve precision. Synthetic request vectors live at
238
+ `packages/bigquery/testdata/requests/timestamp-parameters-r1.json`; the Go serializer
239
+ must consume and verify these vectors before cross-runtime request parity is
240
+ claimed. This additive request fixture is separate from the frozen Go scalar
241
+ corpus below and does not close the shared raw HTTP/state acceptance gate.
242
+
243
+ Metadata refuses conflicting current/deprecated partition-filter flags. Result
244
+ delivery uses the stricter approved query LIMIT and row bound, and rejects
245
+ contradictory row counts, `totalRows` and continuation metadata. Partial-page
246
+ Resume counts only undelivered rows and preserves reservations on validation
247
+ failure; no response contradiction can authorize additional delivery or a rerun.
248
+
249
+ `canonicalJSON` emits RFC8785 bytes for adapter-owned payloads whose JSON
250
+ number tokens are exact safe integers. `hashPayload` uses browser Web Crypto
251
+ and the explicit `ReadPlan`, `SourceProfile`, `Observation` and `Approval`
252
+ projections in the frozen manifest. Only declared top-level exclusions are
253
+ omitted; nested properties called `digest` remain bound. Unicode is never
254
+ normalized and keys sort by UTF-16 code units, including integer-like names.
255
+
256
+ `operationDeadline` computes the earlier of the original execution deadline,
257
+ caller deadline and per-HTTP limit. Explicit status/cancel control operations
258
+ may use a fresh limit of at most 15 seconds, while exhausted cumulative bytes
259
+ still reject. This pure helper never creates or persists a run, dispatches an
260
+ HTTP request, resets counters, reconciles billing or releases reservations.
261
+
262
+ The immutable 168-case revision-3 corpus is vendored from Go driver commit
263
+ `b051a8cd34e9e1e51540d3598bc6d54714da52fc`, tree
264
+ `6a7f20a6fe827ba1cc362e38f876cf6b8e060c7b`; manifest SHA-256 is
265
+ `094b5caa11df6eb0ddef6498394b0c529464ee6ad01c75611a7a3cdb22ad64c1`.
266
+ All original 70 case bytes are unchanged. `testdata/contract/origin.json`
267
+ records provenance. The production runner executes the 93 canonical/scalar/row/
268
+ hash cases and 75 raw HTTP/state cases, including complete request bodies,
269
+ headers, one-byte response schedules, reconnect, counters and absolute deadlines.
270
+ Fixture HTTP runs inject fetch and never contact BigQuery.
271
+
272
+ Set `BIGQUERY_CONTRACT_REPORT` to a private output file for the unmodified HTTP
273
+ report and `BIGQUERY_PARITY_REPORT` for the complete corpus result index. Set
274
+ `BIGQUERY_GO_CONTRACT_REPORT` to the Go `TestSharedCorpus` HTTP report to compare
275
+ all report fields exactly. Only the immutable scenario's explicit Go/JS exposed
276
+ byte counter difference on rejected decompressed overflow is admitted; no
277
+ counter is clamped or dropped. Independent review of the exact candidate and
278
+ both production reports is still required for joint runtime acceptance.
279
+
280
+ Remaining gates include independent joint review, supplemental timestamp REST
281
+ fixture verification by Go, protected DALgo consumer integration, application core
282
+ adoption, actual GIS/browser and CLI/local-server acceptance, canonical
283
+ source/rights admission, and both operator-authorized live journeys.
284
+ The analytical module has no runtime core import. The ordinary adapter uses
285
+ the published `@dalgo/core@0.1.0` baseline. Application consumers using other
286
+ core source commits still require their own compatibility and adoption checks.
287
+ Analytical protocol checks do not establish application compatibility. Package publication still requires
288
+ root-controlled shared release wiring and permission/provenance gates.
289
+
290
+ ## DALgo record adapter
291
+
292
+ The ordinary `@dalgo/bigquery` export implements the read and structured-query portions of
293
+ [`@dalgo/core`](https://github.com/dal-go/dalgo-js) through BigQuery's
294
+ official REST `jobs.query` and `jobs.getQueryResults` endpoints. It uses plain
295
+ `fetch`, not a server SDK.
296
+
297
+ BigQuery is an analytical warehouse, not a browser-first transactional record
298
+ database. This package is therefore **HTTP-capable**, rather than
299
+ browser-ready: browser use requires an application-issued, short-lived OAuth
300
+ token, CORS verification for the exact deployment, least-privilege IAM, and a
301
+ strict `maximumBytesBilled` cap. Do not ship service-account JSON, refresh
302
+ tokens, or broad project credentials to the browser.
303
+
304
+ ## Install
305
+
306
+ Once the package is published to npm:
307
+
308
+ ```sh
309
+ pnpm add @dalgo/bigquery '@dalgo/core@^0.1.0'
310
+ ```
311
+
312
+ The required peer is `@dalgo/core@^0.1.0`; package development uses exact
313
+ registry version `0.1.0`.
314
+
315
+ ## Configure an explicit record projection
316
+
317
+ Each DALgo collection maps to one table, a key column, and every DALgo data
318
+ field it may read or write. The mapping bounds generated SQL to known
319
+ identifiers, keeps table names out of application input, and makes parameter
320
+ types explicit.
321
+
322
+ ```ts
323
+ import { collection } from "@dalgo/core";
324
+ import { BigQueryDatabase } from "@dalgo/bigquery";
325
+
326
+ interface Item {
327
+ title: string;
328
+ done: boolean;
329
+ rank: string;
330
+ }
331
+
332
+ const db = new BigQueryDatabase({
333
+ projectId: "example-project",
334
+ location: "EU",
335
+ maximumBytesBilled: "10000000",
336
+ maxRows: 500,
337
+ accessToken: () => currentGoogleAccessToken(),
338
+ tables: {
339
+ items: {
340
+ datasetId: "app_data",
341
+ tableId: "items",
342
+ keyColumn: { column: "id", type: "STRING", nullable: false },
343
+ columns: {
344
+ title: { column: "title", type: "STRING" },
345
+ done: { column: "done", type: "BOOL" },
346
+ rank: { column: "rank", type: "INT64", nullable: false },
347
+ },
348
+ },
349
+ },
350
+ });
351
+
352
+ const items = collection<Item>("items");
353
+ const first = await db.query(
354
+ items.query().where("done", "==", false).orderBy("rank").limit(25).build(),
355
+ );
356
+ ```
357
+
358
+ `accessToken` is called for every HTTP request, including polling and result
359
+ pages, so callers can rotate access tokens. The adapter only accepts the
360
+ Google HTTPS endpoint, uses `redirect: "error"`, omits response bodies from
361
+ HTTP errors, and bounds each operation with timeouts, result page size, and
362
+ `maxRows`.
363
+
364
+ ## Semantics and supported surface
365
+
366
+ - Top-level collection point reads, ordered `getMany`, structured AND filters,
367
+ ordering, limits, and non-null `startAfter` value cursors.
368
+ - GoogleSQL **named query parameters** for every runtime value. Identifiers
369
+ come only from validated configuration.
370
+ - BigQuery query jobs are polled with `getQueryResults`; multi-page results are
371
+ fetched internally up to the requested, bounded DALgo page.
372
+ - BigQuery REST JSON wire values are preserved before a DALgo codec runs. In
373
+ particular, `INT64`, `NUMERIC`, and `BIGNUMERIC` commonly arrive as strings.
374
+ Supply a codec when application types need conversion.
375
+ - BigQuery REST wire values are normalized only when a returned cursor becomes
376
+ a typed query parameter (for example `"false"` is accepted for `BOOL` and a
377
+ finite decimal string for `FLOAT64`). Record data itself remains wire-faithful.
378
+
379
+ The following deliberately reject rather than approximate a different
380
+ semantic:
381
+
382
+ - `insert`, `set`, `update`, and `delete`: BigQuery does not atomically enforce
383
+ a unique key for ordinary tables, so the adapter cannot prove DALgo
384
+ one-record create, replacement, update, or delete semantics. They reject
385
+ before submitting any DML job.
386
+ - callback transactions, collection-group/nested collections, offsets,
387
+ inclusive/end cursors, repeated-column filters, null cursor values, and
388
+ unmapped/nested fields.
389
+
390
+ The adapter appends the mapped key column as an ascending deterministic
391
+ tie-breaker to ordered queries. Its returned `nextCursor` therefore contains
392
+ the explicit order values **plus** that key value; pass all of them to
393
+ `startAfter`. Every field in a paginated order, including the key tie-breaker,
394
+ must be configured with `nullable: false`; this prevents SQL NULL sort rules
395
+ from skipping or duplicating records.
396
+
397
+ These legacy value cursors compile and submit another query. They are not A0
398
+ same-job cursors and cannot substitute for approved job paging or Resume.
399
+
400
+ ## BigQuery security and cost caveats
401
+
402
+ The REST API accepts OAuth scopes such as `bigquery` or `cloud-platform`, but
403
+ permissions are additionally evaluated against the SQL and referenced data.
404
+ Use a narrow, expiring token broker or a server-side proxy for browser apps;
405
+ the official Node.js client is not a browser credential model. Browser CORS,
406
+ organization policy, IAM, dataset location, query quotas, and billing must be
407
+ verified in the target project.
408
+
409
+ Set `maximumBytesBilled` in production. This adapter does not run a dry run
410
+ before every query because a dry run and execution are separate jobs and can
411
+ double request/authorization overhead; it lets BigQuery reject a query that
412
+ would exceed the configured billing limit.
413
+
414
+ The adapter also sends BigQuery `jobTimeoutMs` as a best-effort server-side
415
+ limit. Its local deadline aborts client requests and stops polling, but it does
416
+ **not** call `jobs.cancel`; a submitted query can therefore continue running
417
+ and incur charges after the caller gives up. Use a conservative billing cap and
418
+ monitor/cancel jobs operationally when that risk is unacceptable.
419
+
420
+ ## Verification
421
+
422
+ `pnpm check` runs deterministic mocked REST contract tests, ESLint, and a
423
+ TypeScript build. It does not use a live BigQuery project, make billable
424
+ queries, or prove a particular browser's CORS/IAM configuration.
425
+
426
+ ## Official API references
427
+
428
+ - [jobs.query](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query)
429
+ - [jobs.getQueryResults](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/getQueryResults)
430
+ - [Parameterized queries](https://cloud.google.com/bigquery/docs/parameterized-queries)
431
+ - [BigQuery authentication](https://cloud.google.com/bigquery/docs/authentication)
432
+
433
+ ## License
434
+
435
+ MIT
436
+
437
+
438
+ ## Protected fixture metadata consumer
439
+
440
+ `MetadataFixtureHarness` composes `GoogleTokenIdentityProvider` and
441
+ `BigQueryMetadataClient` with private current owner, Google subject/generation,
442
+ metadata consent, exact source and selected project state. Its constructor
443
+ requires explicit `identityFetch` and `metadataFetch` fixture callbacks; there
444
+ is no configured live mode. Call `setOwner`, `select`, `connect` from the trusted
445
+ fixture application, then explicitly call `consentToMetadata`. Token connection
446
+ does not grant application metadata consent. Do not expose these trusted state
447
+ methods as caller-JSON commands or construct protected state from URL parameters.
448
+
449
+ Sign-out (`setOwner(undefined)`), disconnect, source/project changes, denied
450
+ consent and token rotations invalidate the joint snapshot synchronously before
451
+ asynchronous work. Consent must be granted again after changes. Discovery uses
452
+ only the allowlisted dataset METADATA and exact table STORAGE_STATS GETs and
453
+ rechecks current state before physical dispatch and delivery. The harness also
454
+ rechecks after asynchronous projection hashing and retains the original
455
+ operation deadline. Tokens remain in provider memory; no ledger or browser
456
+ storage is created, and the returned fixture projection excludes owner,
457
+ consent, Google subject/generation, email, job project, raw bodies and rows.
458
+
459
+ `projectFixtureMetadata` emits only `synthetic-fixture` provenance in the exact
460
+ `ovdb-bigquery-observation/draft-1` envelope accepted by the registry at
461
+ `253e419214da22a1bcc2b5e78577bb2d46323074`. It preserves native types, field order
462
+ and normalized modes, recursively copies only name/type/mode/nested fields,
463
+ and enforces 8 schema levels, 500 total fields and 65,536 public bytes. Optional
464
+ native descriptors and security/free-text/configuration properties are excluded.
465
+ The vendored shared golden's raw SHA-256 is
466
+ `2dcf87f754c51b7c655a42ff73d27f0e7b0fab79e9d229db06656203d8e63117`.
467
+ Provider authenticity requires independent operator review; synthetic output
468
+ cannot activate any source or clear query, cost, rights, billing, or retention
469
+ gates. Neither helper offers jobs, dry runs,
470
+ tables.list, result rows, snapshots, exports or DataTug UI.
471
+
472
+ Package publication does not establish compatibility with other `@dalgo/core`
473
+ versions or a live deployed-origin OAuth/CORS journey.
@@ -0,0 +1,10 @@
1
+ /** RFC8785 bytes for adapter-owned safe-integer payloads, without normalization. */
2
+ export declare function canonicalJSON(raw: Uint8Array): Uint8Array;
3
+ export type HashPayloadName = "ReadPlan" | "SourceProfile" | "Observation" | "Approval";
4
+ export interface HashedPayload {
5
+ readonly digest: string;
6
+ readonly canonical: Uint8Array;
7
+ }
8
+ /** Explicit top-level projections retain nested digest properties as bound data. */
9
+ export declare function hashPayload(name: HashPayloadName, raw: Uint8Array): Promise<HashedPayload>;
10
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../../src/analytical/canonical.ts"],"names":[],"mappings":"AAqBA,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAEzD;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,eAAe,GAAG,aAAa,GAAG,UAAU,CAAC;AACxF,MAAM,WAAW,aAAa;IAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAA;CAAE;AAS1F,oFAAoF;AACpF,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAchG"}
@@ -0,0 +1,50 @@
1
+ import { fail, isObject, JsonNumber, MAX_RESPONSE_BYTES, parseJSON } from "./wire.js";
2
+ const encoder = new TextEncoder();
3
+ function canonical(value) {
4
+ if (value instanceof JsonNumber) {
5
+ // Digest payloads deliberately permit integer tokens only, never exponents.
6
+ if (!/^-?(?:0|[1-9][0-9]*)$/u.test(value.text))
7
+ fail("unsupported_value");
8
+ const integer = BigInt(value.text);
9
+ if (integer < -9007199254740991n || integer > 9007199254740991n)
10
+ fail("unsupported_value");
11
+ return integer.toString();
12
+ }
13
+ if (Array.isArray(value))
14
+ return `[${value.map(canonical).join(",")}]`;
15
+ if (isObject(value)) {
16
+ // Array.sort compares UTF-16 code units. Build text directly: JSON.stringify
17
+ // on a sorted object would reorder integer-like property names numerically.
18
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
19
+ }
20
+ return JSON.stringify(value);
21
+ }
22
+ /** RFC8785 bytes for adapter-owned safe-integer payloads, without normalization. */
23
+ export function canonicalJSON(raw) {
24
+ return encoder.encode(canonical(parseJSON(raw, MAX_RESPONSE_BYTES)));
25
+ }
26
+ const payloads = {
27
+ ReadPlan: { fields: "version sourceDigest projection where order limit parameters sql".split(" "), optional: ["digest"] },
28
+ SourceProfile: { fields: "version sourceId descriptorDigest logicalCollection sourceProject datasetId tableId location schema publisherReviewRef rightsReviewRef use".split(" "), optional: [] },
29
+ Observation: { fields: "table location type config schema".split(" "), optional: "digest observedAt etag lastModified".split(" ") },
30
+ Approval: { fields: "effectivePlanDigest observationDigest policyDigest principal jobProject location maximumBytesBilled sessionBudgetBytes bounds estimatedBytes".split(" "), optional: "digest createdAt expiresAt nonce".split(" ") },
31
+ };
32
+ /** Explicit top-level projections retain nested digest properties as bound data. */
33
+ export async function hashPayload(name, raw) {
34
+ const input = parseJSON(raw, MAX_RESPONSE_BYTES);
35
+ if (!isObject(input) || !Object.hasOwn(payloads, name))
36
+ fail("invalid_input");
37
+ const { fields, optional } = payloads[name];
38
+ const allowed = new Set([...fields, ...optional]);
39
+ const projection = Object.create(null);
40
+ for (const key of fields) {
41
+ if (!Object.hasOwn(input, key))
42
+ fail("invalid_input");
43
+ projection[key] = input[key];
44
+ }
45
+ if (Object.keys(input).some((key) => !allowed.has(key)))
46
+ fail("invalid_input");
47
+ const bytes = encoder.encode(canonical(projection));
48
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
49
+ return { canonical: bytes, digest: Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("") };
50
+ }
@@ -0,0 +1,84 @@
1
+ import { AnalyticalError } from "./wire.js";
2
+ import { type Bounds, type Execution, type JobRef, type Page, type ReadPlan, type ReadQuery, type Receipt, type SourceProfile } from "./protocol.js";
3
+ import { type Ledger, type Preview } from "./ledger.js";
4
+ import { type Clock, type IdentityProvider, type SafeFetch } from "./transport.js";
5
+ export interface PreparedRead {
6
+ readonly source: SourceProfile;
7
+ readonly query: ReadQuery;
8
+ readonly policyDigest: string;
9
+ }
10
+ export interface ClientConfig {
11
+ readonly profiles: readonly SourceProfile[];
12
+ readonly prepare: (signal?: AbortSignal) => Promise<PreparedRead>;
13
+ readonly provider: IdentityProvider;
14
+ readonly ledger: Ledger;
15
+ readonly fetch?: SafeFetch;
16
+ readonly clock?: Clock;
17
+ }
18
+ export interface OperationOptions {
19
+ readonly signal?: AbortSignal;
20
+ readonly callerDeadline?: number;
21
+ }
22
+ export interface JobStatus {
23
+ readonly job: JobRef;
24
+ readonly state: "running" | "completed" | "failed" | "cancelled";
25
+ readonly billedBytes?: string;
26
+ readonly warnings: readonly string[];
27
+ }
28
+ export interface CancelResult {
29
+ readonly job: JobRef;
30
+ readonly state: "cancel_requested" | "unknown";
31
+ }
32
+ /** Capability minted by this client only; JSON cannot manufacture approval. */
33
+ export declare class Approval {
34
+ #private;
35
+ private constructor();
36
+ static mint(): Approval;
37
+ }
38
+ interface RunDriver {
39
+ receipt(): Promise<Receipt>;
40
+ schema(): Promise<readonly import("./protocol.js").SchemaField[]>;
41
+ next(mode: "row" | "page", options: OperationOptions): Promise<Page | null>;
42
+ close(): Promise<void>;
43
+ }
44
+ /** Returns immutable page/row receipts; local stop never implies server cancel. */
45
+ export declare class AnalyticalRun {
46
+ #private;
47
+ constructor(driver: RunDriver);
48
+ receipt(): Promise<Receipt>;
49
+ schema(): Promise<readonly import("./protocol.js").SchemaField[]>;
50
+ nextPage(options?: OperationOptions): Promise<Page | null>;
51
+ nextRow(options?: OperationOptions): Promise<Page | null>;
52
+ close(): Promise<void>;
53
+ }
54
+ export declare function queryRequest(plan: ReadPlan, execution: Execution, limits: Bounds, location: string, dryRun: boolean): unknown;
55
+ export declare class BigQueryAnalyticalClient {
56
+ #private;
57
+ private constructor();
58
+ static create(config: ClientConfig): Promise<BigQueryAnalyticalClient>;
59
+ preview(execution: Execution, requestedBounds?: Partial<Bounds>, options?: OperationOptions): Promise<Preview>;
60
+ approve(preview: Preview, approvedDigest: string): Promise<Approval>;
61
+ execute(approval: Approval, options?: OperationOptions): Promise<AnalyticalRun>;
62
+ resume(receipt: Receipt, cursor: string, options?: OperationOptions): Promise<AnalyticalRun>;
63
+ /** Explicit reconnect of an existing known job after the trusted provider has
64
+ * verified the same stable subject. Keeps original approval, budget and deadline;
65
+ * invalidates old cursors and never submits a query. Approval principal provenance
66
+ * remains immutable; only the separately persisted access generation changes.
67
+ */
68
+ rebind(receipt: Receipt, cursor: string | null, options?: OperationOptions): Promise<{
69
+ receipt: Receipt;
70
+ cursor: string | null;
71
+ }>;
72
+ status(receipt: Receipt, options?: OperationOptions): Promise<JobStatus>;
73
+ cancel(receipt: Receipt, options?: OperationOptions): Promise<CancelResult>;
74
+ }
75
+ /** Failed operations expose the existing sanitized receipt and recoverable run;
76
+ * the message/code never includes query values, OAuth tokens or provider bodies.
77
+ */
78
+ export declare class ExecutionFailure extends AnalyticalError {
79
+ readonly receipt: Receipt;
80
+ readonly run: AnalyticalRun;
81
+ constructor(error: unknown, receipt: Receipt, run: AnalyticalRun);
82
+ }
83
+ export {};
84
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/analytical/client.ts"],"names":[],"mappings":"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"}