Skip to content

Commit fecb53a

Browse files
committed
feat: complete Java SDK parity contract
1 parent b95a4ca commit fecb53a

13 files changed

Lines changed: 451 additions & 66 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- **Security/reliability:** Mutations now default to a single attempt. Automatic header-replay
6+
retries are limited to chart create/copy, event create, and workspace create, preventing
7+
transient failures from duplicating holds or best-available results and from issuing extra
8+
show-once credentials.
9+
310
## 0.2.0 — 2026-08-12
411

512
- Added channel allocation management and origin-bound buyer access sessions.

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,18 @@ support requests. All are unchecked, so they do not force `throws` clauses throu
228228

229229
## Reliability
230230

231-
**Retries.** 429, 408 and 5xx are retried with exponential backoff and full jitter; `Retry-After`
232-
wins when the server sends it. 4xx is never retried — it will not start succeeding.
233-
234-
**Idempotency.** Every mutating request carries an `Idempotency-Key`, generated if you do not supply
235-
one, and **reused across retries** so a retried booking cannot become two bookings.
231+
**Retries and idempotency.** Reads (`GET`/`HEAD`) retry connection failures, 408, 429 and 5xx with
232+
exponential backoff and full jitter; `Retry-After` wins when the server sends it. Four create
233+
operations have the same retry behaviour with header replay: `charts().create`, `charts().copy`,
234+
`events().create`, and `workspaces().create`. They generate an `Idempotency-Key` when absent and
235+
reuse that key across every attempt. Overloads that accept a key let you provide a stable
236+
provisioning key instead.
237+
238+
All other mutations are single-attempt: holds, bookings, lifecycle changes, channel changes,
239+
show-once secret creation, and raw requests. The SDK does not generate a key for them. A supplied
240+
key on an existing typed-method overload is validated and forwarded once for compatibility, but it
241+
does not enable retries or promise replay. Reconcile bookings with their required `bookingRef`;
242+
never retry an unknown booking outcome as though the transport had made it safe.
236243

237244
```java
238245
SeatLayer.builder()
@@ -244,7 +251,8 @@ SeatLayer.builder()
244251

245252
## Escape hatch
246253

247-
For surface this SDK does not wrap yet — same auth, retries, idempotency and error mapping:
254+
For surface this SDK does not wrap yet. Raw reads retain read retries; raw mutations use the same
255+
auth and error mapping but are sent once and never receive an automatically generated key:
248256

249257
```java
250258
seatlayer.request("POST", "/v1/events/ev_1/some-new-route", null, Map.of("qty", 2));

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.seatlayer</groupId>
88
<artifactId>seatlayer-java</artifactId>
9-
<version>0.2.0</version>
9+
<version>0.3.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>SeatLayer Java SDK</name>

src/main/java/io/seatlayer/Channels.java

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public Map<String, Object> retrieveAccessPreview(
8888
path(eventKey, "/preview"),
8989
body(
9090
"channelIds", channelIds == null ? null : String.join(",", channelIds),
91-
"includePublic", includePublic == null ? null : includePublic ? "1" : "0"));
91+
"includePublic", Boolean.TRUE.equals(includePublic) ? "1" : null));
9292
}
9393

9494
public Map<String, Object> retrieveReport(String eventKey) {
@@ -105,9 +105,11 @@ public Map<String, Object> unpause(String eventKey, String channelId, String rea
105105

106106
public Map<String, Object> archive(
107107
String eventKey, String channelId, String destination, String reason) {
108+
Map<String, Object> request = new LinkedHashMap<>(body("reason", reason));
109+
request.put("destination", destination);
108110
return http.post(
109111
path(eventKey, "/" + encode(channelId) + "/archive"),
110-
body("destination", destination, "reason", reason));
112+
request);
111113
}
112114

113115
/** Mints a short-lived, origin-bound buyer access token. */
@@ -136,15 +138,62 @@ public Map<String, Object> createBuyerAccessSession(
136138
idempotencyKey);
137139
}
138140

139-
public Map<String, Object> listBuyerAccessSessions(
140-
String eventKey, String state, Integer limit, String cursor) {
141+
public Map<String, Object> listBuyerAccessSessions(String eventKey, Integer limit) {
141142
return http.get(
142143
"/v1/events/" + encode(eventKey) + "/buyer-access-sessions",
143-
body("state", state, "limit", limit, "cursor", cursor));
144+
body("limit", limit));
144145
}
145146

