@dreamboard-games/api-client 0.1.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 (87) hide show
  1. package/LICENSE +89 -0
  2. package/NOTICE +1 -0
  3. package/dist/@tanstack/react-query.gen.d.ts +1037 -0
  4. package/dist/@tanstack/react-query.gen.d.ts.map +1 -0
  5. package/dist/@tanstack/react-query.gen.js +1016 -0
  6. package/dist/client/client.gen.d.ts +3 -0
  7. package/dist/client/client.gen.d.ts.map +1 -0
  8. package/dist/client/client.gen.js +235 -0
  9. package/dist/client/index.d.ts +9 -0
  10. package/dist/client/index.d.ts.map +1 -0
  11. package/dist/client/index.js +6 -0
  12. package/dist/client/types.gen.d.ts +118 -0
  13. package/dist/client/types.gen.d.ts.map +1 -0
  14. package/dist/client/types.gen.js +2 -0
  15. package/dist/client/utils.gen.d.ts +34 -0
  16. package/dist/client/utils.gen.d.ts.map +1 -0
  17. package/dist/client/utils.gen.js +233 -0
  18. package/dist/client.gen.d.ts +13 -0
  19. package/dist/client.gen.d.ts.map +1 -0
  20. package/dist/client.gen.js +3 -0
  21. package/dist/core/auth.gen.d.ts +19 -0
  22. package/dist/core/auth.gen.d.ts.map +1 -0
  23. package/dist/core/auth.gen.js +14 -0
  24. package/dist/core/bodySerializer.gen.d.ts +26 -0
  25. package/dist/core/bodySerializer.gen.d.ts.map +1 -0
  26. package/dist/core/bodySerializer.gen.js +57 -0
  27. package/dist/core/params.gen.d.ts +44 -0
  28. package/dist/core/params.gen.d.ts.map +1 -0
  29. package/dist/core/params.gen.js +100 -0
  30. package/dist/core/pathSerializer.gen.d.ts +34 -0
  31. package/dist/core/pathSerializer.gen.d.ts.map +1 -0
  32. package/dist/core/pathSerializer.gen.js +114 -0
  33. package/dist/core/queryKeySerializer.gen.d.ts +19 -0
  34. package/dist/core/queryKeySerializer.gen.d.ts.map +1 -0
  35. package/dist/core/queryKeySerializer.gen.js +99 -0
  36. package/dist/core/serverSentEvents.gen.d.ts +72 -0
  37. package/dist/core/serverSentEvents.gen.d.ts.map +1 -0
  38. package/dist/core/serverSentEvents.gen.js +136 -0
  39. package/dist/core/types.gen.d.ts +79 -0
  40. package/dist/core/types.gen.d.ts.map +1 -0
  41. package/dist/core/types.gen.js +2 -0
  42. package/dist/core/utils.gen.d.ts +20 -0
  43. package/dist/core/utils.gen.d.ts.map +1 -0
  44. package/dist/core/utils.gen.js +87 -0
  45. package/dist/generated/problem-types.gen.d.ts +26 -0
  46. package/dist/generated/problem-types.gen.d.ts.map +1 -0
  47. package/dist/generated/problem-types.gen.js +23 -0
  48. package/dist/index.d.ts +5 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +4 -0
  51. package/dist/sdk.gen.d.ts +364 -0
  52. package/dist/sdk.gen.d.ts.map +1 -0
  53. package/dist/sdk.gen.js +634 -0
  54. package/dist/source-revisions.d.ts +27 -0
  55. package/dist/source-revisions.d.ts.map +1 -0
  56. package/dist/source-revisions.js +174 -0
  57. package/dist/storage-paths.d.ts +17 -0
  58. package/dist/storage-paths.d.ts.map +1 -0
  59. package/dist/storage-paths.js +16 -0
  60. package/dist/types.gen.d.ts +5007 -0
  61. package/dist/types.gen.d.ts.map +1 -0
  62. package/dist/types.gen.js +2 -0
  63. package/dist/zod.gen.d.ts +37398 -0
  64. package/dist/zod.gen.d.ts.map +1 -0
  65. package/dist/zod.gen.js +2396 -0
  66. package/package.json +105 -0
  67. package/src/@tanstack/react-query.gen.ts +1114 -0
  68. package/src/client/client.gen.ts +311 -0
  69. package/src/client/index.ts +25 -0
  70. package/src/client/types.gen.ts +241 -0
  71. package/src/client/utils.gen.ts +332 -0
  72. package/src/client.gen.ts +16 -0
  73. package/src/core/auth.gen.ts +42 -0
  74. package/src/core/bodySerializer.gen.ts +100 -0
  75. package/src/core/params.gen.ts +176 -0
  76. package/src/core/pathSerializer.gen.ts +181 -0
  77. package/src/core/queryKeySerializer.gen.ts +136 -0
  78. package/src/core/serverSentEvents.gen.ts +265 -0
  79. package/src/core/types.gen.ts +118 -0
  80. package/src/core/utils.gen.ts +143 -0
  81. package/src/generated/problem-types.gen.ts +35 -0
  82. package/src/index.ts +7 -0
  83. package/src/sdk.gen.ts +709 -0
  84. package/src/source-revisions.ts +283 -0
  85. package/src/storage-paths.ts +19 -0
  86. package/src/types.gen.ts +5486 -0
  87. package/src/zod.gen.ts +2705 -0
