@playcademy/sdk 0.2.13 → 0.3.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.
@@ -1264,6 +1264,23 @@ declare const games: drizzle_orm_pg_core.PgTableWithColumns<{
1264
1264
  identity: undefined;
1265
1265
  generated: undefined;
1266
1266
  }, {}, {}>;
1267
+ visibility: drizzle_orm_pg_core.PgColumn<{
1268
+ name: "visibility";
1269
+ tableName: "games";
1270
+ dataType: "string";
1271
+ columnType: "PgEnumColumn";
1272
+ data: "visible" | "unlisted" | "internal";
1273
+ driverParam: string;
1274
+ notNull: true;
1275
+ hasDefault: true;
1276
+ isPrimaryKey: false;
1277
+ isAutoincrement: false;
1278
+ hasRuntimeDefault: false;
1279
+ enumValues: ["visible", "unlisted", "internal"];
1280
+ baseColumn: never;
1281
+ identity: undefined;
1282
+ generated: undefined;
1283
+ }, {}, {}>;
1267
1284
  metadata: drizzle_orm_pg_core.PgColumn<{
1268
1285
  name: "metadata";
1269
1286
  tableName: "games";
@@ -3653,6 +3670,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3653
3670
  platform: z.ZodEnum<["web", "godot", "unity"]>;
3654
3671
  metadata: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
3655
3672
  gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
3673
+ visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
3656
3674
  externalUrl: z.ZodOptional<z.ZodString>;
3657
3675
  }, "strip", z.ZodTypeAny, {
3658
3676
  metadata: Record<string, unknown>;
@@ -3661,6 +3679,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3661
3679
  platform: "web" | "godot" | "unity";
3662
3680
  externalUrl?: string | undefined;
3663
3681
  mapElementId?: string | null | undefined;
3682
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3664
3683
  }, {
3665
3684
  displayName: string;
3666
3685
  platform: "web" | "godot" | "unity";
@@ -3668,6 +3687,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3668
3687
  gameType?: "hosted" | "external" | undefined;
3669
3688
  externalUrl?: string | undefined;
3670
3689
  mapElementId?: string | null | undefined;
3690
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3671
3691
  }>, {
3672
3692
  metadata: Record<string, unknown>;
3673
3693
  displayName: string;
@@ -3675,6 +3695,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3675
3695
  platform: "web" | "godot" | "unity";
3676
3696
  externalUrl?: string | undefined;
3677
3697
  mapElementId?: string | null | undefined;
3698
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3678
3699
  }, {
3679
3700
  displayName: string;
3680
3701
  platform: "web" | "godot" | "unity";
@@ -3682,6 +3703,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3682
3703
  gameType?: "hosted" | "external" | undefined;
3683
3704
  externalUrl?: string | undefined;
3684
3705
  mapElementId?: string | null | undefined;
3706
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3685
3707
  }>;
3686
3708
 
3687
3709
  declare const InsertItemSchema: drizzle_zod.BuildSchema<"insert", {
@@ -6501,6 +6523,14 @@ interface DatabaseIntegration {
6501
6523
  * When omitted, auto-detects based on presence of a migrations directory with _journal.json. */
6502
6524
  strategy?: 'push' | 'migrate';
6503
6525
  }
6526
+ interface QueueConfig {
6527
+ maxBatchSize?: number;
6528
+ maxRetries?: number;
6529
+ maxBatchTimeout?: number;
6530
+ maxConcurrency?: number;
6531
+ retryDelay?: number;
6532
+ deadLetterQueue?: string;
6533
+ }
6504
6534
  /**
6505
6535
  * Integrations configuration
6506
6536
  * All backend features (database, custom routes, external services) are configured here
@@ -6518,6 +6548,8 @@ interface IntegrationsConfig {
6518
6548
  bucket?: boolean;
6519
6549
  /** Authentication (optional) */
6520
6550
  auth?: boolean;
6551
+ /** Queues (optional) */
6552
+ queues?: Record<string, QueueConfig | boolean>;
6521
6553
  }