146147
public Map<String, Object> revokeBuyerAccessSession(String eventKey, String sessionId) {
147148
return http.delete(
148149
"/v1/events/" + encode(eventKey) + "/buyer-access-sessions/" + encode(sessionId));
149150
}
151+
152+
public Map<String, Object> createAccessLink(
153+
String eventKey,
154+
String channelId,
155+
String label,
156+
Boolean includePublic,
157+
Long expiresAt,
158+
Integer maxRedemptions,
159+
Integer maxQuantity,
160+
Integer sessionTtlSeconds,
161+
String reason) {
162+
return http.post(
163+
accessLinkPath(eventKey, channelId, ""),
164+
body(
165+
"label", label,
166+
"includePublic", includePublic,
167+
"expiresAt", expiresAt,
168+
"maxRedemptions", maxRedemptions,
169+
"maxQuantity", maxQuantity,
170+
"sessionTtlSeconds", sessionTtlSeconds,
171+
"reason", reason));
172+
}
173+
174+
public Map<String, Object> listAccessLinks(String eventKey, String channelId) {
175+
return http.get(accessLinkPath(eventKey, channelId, ""));
176+
}
177+
178+
public Map<String, Object> rotateAccessLink(
179+
String eventKey, String channelId, String linkId, boolean endActiveSessions, String reason) {
180+
return http.post(
181+
accessLinkPath(eventKey, channelId, "/" + encode(linkId) + "/rotate"),
182+
body("endActiveSessions", endActiveSessions, "reason", reason));
183+
}
184+
185+
public Map<String, Object> revokeAccessLink(
186+
String eventKey,
187+
String channelId,
188+
String linkId,
189+
boolean endActiveSessions,
190+
String reason) {
191+
return http.delete(
192+
accessLinkPath(eventKey, channelId, "/" + encode(linkId)),
193+
body("endActiveSessions", endActiveSessions ? "1" : null, "reason", reason));
194+
}
195+
196+
private String accessLinkPath(String eventKey, String channelId, String suffix) {
197+
return path(eventKey, "/" + encode(channelId) + "/access-links" + suffix);
198+
}
150199
}

src/main/java/io/seatlayer/Charts.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ public Iterable<Map<String, Object>> listAll() {
5252
}
5353

5454
public Map<String, Object> create(String name) {
55-
return http.post("/v1/charts", body("name", name));
55+
return http.postWithHeaderReplay("/v1/charts", body("name", name));
5656
}
5757