@@ -0,0 +1,283 @@
1
+ import { createSourceBlobUploadSession } from "./sdk.gen.js";
2
+ import type {
3
+ SourceBlobUploadDescriptor,
4
+ SourceBlobUploadTarget,
5
+ SourceChangeOperation,
6
+ } from "./types.gen.js";
7
+
8
+ export type SourceContentChangeOperation =
9
+ | {
10
+ kind: "upsert";
11
+ path: string;
12
+ content: string;
13
+ }
14
+ | {
15
+ kind: "delete";
16
+ path: string;
17
+ };
18
+
19
+ const textEncoder = new TextEncoder();
20
+
21
+ function bytesToHex(bytes: Uint8Array): string {
22
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
23
+ }
24
+
25
+ async function sha256Hex(bytes: Uint8Array): Promise<string> {
26
+ const normalizedBytes = new Uint8Array(bytes.byteLength);
27
+ normalizedBytes.set(bytes);
28
+ const digest = await crypto.subtle.digest(
29
+ "SHA-256",
30
+ normalizedBytes.buffer,
31
+ );
32
+ return bytesToHex(new Uint8Array(digest));
33
+ }
34
+
35
+ function getUtf8ByteSize(content: string): number {
36
+ return textEncoder.encode(content).byteLength;
37
+ }
38
+
39
+ async function computeSourceContentHash(content: string): Promise<string> {
40
+ return sha256Hex(textEncoder.encode(content));
41
+ }
42
+
43
+ async function describeSourceBlob(
44
+ content: string,
45
+ ): Promise<SourceBlobUploadDescriptor> {
46
+ return {
47
+ contentHash: await computeSourceContentHash(content),
48
+ byteSize: getUtf8ByteSize(content),
49
+ };
50
+ }
51
+
52
+ export async function materializeSourceChangeOperations(
53
+ changes: Iterable<SourceContentChangeOperation>,
54
+ ): Promise<{
55
+ blobs: SourceBlobUploadDescriptor[];
56
+ changes: SourceChangeOperation[];
57
+ }> {
58
+ const blobsByHash = new Map<string, SourceBlobUploadDescriptor>();
59
+ const materialized = await Promise.all(
60
+ Array.from(changes, async (change): Promise<SourceChangeOperation> => {
61
+ if (change.kind === "delete") {
62
+ return change;
63
+ }
64
+
65
+ const blob = await describeSourceBlob(change.content);
66
+ const existing = blobsByHash.get(blob.contentHash);
67
+ if (!existing) {
68
+ blobsByHash.set(blob.contentHash, blob);
69
+ }
70
+
71
+ return {
72
+ kind: "upsert",
73
+ path: change.path,
74
+ contentHash: blob.contentHash,
75
+ byteSize: blob.byteSize,
76
+ };
77
+ }),
78
+ );
79
+
80
+ return {
81
+ blobs: Array.from(blobsByHash.values()).sort((left, right) =>
82
+ left.contentHash.localeCompare(right.contentHash),
83
+ ),
84
+ changes: materialized,
85
+ };
86
+ }
87
+
88
+ export type SourceBlobUploadInput = SourceBlobUploadDescriptor & {
89
+ content: string;
90
+ };
91
+
92
+ export function mapUpsertBlobContentsByContentHash(
93
+ localChanges: readonly SourceContentChangeOperation[],
94
+ materializedChanges: readonly SourceChangeOperation[],
95
+ ): Map<string, SourceBlobUploadInput> {
96
+ const uploadBlobs = new Map<string, SourceBlobUploadInput>();
97
+ const length = Math.min(localChanges.length, materializedChanges.length);
98
+ for (let index = 0; index < length; index += 1) {
99
+ const localChange = localChanges[index];
100
+ const materializedChange = materializedChanges[index];
101
+ if (
102
+ localChange?.kind !== "upsert" ||
103
+ materializedChange?.kind !== "upsert"
104
+ ) {
105
+ continue;
106
+ }
107
+
108
+ uploadBlobs.set(materializedChange.contentHash, {
109
+ contentHash: materializedChange.contentHash,
110
+ byteSize: materializedChange.byteSize,
111
+ content: localChange.content,
112
+ });
113
+ }
114
+
115
+ return uploadBlobs;
116
+ }
117
+
118
+ class SourceBlobUploadError extends Error {
119
+ readonly status: number;
120
+ readonly details: string;
121
+
122
+ constructor(status: number, details: string) {
123
+ const suffix = details.trim().length > 0 ? `: ${details.trim()}` : "";
124
+ super(`Failed to upload source blob (HTTP ${status}${suffix})`);
125
+ this.name = "SourceBlobUploadError";
126
+ this.status = status;
127
+ this.details = details;
128
+ }
129
+ }
130
+
131
+ function isDuplicateDirectUploadError(
132
+ error: unknown,
133
+ ): error is SourceBlobUploadError {
134
+ if (!(error instanceof SourceBlobUploadError)) {
135
+ return false;
136
+ }
137
+
138
+ if (error.status === 409) {
139
+ return true;
140
+ }
141
+
142
+ const normalizedDetails = error.details.toLowerCase();
143
+ return (
144
+ normalizedDetails.includes("duplicate") ||
145
+ normalizedDetails.includes("already exists") ||
146
+ normalizedDetails.includes("resource already exists")
147
+ );
148
+ }
149
+
150
+ async function uploadSourceBlob(
151
+ uploadTarget: SourceBlobUploadTarget,
152
+ content: string,
153
+ ): Promise<void> {
154
+ const response = await fetch(uploadTarget.url, {
155
+ method: uploadTarget.method,
156
+ headers: uploadTarget.headers,
157
+ body: textEncoder.encode(content),
158
+ });
159
+
160
+ if (response.ok) {
161
+ return;
162
+ }
163
+
164
+ const details = await response.text().catch(() => "");
165
+ throw new SourceBlobUploadError(response.status, details);
166
+ }
167
+
168
+ export class SourceBlobSessionRequestError extends Error {
169
+ readonly apiError: unknown;
170
+ readonly response: Response | undefined;
171
+
172
+ constructor(
173
+ message: string,
174
+ apiError: unknown,
175
+ response: Response | undefined,
176
+ ) {
177
+ super(message);
178
+ this.name = "SourceBlobSessionRequestError";
179
+ this.apiError = apiError;
180
+ this.response = response;
181
+ }
182
+ }
183
+
184
+ async function confirmSourceBlobAlreadyExists(options: {
185
+ gameId: string;
186
+ blob: SourceBlobUploadInput;
187
+ }): Promise<boolean> {
188
+ const { gameId, blob } = options;
189
+ const { data, error, response } = await createSourceBlobUploadSession({
190
+ path: { gameId },
191
+ body: {
192
+ blobs: [
193
+ {
194
+ contentHash: blob.contentHash,
195
+ byteSize: blob.byteSize,
196
+ },
197
+ ],
198
+ },
199
+ });
200
+
201
+ if (error || !data) {
202
+ throw new SourceBlobSessionRequestError(
203
+ "Failed to create source blob upload session",
204
+ error,
205
+ response,
206
+ );
207
+ }
208
+
209
+ return data.uploads[0]?.status === "exists";
210
+ }
211
+
212
+ export async function uploadGameSourceBlobs(options: {
213
+ gameId: string;
214
+ blobs: SourceBlobUploadInput[];
215
+ }): Promise<void> {
216
+ const { gameId, blobs } = options;
217
+ const uniqueBlobs = new Map<string, SourceBlobUploadInput>();
218
+ for (const blob of blobs) {
219
+ const existing = uniqueBlobs.get(blob.contentHash);
220
+ if (!existing) {
221
+ uniqueBlobs.set(blob.contentHash, blob);
222
+ continue;
223
+ }
224
+
225
+ if (existing.byteSize !== blob.byteSize) {
226
+ throw new Error(
227
+ `Source blob ${blob.contentHash} has conflicting byte sizes.`,
228
+ );
229
+ }
230
+ }
231
+
232
+ if (uniqueBlobs.size === 0) {
233
+ return;
234
+ }
235
+
236
+ const { data, error, response } = await createSourceBlobUploadSession({
237
+ path: { gameId },
238
+ body: {
239
+ blobs: Array.from(uniqueBlobs.values(), ({ contentHash, byteSize }) => ({
240
+ contentHash,
241
+ byteSize,
242
+ })),
243
+ },
244
+ });
245
+
246
+ if (error || !data) {
247
+ throw new SourceBlobSessionRequestError(
248
+ "Failed to create source blob upload session",
249
+ error,
250
+ response,
251
+ );
252
+ }
253
+
254
+ for (const upload of data.uploads) {
255
+ if (upload.status !== "upload_required") {
256
+ continue;
257
+ }
258
+
259
+ const blob = uniqueBlobs.get(upload.contentHash);
260
+ if (!blob) {
261
+ throw new Error(
262
+ `Upload session referenced unknown source blob ${upload.contentHash}.`,
263
+ );
264
+ }
265
+ if (!upload.uploadTarget) {
266
+ throw new Error(
267
+ `Upload target missing for source blob ${upload.contentHash}.`,
268
+ );
269
+ }
270
+
271
+ try {
272
+ await uploadSourceBlob(upload.uploadTarget, blob.content);
273
+ } catch (error) {
274
+ if (
275
+ isDuplicateDirectUploadError(error) &&
276
+ (await confirmSourceBlobAlreadyExists({ gameId, blob }))
277
+ ) {
278
+ continue;
279
+ }
280
+ throw error;
281
+ }
282
+ }
283
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Storage path constants shared between frontend and backend.
3
+ * These mirror the values in the Kotlin StoragePathUtils.
4
+ */
5
+
6
+ /**
7
+ * Storage bucket for compiled scripts (game logic and UI bundles)
8
+ */
9
+ export const STORAGE_BUCKET = "scripts";
10
+
11
+ /**
12
+ * Directory name for source files
13
+ */
14
+ export const SOURCE_DIR = "src";
15
+
16
+ /**
17
+ * Directory name for compiled output
18
+ */
19
+ export const DIST_DIR = "dist";