@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/dist/sql.js ADDED
@@ -0,0 +1,198 @@
1
+ import { DOCUMENT_ID, UnsupportedError } from "@dalgo/core";
2
+ const identifier = /^[A-Za-z_][A-Za-z0-9_]*$/u;
3
+ export function validateIdentifier(value, label) {
4
+ if (!identifier.test(value))
5
+ throw new TypeError(`${label} must be a simple BigQuery identifier`);
6
+ }
7
+ /** Google project IDs may contain hyphens, unlike dataset/table/column identifiers. */
8
+ export function validateProjectId(value) {
9
+ if (!/^[A-Za-z0-9][A-Za-z0-9-]{0,61}$/u.test(value)) {
10
+ throw new TypeError("projectId must contain only letters, numbers, and hyphens");
11
+ }
12
+ }
13
+ function quoteIdentifier(value, label) {
14
+ validateIdentifier(value, label);
15
+ return `\`${value}\``;
16
+ }
17
+ export function quoteTable(projectId, table) {
18
+ validateProjectId(projectId);
19
+ validateIdentifier(table.datasetId, "datasetId");
20
+ validateIdentifier(table.tableId, "tableId");
21
+ return `\`${projectId}.${table.datasetId}.${table.tableId}\``;
22
+ }
23
+ function scalarValue(value, type) {
24
+ if (value === undefined)
25
+ throw new TypeError(`${type} parameters cannot be undefined`);
26
+ if (value === null)
27
+ return {};
28
+ switch (type) {
29
+ case "BOOL":
30
+ if (typeof value === "boolean")
31
+ return { value: value ? "true" : "false" };
32
+ if (value === "true" || value === "false")
33
+ return { value };
34
+ throw new TypeError("BOOL parameters require a boolean or BigQuery wire boolean string");
35
+ case "INT64":
36
+ if ((typeof value !== "string" && typeof value !== "number") || (typeof value === "number" && !Number.isSafeInteger(value))) {
37
+ throw new TypeError("INT64 parameters require a decimal string or safe integer");
38
+ }
39
+ if (typeof value === "string" && !/^-?(?:0|[1-9]\d*)$/u.test(value)) {
40
+ throw new TypeError("INT64 parameters require a decimal string or safe integer");
41
+ }
42
+ return { value: String(value) };
43
+ case "FLOAT64":
44
+ if (typeof value === "number" && Number.isFinite(value))
45
+ return { value: String(value) };
46
+ if (typeof value === "string" && value.trim().length > 0 && Number.isFinite(Number(value)))
47
+ return { value };
48
+ throw new TypeError("FLOAT64 parameters require a finite number or BigQuery wire float string");
49
+ case "NUMERIC":
50
+ case "BIGNUMERIC":
51
+ if (typeof value !== "string" && typeof value !== "number")
52
+ throw new TypeError(`${type} parameters require a decimal string or number`);
53
+ if ((typeof value === "number" && !Number.isFinite(value)) || (typeof value === "string" && !/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value))) {
54
+ throw new TypeError(`${type} parameters require a finite decimal value`);
55
+ }
56
+ return { value: String(value) };
57
+ case "JSON":
58
+ if (typeof value === "string")
59
+ return { value };
60
+ return { value: JSON.stringify(value) };
61
+ default:
62
+ if (typeof value !== "string")
63
+ throw new TypeError(`${type} parameters require a string`);
64
+ return { value };
65
+ }
66
+ }
67
+ export function parameter(name, column, value) {
68
+ return { name, parameterType: { type: column.type }, parameterValue: scalarValue(value, column.type) };
69
+ }
70
+ function arrayParameter(name, column, values) {
71
+ if (!Array.isArray(values) || values.length === 0)
72
+ throw new TypeError("in and not-in filters require a non-empty array");
73
+ if (values.some((value) => value === null))
74
+ throw new UnsupportedError("BigQuery membership filters containing null");
75
+ return {
76
+ name,
77
+ parameterType: { type: "ARRAY", arrayType: { type: column.type } },
78
+ parameterValue: { arrayValues: values.map((value) => scalarValue(value, column.type)) },
79
+ };
80
+ }
81
+ function columnForField(table, field) {
82
+ if (field === DOCUMENT_ID)
83
+ return table.keyColumn;
84
+ const column = table.columns[field];
85
+ if (column === undefined)
86
+ throw new UnsupportedError(`BigQuery field not declared in table mapping: ${field}`);
87
+ return column;
88
+ }
89
+ function columnExpression(column) {
90
+ return `t.${quoteIdentifier(column.column, "column name")}`;
91
+ }
92
+ function filterSql(filter, table, index) {
93
+ const column = columnForField(table, String(filter.field));
94
+ const expr = columnExpression(column);
95
+ const name = `p${String(index)}`;
96
+ switch (filter.operator) {
97
+ case "==": return filter.value === null
98
+ ? { sql: `${expr} IS NULL` }
99
+ : { sql: `${expr} = @${name}`, parameter: parameter(name, column, filter.value) };
100
+ case "!=": return filter.value === null
101
+ ? { sql: `${expr} IS NOT NULL` }
102
+ : { sql: `${expr} IS NOT NULL AND ${expr} != @${name}`, parameter: parameter(name, column, filter.value) };
103
+ case "<": return { sql: `${expr} < @${name}`, parameter: parameter(name, column, filter.value) };
104
+ case "<=": return { sql: `${expr} <= @${name}`, parameter: parameter(name, column, filter.value) };
105
+ case ">": return { sql: `${expr} > @${name}`, parameter: parameter(name, column, filter.value) };
106
+ case ">=": return { sql: `${expr} >= @${name}`, parameter: parameter(name, column, filter.value) };
107
+ case "in": return { sql: `${expr} IN UNNEST(@${name})`, parameter: arrayParameter(name, column, filter.value) };
108
+ case "not-in": return { sql: `${expr} IS NOT NULL AND ${expr} NOT IN UNNEST(@${name})`, parameter: arrayParameter(name, column, filter.value) };
109
+ case "array-contains":
110
+ case "array-contains-any":
111
+ throw new UnsupportedError(`BigQuery ${filter.operator} filters on repeated columns`);
112
+ default:
113
+ throw new UnsupportedError(`BigQuery query operator: ${String(filter.operator)}`);
114
+ }
115
+ }
116
+ function orderColumns(orders, table) {
117
+ const columns = orders.map((order) => columnForField(table, String(order.field)));
118
+ if (!columns.some((column) => column.column === table.keyColumn.column))
119
+ return [...columns, table.keyColumn];
120
+ return columns;
121
+ }
122
+ function requireNonNullableCursorColumns(columns) {
123
+ for (const column of columns) {
124
+ if (column.nullable !== false) {
125
+ throw new UnsupportedError(`BigQuery paginated order requires nullable: false for ${column.column}`);
126
+ }
127
+ }
128
+ }
129
+ function orderSql(orders, columns) {
130
+ return columns.map((column, index) => {
131
+ const explicit = orders[index];
132
+ if (explicit !== undefined && explicit.direction !== "asc" && explicit.direction !== "desc") {
133
+ throw new TypeError("BigQuery order direction must be asc or desc");
134
+ }
135
+ return `${columnExpression(column)} ${explicit?.direction === "desc" ? "DESC" : "ASC"}`;
136
+ }).join(", ");
137
+ }
138
+ function cursorSql(query, columns, parameters) {
139
+ if (query.startAt !== undefined || query.endAt !== undefined || query.endBefore !== undefined) {
140
+ throw new UnsupportedError("BigQuery inclusive or end cursors");
141
+ }
142
+ if (query.startAfter === undefined)
143
+ return undefined;
144
+ if (query.orders.length === 0)
145
+ throw new UnsupportedError("BigQuery startAfter without an explicit order");
146
+ if (query.startAfter.values.length !== columns.length) {
147
+ throw new TypeError("BigQuery cursor value count must include every order field and the key tie-breaker");
148
+ }
149
+ if (query.startAfter.values.some((value) => value === null)) {
150
+ throw new UnsupportedError("BigQuery cursors containing null values");
151
+ }
152
+ const terms = [];
153
+ for (let index = 0; index < columns.length; index += 1) {
154
+ const column = columns[index];
155
+ const value = query.startAfter.values[index];
156
+ if (column === undefined || value === undefined)
157
+ throw new TypeError("invalid BigQuery cursor");
158
+ const name = `c${String(index)}`;
159
+ parameters.push(parameter(name, column, value));
160
+ const equal = columns.slice(0, index).map((previous, previousIndex) => `${columnExpression(previous)} = @c${String(previousIndex)}`);
161
+ const direction = query.orders[index]?.direction ?? "asc";
162
+ if (direction !== "asc" && direction !== "desc")
163
+ throw new TypeError("BigQuery order direction must be asc or desc");
164
+ terms.push(`(${[...equal, `${columnExpression(column)} ${direction === "desc" ? "<" : ">"} @${name}`].join(" AND ")})`);
165
+ }
166
+ return `(${terms.join(" OR ")})`;
167
+ }
168
+ export function compileBigQueryQuery(projectId, table, query, rowLimit) {
169
+ if (query.source.kind !== "collection" || query.source.parent !== undefined)
170
+ throw new UnsupportedError("BigQuery collection-group or nested collection queries");
171
+ if (!Number.isSafeInteger(rowLimit) || rowLimit < 1)
172
+ throw new TypeError("row limit must be a positive safe integer");
173
+ const parameters = [];
174
+ const clauses = query.filters.map((filter, index) => {
175
+ const compiled = filterSql(filter, table, index);
176
+ if (compiled.parameter !== undefined)
177
+ parameters.push(compiled.parameter);
178
+ return compiled.sql;
179
+ });
180
+ const cursorColumns = orderColumns(query.orders, table);
181
+ if (query.orders.length > 0) {
182
+ requireNonNullableCursorColumns(cursorColumns);
183
+ }
184
+ const cursor = cursorSql(query, cursorColumns, parameters);
185
+ if (cursor !== undefined)
186
+ clauses.push(cursor);
187
+ const selected = [
188
+ `${columnExpression(table.keyColumn)} AS \`__dalgo_key\``,
189
+ ...Object.entries(table.columns).map(([field, column]) => `${columnExpression(column)} AS ${quoteIdentifier(field, "field name")}`),
190
+ ];
191
+ const where = clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`;
192
+ const ordered = query.orders.length === 0 ? "" : ` ORDER BY ${orderSql(query.orders, cursorColumns)}`;
193
+ return {
194
+ sql: `SELECT ${selected.join(", ")} FROM ${quoteTable(projectId, table)} AS t${where}${ordered} LIMIT ${String(rowLimit)}`,
195
+ parameters,
196
+ cursorColumns,
197
+ };
198
+ }
@@ -0,0 +1,49 @@
1
+ /** Supported scalar parameter types for mapped BigQuery columns. */
2
+ export type BigQueryScalarType = "STRING" | "INT64" | "FLOAT64" | "BOOL" | "NUMERIC" | "BIGNUMERIC" | "DATE" | "DATETIME" | "TIME" | "TIMESTAMP" | "JSON";
3
+ export interface BigQueryColumn {
4
+ /** BigQuery column name. Nested/repeated columns are deliberately unsupported. */
5
+ readonly column: string;
6
+ readonly type: BigQueryScalarType;
7
+ /** Required as false for an ordered field that participates in DALgo pagination. */
8
+ readonly nullable?: boolean;
9
+ }
10
+ /**
11
+ * The bounded record projection that a DALgo collection represents.
12
+ * All application fields that this adapter reads or writes must be declared in
13
+ * `columns`; unrelated warehouse columns remain outside DALgo's model.
14
+ */
15
+ export interface BigQueryTable {
16
+ readonly datasetId: string;
17
+ readonly tableId: string;
18
+ readonly keyColumn: BigQueryColumn;
19
+ readonly columns: Readonly<Record<string, BigQueryColumn>>;
20
+ }
21
+ export type AccessTokenProvider = () => string | Promise<string>;
22
+ export type BigQueryFetch = typeof globalThis.fetch;
23
+ export interface BigQueryDatabaseOptions {
24
+ /** Google Cloud project that owns query jobs. */
25
+ readonly projectId: string;
26
+ /** Maps each DALgo collection name to one bounded BigQuery table projection. */
27
+ readonly tables: Readonly<Record<string, BigQueryTable>>;
28
+ /** Returns a short-lived OAuth 2.0 access token for every request. */
29
+ readonly accessToken: AccessTokenProvider;
30
+ readonly location?: string;
31
+ /** Caps query cost; omitted only when the caller explicitly accepts project defaults. */
32
+ readonly maximumBytesBilled?: bigint | number | string;
33
+ /** Maximum DALgo records returned by one query; defaults to 1000. */
34
+ readonly maxRows?: number;
35
+ /** Maximum rows requested from one BigQuery results response; defaults to 500. */
36
+ readonly pageSize?: number;
37
+ /** End-to-end request and polling deadline; defaults to 30 seconds. */
38
+ readonly timeoutMs?: number;
39
+ /** Initial synchronous wait supplied to jobs.query; defaults to 1000 milliseconds. */
40
+ readonly initialWaitMs?: number;
41
+ readonly fetch?: BigQueryFetch;
42
+ }
43
+ export interface BigQueryQueryMetadata {
44
+ readonly jobId?: string;
45
+ readonly totalBytesProcessed?: string;
46
+ readonly totalBytesBilled?: string;
47
+ readonly cacheHit?: boolean;
48
+ }
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,OAAO,GACP,SAAS,GACT,MAAM,GACN,SAAS,GACT,YAAY,GACZ,MAAM,GACN,UAAU,GACV,MAAM,GACN,WAAW,GACX,MAAM,CAAC;AAEX,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CAC5D;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACvD,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@dalgo/bigquery",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "BigQuery REST API adapter for DALgo TypeScript",
5
+ "type": "module",
6
+ "packageManager": "pnpm@11.20.0",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./analytical": {
14
+ "types": "./dist/analytical.d.ts",
15
+ "import": "./dist/analytical.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "LICENSE",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.build.json",
25
+ "check": "eslint . && vitest run && tsc -p tsconfig.build.json",
26
+ "lint": "eslint .",
27
+ "prepack": "npm run build",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/dal-go/dalgo-http-adapters.git",
34
+ "directory": "packages/bigquery"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/dal-go/dalgo-http-adapters/issues"
38
+ },
39
+ "homepage": "https://github.com/dal-go/dalgo-http-adapters/tree/main/packages/bigquery#readme",
40
+ "license": "MIT",
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "peerDependencies": {
45
+ "@dalgo/core": "^0.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "@dalgo/core": "0.1.0",
49
+ "@eslint/js": "10.0.1",
50
+ "eslint": "10.11.0",
51
+ "fake-indexeddb": "6.2.5",
52
+ "typescript": "6.0.3",
53
+ "typescript-eslint": "8.70.0",
54
+ "vitest": "5.0.1"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "gitHead": "10d0d6575c38338350feb7a9284530561f5b139e"
60
+ }