5858
public Map<String, Object> create(
5959
String name, Map<String, Object> doc, String externalRef, String workspaceId, String idempotencyKey) {
60-
return http.post(
60+
return http.postWithHeaderReplay(
6161
"/v1/charts",
6262
body("name", name, "doc", doc, "externalRef", externalRef, "workspaceId", workspaceId),
6363
idempotencyKey);
@@ -83,13 +83,33 @@ public Map<String, Object> update(String chartId, Map<String, Object> doc, long
8383
"/v1/charts/" + encode(chartId), body("doc", doc, "expectedUpdatedAt", expectedUpdatedAt));
8484
}
8585

86+
/** Updates name, issues, or externalRef without replacing the chart document. */
87+
public Map<String, Object> update(String chartId, Map<String, Object> fields) {
88+
return http.put("/v1/charts/" + encode(chartId), fields);
89+
}
90+
8691
public Map<String, Object> delete(String chartId) {
8792
return http.delete("/v1/charts/" + encode(chartId));
8893
}
8994

9095
/** Copy a chart — the usual way to provision a venue from a template. */
9196
public Map<String, Object> copy(String chartId) {
92-
return http.post("/v1/charts/" + encode(chartId) + "/duplicate");
97+
return http.postWithHeaderReplay("/v1/charts/" + encode(chartId) + "/duplicate");
98+
}
99+
100+
public Map<String, Object> copy(
101+
String chartId,
102+
String name,
103+
String externalRef,
104+
String workspaceId,
105+
String idempotencyKey) {
106+
return http.postWithHeaderReplay(
107+
"/v1/charts/" + encode(chartId) + "/duplicate",
108+
body(
109+
"name", name,
110+
"externalRef", externalRef,
111+
"workspaceId", workspaceId),
112+
idempotencyKey);
93113
}
94114

95115
public Map<String, Object> archive(String chartId) {

src/main/java/io/seatlayer/Events.java

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,20 @@ public Iterable<Map<String, Object>> listAll() {
5252
}
5353

5454
public Map<String, Object> create(String chartId) {
55-
return http.post("/v1/events", body("chartId", chartId));
55+
return http.postWithHeaderReplay("/v1/events", body("chartId", chartId));
5656
}
5757

5858
public Map<String, Object> create(String chartId, String name) {
59-
return http.post("/v1/events", body("chartId", chartId, "name", name));
59+
return http.postWithHeaderReplay("/v1/events", body("chartId", chartId, "name", name));
60+
}
61+
62+
/** Creates an event with the full public metadata request shape. */
63+
public Map<String, Object> create(Map<String, Object> params) {
64+
return http.postWithHeaderReplay("/v1/events", params);
6065
}
6166

6267
public Map<String, Object> create(Map<String, Object> params, String idempotencyKey) {
63-
return http.post("/v1/events", params, idempotencyKey);
68+
return http.postWithHeaderReplay("/v1/events", params, idempotencyKey);
6469
}
6570

6671
public Map<String, Object> retrieve(String eventKey) {
@@ -75,9 +80,27 @@ public Map<String, Object> delete(String eventKey) {
7580
return http.delete("/v1/events/" + encode(eventKey));
7681
}
7782

83+
/** Upload raw PNG, JPEG, or WebP poster bytes (maximum 5 MiB). */
84+
public Map<String, Object> updatePoster(String eventKey, byte[] bytes, String contentType) {
85+
return http.putBinary("/v1/events/" + encode(eventKey) + "/poster", bytes, contentType);
86+
}
87+
88+
public Map<String, Object> deletePoster(String eventKey) {
89+
return http.delete("/v1/events/" + encode(eventKey) + "/poster");
90+
}
91+
7892
/** Move a live event onto the latest published version of its chart. */
7993
public Map<String, Object> updateChart(String eventKey) {
80-
return http.post("/v1/events/" + encode(eventKey) + "/update-chart");
94+
return updateChart(eventKey, null, null);
95+
}
96+
97+
public Map<String, Object> updateChart(
98+
String eventKey, Boolean acknowledgeDroppedAssignments, String reason) {
99+
return http.post(
100+
"/v1/events/" + encode(eventKey) + "/update-chart",
101+
body(
102+
"acknowledgeDroppedAssignments", acknowledgeDroppedAssignments,
103+
"reason", reason));
81104
}
82105

83106
/** Stop buyer sales. Existing holds keep their TTL. */
@@ -97,8 +120,10 @@ public Map<String, Object> retrieveHoldTtl(String eventKey) {
97120
return http.get("/v1/events/" + encode(eventKey) + "/hold-ttl");
98121
}
99122

100-
public Map<String, Object> updateHoldTtl(String eventKey, long holdTtlMs) {
101-
return http.post("/v1/events/" + encode(eventKey) + "/hold-ttl", body("holdTtlMs", holdTtlMs));
123+
public Map<String, Object> updateHoldTtl(String eventKey, Long holdTtlMs) {
124+
Map<String, Object> request = new LinkedHashMap<>();
125+
request.put("holdTtlMs", holdTtlMs);
126+
return http.post("/v1/events/" + encode(eventKey) + "/hold-ttl", request);
102127
}
103128

104129
public Map<String, Object> retrieveReport(String eventKey) {
@@ -108,4 +133,10 @@ public Map<String, Object> retrieveReport(String eventKey) {
108133
public Map<String, Object> retrieveLog(String eventKey) {
109134
return http.get("/v1/events/" + encode(eventKey) + "/log");
110135
}
136+
137+
public Map<String, Object> retrieveLog(String eventKey, Integer limit, Long before) {
138+
return http.get(
139+
"/v1/events/" + encode(eventKey) + "/log",
140+
body("limit", limit, "before", before));
141+
}
111142
}

src/main/java/io/seatlayer/Inventory.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public Map<String, Object> hold(String eventKey, List<String> labels) {
3535
return http.post(path(eventKey, "/hold"), body("labels", labels));
3636
}
3737

38+
/** Full hold request, including selections and replacement-hold semantics. */
39+
public Map<String, Object> hold(String eventKey, Map<String, Object> request) {
40+
return http.post(path(eventKey, "/hold"), request);
41+
}
42+
3843
public Map<String, Object> hold(String eventKey, List<String> labels, Long ttlMs, String idempotencyKey) {
3944
return http.post(path(eventKey, "/hold"), body("labels", labels, "ttlMs", ttlMs), idempotencyKey);
4045
}
@@ -158,6 +163,23 @@ public Map<String, Object> extendHold(String eventKey, String holdId, Long ttlMs
158163
return http.post(path(eventKey, "/extend"), body("holdId", holdId, "ttlMs", ttlMs));
159164
}
160165

166+
public Map<String, Object> extendHold(
167+
String eventKey,
168+
String holdId,
169+
Long ttlMs,
170+
List<String> channelIds,
171+
Boolean ignoreChannelRestrictions,
172+
String reason) {
173+
return http.post(
174+
path(eventKey, "/extend"),
175+
body(
176+
"holdId", holdId,
177+
"ttlMs", ttlMs,
178+
"channelIds", channelIds,
179+
"ignoreChannelRestrictions", ignoreChannelRestrictions,
180+
"reason", reason));
181+
}
182+
161183
/** Authoritative items and prices. Charge from this, not the browser. */
162184
public Map<String, Object> retrieveHold(String eventKey, String holdId) {
163185
return http.get(path(eventKey, "/holds/" + encode(holdId)));
@@ -171,6 +193,13 @@ public Map<String, Object> book(String eventKey, String holdId, String bookingRe
171193
return http.post(path(eventKey, "/book"), body("holdId", holdId, "bookingRef", bookingRef(bookingRef)));
172194
}
173195

196+
/** Full book request, including optional labels beside a hold id. */
197+
public Map<String, Object> book(String eventKey, Map<String, Object> request) {
198+
Map<String, Object> normalized = new java.util.LinkedHashMap<>(request);
199+
normalized.put("bookingRef", bookingRef((String) request.get("bookingRef")));
200+
return http.post(path(eventKey, "/book"), normalized);
201+
}
202+
174203
public Map<String, Object> book(
175204
String eventKey,
176205
String holdId,
@@ -229,6 +258,10 @@ public Map<String, Object> block(String eventKey, List<String> labels) {
229258
return http.post(path(eventKey, "/block"), body("labels", labels));
230259
}
231260

261+
public Map<String, Object> block(String eventKey, List<String> labels, Long releaseAt) {
262+
return http.post(path(eventKey, "/block"), body("labels", labels, "releaseAt", releaseAt));
263+
}
264+
232265
public Map<String, Object> unblock(String eventKey, List<String> labels) {
233266
return http.post(path(eventKey, "/unblock"), body("labels", labels));
234267
}
@@ -241,8 +274,8 @@ public Map<String, Object> retrieveAvailability(String eventKey) {
241274
return http.get(path(eventKey, "/availability"));
242275
}
243276

244-
public Map<String, Object> updateAvailability(String eventKey, Map<String, Object> fields) {
245-
return http.post(path(eventKey, "/availability"), fields);
277+
public Map<String, Object> updateAvailability(String eventKey, Map<String, Object> rules) {
278+
return http.post(path(eventKey, "/availability"), body("rules", rules));
246279
}
247280

248281
/** One page of booking lifecycle records, newest first. */

src/main/java/io/seatlayer/SeatLayer.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,18 @@ public Map<String, Object> ready() {
8484
return http.get("/health/ready");
8585
}
8686

87+
/** Dependency-aware readiness probe with the Durable Object check enabled on demand. */
88+
public Map<String, Object> ready(boolean deep) {
89+
return http.get("/health/ready", deep ? Map.of("deep", "1") : Map.of());
90+
}
91+
8792
/**
88-
* Escape hatch for surface this SDK does not wrap yet. Carries the same auth,
89-
* retries, idempotency and error mapping.
93+
* Escape hatch for surface this SDK does not wrap yet. Reads retain transport
94+
* retries; raw mutations are sent once without an Idempotency-Key.
9095
*/
9196
public Map<String, Object> request(
9297
String method, String path, Map<String, Object> query, Map<String, Object> body) {
93-
return http.request(method, path, query, body, null);
98+
return http.request(method, path, query, body);
9499
}
95100

96101
/** Builder for base URL, retries, timeout, and a custom transport. */

0 commit comments

Comments
 (0)