@adcp/sdk 15.5.0 → 15.7.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 (81) 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/handler.js +20 -8
  23. package/dist/lib/reporting/ledger/handler.mjs +20 -8
  24. package/dist/lib/reporting/ledger/postgres.d.mts +21 -0
  25. package/dist/lib/reporting/ledger/postgres.d.ts +21 -0
  26. package/dist/lib/reporting/ledger/postgres.js +35 -2
  27. package/dist/lib/reporting/ledger/postgres.mjs +35 -2
  28. package/dist/lib/reporting/ledger/row-storage-object.js +31 -8
  29. package/dist/lib/reporting/ledger/row-storage-object.mjs +32 -9
  30. package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +0 -7
  31. package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +0 -7
  32. package/dist/lib/reporting/ledger/row-storage-provider-io.js +0 -10
  33. package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +0 -8
  34. package/dist/lib/reporting/s3/index.d.mts +29 -0
  35. package/dist/lib/reporting/s3/index.d.ts +29 -0
  36. package/dist/lib/reporting/s3/index.js +324 -0
  37. package/dist/lib/reporting/s3/index.mjs +317 -0
  38. package/dist/lib/reporting/service/index.d.mts +39 -22
  39. package/dist/lib/reporting/service/index.d.ts +39 -22
  40. package/dist/lib/reporting/service/index.js +75 -64
  41. package/dist/lib/reporting/service/index.mjs +79 -63
  42. package/dist/lib/reporting/service/maintenance.d.mts +91 -0
  43. package/dist/lib/reporting/service/maintenance.d.ts +91 -0
  44. package/dist/lib/reporting/service/maintenance.js +140 -0
  45. package/dist/lib/reporting/service/maintenance.mjs +114 -0
  46. package/dist/lib/schemas/browser.d.mts +14 -0
  47. package/dist/lib/schemas/browser.d.ts +14 -0
  48. package/dist/lib/schemas/browser.js +48 -0
  49. package/dist/lib/schemas/browser.mjs +20 -0
  50. package/dist/lib/schemas/index.d.mts +2 -120
  51. package/dist/lib/schemas/index.d.ts +2 -120
  52. package/dist/lib/schemas/index.js +3 -11
  53. package/dist/lib/schemas/index.mjs +1 -9
  54. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  55. package/dist/lib/testing/index.d.mts +1 -0
  56. package/dist/lib/testing/index.d.ts +1 -0
  57. package/dist/lib/testing/index.js +3 -0
  58. package/dist/lib/testing/index.mjs +2 -0
  59. package/dist/lib/testing/pending-web-flow-store.d.mts +20 -0
  60. package/dist/lib/testing/pending-web-flow-store.d.ts +20 -0
  61. package/dist/lib/testing/pending-web-flow-store.js +132 -0
  62. package/dist/lib/testing/pending-web-flow-store.mjs +98 -0
  63. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  64. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  65. package/dist/lib/validation/get-products-request.d.mts +122 -0
  66. package/dist/lib/validation/get-products-request.d.ts +122 -0
  67. package/dist/lib/validation/get-products-request.js +39 -0
  68. package/dist/lib/validation/get-products-request.mjs +14 -0
  69. package/dist/lib/version.d.mts +3 -3
  70. package/dist/lib/version.d.ts +3 -3
  71. package/dist/lib/version.js +3 -3
  72. package/dist/lib/version.mjs +3 -3
  73. package/docs/TYPE-SUMMARY.md +1 -1
  74. package/docs/ZOD-SCHEMAS.md +13 -1
  75. package/docs/guides/OAUTH-ISSUER-BINDING.md +6 -0
  76. package/docs/guides/REPORTING-LEDGER.md +254 -0
  77. package/docs/guides/REPORTING-OPERATIONS.md +82 -1
  78. package/docs/guides/WEB-OAUTH.md +48 -1
  79. package/docs/llms.txt +1 -1
  80. package/docs/migration-14.x-rc-worksheet.md +6 -4
  81. package/package.json +64 -1