6522
6554
  /**
6523
6555
  * Unified Playcademy configuration
@@ -6624,6 +6656,8 @@ interface BackendResourceBindings {
6624
6656
  keyValue?: string[];
6625
6657
  /** Object storage buckets to bind (maps to R2 on Cloudflare) */
6626
6658
  bucket?: string[];
6659
+ /** Queue bindings to create and bind */
6660
+ queues?: Record<string, QueueConfig | boolean>;
6627
6661
  }
6628
6662
  /**
6629
6663
  * Backend deployment bundle for uploading to Playcademy platform
@@ -6637,8 +6671,6 @@ interface BackendDeploymentBundle {
6637
6671
  bindings?: BackendResourceBindings;
6638
6672
  /** Optional schema information for database setup */
6639
6673
  schema?: SchemaInfo;
6640
- /** Optional game secrets */
6641
- secrets?: Record<string, string>;
6642
6674
  }
6643
6675
 
6644
6676
  /**
package/dist/internal.js CHANGED
@@ -1650,6 +1650,38 @@ function createAdminNamespace(client) {
1650
1650
  }
1651
1651
  // src/namespaces/platform/dev.ts
1652
1652
  function createDevNamespace(client) {
1653
+ const DEPLOY_JOB_POLL_INTERVAL_MS = 1000;
1654
+ const DEPLOY_JOB_INACTIVITY_TIMEOUT_MS = 60 * 1000;
1655
+ async function pollDeployJob(slug, jobId, hooks) {
1656
+ hooks?.onEvent?.({ type: "finalizeStart" });
1657
+ let seenEvents = 0;
1658
+ let lastProgressAt = Date.now();
1659
+ while (true) {
1660
+ if (Date.now() - lastProgressAt > DEPLOY_JOB_INACTIVITY_TIMEOUT_MS) {
1661
+ throw new Error("Deployment job timed out after 5 minutes without progress");
1662
+ }
1663
+ const job = await client["request"](`/games/${slug}/deploy?jobId=${encodeURIComponent(jobId)}`, "GET");
1664
+ const newEvents = job.events.slice(seenEvents);
1665
+ seenEvents = job.events.length;
1666
+ if (newEvents.length > 0) {
1667
+ lastProgressAt = Date.now();
1668
+ }
1669
+ for (const event of newEvents) {
1670
+ hooks?.onEvent?.({
1671
+ type: "finalizeStatus",
1672
+ message: event.durationMs ? `${event.message} (${event.durationMs}ms)` : event.message
1673
+ });
1674
+ }
1675
+ if (job.status === "succeeded") {
1676
+ hooks?.onEvent?.({ type: "complete" });
1677
+ return job;
1678
+ }
1679
+ if (job.status === "failed") {
1680
+ throw new ApiError(job.errorStatus ?? 500, job.errorCode ?? "INTERNAL_ERROR", job.error || "Deployment failed", job);
1681
+ }
1682
+ await new Promise((resolve) => setTimeout(resolve, DEPLOY_JOB_POLL_INTERVAL_MS));
1683
+ }
1684
+ }
1653
1685
  return {
1654
1686
  status: {
1655
1687
  apply: () => client["request"]("/dev/apply", "POST"),
@@ -1723,19 +1755,6 @@ function createDevNamespace(client) {
1723
1755
  }
1724
1756
  }
1725
1757
  if (uploadToken || backend) {
1726
- const deployUrl = `${client.baseUrl}/games/${slug}/deploy`;
1727
- const authToken = client.getToken();
1728
- const tokenType = client.getTokenType();
1729
- const headers = {
1730
- "Content-Type": "application/json"
1731
- };
1732
- if (authToken) {
1733
- if (tokenType === "apiKey") {
1734
- headers["x-api-key"] = authToken;
1735
- } else {
1736
- headers["Authorization"] = `Bearer ${authToken}`;
1737
- }
1738
- }
1739
1758
  const requestBody = {};
1740
1759
  if (uploadToken)
1741
1760
  requestBody.uploadToken = uploadToken;
@@ -1748,89 +1767,24 @@ function createDevNamespace(client) {
1748
1767
  requestBody.bindings = backend.bindings;
1749
1768
  if (backend.schema)
1750
1769
  requestBody.schema = backend.schema;
1751
- if (backend.secrets)
1752
- requestBody.secrets = backend.secrets;
1753
1770
  }
1754
- const finalizeResponse = await fetch(deployUrl, {
1755
- method: "POST",
1756
- headers,
1757
- body: JSON.stringify(requestBody),
1758
- credentials: "omit"
1771
+ const job = await client["request"](`/games/${slug}/deploy`, "POST", {
1772
+ body: requestBody
1759
1773
  });
1760
- if (!finalizeResponse.ok) {
1761
- const errText = await finalizeResponse.text().catch(() => "");
1762
- throw new Error(`Deploy request failed: ${finalizeResponse.status} ${finalizeResponse.statusText}${errText ? ` - ${errText}` : ""}`);
1763
- }
1764
- if (!finalizeResponse.body) {
1765
- throw new Error("Deploy response body missing");
1774
+ const completedJob = await pollDeployJob(slug, job.id, hooks);
1775
+ if (!completedJob.result?.url) {
1776
+ throw new Error("Deployment completed but no deployment URL was recorded");
1766
1777
  }
1767
- hooks?.onEvent?.({ type: "finalizeStart" });
1768
- let sawAnyServerEvent = false;
1769
- const reader = finalizeResponse.body.pipeThrough(new TextDecoderStream).getReader();
1770
- let buffer = "";
1771
- const processBuffer = () => {
1772
- let eolIndex;
1773
- while ((eolIndex = buffer.indexOf(`
1774
-
1775
- `)) >= 0) {
1776
- const message = buffer.slice(0, eolIndex);
1777
- buffer = buffer.slice(eolIndex + 2);
1778
- const eventLine = message.match(/^event: (.*)$/m);
1779
- const dataLine = message.match(/^data: (.*)$/m);
1780
- if (eventLine && dataLine) {
1781
- const eventType = eventLine[1];
1782
- const eventData = JSON.parse(dataLine[1]);
1783
- sawAnyServerEvent = true;
1784
- switch (eventType) {
1785
- case "uploadProgress": {
1786
- const percent = (eventData.value ?? 0) / 100;
1787
- hooks?.onEvent?.({
1788
- type: "finalizeProgress",
1789
- percent,
1790
- currentFileLabel: eventData.currentFileLabel || ""
1791
- });
1792
- break;
1793
- }
1794
- case "status": {
1795
- if (eventData.message) {
1796
- hooks?.onEvent?.({
1797
- type: "finalizeStatus",
1798
- message: eventData.message
1799
- });
1800
- }
1801
- break;
1802
- }
1803
- case "complete": {
1804
- hooks?.onEvent?.({ type: "complete" });
1805
- reader.cancel();
1806
- return eventData;
1807
- }
1808
- case "error": {
1809
- reader.cancel();
1810
- throw ApiError.fromResponse(eventData?.error?.status ?? 500, "Deployment error", eventData);
1811
- }
1812
- }
1813
- }
1814
- }
1815
- return null;
1816
- };
1817
- while (true) {
1818
- const { done, value } = await reader.read();
1819
- if (value) {
1820
- buffer += value;
1821
- }
1822
- const result = processBuffer();
1823
- if (result)
1824
- return result;
1825
- if (done) {
1826
- if (!sawAnyServerEvent) {
1827
- hooks?.onClose?.();
1828
- hooks?.onEvent?.({ type: "close" });
1778
+ for (let attempt = 0;attempt < 3; attempt++) {
1779
+ try {
1780
+ return await client["request"](`/games/${slug}`, "GET");
1781
+ } catch {
1782
+ if (attempt < 2) {
1783
+ await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
1829
1784
  }
1830
- break;
1831
1785
  }
1832
1786
  }
1833
- throw new Error("Deployment completed but no final game data received");
1787
+ throw new Error("Deploy succeeded but failed to fetch updated game after 3 attempts");
1834
1788
  }
1835
1789
  if (game) {
1836
1790
  return game;
package/dist/server.d.ts CHANGED
@@ -386,6 +386,14 @@ interface DatabaseIntegration {
386
386
  * When omitted, auto-detects based on presence of a migrations directory with _journal.json. */
