diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e51b8a..09d3fd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## Unreleased +## 0.6.0 — 2026-08-23 + +- Added exact immutable Event configuration binding reads and compare-and-set + attach/detach through `Events.retrieveConfigurationBinding` and + `Events.updateConfigurationBinding`. Updates remain deliberately + single-attempt. ## 0.5.1 — 2026-08-21 diff --git a/README.md b/README.md index 0fe8c51..bf62fb2 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,7 @@ await seatlayer.request('POST', '/v1/events/ev_1/some-new-route', { body: { … | Resource | Methods | | --- | --- | | `charts` | `list` `listAll` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` | -| `events` | `list` `listAll` `create` `retrieve` `update` `delete` `updatePoster` `deletePoster` `updateChart` `close` `reopen` `archive` `retrieveHoldTtl` `updateHoldTtl` `retrieveReport` `retrieveLog` | +| `events` | `list` `listAll` `create` `retrieve` `retrieveConfigurationBinding` `updateConfigurationBinding` `update` `delete` `updatePoster` `deletePoster` `updateChart` `close` `reopen` `archive` `retrieveHoldTtl` `updateHoldTtl` `listTicketReleases` `updateTicketReleases` `closeTicketRelease` `retrieveReport` `retrieveLog` | | `inventory` | `hold` `holdBestAvailable` `bookBestAvailable` `extendHold` `retrieveHold` `release` `book` `boxOfficeBook` `unbook` `block` `unblock` `unblockAll` `retrieveAvailability` `updateAvailability` `listBookings` `retrieveBooking` `listInventoryBookings` `retrieveInventoryBooking` | | `channels` | `listChannels` `createChannel` `updateChannel` `updateChannelAssignments` `listChannelAllocation` `retrieveChannelAccessPreview` `pauseChannel` `unpauseChannel` `archiveChannel` `retrieveChannelReport` `createBuyerAccessSession` `listBuyerAccessSessions` `revokeBuyerAccessSession` `createAccessLink` `listAccessLinks` `rotateAccessLink` `revokeAccessLink` | | `sessions` | `createManageSession` `revokeManageSession` `createDesignerSession` `revokeDesignerSession` | diff --git a/package.json b/package.json index fd393ed..f241fe2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seatlayer/server", - "version": "0.5.1", + "version": "0.6.0", "description": "Official Node.js server SDK for SeatLayer inventory, holds, booking references, allocations, and reports. Secret-key only.", "license": "MIT", "homepage": "https://seatlayer.io/", diff --git a/src/resources/events.ts b/src/resources/events.ts index 7136742..f7414a5 100644 --- a/src/resources/events.ts +++ b/src/resources/events.ts @@ -2,6 +2,8 @@ import type { HttpClient } from '../http.js'; import type { ArchiveEventResult, EventCounts, + EventConfigurationBinding, + EventConfigurationBindingUpdateParams, EventDetail, EventEnvelope, EventLogPage, @@ -130,6 +132,28 @@ export class Events { return this.#http.get(`/v1/events/${encodeURIComponent(eventKey)}`); } + /** Read the Event's exact immutable configuration binding and audit history. */ + retrieveConfigurationBinding(eventKey: string): Promise { + return this.#http.get( + `/v1/events/${encodeURIComponent(eventKey)}/event-configuration`, + ); + } + + /** + * Bind an exact published version, or pass `configuration: null` to detach. + * This compare-and-set mutation is deliberately single-attempt because the + * public operation does not promise exact response replay. + */ + updateConfigurationBinding( + eventKey: string, + params: EventConfigurationBindingUpdateParams, + ): Promise { + return this.#http.put( + `/v1/events/${encodeURIComponent(eventKey)}/event-configuration`, + { body: params }, + ); + } + update(eventKey: string, params: EventUpdateParams): Promise { return this.#http.patch(`/v1/events/${encodeURIComponent(eventKey)}`, { body: params }); } diff --git a/src/types.ts b/src/types.ts index b9c45ae..d3ce3ca 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,6 +105,37 @@ export interface EventDetail { }; } +/** Exact immutable Event configuration version. */ +export interface EventConfigurationRef { + id: string; + version: number; +} + +/** One append-only Event configuration binding transition. */ +export interface EventConfigurationBindingAudit { + id: string; + from: EventConfigurationRef | null; + to: EventConfigurationRef | null; + revision: number; + actor: string; + createdAt: number; +} + +/** Current exact Event configuration binding and its complete audit history. */ +export interface EventConfigurationBinding { + configuration: EventConfigurationRef | null; + revision: number; + changedBy: string | null; + changedAt: number | null; + audit: EventConfigurationBindingAudit[]; +} + +/** Compare-and-set input for binding or detaching an exact configuration version. */ +export interface EventConfigurationBindingUpdateParams { + expectedRevision: number; + configuration: EventConfigurationRef | null; +} + export interface SalesAliasResult { status: string; state: string; diff --git a/test/client.test.ts b/test/client.test.ts index ff39ecb..468093d 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -13,6 +13,7 @@ import { type ChannelReportEnvelope, type EventMeta, type EventDetail, + type EventConfigurationBinding, type EventLogPage, type EventReportEnvelope, type TicketReleaseList, @@ -114,6 +115,48 @@ describe('requests', () => { expect(call(0).url).toBe('https://api.seatlayer.io/v1/events/ev%2F..%2Fadmin'); }); + it('reads, binds and explicitly detaches an exact Event configuration version', async () => { + const binding = { + configuration: { id: 'ec_touring', version: 3 }, revision: 7, + changedBy: 'api-key:key_1', changedAt: 123, + audit: [{ + id: 'eca_1', from: null, to: { id: 'ec_touring', version: 3 }, + revision: 7, actor: 'api-key:key_1', createdAt: 123, + }], + }; + const { sdk, call } = client([ + { status: 200, body: binding }, + { status: 200, body: binding }, + { status: 200, body: { ...binding, configuration: null, revision: 8 } }, + ]); + + const retrieved = await sdk.events.retrieveConfigurationBinding('ev / main'); + expectTypeOf(retrieved).toEqualTypeOf(); + expect(retrieved.audit[0]?.to).toEqual({ id: 'ec_touring', version: 3 }); + await sdk.events.updateConfigurationBinding('ev / main', { + expectedRevision: 6, + configuration: { id: 'ec_touring', version: 3 }, + }); + await sdk.events.updateConfigurationBinding('ev / main', { + expectedRevision: 7, + configuration: null, + }); + + for (const index of [0, 1, 2]) { + expect(call(index).url).toBe( + 'https://api.seatlayer.io/v1/events/ev%20%2F%20main/event-configuration', + ); + } + expect(call(0).method).toBe('GET'); + expect(JSON.parse(call(1).body)).toEqual({ + expectedRevision: 6, + configuration: { id: 'ec_touring', version: 3 }, + }); + expect(JSON.parse(call(2).body)).toEqual({ expectedRevision: 7, configuration: null }); + expect(call(1).headers['Idempotency-Key']).toBeUndefined(); + expect(call(2).headers['Idempotency-Key']).toBeUndefined(); + }); + it('generates an Idempotency-Key only for header-replay mutations', async () => { const { sdk, call } = client([ { status: 200, body: {} },