@adcp/sdk 15.6.0 → 15.7.1
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.
- package/dist/lib/reporting/ledger/handler.js +20 -8
- package/dist/lib/reporting/ledger/handler.mjs +20 -8
- package/dist/lib/reporting/ledger/postgres.d.mts +11 -0
- package/dist/lib/reporting/ledger/postgres.d.ts +11 -0
- package/dist/lib/reporting/ledger/postgres.js +12 -0
- package/dist/lib/reporting/ledger/postgres.mjs +12 -0
- package/dist/lib/reporting/service/index.d.mts +36 -21
- package/dist/lib/reporting/service/index.d.ts +36 -21
- package/dist/lib/reporting/service/index.js +75 -65
- package/dist/lib/reporting/service/index.mjs +79 -64
- package/dist/lib/reporting/service/maintenance.d.mts +91 -0
- package/dist/lib/reporting/service/maintenance.d.ts +91 -0
- package/dist/lib/reporting/service/maintenance.js +140 -0
- package/dist/lib/reporting/service/maintenance.mjs +114 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/schemas.generated.js +2542 -2542
- package/dist/lib/types/schemas.generated.mjs +2542 -2542
- package/dist/lib/validation/bidding-policy.js +1 -1
- package/dist/lib/validation/bidding-policy.mjs +1 -1
- package/dist/lib/validation/budget-allocation.js +1 -1
- package/dist/lib/validation/budget-allocation.mjs +1 -1
- package/dist/lib/validation/get-products-request.js +3 -3
- package/dist/lib/validation/get-products-request.mjs +3 -3
- package/dist/lib/validation/sync-creatives.js +9 -13
- package/dist/lib/validation/sync-creatives.mjs +9 -13
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/ZOD-SCHEMAS.md +11 -1
- package/docs/guides/REPORTING-LEDGER.md +89 -0
- package/docs/guides/REPORTING-OPERATIONS.md +6 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +1 -1
|
@@ -410,6 +410,12 @@ function createReportingStatusHandler(store, options) {
|
|
|
410
410
|
};
|
|
411
411
|
}
|
|
412
412
|
const REPORTING_STATUS_REVISION_VIEW_MAX_ROWS = 1e4;
|
|
413
|
+
function reportingRevisionNotFound() {
|
|
414
|
+
return new import_async_outcome.AdcpError("REFERENCE_NOT_FOUND", {
|
|
415
|
+
message: "Reporting revision is unavailable",
|
|
416
|
+
field: "reporting_revision_id"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
413
419
|
function createReportingDeliveryHandler(store) {
|
|
414
420
|
const activeReadsByAccount = /* @__PURE__ */ new Map();
|
|
415
421
|
return async (request, context) => {
|
|
@@ -428,14 +434,15 @@ function createReportingDeliveryHandler(store) {
|
|
|
428
434
|
try {
|
|
429
435
|
const raw = request;
|
|
430
436
|
if (typeof raw.reporting_revision_id !== "string" || !raw.reporting_revision_id) {
|
|
431
|
-
throw new
|
|
437
|
+
throw new import_async_outcome.AdcpError("VALIDATION_ERROR", {
|
|
438
|
+
message: "Ledger delivery reads require reporting_revision_id",
|
|
439
|
+
field: "reporting_revision_id"
|
|
440
|
+
});
|
|
432
441
|
}
|
|
433
442
|
const revision = await (0, import_revision_metadata.getRevisionMetadataFromStore)(store, raw.reporting_revision_id, accountId);
|
|
434
|
-
if (!revision) throw
|
|
443
|
+
if (!revision) throw reportingRevisionNotFound();
|
|
435
444
|
const obligation = await store.getObligation(revision.reporting_obligation_id);
|
|
436
|
-
if (!obligation || obligation.account.account_id !== accountId)
|
|
437
|
-
throw new Error("Reporting revision is unavailable");
|
|
438
|
-
}
|
|
445
|
+
if (!obligation || obligation.account.account_id !== accountId) throw reportingRevisionNotFound();
|
|
439
446
|
const pagination = isRecord(raw.pagination) ? raw.pagination : {};
|
|
440
447
|
const maxResults = typeof pagination.max_results === "number" && Number.isFinite(pagination.max_results) ? Math.max(1, Math.min(500, Math.trunc(pagination.max_results))) : 100;
|
|
441
448
|
const totalCount = revision.binding.rowCount;
|
|
@@ -448,7 +455,7 @@ function createReportingDeliveryHandler(store) {
|
|
|
448
455
|
}
|
|
449
456
|
offset = cursor.offset;
|
|
450
457
|
} catch {
|
|
451
|
-
throw
|
|
458
|
+
throw reportingRevisionNotFound();
|
|
452
459
|
}
|
|
453
460
|
}
|
|
454
461
|
let reportingRows = [];
|
|
@@ -464,14 +471,19 @@ function createReportingDeliveryHandler(store) {
|
|
|
464
471
|
});
|
|
465
472
|
} catch (error) {
|
|
466
473
|
if ((0, import_row_storage.isReportingRowStoreError)(error)) {
|
|
467
|
-
if (error.code === "ROWS_EXPIRED") throw
|
|
474
|
+
if (error.code === "ROWS_EXPIRED") throw reportingRevisionNotFound();
|
|
468
475
|
throw new import_async_outcome.AdcpError("SERVICE_UNAVAILABLE", {
|
|
469
476
|
message: "Reporting revision rows are temporarily unavailable"
|
|
470
477
|
});
|
|
471
478
|
}
|
|
472
479
|
throw error;
|
|
473
480
|
}
|
|
474
|
-
if (!page
|
|
481
|
+
if (!page) throw reportingRevisionNotFound();
|
|
482
|
+
if (page.total !== totalCount) {
|
|
483
|
+
throw new import_async_outcome.AdcpError("SERVICE_UNAVAILABLE", {
|
|
484
|
+
message: "Reporting revision rows are temporarily unavailable"
|
|
485
|
+
});
|
|
486
|
+
}
|
|
475
487
|
reportingRows = page.rows;
|
|
476
488
|
}
|
|
477
489
|
const nextOffset = offset + reportingRows.length;
|
|
@@ -393,6 +393,12 @@ function createReportingStatusHandler(store, options) {
|
|
|
393
393
|
};
|
|
394
394
|
}
|
|
395
395
|
const REPORTING_STATUS_REVISION_VIEW_MAX_ROWS = 1e4;
|
|
396
|
+
function reportingRevisionNotFound() {
|
|
397
|
+
return new AdcpError("REFERENCE_NOT_FOUND", {
|
|
398
|
+
message: "Reporting revision is unavailable",
|
|
399
|
+
field: "reporting_revision_id"
|
|
400
|
+
});
|
|
401
|
+
}
|
|
396
402
|
function createReportingDeliveryHandler(store) {
|
|
397
403
|
const activeReadsByAccount = /* @__PURE__ */ new Map();
|
|
398
404
|
return async (request, context) => {
|
|
@@ -411,14 +417,15 @@ function createReportingDeliveryHandler(store) {
|
|
|
411
417
|
try {
|
|
412
418
|
const raw = request;
|
|
413
419
|
if (typeof raw.reporting_revision_id !== "string" || !raw.reporting_revision_id) {
|
|
414
|
-
throw new
|
|
420
|
+
throw new AdcpError("VALIDATION_ERROR", {
|
|
421
|
+
message: "Ledger delivery reads require reporting_revision_id",
|
|
422
|
+
field: "reporting_revision_id"
|
|
423
|
+
});
|
|
415
424
|
}
|
|
416
425
|
const revision = await getRevisionMetadataFromStore(store, raw.reporting_revision_id, accountId);
|
|
417
|
-
if (!revision) throw
|
|
426
|
+
if (!revision) throw reportingRevisionNotFound();
|
|
418
427
|
const obligation = await store.getObligation(revision.reporting_obligation_id);
|
|
419
|
-
if (!obligation || obligation.account.account_id !== accountId)
|
|
420
|
-
throw new Error("Reporting revision is unavailable");
|
|
421
|
-
}
|
|
428
|
+
if (!obligation || obligation.account.account_id !== accountId) throw reportingRevisionNotFound();
|
|
422
429
|
const pagination = isRecord(raw.pagination) ? raw.pagination : {};
|
|
423
430
|
const maxResults = typeof pagination.max_results === "number" && Number.isFinite(pagination.max_results) ? Math.max(1, Math.min(500, Math.trunc(pagination.max_results))) : 100;
|
|
424
431
|
const totalCount = revision.binding.rowCount;
|
|
@@ -431,7 +438,7 @@ function createReportingDeliveryHandler(store) {
|
|
|
431
438
|
}
|
|
432
439
|
offset = cursor.offset;
|
|
433
440
|
} catch {
|
|
434
|
-
throw
|
|
441
|
+
throw reportingRevisionNotFound();
|
|
435
442
|
}
|
|
436
443
|
}
|
|
437
444
|
let reportingRows = [];
|
|
@@ -447,14 +454,19 @@ function createReportingDeliveryHandler(store) {
|
|
|
447
454
|
});
|
|
448
455
|
} catch (error) {
|
|
449
456
|
if (isReportingRowStoreError(error)) {
|
|
450
|
-
if (error.code === "ROWS_EXPIRED") throw
|
|
457
|
+
if (error.code === "ROWS_EXPIRED") throw reportingRevisionNotFound();
|
|
451
458
|
throw new AdcpError("SERVICE_UNAVAILABLE", {
|
|
452
459
|
message: "Reporting revision rows are temporarily unavailable"
|
|
453
460
|
});
|
|
454
461
|
}
|
|
455
462
|
throw error;
|
|
456
463
|
}
|
|
457
|
-
if (!page
|
|
464
|
+
if (!page) throw reportingRevisionNotFound();
|
|
465
|
+
if (page.total !== totalCount) {
|
|
466
|
+
throw new AdcpError("SERVICE_UNAVAILABLE", {
|
|
467
|
+
message: "Reporting revision rows are temporarily unavailable"
|
|
468
|
+
});
|
|
469
|
+
}
|
|
458
470
|
reportingRows = page.rows;
|
|
459
471
|
}
|
|
460
472
|
const nextOffset = offset + reportingRows.length;
|
|
@@ -137,6 +137,17 @@ export declare class PostgresReportingLedgerStore implements ReportingLedgerStor
|
|
|
137
137
|
private readonly rowStorageWrites;
|
|
138
138
|
private readonly changeFeed;
|
|
139
139
|
constructor(pool: ReportingPgPool, options?: PostgresReportingLedgerStoreOptions);
|
|
140
|
+
/** Whether this store writes the host change feed (`changeFeed: true`). */
|
|
141
|
+
get changeFeedEnabled(): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Delete expired cursor snapshots and changes checkpoints, a bounded batch
|
|
144
|
+
* at a time. The store-bound form of `sweepExpiredReportingLedgerState`, for
|
|
145
|
+
* hosts that hold the store but not the pool.
|
|
146
|
+
*/
|
|
147
|
+
sweepExpiredState(limit?: number): Promise<{
|
|
148
|
+
snapshotsDeleted: number;
|
|
149
|
+
checkpointsDeleted: number;
|
|
150
|
+
}>;
|
|
140
151
|
/** Append one host change-feed row inside the caller's account-locked transaction. */
|
|
141
152
|
private appendChange;
|
|
142
153
|
/** Probe and register row storage when enabled. Safe to call repeatedly. */
|
|
@@ -137,6 +137,17 @@ export declare class PostgresReportingLedgerStore implements ReportingLedgerStor
|
|
|
137
137
|
private readonly rowStorageWrites;
|
|
138
138
|
private readonly changeFeed;
|
|
139
139
|
constructor(pool: ReportingPgPool, options?: PostgresReportingLedgerStoreOptions);
|
|
140
|
+
/** Whether this store writes the host change feed (`changeFeed: true`). */
|
|
141
|
+
get changeFeedEnabled(): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Delete expired cursor snapshots and changes checkpoints, a bounded batch
|
|
144
|
+
* at a time. The store-bound form of `sweepExpiredReportingLedgerState`, for
|
|
145
|
+
* hosts that hold the store but not the pool.
|
|
146
|
+
*/
|
|
147
|
+
sweepExpiredState(limit?: number): Promise<{
|
|
148
|
+
snapshotsDeleted: number;
|
|
149
|
+
checkpointsDeleted: number;
|
|
150
|
+
}>;
|
|
140
151
|
/** Append one host change-feed row inside the caller's account-locked transaction. */
|
|
141
152
|
private appendChange;
|
|
142
153
|
/** Probe and register row storage when enabled. Safe to call repeatedly. */
|
|
@@ -424,6 +424,18 @@ class PostgresReportingLedgerStore {
|
|
|
424
424
|
rowStorage;
|
|
425
425
|
rowStorageWrites;
|
|
426
426
|
changeFeed;
|
|
427
|
+
/** Whether this store writes the host change feed (`changeFeed: true`). */
|
|
428
|
+
get changeFeedEnabled() {
|
|
429
|
+
return this.changeFeed;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Delete expired cursor snapshots and changes checkpoints, a bounded batch
|
|
433
|
+
* at a time. The store-bound form of `sweepExpiredReportingLedgerState`, for
|
|
434
|
+
* hosts that hold the store but not the pool.
|
|
435
|
+
*/
|
|
436
|
+
async sweepExpiredState(limit = 1e3) {
|
|
437
|
+
return sweepExpiredReportingLedgerState(this.pool, limit);
|
|
438
|
+
}
|
|
427
439
|
/** Append one host change-feed row inside the caller's account-locked transaction. */
|
|
428
440
|
async appendChange(client, kind, accountId, recordId, obligationId) {
|
|
429
441
|
if (!this.changeFeed) return;
|
|
@@ -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;
|
|
@@ -6,6 +6,7 @@ import type { ReportingDeliveryCapabilities, ReportingDeliveryOffering } from '.
|
|
|
6
6
|
import { type CreatePostgresPersistentNotificationRuntimeOptions, type PostgresPersistentNotificationRuntime } from '../../server/notification-subscriptions/index.mjs';
|
|
7
7
|
import { REPORTING_LEDGER_MIGRATION, PostgresReportingLedgerStore, PostgresReportingManagedDeliveryStore, type CreateReportingProducerOptionsV1, type ReportingConsumerMismatchEscalationV1, type ReportingLedgerConfigurationV1, type ReportingLedgerStore, type ReportingProducerContactV1, type ReportingProducerV1, type ReportingManagedDeliveryAdapterV1, type ReportingManagedDeliveryRuntimeV1, type PostgresReportingManagedDeliveryStoreOptions, type PostgresReportingNotificationActivityOptions, type PostgresReportingNotificationActivityRuntime, type ReportingPgPool, type ReportingRowStorageOptionsV1, type ReportingSourceWithReaderV1 } from '../ledger/index.mjs';
|
|
8
8
|
import { type PostgresReportingWebhookActivityV1 } from '../webhook-activity.mjs';
|
|
9
|
+
import { type ReportingChangeFeedMaintenanceOptionsV1, type ReportingLedgerMaintenanceTaskResultV1, type ReportingRetentionOptionsV1 } from './maintenance.mjs';
|
|
9
10
|
import type { InlineReportingReplayRetentionV1 } from '../source/index.mjs';
|
|
10
11
|
import { type InlineReportingDeliveryFetchV1, type ReportingSourceOfferingV1 } from '../source/index.mjs';
|
|
11
12
|
/** The intentionally small provider boundary: one bounded slice fetch plus immutable metadata. */
|
|
@@ -149,6 +150,29 @@ export interface CreateReliableReportingServiceOptionsV1<TCtxMeta = Record<strin
|
|
|
149
150
|
/** Ledger metadata retention commitment advertised to buyers. */
|
|
150
151
|
statusRetentionDays: number;
|
|
151
152
|
subscribers?: CreateReportingProducerOptionsV1['subscribers'];
|
|
153
|
+
/**
|
|
154
|
+
* Opt in to scheduled ledger maintenance for a Core-only deployment on a
|
|
155
|
+
* `PostgresReportingLedgerStore`. `start()` runs it at most once per
|
|
156
|
+
* `intervalMilliseconds`; `runMaintenance()` runs it on demand for a host
|
|
157
|
+
* with its own scheduler. The production service does this itself and does
|
|
158
|
+
* not take this option.
|
|
159
|
+
*
|
|
160
|
+
* Always swept: expired cursor snapshots and checkpoints, and abandoned row
|
|
161
|
+
* uploads. `retention` and `changeFeed` have the same shape and meaning as
|
|
162
|
+
* on `createPostgresReliableReportingProductionService`. Retention uses
|
|
163
|
+
* this service's `statusRetentionDays`, so it can never retire a period
|
|
164
|
+
* earlier than the retention advertised to buyers. `changeFeed` requires the
|
|
165
|
+
* store to be built with `changeFeed: true`.
|
|
166
|
+
*/
|
|
167
|
+
maintenance?: ReliableReportingMaintenanceOptionsV1;
|
|
168
|
+
}
|
|
169
|
+
export interface ReliableReportingMaintenanceOptionsV1 {
|
|
170
|
+
/** Minimum time between scheduled maintenance passes. Defaults to 300000 (5 minutes). */
|
|
171
|
+
intervalMilliseconds?: number;
|
|
172
|
+
/** Period-aligned retention; see the production service's `retention`. */
|
|
173
|
+
retention?: ReportingRetentionOptionsV1;
|
|
174
|
+
/** Change-feed pruning and consumer hold; see the production service's `changeFeed`. */
|
|
175
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1;
|
|
152
176
|
}
|
|
153
177
|
export interface ReliableReportingSetupV1 {
|
|
154
178
|
readonly component: 'reliable-reporting-core';
|
|
@@ -179,6 +203,14 @@ export interface ReliableReportingServiceV1<TCtxMeta = Record<string, unknown>>
|
|
|
179
203
|
notReady: number;
|
|
180
204
|
failed: number;
|
|
181
205
|
}>;
|
|
206
|
+
/**
|
|
207
|
+
* Run one ledger-maintenance pass now and return each task's outcome. For
|
|
208
|
+
* hosts that run their own scheduler instead of `start()`. A task failure is
|
|
209
|
+
* reported in its result, not thrown. Requires the `maintenance` option.
|
|
210
|
+
*/
|
|
211
|
+
runMaintenance(options?: {
|
|
212
|
+
signal?: AbortSignal;
|
|
213
|
+
}): Promise<ReportingLedgerMaintenanceTaskResultV1[]>;
|
|
182
214
|
start(options: ReliableReportingSchedulerOptionsV1): void;
|
|
183
215
|
stop(): Promise<void>;
|
|
184
216
|
}
|
|
@@ -188,7 +220,7 @@ export interface ReliableReportingServiceV1<TCtxMeta = Record<string, unknown>>
|
|
|
188
220
|
*/
|
|
189
221
|
export declare function createReliableReportingService<TCtxMeta = Record<string, unknown>>(options: CreateReliableReportingServiceOptionsV1<TCtxMeta>): ReliableReportingServiceV1<TCtxMeta>;
|
|
190
222
|
type ProductionNotificationOptions = Omit<CreatePostgresPersistentNotificationRuntimeOptions, 'db' | 'publisherScope' | 'checkpointDeliveryAttempt' | 'supportedAccountEventTypes'>;
|
|
191
|
-
export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta = Record<string, unknown>> = Omit<CreateReliableReportingServiceOptionsV1<TCtxMeta>, 'store' | 'subscribers'> & {
|
|
223
|
+
export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta = Record<string, unknown>> = Omit<CreateReliableReportingServiceOptionsV1<TCtxMeta>, 'store' | 'subscribers' | 'maintenance'> & {
|
|
192
224
|
db: ReportingPgPool;
|
|
193
225
|
/** Stable, non-secret deployment namespace shared by reporting workers. */
|
|
194
226
|
namespace: string;
|
|
@@ -211,11 +243,7 @@ export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta =
|
|
|
211
243
|
* period is also held until every live registered feed consumer (updated
|
|
212
244
|
* within `changeFeed.maxFeedHoldDays`, default 7) has read its changes.
|
|
213
245
|
*/
|
|
214
|
-
retention?:
|
|
215
|
-
enabled: true;
|
|
216
|
-
recordRetentionDays?: number;
|
|
217
|
-
limit?: number;
|
|
218
|
-
};
|
|
246
|
+
retention?: ReportingRetentionOptionsV1;
|
|
219
247
|
/**
|
|
220
248
|
* Maintain the host change feed (`changesAfter`, `getCurrentRevision`,
|
|
221
249
|
* `listCurrentRevisions` on `stores.core`). Adds
|
|
@@ -223,10 +251,7 @@ export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta =
|
|
|
223
251
|
* changes older than `changeRetentionDays` (default 30) unless a registered
|
|
224
252
|
* consumer updated within `maxFeedHoldDays` (default 7) still needs them.
|
|
225
253
|
*/
|
|
226
|
-
changeFeed?:
|
|
227
|
-
changeRetentionDays?: number;
|
|
228
|
-
maxFeedHoldDays?: number;
|
|
229
|
-
};
|
|
254
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1;
|
|
230
255
|
notifications: ProductionNotificationOptions;
|
|
231
256
|
managedDelivery: {
|
|
232
257
|
adapter: ReportingManagedDeliveryAdapterV1;
|
|
@@ -304,14 +329,4 @@ export interface PostgresReliableReportingProductionServiceV1<TCtxMeta = Record<
|
|
|
304
329
|
*/
|
|
305
330
|
export declare function createPostgresReliableReportingProductionService<TCtxMeta = Record<string, unknown>>(options: CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta>): Promise<PostgresReliableReportingProductionServiceV1<TCtxMeta>>;
|
|
306
331
|
export * from './conformance.mjs';
|
|
307
|
-
|
|
308
|
-
* A scheduled maintenance pass completed but could not process some items
|
|
309
|
-
* (for example, a retention period whose objects could not be deleted). The
|
|
310
|
-
* pass resumes them next time; repeated reports need operator attention.
|
|
311
|
-
*/
|
|
312
|
-
export declare class ReportingMaintenancePartialFailureError extends Error {
|
|
313
|
-
readonly task: string;
|
|
314
|
-
readonly failedCount: number;
|
|
315
|
-
readonly failedIds: readonly string[];
|
|
316
|
-
constructor(task: string, failedCount: number, failedIds: readonly string[]);
|
|
317
|
-
}
|
|
332
|
+
export { ReportingMaintenancePartialFailureError, type ReportingChangeFeedMaintenanceOptionsV1, type ReportingLedgerMaintenanceStoreV1, type ReportingLedgerMaintenanceTaskNameV1, type ReportingLedgerMaintenanceTaskResultV1, type ReportingRetentionOptionsV1, } from './maintenance.mjs';
|
|
@@ -6,6 +6,7 @@ import type { ReportingDeliveryCapabilities, ReportingDeliveryOffering } from '.
|
|
|
6
6
|
import { type CreatePostgresPersistentNotificationRuntimeOptions, type PostgresPersistentNotificationRuntime } from '../../server/notification-subscriptions';
|
|
7
7
|
import { REPORTING_LEDGER_MIGRATION, PostgresReportingLedgerStore, PostgresReportingManagedDeliveryStore, type CreateReportingProducerOptionsV1, type ReportingConsumerMismatchEscalationV1, type ReportingLedgerConfigurationV1, type ReportingLedgerStore, type ReportingProducerContactV1, type ReportingProducerV1, type ReportingManagedDeliveryAdapterV1, type ReportingManagedDeliveryRuntimeV1, type PostgresReportingManagedDeliveryStoreOptions, type PostgresReportingNotificationActivityOptions, type PostgresReportingNotificationActivityRuntime, type ReportingPgPool, type ReportingRowStorageOptionsV1, type ReportingSourceWithReaderV1 } from '../ledger';
|
|
8
8
|
import { type PostgresReportingWebhookActivityV1 } from '../webhook-activity';
|
|
9
|
+
import { type ReportingChangeFeedMaintenanceOptionsV1, type ReportingLedgerMaintenanceTaskResultV1, type ReportingRetentionOptionsV1 } from './maintenance';
|
|
9
10
|
import type { InlineReportingReplayRetentionV1 } from '../source';
|
|
10
11
|
import { type InlineReportingDeliveryFetchV1, type ReportingSourceOfferingV1 } from '../source';
|
|
11
12
|
/** The intentionally small provider boundary: one bounded slice fetch plus immutable metadata. */
|
|
@@ -149,6 +150,29 @@ export interface CreateReliableReportingServiceOptionsV1<TCtxMeta = Record<strin
|
|
|
149
150
|
/** Ledger metadata retention commitment advertised to buyers. */
|
|
150
151
|
statusRetentionDays: number;
|
|
151
152
|
subscribers?: CreateReportingProducerOptionsV1['subscribers'];
|
|
153
|
+
/**
|
|
154
|
+
* Opt in to scheduled ledger maintenance for a Core-only deployment on a
|
|
155
|
+
* `PostgresReportingLedgerStore`. `start()` runs it at most once per
|
|
156
|
+
* `intervalMilliseconds`; `runMaintenance()` runs it on demand for a host
|
|
157
|
+
* with its own scheduler. The production service does this itself and does
|
|
158
|
+
* not take this option.
|
|
159
|
+
*
|
|
160
|
+
* Always swept: expired cursor snapshots and checkpoints, and abandoned row
|
|
161
|
+
* uploads. `retention` and `changeFeed` have the same shape and meaning as
|
|
162
|
+
* on `createPostgresReliableReportingProductionService`. Retention uses
|
|
163
|
+
* this service's `statusRetentionDays`, so it can never retire a period
|
|
164
|
+
* earlier than the retention advertised to buyers. `changeFeed` requires the
|
|
165
|
+
* store to be built with `changeFeed: true`.
|
|
166
|
+
*/
|
|
167
|
+
maintenance?: ReliableReportingMaintenanceOptionsV1;
|
|
168
|
+
}
|
|
169
|
+
export interface ReliableReportingMaintenanceOptionsV1 {
|
|
170
|
+
/** Minimum time between scheduled maintenance passes. Defaults to 300000 (5 minutes). */
|
|
171
|
+
intervalMilliseconds?: number;
|
|
172
|
+
/** Period-aligned retention; see the production service's `retention`. */
|
|
173
|
+
retention?: ReportingRetentionOptionsV1;
|
|
174
|
+
/** Change-feed pruning and consumer hold; see the production service's `changeFeed`. */
|
|
175
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1;
|
|
152
176
|
}
|
|
153
177
|
export interface ReliableReportingSetupV1 {
|
|
154
178
|
readonly component: 'reliable-reporting-core';
|
|
@@ -179,6 +203,14 @@ export interface ReliableReportingServiceV1<TCtxMeta = Record<string, unknown>>
|
|
|
179
203
|
notReady: number;
|
|
180
204
|
failed: number;
|
|
181
205
|
}>;
|
|
206
|
+
/**
|
|
207
|
+
* Run one ledger-maintenance pass now and return each task's outcome. For
|
|
208
|
+
* hosts that run their own scheduler instead of `start()`. A task failure is
|
|
209
|
+
* reported in its result, not thrown. Requires the `maintenance` option.
|
|
210
|
+
*/
|
|
211
|
+
runMaintenance(options?: {
|
|
212
|
+
signal?: AbortSignal;
|
|
213
|
+
}): Promise<ReportingLedgerMaintenanceTaskResultV1[]>;
|
|
182
214
|
start(options: ReliableReportingSchedulerOptionsV1): void;
|
|
183
215
|
stop(): Promise<void>;
|
|
184
216
|
}
|
|
@@ -188,7 +220,7 @@ export interface ReliableReportingServiceV1<TCtxMeta = Record<string, unknown>>
|
|
|
188
220
|
*/
|
|
189
221
|
export declare function createReliableReportingService<TCtxMeta = Record<string, unknown>>(options: CreateReliableReportingServiceOptionsV1<TCtxMeta>): ReliableReportingServiceV1<TCtxMeta>;
|
|
190
222
|
type ProductionNotificationOptions = Omit<CreatePostgresPersistentNotificationRuntimeOptions, 'db' | 'publisherScope' | 'checkpointDeliveryAttempt' | 'supportedAccountEventTypes'>;
|
|
191
|
-
export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta = Record<string, unknown>> = Omit<CreateReliableReportingServiceOptionsV1<TCtxMeta>, 'store' | 'subscribers'> & {
|
|
223
|
+
export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta = Record<string, unknown>> = Omit<CreateReliableReportingServiceOptionsV1<TCtxMeta>, 'store' | 'subscribers' | 'maintenance'> & {
|
|
192
224
|
db: ReportingPgPool;
|
|
193
225
|
/** Stable, non-secret deployment namespace shared by reporting workers. */
|
|
194
226
|
namespace: string;
|
|
@@ -211,11 +243,7 @@ export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta =
|
|
|
211
243
|
* period is also held until every live registered feed consumer (updated
|
|
212
244
|
* within `changeFeed.maxFeedHoldDays`, default 7) has read its changes.
|
|
213
245
|
*/
|
|
214
|
-
retention?:
|
|
215
|
-
enabled: true;
|
|
216
|
-
recordRetentionDays?: number;
|
|
217
|
-
limit?: number;
|
|
218
|
-
};
|
|
246
|
+
retention?: ReportingRetentionOptionsV1;
|
|
219
247
|
/**
|
|
220
248
|
* Maintain the host change feed (`changesAfter`, `getCurrentRevision`,
|
|
221
249
|
* `listCurrentRevisions` on `stores.core`). Adds
|
|
@@ -223,10 +251,7 @@ export type CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta =
|
|
|
223
251
|
* changes older than `changeRetentionDays` (default 30) unless a registered
|
|
224
252
|
* consumer updated within `maxFeedHoldDays` (default 7) still needs them.
|
|
225
253
|
*/
|
|
226
|
-
changeFeed?:
|
|
227
|
-
changeRetentionDays?: number;
|
|
228
|
-
maxFeedHoldDays?: number;
|
|
229
|
-
};
|
|
254
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1;
|
|
230
255
|
notifications: ProductionNotificationOptions;
|
|
231
256
|
managedDelivery: {
|
|
232
257
|
adapter: ReportingManagedDeliveryAdapterV1;
|
|
@@ -304,14 +329,4 @@ export interface PostgresReliableReportingProductionServiceV1<TCtxMeta = Record<
|
|
|
304
329
|
*/
|
|
305
330
|
export declare function createPostgresReliableReportingProductionService<TCtxMeta = Record<string, unknown>>(options: CreatePostgresReliableReportingProductionServiceOptionsV1<TCtxMeta>): Promise<PostgresReliableReportingProductionServiceV1<TCtxMeta>>;
|
|
306
331
|
export * from './conformance';
|
|
307
|
-
|
|
308
|
-
* A scheduled maintenance pass completed but could not process some items
|
|
309
|
-
* (for example, a retention period whose objects could not be deleted). The
|
|
310
|
-
* pass resumes them next time; repeated reports need operator attention.
|
|
311
|
-
*/
|
|
312
|
-
export declare class ReportingMaintenancePartialFailureError extends Error {
|
|
313
|
-
readonly task: string;
|
|
314
|
-
readonly failedCount: number;
|
|
315
|
-
readonly failedIds: readonly string[];
|
|
316
|
-
constructor(task: string, failedCount: number, failedIds: readonly string[]);
|
|
317
|
-
}
|
|
332
|
+
export { ReportingMaintenancePartialFailureError, type ReportingChangeFeedMaintenanceOptionsV1, type ReportingLedgerMaintenanceStoreV1, type ReportingLedgerMaintenanceTaskNameV1, type ReportingLedgerMaintenanceTaskResultV1, type ReportingRetentionOptionsV1, } from './maintenance';
|
|
@@ -19,7 +19,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
|
|
|
19
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
20
|
var service_exports = {};
|
|
21
21
|
__export(service_exports, {
|
|
22
|
-
ReportingMaintenancePartialFailureError: () => ReportingMaintenancePartialFailureError,
|
|
22
|
+
ReportingMaintenancePartialFailureError: () => import_maintenance2.ReportingMaintenancePartialFailureError,
|
|
23
23
|
createPostgresReliableReportingProductionService: () => createPostgresReliableReportingProductionService,
|
|
24
24
|
createReliableReportingService: () => createReliableReportingService
|
|
25
25
|
});
|
|
@@ -31,11 +31,14 @@ var import_schemas = require('../../types/schemas.generated.js');
|
|
|
31
31
|
var import_notification_subscriptions = require('../../server/notification-subscriptions/index.js');
|
|
32
32
|
var import_ledger = require('../ledger/index.js');
|
|
33
33
|
var import_webhook_activity = require('../webhook-activity.js');
|
|
34
|
+
var import_maintenance = require('./maintenance.js');
|
|
34
35
|
var import_schedule = require('../ledger/schedule.js');
|
|
35
36
|
var import_source = require('../source/index.js');
|
|
36
37
|
__reExport(service_exports, require('./conformance.js'), module.exports);
|
|
38
|
+
var import_maintenance2 = require('./maintenance.js');
|
|
37
39
|
const ADAPTER_SCOPE_KEY = "_adcp_reporting_adapter";
|
|
38
40
|
const PRODUCTION_MANAGED_COMPOSITION = /* @__PURE__ */ Symbol("production-managed-reporting-composition");
|
|
41
|
+
const DEFAULT_MAINTENANCE_INTERVAL_MILLISECONDS = 3e5;
|
|
39
42
|
function createReliableReportingService(options) {
|
|
40
43
|
const allowManagedDelivery = options[PRODUCTION_MANAGED_COMPOSITION] === true;
|
|
41
44
|
const adapterEntries = Object.entries(options.adapters);
|
|
@@ -97,6 +100,42 @@ function createReliableReportingService(options) {
|
|
|
97
100
|
if (hasConsumerStatus) assertConsumerStatusStore(options.store);
|
|
98
101
|
const retentionDays = options.statusRetentionDays;
|
|
99
102
|
positiveInteger(retentionDays, "statusRetentionDays");
|
|
103
|
+
const maintenance = options.maintenance;
|
|
104
|
+
let maintenanceStore;
|
|
105
|
+
let maintenanceIntervalMilliseconds = DEFAULT_MAINTENANCE_INTERVAL_MILLISECONDS;
|
|
106
|
+
if (maintenance !== void 0) {
|
|
107
|
+
if (maintenance === null || typeof maintenance !== "object") {
|
|
108
|
+
throw new TypeError("maintenance must be an options object");
|
|
109
|
+
}
|
|
110
|
+
const candidate = options.store;
|
|
111
|
+
for (const method of ["retireExpiredPeriods", "pruneChanges", "sweepRowWriteIntents"]) {
|
|
112
|
+
if (typeof candidate[method] !== "function") {
|
|
113
|
+
throw new TypeError(
|
|
114
|
+
`maintenance requires a PostgresReportingLedgerStore (or a store with ${method}); the configured store cannot run ledger maintenance`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
maintenanceStore = candidate;
|
|
119
|
+
if (maintenance.changeFeed && maintenanceStore.changeFeedEnabled === false) {
|
|
120
|
+
throw new TypeError(
|
|
121
|
+
"maintenance.changeFeed requires the store to be built with changeFeed: true; otherwise pruning silently does nothing"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (maintenance.intervalMilliseconds !== void 0) {
|
|
125
|
+
positiveInteger(maintenance.intervalMilliseconds, "maintenance.intervalMilliseconds");
|
|
126
|
+
maintenanceIntervalMilliseconds = maintenance.intervalMilliseconds;
|
|
127
|
+
}
|
|
128
|
+
(0, import_maintenance.assertReportingLedgerMaintenanceOptions)(maintenance, retentionDays);
|
|
129
|
+
}
|
|
130
|
+
const runLedgerMaintenance = (signal, report) => (0, import_maintenance.runReportingLedgerMaintenance)({
|
|
131
|
+
store: maintenanceStore,
|
|
132
|
+
...typeof maintenanceStore.sweepExpiredState === "function" ? { sweepSnapshots: (limit) => maintenanceStore.sweepExpiredState(limit) } : {},
|
|
133
|
+
statusRetentionDays: retentionDays,
|
|
134
|
+
retention: maintenance.retention,
|
|
135
|
+
changeFeed: maintenance.changeFeed,
|
|
136
|
+
signal,
|
|
137
|
+
...report ? { report } : {}
|
|
138
|
+
});
|
|
100
139
|
const capabilities = deepFreeze({
|
|
101
140
|
supported: true,
|
|
102
141
|
reliable_reporting_version: "1.0",
|
|
@@ -269,12 +308,26 @@ function createReliableReportingService(options) {
|
|
|
269
308
|
});
|
|
270
309
|
return { planned: planned.length, ...result };
|
|
271
310
|
},
|
|
311
|
+
async runMaintenance(maintenanceOptions) {
|
|
312
|
+
if (maintenance === void 0) {
|
|
313
|
+
throw new TypeError("runMaintenance requires the maintenance option");
|
|
314
|
+
}
|
|
315
|
+
maintenanceOptions?.signal?.throwIfAborted();
|
|
316
|
+
return runLedgerMaintenance(maintenanceOptions?.signal);
|
|
317
|
+
},
|
|
272
318
|
start(scheduler) {
|
|
273
319
|
if (schedulerPromise) throw new Error("Reliable reporting scheduler is already running");
|
|
274
320
|
positiveInteger(scheduler.intervalMilliseconds, "intervalMilliseconds");
|
|
275
321
|
schedulerAbort = new AbortController();
|
|
276
322
|
const signal = schedulerAbort.signal;
|
|
277
|
-
|
|
323
|
+
const scheduledMaintenance = maintenance === void 0 ? void 0 : {
|
|
324
|
+
intervalMilliseconds: maintenanceIntervalMilliseconds,
|
|
325
|
+
run: () => runLedgerMaintenance(
|
|
326
|
+
signal,
|
|
327
|
+
(error) => reportSchedulerError(scheduler, signal, error, { phase: "maintenance" })
|
|
328
|
+
)
|
|
329
|
+
};
|
|
330
|
+
schedulerPromise = schedulerLoop(service, scheduler, signal, options.store, scheduledMaintenance).finally(() => {
|
|
278
331
|
schedulerPromise = void 0;
|
|
279
332
|
schedulerAbort = void 0;
|
|
280
333
|
});
|
|
@@ -506,53 +559,15 @@ async function createPostgresReliableReportingProductionService(options) {
|
|
|
506
559
|
for (const result of pruneResults) {
|
|
507
560
|
if (result.status === "rejected" && !signal.aborted) await reportAuxiliaryError(scheduler, result.reason);
|
|
508
561
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
...options.changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: options.changeFeed.maxFeedHoldDays } : {}
|
|
519
|
-
} : {}
|
|
520
|
-
)
|
|
521
|
-
]
|
|
522
|
-
] : [],
|
|
523
|
-
...options.retention?.enabled ? [
|
|
524
|
-
[
|
|
525
|
-
"retention",
|
|
526
|
-
coreStore.retireExpiredPeriods({
|
|
527
|
-
statusRetentionDays: options.statusRetentionDays,
|
|
528
|
-
...options.retention.recordRetentionDays !== void 0 ? { recordRetentionDays: options.retention.recordRetentionDays } : {},
|
|
529
|
-
...typeof options.changeFeed === "object" && options.changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: options.changeFeed.maxFeedHoldDays } : {},
|
|
530
|
-
limit: options.retention.limit ?? 100,
|
|
531
|
-
signal
|
|
532
|
-
})
|
|
533
|
-
]
|
|
534
|
-
] : []
|
|
535
|
-
];
|
|
536
|
-
const maintenanceResults = await Promise.allSettled(ledgerMaintenance.map(([, task]) => task));
|
|
537
|
-
for (const [index, result] of maintenanceResults.entries()) {
|
|
538
|
-
const name = ledgerMaintenance[index][0];
|
|
539
|
-
if (result.status === "rejected") {
|
|
540
|
-
if (!signal.aborted) await reportAuxiliaryError(scheduler, result.reason);
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
const value = result.value;
|
|
544
|
-
const failed = Array.isArray(value?.failed) ? value.failed.length : typeof value?.failed === "number" ? value.failed : 0;
|
|
545
|
-
if (failed > 0 && !signal.aborted) {
|
|
546
|
-
await reportAuxiliaryError(
|
|
547
|
-
scheduler,
|
|
548
|
-
new ReportingMaintenancePartialFailureError(
|
|
549
|
-
name,
|
|
550
|
-
failed,
|
|
551
|
-
value?.failures?.length ? value.failures.map((entry) => `${entry.reporting_obligation_id} (${entry.cause})`) : Array.isArray(value?.failed) ? value.failed : []
|
|
552
|
-
)
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
562
|
+
await (0, import_maintenance.runReportingLedgerMaintenance)({
|
|
563
|
+
store: coreStore,
|
|
564
|
+
sweepSnapshots: (limit) => (0, import_ledger.sweepExpiredReportingLedgerState)(options.db, limit),
|
|
565
|
+
statusRetentionDays: options.statusRetentionDays,
|
|
566
|
+
retention: options.retention,
|
|
567
|
+
changeFeed: options.changeFeed,
|
|
568
|
+
signal,
|
|
569
|
+
report: (error) => reportAuxiliaryError(scheduler, error)
|
|
570
|
+
});
|
|
556
571
|
const accountIds = rotate(await deploymentWideAccountIds(coreStore), auxiliaryRotation);
|
|
557
572
|
auxiliaryRotation += 1;
|
|
558
573
|
for (const accountId of accountIds) {
|
|
@@ -678,8 +693,9 @@ function routeSource(sources, sourceScope) {
|
|
|
678
693
|
if (!source) throw new TypeError("Reporting sourceScope has no installed adapter route");
|
|
679
694
|
return source;
|
|
680
695
|
}
|
|
681
|
-
async function schedulerLoop(service, options, signal, store) {
|
|
696
|
+
async function schedulerLoop(service, options, signal, store, maintenance) {
|
|
682
697
|
let rotation = 0;
|
|
698
|
+
let lastMaintenanceAt;
|
|
683
699
|
while (!signal.aborted) {
|
|
684
700
|
let accountIds = [];
|
|
685
701
|
try {
|
|
@@ -716,6 +732,14 @@ async function schedulerLoop(service, options, signal, store) {
|
|
|
716
732
|
await reportSchedulerError(options, signal, error, { phase: "cycle", accountId });
|
|
717
733
|
}
|
|
718
734
|
}
|
|
735
|
+
if (maintenance && !signal.aborted && (lastMaintenanceAt === void 0 || Date.now() - lastMaintenanceAt >= maintenance.intervalMilliseconds)) {
|
|
736
|
+
lastMaintenanceAt = Date.now();
|
|
737
|
+
try {
|
|
738
|
+
await maintenance.run();
|
|
739
|
+
} catch (error) {
|
|
740
|
+
await reportSchedulerError(options, signal, error, { phase: "maintenance" });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
719
743
|
if (!signal.aborted) await abortableDelay(options.intervalMilliseconds, signal);
|
|
720
744
|
}
|
|
721
745
|
}
|
|
@@ -732,7 +756,7 @@ async function reportSchedulerError(options, signal, error, site) {
|
|
|
732
756
|
warnSchedulerFailure(options, site, error);
|
|
733
757
|
}
|
|
734
758
|
function warnSchedulerFailure(options, site, error, observerError) {
|
|
735
|
-
const where = site.phase === "roster" ? "could not resolve its account roster, so this pass ran no tenants" : site.accountId === void 0 ? "deployment-wide cycle failed; the scheduler continues" : `cycle failed for account ${site.accountId}; later tenants continue`;
|
|
759
|
+
const where = site.phase === "roster" ? "could not resolve its account roster, so this pass ran no tenants" : site.phase === "maintenance" ? "ledger maintenance failed; the scheduler continues" : site.accountId === void 0 ? "deployment-wide cycle failed; the scheduler continues" : `cycle failed for account ${site.accountId}; later tenants continue`;
|
|
736
760
|
const observed = observerError === void 0 ? "" : ` (the configured onError also threw: ${safeMessage(observerError)})`;
|
|
737
761
|
const message = `[adcp/reporting] scheduler ${where}: ${safeMessage(error)}${observed}`;
|
|
738
762
|
const logger = options.logger;
|
|
@@ -1200,20 +1224,6 @@ function deepFreeze(value) {
|
|
|
1200
1224
|
}
|
|
1201
1225
|
return value;
|
|
1202
1226
|
}
|
|
1203
|
-
class ReportingMaintenancePartialFailureError extends Error {
|
|
1204
|
-
constructor(task, failedCount, failedIds) {
|
|
1205
|
-
super(
|
|
1206
|
-
`Reporting ${task} could not process ${failedCount} item(s)${failedIds.length ? `: ${failedIds.slice(0, 20).join(", ")}` : ""}`
|
|
1207
|
-
);
|
|
1208
|
-
this.task = task;
|
|
1209
|
-
this.failedCount = failedCount;
|
|
1210
|
-
this.failedIds = failedIds;
|
|
1211
|
-
this.name = "ReportingMaintenancePartialFailureError";
|
|
1212
|
-
}
|
|
1213
|
-
task;
|
|
1214
|
-
failedCount;
|
|
1215
|
-
failedIds;
|
|
1216
|
-
}
|
|
1217
1227
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1218
1228
|
0 && (module.exports = {
|
|
1219
1229
|
ReportingMaintenancePartialFailureError,
|