387
387
  strategy?: 'push' | 'migrate';
388
388
  }
389
+ interface QueueConfig {
390
+ maxBatchSize?: number;
391
+ maxRetries?: number;
392
+ maxBatchTimeout?: number;
393
+ maxConcurrency?: number;
394
+ retryDelay?: number;
395
+ deadLetterQueue?: string;
396
+ }
389
397
  /**
390
398
  * Integrations configuration
391
399
  * All backend features (database, custom routes, external services) are configured here
@@ -403,6 +411,8 @@ interface IntegrationsConfig {
403
411
  bucket?: boolean;
404
412
  /** Authentication (optional) */
405
413
  auth?: boolean;
414
+ /** Queues (optional) */
415
+ queues?: Record<string, QueueConfig | boolean>;
406
416
  }
407
417
  /**
408
418
  * Unified Playcademy configuration
@@ -509,6 +519,8 @@ interface BackendResourceBindings {
509
519
  keyValue?: string[];
510
520
  /** Object storage buckets to bind (maps to R2 on Cloudflare) */
511
521
  bucket?: string[];
522
+ /** Queue bindings to create and bind */
523
+ queues?: Record<string, QueueConfig | boolean>;
512
524
  }
513
525
  /**
514
526
  * Backend deployment bundle for uploading to Playcademy platform
@@ -522,8 +534,6 @@ interface BackendDeploymentBundle {
522
534
  bindings?: BackendResourceBindings;
523
535
  /** Optional schema information for database setup */