@@ -423,6 +423,18 @@ class PostgresReportingLedgerStore {
423
423
  rowStorage;
424
424
  rowStorageWrites;
425
425
  changeFeed;
426
+ /** Whether this store writes the host change feed (`changeFeed: true`). */
427
+ get changeFeedEnabled() {
428
+ return this.changeFeed;
429
+ }
430
+ /**
431
+ * Delete expired cursor snapshots and changes checkpoints, a bounded batch
432
+ * at a time. The store-bound form of `sweepExpiredReportingLedgerState`, for
433
+ * hosts that hold the store but not the pool.
434
+ */
435
+ async sweepExpiredState(limit = 1e3) {
436
+ return sweepExpiredReportingLedgerState(this.pool, limit);
437
+ }
426
438
  /** Append one host change-feed row inside the caller's account-locked transaction. */
427
439
  async appendChange(client, kind, accountId, recordId, obligationId) {
428
440
  if (!this.changeFeed) return;
@@ -1288,6 +1300,15 @@ ${managedDueArm} )
1288
1300
  *
1289
1301
  * Planners skip retired periods, and `ledger_retained_from` advances past
1290
1302
  * them because their obligations no longer exist.
1303
+ *
1304
+ * When the change-feed tables exist, a period that is not yet retiring is
1305
+ * also held while any live registered feed consumer (updated within
1306
+ * `maxFeedHoldDays`, default 7) has not passed that period's change rows in
1307
+ * the consumer's own feed order, the same rule `pruneChanges` applies. A
1308
+ * held period is simply not a candidate this pass. A consumer that stays
1309
+ * silent past `maxFeedHoldDays` stops holding, so retention is never
1310
+ * blocked forever; it then falls behind retention and a sink sees the
1311
+ * retired revisions as missing.
1291
1312
  */
1292
1313
  async retireExpiredPeriods(input) {
1293
1314
  if (!Number.isSafeInteger(input.statusRetentionDays) || input.statusRetentionDays < 1) {
@@ -1299,8 +1320,18 @@ ${managedDueArm} )
1299
1320
  }
1300
1321
  const limit = input.limit ?? 100;
1301
1322
  positiveInteger(limit, "limit");
1323
+ const holdDays = input.maxFeedHoldDays ?? 7;
1324
+ positiveInteger(holdDays, "maxFeedHoldDays");
1302
1325
  const tables = await this.optionalTables();
1303
1326
  const materializationHold = tables.has("adcp_reporting_materializations") ? `AND NOT EXISTS (SELECT 1 FROM adcp_reporting_materializations hold WHERE hold.obligation_id = obligation.obligation_id)` : "";
1327
+ const feedHold = tables.has("adcp_reporting_changes") && tables.has("adcp_reporting_feed_consumers") ? `AND NOT EXISTS (
1328
+ SELECT 1 FROM adcp_reporting_changes change
1329
+ JOIN adcp_reporting_feed_consumers consumer
1330
+ ON consumer.updated_at > clock_timestamp() - ($4::integer * INTERVAL '1 day')
1331
+ WHERE change.obligation_id = obligation.obligation_id
1332
+ AND ((consumer.account_id IS NULL
1333
+ AND (change.xid, change.seq) > (consumer.cursor_xid, consumer.cursor_seq))
1334
+ OR (consumer.account_id = change.account_id AND change.seq > consumer.cursor_seq)))` : "";
1304
1335
  const candidates = await this.query(
1305
1336
  `SELECT obligation_id, account_id FROM (
1306
1337
  SELECT tombstone.obligation_id, tombstone.account_id, 0 AS resume, tombstone.period_end
@@ -1321,10 +1352,11 @@ ${managedDueArm} )
1321
1352
  AND NOT EXISTS (SELECT 1 FROM adcp_reporting_obligation_tombstones tombstone
1322
1353
  WHERE tombstone.obligation_id = obligation.obligation_id)
1323
1354
  ${materializationHold}
1355
+ ${feedHold}
1324
1356
  ) candidate
1325
1357
  ORDER BY resume, period_end, obligation_id
1326
1358
  LIMIT $1`,
1327
- [limit, input.account_id ?? null, recordDays]
1359
+ feedHold ? [limit, input.account_id ?? null, recordDays, holdDays] : [limit, input.account_id ?? null, recordDays]
1328
1360
  );
1329
1361
  let retired = 0;
1330
1362
  const failed = [];
@@ -1496,7 +1528,8 @@ ${managedDueArm} )
1496
1528
  `SELECT table_name AS name FROM information_schema.tables
1497
1529
  WHERE table_schema = current_schema()
1498
1530
  AND table_name IN ('adcp_reporting_row_sets', 'adcp_reporting_row_write_intents',
1499
- 'adcp_reporting_materializations')`
1531
+ 'adcp_reporting_materializations', 'adcp_reporting_changes',
1532
+ 'adcp_reporting_feed_consumers')`
1500
1533
  );
1501
1534
  return new Set(result.rows.map((row) => row.name));
1502
1535
  }
@@ -127,10 +127,8 @@ function createFilesystemReportingRowObjectProviderV1(options) {
127
127
  }
128
128
  return target;
129
129
  };
130
- const version = async (target) => {
131
- const stat = await import_fs.promises.stat(target);
132
- return `${stat.ino}:${stat.mtimeMs}:${stat.size}`;
133
- };
130
+ const versionOf = (stat) => `${stat.ino}:${stat.birthtimeNs}:${stat.mtimeNs}:${stat.size}`;
131
+ const version = async (target) => versionOf(await import_fs.promises.stat(target, { bigint: true }));
134
132
  return {
135
133
  name: "filesystem",
136
134
  rangedReads: true,
@@ -153,6 +151,8 @@ function createFilesystemReportingRowObjectProviderV1(options) {
153
151
  const handle = await import_fs.promises.open(temporary, "wx");
154
152
  try {
155
153
  await handle.writeFile(input.bytes);
154
+ const mtime = Date.now() / 1e3 + (0, import_crypto.randomInt)(0, 1e3) / 1e6;
155
+ await handle.utimes(mtime, mtime);
156
156
  await handle.sync();
157
157
  } finally {
158
158
  await handle.close();
@@ -180,11 +180,12 @@ function createFilesystemReportingRowObjectProviderV1(options) {
180
180
  throw new import_row_storage_errors.ReportingRowStoreError("PROVIDER_UNAVAILABLE", "filesystem read failed");
181
181
  }
182
182
  try {
183
- const stat = await handle.stat();
184
- if (`${stat.ino}:${stat.mtimeMs}:${stat.size}` !== input.nativeVersion) return null;
183
+ const stat = await handle.stat({ bigint: true });
184
+ if (versionOf(stat) !== input.nativeVersion) return null;
185
+ const size = Number(stat.size);
185
186
  const offset = input.range?.offset ?? 0;
186
- const length = input.range?.length ?? stat.size - offset;
187
- if (offset < 0 || length < 0 || offset + length > stat.size) {
187
+ const length = input.range?.length ?? size - offset;
188
+ if (offset < 0 || length < 0 || offset + length > size) {
188
189
  throw new import_row_storage_errors.ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "requested range exceeds the stored object");
189
190
  }
190
191
  if (length > input.maxBytes) {
@@ -262,6 +263,28 @@ async function runReportingRowObjectProviderConformanceV1(provider, input) {
262
263
  throw new Error("repeated delete was not idempotent");
263
264
  }
264
265
  passed.push("exact-version-delete");
266
+ const recreated = await provider.putIfAbsent(
267
+ { ...base, key, bytes: first, contentType: "application/x-ndjson", metadata },
268
+ context
269
+ );
270
+ if (!recreated.created || !recreated.nativeVersion) throw new Error("putIfAbsent did not re-create a deleted object");
271
+ if (recreated.nativeVersion === created.nativeVersion) {
272
+ throw new Error("re-creating identical bytes produced the same native version; versions must be write-unique");
273
+ }
274
+ if (await provider.delete({ ...base, key, nativeVersion: created.nativeVersion }, context) !== "absent") {
275
+ throw new Error("delete of a stale version removed or reported a later write");
276
+ }
277
+ const survivor = await provider.get(
278
+ { ...base, key, nativeVersion: recreated.nativeVersion, maxBytes: 1024 },
279
+ context
280
+ );
281
+ if (!survivor || !Buffer.from(survivor).equals(first)) {
282
+ throw new Error("delete of a stale version removed a later write with identical bytes");
283
+ }
284
+ if (await provider.delete({ ...base, key, nativeVersion: recreated.nativeVersion }, context) !== "deleted") {
285
+ throw new Error("delete did not remove the re-created version");
286
+ }
287
+ passed.push("write-unique-version");
265
288
  return passed;
266
289
  }
267
290
  function sha256Hex(value) {
@@ -1,4 +1,4 @@
1
- import { createHash, randomUUID } from "crypto";
1
+ import { createHash, randomInt, randomUUID } from "crypto";
2
2
  import { promises as fs } from "fs";
3
3
  import * as path from "path";
4
4
  import { canonicalize } from "../../utils/jcs.mjs";
@@ -86,10 +86,8 @@ function createFilesystemReportingRowObjectProviderV1(options) {
86
86
  }
87
87
  return target;
88
88
  };
89
- const version = async (target) => {
90
- const stat = await fs.stat(target);
91
- return `${stat.ino}:${stat.mtimeMs}:${stat.size}`;
92
- };
89
+ const versionOf = (stat) => `${stat.ino}:${stat.birthtimeNs}:${stat.mtimeNs}:${stat.size}`;
90
+ const version = async (target) => versionOf(await fs.stat(target, { bigint: true }));
93
91
  return {
94
92
  name: "filesystem",
95
93
  rangedReads: true,
@@ -112,6 +110,8 @@ function createFilesystemReportingRowObjectProviderV1(options) {
112
110
  const handle = await fs.open(temporary, "wx");
113
111
  try {
114
112
  await handle.writeFile(input.bytes);
113
+ const mtime = Date.now() / 1e3 + randomInt(0, 1e3) / 1e6;
114
+ await handle.utimes(mtime, mtime);
115
115
  await handle.sync();
116
116
  } finally {
117
117
  await handle.close();
@@ -139,11 +139,12 @@ function createFilesystemReportingRowObjectProviderV1(options) {
139
139
  throw new ReportingRowStoreError("PROVIDER_UNAVAILABLE", "filesystem read failed");
140
140
  }
141
141
  try {
142
- const stat = await handle.stat();
143
- if (`${stat.ino}:${stat.mtimeMs}:${stat.size}` !== input.nativeVersion) return null;
142
+ const stat = await handle.stat({ bigint: true });
143
+ if (versionOf(stat) !== input.nativeVersion) return null;
144
+ const size = Number(stat.size);
144
145
  const offset = input.range?.offset ?? 0;
145
- const length = input.range?.length ?? stat.size - offset;
146
- if (offset < 0 || length < 0 || offset + length > stat.size) {
146
+ const length = input.range?.length ?? size - offset;
147
+ if (offset < 0 || length < 0 || offset + length > size) {
147
148
  throw new ReportingRowStoreError("ROWS_INTEGRITY_FAILED", "requested range exceeds the stored object");
148
149
  }
149
150
  if (length > input.maxBytes) {
@@ -221,6 +222,28 @@ async function runReportingRowObjectProviderConformanceV1(provider, input) {
221
222
  throw new Error("repeated delete was not idempotent");
222
223
  }
223
224
  passed.push("exact-version-delete");
225
+ const recreated = await provider.putIfAbsent(
226
+ { ...base, key, bytes: first, contentType: "application/x-ndjson", metadata },
227
+ context
228
+ );
229
+ if (!recreated.created || !recreated.nativeVersion) throw new Error("putIfAbsent did not re-create a deleted object");
230
+ if (recreated.nativeVersion === created.nativeVersion) {
231
+ throw new Error("re-creating identical bytes produced the same native version; versions must be write-unique");
232
+ }
233
+ if (await provider.delete({ ...base, key, nativeVersion: created.nativeVersion }, context) !== "absent") {
234
+ throw new Error("delete of a stale version removed or reported a later write");
235
+ }
236
+ const survivor = await provider.get(
237
+ { ...base, key, nativeVersion: recreated.nativeVersion, maxBytes: 1024 },
238
+ context
239
+ );
240
+ if (!survivor || !Buffer.from(survivor).equals(first)) {
241
+ throw new Error("delete of a stale version removed a later write with identical bytes");
242
+ }
243
+ if (await provider.delete({ ...base, key, nativeVersion: recreated.nativeVersion }, context) !== "deleted") {
244
+ throw new Error("delete did not remove the re-created version");
245
+ }
246
+ passed.push("write-unique-version");
224
247
  return passed;
225
248
  }
226
249
  function sha256Hex(value) {
@@ -42,10 +42,3 @@ export declare function assertReportingRowLocationKeys(location: Readonly<Record
42
42
  * owned prefix. An absent or empty filter matches everything.
43
43
  */
44
44
  export declare function reportingRowPrefixFilterOverlaps(filter: string | undefined, bindingPrefix: string): boolean;
45
- /**
46
- * S3 and Azure record a version ID when the store is versioned, else the
47
- * ETag. ETags are always kept quoted, which version IDs never are, so the two
48
- * cannot be confused when the version is read back.
49
- */
50
- export declare function reportingRowEtagVersion(etag: string): string;
51
- export declare function isReportingRowEtagVersion(nativeVersion: string): boolean;
@@ -42,10 +42,3 @@ export declare function assertReportingRowLocationKeys(location: Readonly<Record
42
42
  * owned prefix. An absent or empty filter matches everything.
43
43
  */
44
44
  export declare function reportingRowPrefixFilterOverlaps(filter: string | undefined, bindingPrefix: string): boolean;
45
- /**
46
- * S3 and Azure record a version ID when the store is versioned, else the
47
- * ETag. ETags are always kept quoted, which version IDs never are, so the two
48
- * cannot be confused when the version is read back.
49
- */
50
- export declare function reportingRowEtagVersion(etag: string): string;
51
- export declare function isReportingRowEtagVersion(nativeVersion: string): boolean;
@@ -20,11 +20,9 @@ var row_storage_provider_io_exports = {};
20
20
  __export(row_storage_provider_io_exports, {
21
21
  assertReportingRowLocationKeys: () => assertReportingRowLocationKeys,
22
22
  assertReportingRowReadBounds: () => assertReportingRowReadBounds,
23
- isReportingRowEtagVersion: () => isReportingRowEtagVersion,
24
23
  readReportingRowStream: () => readReportingRowStream,
25
24
  reportingRowAbortError: () => reportingRowAbortError,
26
25
  reportingRowClientMap: () => reportingRowClientMap,
27
- reportingRowEtagVersion: () => reportingRowEtagVersion,
28
26
  reportingRowPrefixFilterOverlaps: () => reportingRowPrefixFilterOverlaps,
29
27
  runReportingRowProviderOperation: () => runReportingRowProviderOperation,
30
28
  selectReportingRowClient: () => selectReportingRowClient
@@ -131,21 +129,13 @@ function reportingRowPrefixFilterOverlaps(filter, bindingPrefix) {
131
129
  const owned = `${bindingPrefix}/`;
132
130
  return owned.startsWith(filter) || filter.startsWith(owned);
133
131
  }
134
- function reportingRowEtagVersion(etag) {
135
- return etag.startsWith('"') && etag.endsWith('"') && etag.length >= 2 ? etag : `"${etag}"`;
136
- }
137
- function isReportingRowEtagVersion(nativeVersion) {
138
- return nativeVersion.length >= 2 && nativeVersion.startsWith('"') && nativeVersion.endsWith('"');
139
- }
140
132
  // Annotate the CommonJS export names for ESM import in node:
141
133
  0 && (module.exports = {
142
134
  assertReportingRowLocationKeys,
143
135
  assertReportingRowReadBounds,
144
- isReportingRowEtagVersion,
145
136
  readReportingRowStream,
146
137
  reportingRowAbortError,
147
138
  reportingRowClientMap,
148
- reportingRowEtagVersion,
149
139
  reportingRowPrefixFilterOverlaps,
150
140
  runReportingRowProviderOperation,
151
141
  selectReportingRowClient
@@ -99,20 +99,12 @@ function reportingRowPrefixFilterOverlaps(filter, bindingPrefix) {
99
99
  const owned = `${bindingPrefix}/`;
100
100
  return owned.startsWith(filter) || filter.startsWith(owned);
101
101
  }
102
- function reportingRowEtagVersion(etag) {
103
- return etag.startsWith('"') && etag.endsWith('"') && etag.length >= 2 ? etag : `"${etag}"`;
104
- }
105
- function isReportingRowEtagVersion(nativeVersion) {
106
- return nativeVersion.length >= 2 && nativeVersion.startsWith('"') && nativeVersion.endsWith('"');
107
- }
108
102
  export {
109
103
  assertReportingRowLocationKeys,
110
104
  assertReportingRowReadBounds,
111
- isReportingRowEtagVersion,
112
105
  readReportingRowStream,
113
106
  reportingRowAbortError,
114
107
  reportingRowClientMap,
115
- reportingRowEtagVersion,
116
108
  reportingRowPrefixFilterOverlaps,
117
109
  runReportingRowProviderOperation,
118
110
  selectReportingRowClient
@@ -0,0 +1,29 @@
1
+ import { type S3Client } from '@aws-sdk/client-s3';
2
+ import type { ReportingRowObjectProviderV1 } from '../ledger/row-storage-object.mjs';
3
+ export interface CreateS3ReportingRowObjectProviderOptionsV1 {
4
+ /** Official client used by bindings without a `credentialRef`. */
5
+ client?: S3Client;
6
+ /**
7
+ * Official clients keyed by `credentialRef`. This map is the closed set of
8
+ * references a binding may name; an unknown reference fails `UNSAFE_BINDING`.
9
+ * Endpoint, region, credentials and path style all come from these clients,
10
+ * never from binding locations.
11
+ */
12
+ clients?: Readonly<Record<string, S3Client>>;
13
+ }
14
+ /**
15
+ * Amazon S3 (and S3-compatible) row-object provider (`@adcp/sdk/reporting/s3`).
16
+ *
17
+ * Inject official `S3Client`s configured by the host (region, endpoint,
18
+ * credentials). Bindings use `location: { bucket, region? }`; when `region` is
19
+ * set the probe requires the selected client to be configured for it.
20
+ *
21
+ * Writes use `PutObject` with `If-None-Match: *`. Bucket versioning must be
22
+ * enabled: the recorded native version is the write-unique `VersionId`, and every
23
+ * read and delete is pinned to it, so a delayed delete can never remove a later
24
+ * write that re-created identical bytes at the same key (ETags are content-derived
25
+ * and cannot fence that). The probe refuses unversioned or suspended buckets.
26
+ * Because S3-compatible stores may silently ignore `If-None-Match`, the probe
27
+ * also proves create-only behaviour empirically before any binding is used.
28
+ */
29
+ export declare function createS3ReportingRowObjectProviderV1(options: CreateS3ReportingRowObjectProviderOptionsV1): ReportingRowObjectProviderV1;
@@ -0,0 +1,29 @@
1
+ import { type S3Client } from '@aws-sdk/client-s3';
2
+ import type { ReportingRowObjectProviderV1 } from '../ledger/row-storage-object';
3
+ export interface CreateS3ReportingRowObjectProviderOptionsV1 {
4
+ /** Official client used by bindings without a `credentialRef`. */
5
+ client?: S3Client;
6
+ /**
7
+ * Official clients keyed by `credentialRef`. This map is the closed set of
8
+ * references a binding may name; an unknown reference fails `UNSAFE_BINDING`.
9
+ * Endpoint, region, credentials and path style all come from these clients,
10
+ * never from binding locations.
11
+ */
12
+ clients?: Readonly<Record<string, S3Client>>;
13
+ }
14
+ /**
15
+ * Amazon S3 (and S3-compatible) row-object provider (`@adcp/sdk/reporting/s3`).
16
+ *
17
+ * Inject official `S3Client`s configured by the host (region, endpoint,
18
+ * credentials). Bindings use `location: { bucket, region? }`; when `region` is
19
+ * set the probe requires the selected client to be configured for it.
20
+ *
21
+ * Writes use `PutObject` with `If-None-Match: *`. Bucket versioning must be
22
+ * enabled: the recorded native version is the write-unique `VersionId`, and every
23
+ * read and delete is pinned to it, so a delayed delete can never remove a later
24
+ * write that re-created identical bytes at the same key (ETags are content-derived
25
+ * and cannot fence that). The probe refuses unversioned or suspended buckets.
26
+ * Because S3-compatible stores may silently ignore `If-None-Match`, the probe
27
+ * also proves create-only behaviour empirically before any binding is used.
28
+ */
29
+ export declare function createS3ReportingRowObjectProviderV1(options: CreateS3ReportingRowObjectProviderOptionsV1): ReportingRowObjectProviderV1;