Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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/",
Expand Down
24 changes: 24 additions & 0 deletions src/resources/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { HttpClient } from '../http.js';
import type {
ArchiveEventResult,
EventCounts,
EventConfigurationBinding,
EventConfigurationBindingUpdateParams,
EventDetail,
EventEnvelope,
EventLogPage,
Expand Down Expand Up @@ -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<EventConfigurationBinding> {
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<EventConfigurationBinding> {
return this.#http.put(
`/v1/events/${encodeURIComponent(eventKey)}/event-configuration`,
{ body: params },
);
}

update(eventKey: string, params: EventUpdateParams): Promise<EventEnvelope> {
return this.#http.patch(`/v1/events/${encodeURIComponent(eventKey)}`, { body: params });
}
Expand Down
31 changes: 31 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type ChannelReportEnvelope,
type EventMeta,
type EventDetail,
type EventConfigurationBinding,
type EventLogPage,
type EventReportEnvelope,
type TicketReleaseList,
Expand Down Expand Up @@ -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<EventConfigurationBinding>();
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: {} },
Expand Down
Loading