524
536
  schema?: SchemaInfo;
525
- /** Optional game secrets */
526
- secrets?: Record<string, string>;
527
537
  }
528
538
 
529
539
  /**
@@ -715,4 +725,4 @@ declare function verifyGameToken(gameToken: string, options?: {
715
725
  }): Promise<VerifyGameTokenResponse>;
716
726
 
717
727
  export { PlaycademyClient, verifyGameToken };
718
- export type { ActivityData, BackendDeploymentBundle, BackendResourceBindings, ComponentConfig, ComponentResourceConfig, EndActivityPayload, IntegrationsConfig, OrganizationConfig, PlaycademyConfig, PlaycademyServerClientConfig, PlaycademyServerClientState, ResourceConfig, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackGrade, TimebackIntegrationConfig, TimebackSubject, UserInfo };
728
+ export type { ActivityData, BackendDeploymentBundle, BackendResourceBindings, ComponentConfig, ComponentResourceConfig, EndActivityPayload, IntegrationsConfig, OrganizationConfig, PlaycademyConfig, PlaycademyServerClientConfig, PlaycademyServerClientState, QueueConfig, ResourceConfig, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackGrade, TimebackIntegrationConfig, TimebackSubject, UserInfo };
package/dist/types.d.ts CHANGED
@@ -1086,6 +1086,23 @@ declare const games: drizzle_orm_pg_core.PgTableWithColumns<{
1086
1086
  identity: undefined;
1087
1087
  generated: undefined;
1088
1088
  }, {}, {}>;
1089
+ visibility: drizzle_orm_pg_core.PgColumn<{
1090
+ name: "visibility";
1091
+ tableName: "games";
1092
+ dataType: "string";
1093
+ columnType: "PgEnumColumn";
1094
+ data: "visible" | "unlisted" | "internal";
1095
+ driverParam: string;
1096
+ notNull: true;
1097
+ hasDefault: true;
1098
+ isPrimaryKey: false;
1099
+ isAutoincrement: false;
1100
+ hasRuntimeDefault: false;
1101
+ enumValues: ["visible", "unlisted", "internal"];
1102
+ baseColumn: never;
1103
+ identity: undefined;
1104
+ generated: undefined;
1105
+ }, {}, {}>;
1089
1106
  metadata: drizzle_orm_pg_core.PgColumn<{
1090
1107
  name: "metadata";
1091
1108
  tableName: "games";
@@ -3475,6 +3492,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3475
3492
  platform: z.ZodEnum<["web", "godot", "unity"]>;
3476
3493
  metadata: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
3477
3494
  gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
3495
+ visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
3478
3496
  externalUrl: z.ZodOptional<z.ZodString>;
3479
3497
  }, "strip", z.ZodTypeAny, {
3480
3498
  metadata: Record<string, unknown>;
@@ -3483,6 +3501,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3483
3501
  platform: "web" | "godot" | "unity";
3484
3502
  externalUrl?: string | undefined;
3485
3503
  mapElementId?: string | null | undefined;
3504
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3486
3505
  }, {
3487
3506
  displayName: string;
3488
3507
  platform: "web" | "godot" | "unity";
@@ -3490,6 +3509,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3490
3509
  gameType?: "hosted" | "external" | undefined;
3491
3510
  externalUrl?: string | undefined;
3492
3511
  mapElementId?: string | null | undefined;
3512
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3493
3513
  }>, {
3494
3514
  metadata: Record<string, unknown>;
3495
3515
  displayName: string;
@@ -3497,6 +3517,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3497
3517
  platform: "web" | "godot" | "unity";
3498
3518
  externalUrl?: string | undefined;
3499
3519
  mapElementId?: string | null | undefined;
3520
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3500
3521
  }, {
3501
3522
  displayName: string;
3502
3523
  platform: "web" | "godot" | "unity";
@@ -3504,6 +3525,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3504
3525
  gameType?: "hosted" | "external" | undefined;
3505
3526
  externalUrl?: string | undefined;
3506
3527
  mapElementId?: string | null | undefined;
3528
+ visibility?: "visible" | "unlisted" | "internal" | undefined;
3507
3529
  }>;
3508
3530
 
3509
3531
  declare const InsertItemSchema: drizzle_zod.BuildSchema<"insert", {
@@ -5684,6 +5706,14 @@ interface DatabaseIntegration {
5684
5706
  * When omitted, auto-detects based on presence of a migrations directory with _journal.json. */
