@adcp/sdk 15.5.0 → 15.6.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 (75) hide show
  1. package/README.md +15 -0
  2. package/dist/lib/auth/oauth/index.d.mts +1 -0
  3. package/dist/lib/auth/oauth/index.d.ts +1 -0
  4. package/dist/lib/auth/oauth/index.js +5 -0
  5. package/dist/lib/auth/oauth/index.mjs +3 -0
  6. package/dist/lib/auth/oauth/pending-web-flow.d.mts +16 -0
  7. package/dist/lib/auth/oauth/pending-web-flow.d.ts +16 -0
  8. package/dist/lib/auth/oauth/pending-web-flow.js +66 -0
  9. package/dist/lib/auth/oauth/pending-web-flow.mjs +41 -0
  10. package/dist/lib/auth/oauth/web-flow.js +3 -3
  11. package/dist/lib/auth/oauth/web-flow.mjs +3 -3
  12. package/dist/lib/registry/types.generated.d.mts +63 -1
  13. package/dist/lib/registry/types.generated.d.ts +63 -1
  14. package/dist/lib/reporting/azure/index.d.mts +26 -0
  15. package/dist/lib/reporting/azure/index.d.ts +26 -0
  16. package/dist/lib/reporting/azure/index.js +247 -0
  17. package/dist/lib/reporting/azure/index.mjs +230 -0
  18. package/dist/lib/reporting/bigquery/index.d.mts +127 -0
  19. package/dist/lib/reporting/bigquery/index.d.ts +127 -0
  20. package/dist/lib/reporting/bigquery/index.js +379 -0
  21. package/dist/lib/reporting/bigquery/index.mjs +341 -0
  22. package/dist/lib/reporting/ledger/postgres.d.mts +10 -0
  23. package/dist/lib/reporting/ledger/postgres.d.ts +10 -0
  24. package/dist/lib/reporting/ledger/postgres.js +23 -2
  25. package/dist/lib/reporting/ledger/postgres.mjs +23 -2
  26. package/dist/lib/reporting/ledger/row-storage-object.js +31 -8
  27. package/dist/lib/reporting/ledger/row-storage-object.mjs +32 -9
  28. package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +0 -7
  29. package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +0 -7
  30. package/dist/lib/reporting/ledger/row-storage-provider-io.js +0 -10
  31. package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +0 -8
  32. package/dist/lib/reporting/s3/index.d.mts +29 -0
  33. package/dist/lib/reporting/s3/index.d.ts +29 -0
  34. package/dist/lib/reporting/s3/index.js +324 -0
  35. package/dist/lib/reporting/s3/index.mjs +317 -0
  36. package/dist/lib/reporting/service/index.d.mts +3 -1
  37. package/dist/lib/reporting/service/index.d.ts +3 -1
  38. package/dist/lib/reporting/service/index.js +1 -0
  39. package/dist/lib/reporting/service/index.mjs +1 -0
  40. package/dist/lib/schemas/browser.d.mts +14 -0
  41. package/dist/lib/schemas/browser.d.ts +14 -0
  42. package/dist/lib/schemas/browser.js +48 -0
  43. package/dist/lib/schemas/browser.mjs +20 -0
  44. package/dist/lib/schemas/index.d.mts +2 -120
  45. package/dist/lib/schemas/index.d.ts +2 -120
  46. package/dist/lib/schemas/index.js +3 -11
  47. package/dist/lib/schemas/index.mjs +1 -9
  48. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  49. package/dist/lib/testing/index.d.mts +1 -0
  50. package/dist/lib/testing/index.d.ts +1 -0
  51. package/dist/lib/testing/index.js +3 -0
  52. package/dist/lib/testing/index.mjs +2 -0
  53. package/dist/lib/testing/pending-web-flow-store.d.mts +20 -0
  54. package/dist/lib/testing/pending-web-flow-store.d.ts +20 -0
  55. package/dist/lib/testing/pending-web-flow-store.js +132 -0
  56. package/dist/lib/testing/pending-web-flow-store.mjs +98 -0
  57. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  58. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  59. package/dist/lib/validation/get-products-request.d.mts +122 -0
  60. package/dist/lib/validation/get-products-request.d.ts +122 -0
  61. package/dist/lib/validation/get-products-request.js +39 -0
  62. package/dist/lib/validation/get-products-request.mjs +14 -0
  63. package/dist/lib/version.d.mts +3 -3
  64. package/dist/lib/version.d.ts +3 -3
  65. package/dist/lib/version.js +3 -3
  66. package/dist/lib/version.mjs +3 -3
  67. package/docs/TYPE-SUMMARY.md +1 -1
  68. package/docs/ZOD-SCHEMAS.md +13 -1
  69. package/docs/guides/OAUTH-ISSUER-BINDING.md +6 -0
  70. package/docs/guides/REPORTING-LEDGER.md +165 -0
  71. package/docs/guides/REPORTING-OPERATIONS.md +76 -1
  72. package/docs/guides/WEB-OAUTH.md +48 -1
  73. package/docs/llms.txt +1 -1
  74. package/docs/migration-14.x-rc-worksheet.md +6 -4
  75. package/package.json +64 -1
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var azure_exports = {};
20
+ __export(azure_exports, {
21
+ createAzureBlobReportingRowObjectProviderV1: () => createAzureBlobReportingRowObjectProviderV1
22
+ });
23
+ module.exports = __toCommonJS(azure_exports);
24
+ var import_node_crypto = require("node:crypto");
25
+ var import_row_storage_errors = require('../ledger/row-storage-errors.js');
26
+ var import_row_storage_provider_io = require('../ledger/row-storage-provider-io.js');
27
+ const ACCOUNT = /^[a-z0-9]{3,24}$/;
28
+ const CONTAINER = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,62}$/;
29
+ const VERSION_ID = /^[0-9A-Za-z:.+-]{1,64}$/;
30
+ function statusOf(error) {
31
+ const status = Number(error?.statusCode);
32
+ return Number.isInteger(status) ? status : void 0;
33
+ }
34
+ function codeOf(error) {
35
+ const value = error?.code ?? error?.details?.errorCode;
36
+ return typeof value === "string" ? value : "";
37
+ }
38
+ const isConflict = (error) => statusOf(error) === 409 || statusOf(error) === 412 || codeOf(error) === "BlobAlreadyExists";
39
+ const isMissing = (error) => statusOf(error) === 404 || statusOf(error) === 412;
40
+ const isForbidden = (error) => statusOf(error) === 401 || statusOf(error) === 403;
41
+ function versionOf(response) {
42
+ const id = response.versionId;
43
+ if (typeof id !== "string" || !VERSION_ID.test(id)) {
44
+ throw new import_row_storage_errors.ReportingRowStoreError(
45
+ "UNSAFE_BINDING",
46
+ "store returned no blob versionId; blob versioning must be enabled on the storage account so deletes are fenced to one write"
47
+ );
48
+ }
49
+ return id;
50
+ }
51
+ function servedVersion(response, versionId) {
52
+ return response.versionId === versionId;
53
+ }
54
+ function metadataOf(metadata) {
55
+ const result = {};
56
+ for (const [name, value] of Object.entries(metadata)) {
57
+ const identifier = name.replace(/[^A-Za-z0-9_]/g, "_");
58
+ result[/^[A-Za-z_]/.test(identifier) ? identifier : `_${identifier}`] = value;
59
+ }
60
+ return result;
61
+ }
62
+ function createAzureBlobReportingRowObjectProviderV1(options) {
63
+ if (!options || typeof options !== "object") {
64
+ throw new import_row_storage_errors.ReportingRowStoreError("INVALID_INPUT", "Azure Blob row provider options are required");
65
+ }
66
+ const clients = (0, import_row_storage_provider_io.reportingRowClientMap)(options.clients);
67
+ const isClient = (value) => !!value && typeof value === "object" && typeof value.getContainerClient === "function" && typeof value.accountName === "string";
68
+ if (options.client !== void 0 && !isClient(options.client) || [...clients.values()].some((client) => !isClient(client)) || options.client === void 0 && clients.size === 0) {
69
+ throw new import_row_storage_errors.ReportingRowStoreError("INVALID_INPUT", "inject an official BlobServiceClient as client or clients");
70
+ }
71
+ const validateLocation = (location) => {
72
+ (0, import_row_storage_provider_io.assertReportingRowLocationKeys)(location, ["account", "container"]);
73
+ if (!ACCOUNT.test(location.account)) {
74
+ throw new import_row_storage_errors.ReportingRowStoreError("INVALID_INPUT", "Azure storage account name is invalid");
75
+ }
76
+ if (!CONTAINER.test(location.container)) {
77
+ throw new import_row_storage_errors.ReportingRowStoreError("INVALID_INPUT", "Azure container name is invalid");
78
+ }
79
+ };
80
+ const containerFor = (input) => {
81
+ validateLocation(input.location);
82
+ const client = (0, import_row_storage_provider_io.selectReportingRowClient)(options.client, clients, input.credentialRef);
83
+ if (client.accountName !== input.location.account) {
84
+ throw new import_row_storage_errors.ReportingRowStoreError("UNSAFE_BINDING", "client does not belong to the binding account");
85
+ }
86
+ return client.getContainerClient(input.location.container);
87
+ };
88
+ const blobFor = (input) => {
89
+ if (typeof input.key !== "string" || input.key.length === 0 || input.key.length > 1024) {
90
+ throw new import_row_storage_errors.ReportingRowStoreError("INVALID_INPUT", "object key is invalid");
91
+ }
92
+ return containerFor(input).getBlockBlobClient(input.key);
93
+ };
94
+ const pinned = (blob, nativeVersion) => typeof nativeVersion === "string" && VERSION_ID.test(nativeVersion) ? { blob: blob.withVersion(nativeVersion), versionId: nativeVersion } : null;
95
+ async function assertCreateOnlyEnforced(container, prefix, signal) {
96
+ const blob = container.getBlockBlobClient(`${prefix}/.adcp-probe/${(0, import_node_crypto.randomUUID)()}`);
97
+ const versionIds = [];
98
+ let written = false;
99
+ const upload = async (body) => {
100
+ const bytes = Buffer.from(body);
101
+ const response = await blob.upload(bytes, bytes.byteLength, {
102
+ conditions: { ifNoneMatch: "*" },
103
+ blobHTTPHeaders: { blobContentType: "application/x-ndjson", blobCacheControl: "no-store" },
104
+ abortSignal: signal
105
+ });
106
+ written = true;
107
+ if (response.versionId) versionIds.push(response.versionId);
108
+ versionOf(response);
109
+ };
110
+ try {
111
+ await upload('{"probe":1}\n');
112
+ try {
113
+ await upload('{"probe":2}\n');
114
+ } catch (error) {
115
+ if (isConflict(error)) return;
116
+ throw error;
117
+ }
118
+ throw new import_row_storage_errors.ReportingRowStoreError("UNSAFE_BINDING", "store ignores If-None-Match and replaces blobs");
119
+ } finally {
120
+ if (written && !signal.aborted) {
121
+ await blob.delete({ abortSignal: signal }).catch(() => void 0);
122
+ for (const versionId of versionIds) {
123
+ if (signal.aborted) break;
124
+ await blob.withVersion(versionId).delete({ abortSignal: signal }).catch(() => void 0);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return {
130
+ name: "azure_blob",
131
+ rangedReads: true,
132
+ validateLocation,
133
+ probe(input, context) {
134
+ return (0, import_row_storage_provider_io.runReportingRowProviderOperation)(context.signal, async () => {
135
+ const container = containerFor(input);
136
+ let properties;
137
+ try {
138
+ properties = await container.getProperties({ abortSignal: context.signal });
139
+ } catch (error) {
140
+ if (statusOf(error) === 404) throw new import_row_storage_errors.ReportingRowStoreError("UNSAFE_BINDING", "container does not exist");
141
+ if (isForbidden(error)) throw new import_row_storage_errors.ReportingRowStoreError("UNSAFE_BINDING", "container is not accessible");
142
+ throw error;
143
+ }
144
+ if (properties.blobPublicAccess) {
145
+ throw new import_row_storage_errors.ReportingRowStoreError("UNSAFE_BINDING", "container allows anonymous read access");
146
+ }
147
+ await assertCreateOnlyEnforced(container, input.prefix, context.signal);
148
+ });
149
+ },
150
+ putIfAbsent(input, context) {
151
+ return (0, import_row_storage_provider_io.runReportingRowProviderOperation)(context.signal, async () => {
152
+ const blob = blobFor(input);
153
+ const bytes = Buffer.from(input.bytes);
154
+ try {
155
+ const response = await blob.upload(bytes, bytes.byteLength, {
156
+ conditions: { ifNoneMatch: "*" },
157
+ blobHTTPHeaders: { blobContentType: input.contentType, blobCacheControl: "no-store" },
158
+ metadata: metadataOf(input.metadata),
159
+ abortSignal: context.signal
160
+ });
161
+ return { created: true, nativeVersion: versionOf(response) };
162
+ } catch (error) {
163
+ if (!isConflict(error)) throw error;
164
+ }
165
+ const existing = await blob.getProperties({ abortSignal: context.signal });
166
+ return { created: false, nativeVersion: versionOf(existing) };
167
+ });
168
+ },
169
+ get(input, context) {
170
+ return (0, import_row_storage_provider_io.runReportingRowProviderOperation)(context.signal, async () => {
171
+ const blob = blobFor(input);
172
+ (0, import_row_storage_provider_io.assertReportingRowReadBounds)(input);
173
+ const target = pinned(blob, input.nativeVersion);
174
+ if (target === null) return null;
175
+ try {
176
+ if (input.range?.length === 0) {
177
+ const properties = await target.blob.getProperties({ abortSignal: context.signal });
178
+ if (!servedVersion(properties, target.versionId)) return null;
179
+ if (input.range.offset > Number(properties.contentLength ?? 0)) {
180
+ throw new import_row_storage_errors.ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "requested range exceeds the stored object");
181
+ }
182
+ return Buffer.alloc(0);
183
+ }
184
+ const response = await target.blob.download(input.range?.offset ?? 0, input.range?.length, {
185
+ abortSignal: context.signal
186
+ });
187
+ const body = response.readableStreamBody;
188
+ if (!body || typeof body[Symbol.asyncIterator] !== "function") {
189
+ throw new import_row_storage_errors.ReportingRowStoreError("PROVIDER_UNAVAILABLE", "blob body is not a stream");
190
+ }
191
+ if (!servedVersion(response, target.versionId)) {
192
+ body.destroy?.();
193
+ return null;
194
+ }
195
+ if (typeof response.contentLength === "number" && response.contentLength > input.maxBytes) {
196
+ body.destroy?.();
197
+ throw new import_row_storage_errors.ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "stored object exceeds its recorded size");
198
+ }
199
+ return await (0, import_row_storage_provider_io.readReportingRowStream)(body, input.maxBytes, context.signal);
200
+ } catch (error) {
201
+ if (isMissing(error)) return null;
202
+ if (statusOf(error) === 400) return null;
203
+ if (statusOf(error) === 416 || codeOf(error) === "InvalidRange") {
204
+ throw new import_row_storage_errors.ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "requested range exceeds the stored object");
205
+ }
206
+ throw error;
207
+ }
208
+ });
209
+ },
210
+ delete(input, context) {
211
+ return (0, import_row_storage_provider_io.runReportingRowProviderOperation)(context.signal, async () => {
212
+ const blob = blobFor(input);
213
+ const target = pinned(blob, input.nativeVersion);
214
+ if (target === null) return "absent";
215
+ const signal = context.signal;
216
+ try {
217
+ const recorded = await target.blob.getProperties({ abortSignal: signal });
218
+ if (recorded.versionId !== target.versionId) return "absent";
219
+ } catch (error) {
220
+ if (isMissing(error) || statusOf(error) === 400) return "absent";
221
+ throw error;
222
+ }
223
+ let deletedCurrent = false;
224
+ try {
225
+ const current = await blob.getProperties({ abortSignal: signal });
226
+ if (current.versionId === target.versionId && current.etag) {
227
+ await blob.delete({ conditions: { ifMatch: current.etag }, abortSignal: signal });
228
+ deletedCurrent = true;
229
+ }
230
+ } catch (error) {
231
+ if (!isMissing(error)) throw error;
232
+ }
233
+ try {
234
+ await target.blob.delete({ abortSignal: signal });
235
+ return "deleted";
236
+ } catch (error) {
237
+ if (isMissing(error) || statusOf(error) === 400) return deletedCurrent ? "deleted" : "absent";
238
+ throw error;
239
+ }
240
+ });
241
+ }
242
+ };
243
+ }
244
+ // Annotate the CommonJS export names for ESM import in node:
245
+ 0 && (module.exports = {
246
+ createAzureBlobReportingRowObjectProviderV1
247
+ });
@@ -0,0 +1,230 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ReportingRowStoreError } from "../ledger/row-storage-errors.mjs";
3
+ import {
4
+ assertReportingRowLocationKeys,
5
+ assertReportingRowReadBounds,
6
+ readReportingRowStream,
7
+ reportingRowClientMap,
8
+ runReportingRowProviderOperation,
9
+ selectReportingRowClient
10
+ } from "../ledger/row-storage-provider-io.mjs";
11
+ const ACCOUNT = /^[a-z0-9]{3,24}$/;
12
+ const CONTAINER = /^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,62}$/;
13
+ const VERSION_ID = /^[0-9A-Za-z:.+-]{1,64}$/;
14
+ function statusOf(error) {
15
+ const status = Number(error?.statusCode);
16
+ return Number.isInteger(status) ? status : void 0;
17
+ }
18
+ function codeOf(error) {
19
+ const value = error?.code ?? error?.details?.errorCode;
20
+ return typeof value === "string" ? value : "";
21
+ }
22
+ const isConflict = (error) => statusOf(error) === 409 || statusOf(error) === 412 || codeOf(error) === "BlobAlreadyExists";
23
+ const isMissing = (error) => statusOf(error) === 404 || statusOf(error) === 412;
24
+ const isForbidden = (error) => statusOf(error) === 401 || statusOf(error) === 403;
25
+ function versionOf(response) {
26
+ const id = response.versionId;
27
+ if (typeof id !== "string" || !VERSION_ID.test(id)) {
28
+ throw new ReportingRowStoreError(
29
+ "UNSAFE_BINDING",
30
+ "store returned no blob versionId; blob versioning must be enabled on the storage account so deletes are fenced to one write"
31
+ );
32
+ }
33
+ return id;
34
+ }
35
+ function servedVersion(response, versionId) {
36
+ return response.versionId === versionId;
37
+ }
38
+ function metadataOf(metadata) {
39
+ const result = {};
40
+ for (const [name, value] of Object.entries(metadata)) {
41
+ const identifier = name.replace(/[^A-Za-z0-9_]/g, "_");
42
+ result[/^[A-Za-z_]/.test(identifier) ? identifier : `_${identifier}`] = value;
43
+ }
44
+ return result;
45
+ }
46
+ function createAzureBlobReportingRowObjectProviderV1(options) {
47
+ if (!options || typeof options !== "object") {
48
+ throw new ReportingRowStoreError("INVALID_INPUT", "Azure Blob row provider options are required");
49
+ }
50
+ const clients = reportingRowClientMap(options.clients);
51
+ const isClient = (value) => !!value && typeof value === "object" && typeof value.getContainerClient === "function" && typeof value.accountName === "string";
52
+ if (options.client !== void 0 && !isClient(options.client) || [...clients.values()].some((client) => !isClient(client)) || options.client === void 0 && clients.size === 0) {
53
+ throw new ReportingRowStoreError("INVALID_INPUT", "inject an official BlobServiceClient as client or clients");
54
+ }
55
+ const validateLocation = (location) => {
56
+ assertReportingRowLocationKeys(location, ["account", "container"]);
57
+ if (!ACCOUNT.test(location.account)) {
58
+ throw new ReportingRowStoreError("INVALID_INPUT", "Azure storage account name is invalid");
59
+ }
60
+ if (!CONTAINER.test(location.container)) {
61
+ throw new ReportingRowStoreError("INVALID_INPUT", "Azure container name is invalid");
62
+ }
63
+ };
64
+ const containerFor = (input) => {
65
+ validateLocation(input.location);
66
+ const client = selectReportingRowClient(options.client, clients, input.credentialRef);
67
+ if (client.accountName !== input.location.account) {
68
+ throw new ReportingRowStoreError("UNSAFE_BINDING", "client does not belong to the binding account");
69
+ }
70
+ return client.getContainerClient(input.location.container);
71
+ };
72
+ const blobFor = (input) => {
73
+ if (typeof input.key !== "string" || input.key.length === 0 || input.key.length > 1024) {
74
+ throw new ReportingRowStoreError("INVALID_INPUT", "object key is invalid");
75
+ }
76
+ return containerFor(input).getBlockBlobClient(input.key);
77
+ };
78
+ const pinned = (blob, nativeVersion) => typeof nativeVersion === "string" && VERSION_ID.test(nativeVersion) ? { blob: blob.withVersion(nativeVersion), versionId: nativeVersion } : null;
79
+ async function assertCreateOnlyEnforced(container, prefix, signal) {
80
+ const blob = container.getBlockBlobClient(`${prefix}/.adcp-probe/${randomUUID()}`);
81
+ const versionIds = [];
82
+ let written = false;
83
+ const upload = async (body) => {
84
+ const bytes = Buffer.from(body);
85
+ const response = await blob.upload(bytes, bytes.byteLength, {
86
+ conditions: { ifNoneMatch: "*" },
87
+ blobHTTPHeaders: { blobContentType: "application/x-ndjson", blobCacheControl: "no-store" },
88
+ abortSignal: signal
89
+ });
90
+ written = true;
91
+ if (response.versionId) versionIds.push(response.versionId);
92
+ versionOf(response);
93
+ };
94
+ try {
95
+ await upload('{"probe":1}\n');
96
+ try {
97
+ await upload('{"probe":2}\n');
98
+ } catch (error) {
99
+ if (isConflict(error)) return;
100
+ throw error;
101
+ }
102
+ throw new ReportingRowStoreError("UNSAFE_BINDING", "store ignores If-None-Match and replaces blobs");
103
+ } finally {
104
+ if (written && !signal.aborted) {
105
+ await blob.delete({ abortSignal: signal }).catch(() => void 0);
106
+ for (const versionId of versionIds) {
107
+ if (signal.aborted) break;
108
+ await blob.withVersion(versionId).delete({ abortSignal: signal }).catch(() => void 0);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ return {
114
+ name: "azure_blob",
115
+ rangedReads: true,
116
+ validateLocation,
117
+ probe(input, context) {
118
+ return runReportingRowProviderOperation(context.signal, async () => {
119
+ const container = containerFor(input);
120
+ let properties;
121
+ try {
122
+ properties = await container.getProperties({ abortSignal: context.signal });
123
+ } catch (error) {
124
+ if (statusOf(error) === 404) throw new ReportingRowStoreError("UNSAFE_BINDING", "container does not exist");
125
+ if (isForbidden(error)) throw new ReportingRowStoreError("UNSAFE_BINDING", "container is not accessible");
126
+ throw error;
127
+ }
128
+ if (properties.blobPublicAccess) {
129
+ throw new ReportingRowStoreError("UNSAFE_BINDING", "container allows anonymous read access");
130
+ }
131
+ await assertCreateOnlyEnforced(container, input.prefix, context.signal);
132
+ });
133
+ },
134
+ putIfAbsent(input, context) {
135
+ return runReportingRowProviderOperation(context.signal, async () => {
136
+ const blob = blobFor(input);
137
+ const bytes = Buffer.from(input.bytes);
138
+ try {
139
+ const response = await blob.upload(bytes, bytes.byteLength, {
140
+ conditions: { ifNoneMatch: "*" },
141
+ blobHTTPHeaders: { blobContentType: input.contentType, blobCacheControl: "no-store" },
142
+ metadata: metadataOf(input.metadata),
143
+ abortSignal: context.signal
144
+ });
145
+ return { created: true, nativeVersion: versionOf(response) };
146
+ } catch (error) {
147
+ if (!isConflict(error)) throw error;
148
+ }
149
+ const existing = await blob.getProperties({ abortSignal: context.signal });
150
+ return { created: false, nativeVersion: versionOf(existing) };
151
+ });
152
+ },
153
+ get(input, context) {
154
+ return runReportingRowProviderOperation(context.signal, async () => {
155
+ const blob = blobFor(input);
156
+ assertReportingRowReadBounds(input);
157
+ const target = pinned(blob, input.nativeVersion);
158
+ if (target === null) return null;
159
+ try {
160
+ if (input.range?.length === 0) {
161
+ const properties = await target.blob.getProperties({ abortSignal: context.signal });
162
+ if (!servedVersion(properties, target.versionId)) return null;
163
+ if (input.range.offset > Number(properties.contentLength ?? 0)) {
164
+ throw new ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "requested range exceeds the stored object");
165
+ }
166
+ return Buffer.alloc(0);
167
+ }
168
+ const response = await target.blob.download(input.range?.offset ?? 0, input.range?.length, {
169
+ abortSignal: context.signal
170
+ });
171
+ const body = response.readableStreamBody;
172
+ if (!body || typeof body[Symbol.asyncIterator] !== "function") {
173
+ throw new ReportingRowStoreError("PROVIDER_UNAVAILABLE", "blob body is not a stream");
174
+ }
175
+ if (!servedVersion(response, target.versionId)) {
176
+ body.destroy?.();
177
+ return null;
178
+ }
179
+ if (typeof response.contentLength === "number" && response.contentLength > input.maxBytes) {
180
+ body.destroy?.();
181
+ throw new ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "stored object exceeds its recorded size");
182
+ }
183
+ return await readReportingRowStream(body, input.maxBytes, context.signal);
184
+ } catch (error) {
185
+ if (isMissing(error)) return null;
186
+ if (statusOf(error) === 400) return null;
187
+ if (statusOf(error) === 416 || codeOf(error) === "InvalidRange") {
188
+ throw new ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "requested range exceeds the stored object");
189
+ }
190
+ throw error;
191
+ }
192
+ });
193
+ },
194
+ delete(input, context) {
195
+ return runReportingRowProviderOperation(context.signal, async () => {
196
+ const blob = blobFor(input);
197
+ const target = pinned(blob, input.nativeVersion);
198
+ if (target === null) return "absent";
199
+ const signal = context.signal;
200
+ try {
201
+ const recorded = await target.blob.getProperties({ abortSignal: signal });
202
+ if (recorded.versionId !== target.versionId) return "absent";
203
+ } catch (error) {
204
+ if (isMissing(error) || statusOf(error) === 400) return "absent";
205
+ throw error;
206
+ }
207
+ let deletedCurrent = false;
208
+ try {
209
+ const current = await blob.getProperties({ abortSignal: signal });
210
+ if (current.versionId === target.versionId && current.etag) {
211
+ await blob.delete({ conditions: { ifMatch: current.etag }, abortSignal: signal });
212
+ deletedCurrent = true;
213
+ }
214
+ } catch (error) {
215
+ if (!isMissing(error)) throw error;
216
+ }
217
+ try {
218
+ await target.blob.delete({ abortSignal: signal });
219
+ return "deleted";
220
+ } catch (error) {
221
+ if (isMissing(error) || statusOf(error) === 400) return deletedCurrent ? "deleted" : "absent";
222
+ throw error;
223
+ }
224
+ });
225
+ }
226
+ };
227
+ }
228
+ export {
229
+ createAzureBlobReportingRowObjectProviderV1
230
+ };
@@ -0,0 +1,127 @@
1
+ import type { PostgresReportingLedgerStore, ReportingLedgerRevisionMetadataV1, ReportingPgPool } from '../ledger/index.mjs';
2
+ /**
3
+ * BigQuery warehouse sink for Reliable Reporting (shared SDK persistence spec,
4
+ * adcontextprotocol/adcp#7996 §3.6).
5
+ *
6
+ * The sink is a derived, non-authoritative copy of committed revisions. It
7
+ * follows the ledger's host change feed, reads every revision's rows through
8
+ * the store's verified reader, maps them to the adopter's columns, and loads
9
+ * one batch per tick with a deterministic job ID. It is never read for
10
+ * serving or verification, so its tables, columns, partitioning and retention
11
+ * are the adopter's.
12
+ *
13
+ * Exactly-once loading: a batch (change-feed range, revision list, job IDs) is
14
+ * planned durably in PostgreSQL before any load. A crash between the load and
15
+ * the cursor commit replays the same batch under the same job IDs, which
16
+ * BigQuery refuses to run twice. A failed load job is retried under a new
17
+ * attempt suffix only after BigQuery reports that job finished with an error,
18
+ * and each table keeps its own attempt count, so no batch lands twice.
19
+ */
20
+ /** Apply after `REPORTING_LEDGER_CHANGES_MIGRATION`. Holds one row per sink. */
21
+ export declare const REPORTING_WAREHOUSE_SINK_MIGRATION: string;
22
+ /** Structural subset of the official `@google-cloud/bigquery` client the sink uses. */
23
+ export interface ReportingBigQueryClientV1 {
24
+ dataset(datasetId: string, options?: {
25
+ projectId?: string;
26
+ location?: string;
27
+ }): {
28
+ table(tableId: string): {
29
+ load(source: string, metadata: Record<string, unknown>): Promise<unknown>;
30
+ };
31
+ };
32
+ job(jobId: string, options?: {
33
+ location?: string;
34
+ }): {
35
+ getMetadata(): Promise<unknown>;
36
+ };
37
+ query(options: {
38
+ query: string;
39
+ location?: string;
40
+ }): Promise<unknown>;
41
+ }
42
+ export interface ReportingWarehouseRowContextV1 {
43
+ revision: ReportingLedgerRevisionMetadataV1;
44
+ ordinal: number;
45
+ }
46
+ export interface CreateBigQueryReportingWarehouseSinkOptionsV1 {
47
+ /** Ledger store with `changeFeed: true`. */
48
+ store: Pick<PostgresReportingLedgerStore, 'changesAfter' | 'saveFeedConsumerCursor' | 'getRevisionMetadata' | 'readRevisionRows'>;
49
+ /** PostgreSQL pool holding `REPORTING_WAREHOUSE_SINK_MIGRATION`. */
50
+ db: Pick<ReportingPgPool, 'query'>;
51
+ /** Host-constructed official BigQuery client. */
52
+ bigquery: ReportingBigQueryClientV1;
53
+ /** Stable sink name; also the registered change-feed consumer name. */
54
+ name: string;
55
+ projectId?: string;
56
+ datasetId: string;
57
+ rowsTableId: string;
58
+ revisionsTableId: string;
59
+ /** Dataset location, so jobs run in the dataset's region. */
60
+ location?: string;
61
+ /** Restrict the sink to one account; omit for the deployment-wide feed. */
62
+ account_id?: string;
63
+ /**
64
+ * Map one verified report row to the adopter's columns. The sink always adds
65
+ * `reporting_revision_id`, `reporting_obligation_id`, `account_id`,
66
+ * `revision_number`, `finality`, `period_date` and `ordinal`. Default: the
67
+ * row as a `row` JSON column.
68
+ */
69
+ mapRow?(row: Record<string, unknown>, context: ReportingWarehouseRowContextV1): Record<string, unknown>;
70
+ /** Revisions per batch. Default 200. */
71
+ maxRevisionsPerBatch?: number;
72
+ /** Job labels for cost attribution. */
73
+ labels?: Readonly<Record<string, string>>;
74
+ /**
75
+ * Deadline for one load (and its status check). Default 10 minutes. It is
76
+ * also sent as the job's `jobTimeoutMs`. A run that times out leaves its
77
+ * plan in place; the next run reuses the same job ID and waits for, or
78
+ * accepts, the job BigQuery is still running.
79
+ */
80
+ loadTimeoutMilliseconds?: number;
81
+ }
82
+ export interface BigQueryReportingWarehouseSinkV1 {
83
+ /**
84
+ * Load at most one batch. Resolves with what was loaded; `idle` when nothing new committed.
85
+ *
86
+ * `missing` lists revision IDs the change feed announced that no longer
87
+ * exist in the ledger because their period was retired before this batch
88
+ * ran. That is a data-loss signal: the sink fell behind retention (it was
89
+ * down longer than `maxFeedHoldDays`, so retention stopped waiting for it)
90
+ * and the warehouse lacks those revisions. Backfill them from another source
91
+ * or accept the gap. The cursor still advances so the sink is not stuck.
92
+ * Alert on a non-empty `missing`. After a crash-and-replay of a planned
93
+ * batch, a listed revision may already have been loaded by the first attempt.
94
+ */
95
+ runOnce(options?: {
96
+ signal?: AbortSignal;
97
+ }): Promise<{
98
+ idle: boolean;
99
+ revisions: number;
100
+ rows: number;
101
+ missing: string[];
102
+ }>;
103
+ /** DDL for the default rows and revisions tables (partitioned and clustered). */
104
+ readonly defaultTablesSql: string;
105
+ /** DDL for the reference `current_rows` view. */
106
+ readonly currentRowsViewSql: string;
107
+ }
108
+ export declare function createBigQueryReportingWarehouseSinkV1(options: CreateBigQueryReportingWarehouseSinkOptionsV1): BigQueryReportingWarehouseSinkV1;
109
+ /** Deterministic load job ID for one planned change-feed range. */
110
+ export declare function reportingWarehouseJobIdV1(name: string, from: string | null, to: string): string;
111
+ interface WarehouseTablesV1 {
112
+ projectId?: string;
113
+ datasetId: string;
114
+ rowsTableId: string;
115
+ revisionsTableId: string;
116
+ }
117
+ /** DDL for the default warehouse tables: partitioned by period date, clustered by account and revision. */
118
+ export declare function reportingWarehouseDefaultTablesSqlV1(tables: WarehouseTablesV1): string;
119
+ /**
120
+ * DDL for the reference `current_rows` view: only each obligation's current
121
+ * revision (its official revision, otherwise its latest snapshot). Summing
122
+ * cumulative snapshots across revisions double-counts; query this view.
123
+ */
124
+ export declare function reportingWarehouseCurrentRowsViewSqlV1(tables: WarehouseTablesV1 & {
125
+ viewId?: string;
126
+ }): string;
127
+ export {};