5685
5707
  strategy?: 'push' | 'migrate';
5686
5708
  }
5709
+ interface QueueConfig {
5710
+ maxBatchSize?: number;
5711
+ maxRetries?: number;
5712
+ maxBatchTimeout?: number;
5713
+ maxConcurrency?: number;
5714
+ retryDelay?: number;
5715
+ deadLetterQueue?: string;
5716
+ }
5687
5717
  /**
5688
5718
  * Integrations configuration
5689
5719
  * All backend features (database, custom routes, external services) are configured here
@@ -5701,6 +5731,8 @@ interface IntegrationsConfig {
5701
5731
  bucket?: boolean;
5702
5732
  /** Authentication (optional) */
5703
5733
  auth?: boolean;
5734
+ /** Queues (optional) */
5735
+ queues?: Record<string, QueueConfig | boolean>;
5704
5736
  }
5705
5737
  /**
5706
5738
  * Unified Playcademy configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.2.13",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -49,7 +49,7 @@
49
49
  "@playcademy/timeback": "0.0.1",
50
50
  "@playcademy/utils": "0.0.1",
51
51
  "@types/bun": "latest",
52
- "playcademy": "0.16.9",
52
+ "playcademy": "0.16.17",
53
53
  "rollup": "^4.50.2",
54
54
  "rollup-plugin-dts": "^6.2.3",
55
55
  "typescript": "^5.7.2"