@crowdedkingdoms/crowdyjs 17.10.0-dev.1 → 17.12.0-dev.1

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.
@@ -1,6 +1,7 @@
1
1
  import { decode, encode } from '@msgpack/msgpack';
2
2
  import { CrowdyError } from '../errors.js';
3
- import { ExecActivateVersionDocument, ExecAppStatusDocument, ExecConnectAsDeveloperDocument, ExecConnectDocument, ExecDeployDocument, ExecInstancesDocument, ExecLogsDocument, ExecSetEnabledDocument, ExecVersionsDocument, } from '../generated/graphql.js';
3
+ import { ExecActivateVersionDocument, ExecAppStatusDocument, ExecBuildDocument, ExecBuildStatusDocument, ExecConnectAsDeveloperDocument, ExecConnectDocument, ExecDeployDocument, ExecInstancesDocument, ExecLogsDocument, ExecAppModsDocument, ExecModBuildDocument, ExecModBuildStatusDocument, ExecModDeleteDocument, ExecModDeployDocument, ExecModInstallDocument, ExecModListingsDocument, ExecModLogsDocument, ExecModPublishDocument, ExecModScope, ExecModSetEnabledDocument, ExecModSetSwitchDocument, ExecModStarterDocument, ExecModSwitchesDocument, ExecModUnpublishDocument, ExecModsDocument, ExecMyModsDocument, ExecSetEnabledDocument, ExecStartersDocument, ExecVersionsDocument, } from '../generated/graphql.js';
4
+ export { ExecModScope };
4
5
  /**
5
6
  * ck-exec, the hub-and-spoke execution service (dev-tier preview).
6
7
  *
@@ -512,13 +513,22 @@ async function sha256Hex(bytes) {
512
513
  const d = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
513
514
  return Array.from(d, (x) => x.toString(16).padStart(2, '0')).join('');
514
515
  }
516
+ /** The node type players call a mod by: `mod:<name>`, keyed by its grid id. */
517
+ export function execModType(name) {
518
+ return `mod:${name}`;
519
+ }
515
520
  function strip(v) {
516
521
  const { __typename: _, ...rest } = v;
517
522
  return rest;
518
523
  }
524
+ function build(b) {
525
+ const { __typename: _, artifacts, ...rest } = b;
526
+ return { ...rest, artifacts: artifacts.map(strip) };
527
+ }
519
528
  /**
520
- * `client.exec`: connecting players to ck-exec, deploying an app's nodes, and operating
521
- * them (logs, instances, versions, rollback, the kill switch, developer connections).
529
+ * `client.exec`: connecting players to ck-exec, building and deploying an app's nodes (and
530
+ * the starter packs), and operating them (logs, instances, versions, rollback, the kill
531
+ * switch, developer connections).
522
532
  */
523
533
  export class ExecAPI {
524
534
  constructor(graphql) {
@@ -585,6 +595,179 @@ export class ExecAPI {
585
595
  const data = await this.graphql.request(ExecSetEnabledDocument, { appId, enabled, nodeType });
586
596
  return strip(data.execSetEnabled);
587
597
  }
598
+ /**
599
+ * The starter packs, which replace the compute templates: a world tick (the root hub), a
600
+ * matchmaker, game sessions and an NPC and mob engine. Build them with {@link build} and
601
+ * deploy the build with `manifest`. Requires `manage_compute`.
602
+ */
603
+ async starters(appId) {
604
+ const data = await this.graphql.request(ExecStartersDocument, { appId });
605
+ return {
606
+ manifest: JSON.parse(data.execStarters.manifestJson),
607
+ starters: data.execStarters.starters.map((s) => ({
608
+ crate: s.crate,
609
+ nodeType: s.nodeType,
610
+ description: s.description,
611
+ files: s.files.map(strip),
612
+ })),
613
+ };
614
+ }
615
+ /**
616
+ * Builds crates into modules on the platform (`ckx-sdk`, `wasm32-unknown-unknown`), so you
617
+ * need no Rust toolchain. Returns at once with the build queued; wait with
618
+ * {@link waitForBuild}, then {@link deploy} with its `buildId`. Requires `manage_compute`.
619
+ */
620
+ async build(appId, crates) {
621
+ const data = await this.graphql.request(ExecBuildDocument, {
622
+ input: {
623
+ appId,
624
+ crates: crates.map((c) => ({
625
+ name: c.name,
626
+ files: Array.isArray(c.files)
627
+ ? c.files
628
+ : Object.entries(c.files).map(([path, content]) => ({ path, content })),
629
+ })),
630
+ },
631
+ });
632
+ return build(data.execBuild);
633
+ }
634
+ /** A build's status, log and modules, or null. Requires `view_compute_diagnostics`. */
635
+ async buildStatus(appId, buildId) {
636
+ const data = await this.graphql.request(ExecBuildStatusDocument, { appId, buildId });
637
+ return data.execBuildStatus ? build(data.execBuildStatus) : null;
638
+ }
639
+ /**
640
+ * Polls a build until it succeeds or fails, and returns it either way; a failed build's
641
+ * `log` says why. Throws when it is not done in `timeoutMs` (default 10 minutes).
642
+ */
643
+ async waitForBuild(appId, buildId, options = {}) {
644
+ const until = Date.now() + (options.timeoutMs ?? 600000);
645
+ for (;;) {
646
+ const b = await this.buildStatus(appId, buildId);
647
+ if (!b)
648
+ throw new CrowdyError({ message: `no build ${buildId} in app ${appId}` });
649
+ if (b.status === 'succeeded' || b.status === 'failed')
650
+ return b;
651
+ if (Date.now() > until)
652
+ throw new CrowdyError({ message: `build ${buildId} is still ${b.status}` });
653
+ await new Promise((r) => setTimeout(r, options.intervalMs ?? 2000));
654
+ }
655
+ }
656
+ // ---- mods: players' code on grids they own ----
657
+ /** The mod starter (`grid-mod`), a crate to build with {@link modBuild}. Requires access to the app. */
658
+ async modStarter(appId) {
659
+ const data = await this.graphql.request(ExecModStarterDocument, { appId });
660
+ const s = data.execModStarter;
661
+ return { crate: s.crate, nodeType: s.nodeType, description: s.description, files: s.files.map(strip) };
662
+ }
663
+ /**
664
+ * Builds a mod from one `ckx-sdk` crate, as {@link build} does a developer's. Returns at
665
+ * once; wait with {@link waitForModBuild}, then {@link modDeploy}. One build at a time per
666
+ * player. Requires `write_server_code` in the app.
667
+ */
668
+ async modBuild(appId, crate) {
669
+ const files = Array.isArray(crate.files)
670
+ ? crate.files
671
+ : Object.entries(crate.files).map(([path, content]) => ({ path, content }));
672
+ const data = await this.graphql.request(ExecModBuildDocument, {
673
+ appId,
674
+ crate: { name: crate.name, files },
675
+ });
676
+ return build(data.execModBuild);
677
+ }
678
+ /** A mod build of yours. */
679
+ async modBuildStatus(appId, buildId) {
680
+ const data = await this.graphql.request(ExecModBuildStatusDocument, { appId, buildId });
681
+ return build(data.execModBuildStatus);
682
+ }
683
+ /** Polls a mod build until it succeeds or fails, like {@link waitForBuild}. */
684
+ async waitForModBuild(appId, buildId, options = {}) {
685
+ const until = Date.now() + (options.timeoutMs ?? 600000);
686
+ for (;;) {
687
+ const b = await this.modBuildStatus(appId, buildId);
688
+ if (b.status === 'succeeded' || b.status === 'failed')
689
+ return b;
690
+ if (Date.now() > until)
691
+ throw new CrowdyError({ message: `build ${buildId} is still ${b.status}` });
692
+ await new Promise((r) => setTimeout(r, options.intervalMs ?? 2000));
693
+ }
694
+ }
695
+ /**
696
+ * Deploys a mod build of yours to a grid you own: a new mod starts switched off, and a
697
+ * running one restarts on the new version. Requires being the grid's owner and
698
+ * `write_server_code` on the app tier and the grid.
699
+ */
700
+ async modDeploy(appId, gridId, name, buildId) {
701
+ const data = await this.graphql.request(ExecModDeployDocument, { appId, gridId, name, buildId });
702
+ return strip(data.execModDeploy);
703
+ }
704
+ /**
705
+ * Switches a mod on your grid on or off. On, it runs as you once the app's code admission
706
+ * admits it. Requires `run_server_code` on the app tier and the grid.
707
+ */
708
+ async modSetEnabled(appId, gridId, name, enabled) {
709
+ const data = await this.graphql.request(ExecModSetEnabledDocument, { appId, gridId, name, enabled });
710
+ return strip(data.execModSetEnabled);
711
+ }
712
+ /** Stops and removes a mod on your grid, with its state. */
713
+ async modDelete(appId, gridId, name) {
714
+ const data = await this.graphql.request(ExecModDeleteDocument, { appId, gridId, name });
715
+ return data.execModDelete;
716
+ }
717
+ /** A grid's mods, which players in it call as `mod:<name>` with the grid id as key. */
718
+ async mods(appId, gridId) {
719
+ const data = await this.graphql.request(ExecModsDocument, { appId, gridId });
720
+ return data.execMods.map(strip);
721
+ }
722
+ /** Your mods in the app, on every grid. */
723
+ async myMods(appId) {
724
+ const data = await this.graphql.request(ExecMyModsDocument, { appId });
725
+ return data.execMyMods.map(strip);
726
+ }
727
+ /** A mod of yours' guest log lines, newest first. */
728
+ async modLogs(appId, gridId, name, options = {}) {
729
+ const data = await this.graphql.request(ExecModLogsDocument, { appId, gridId, name, ...options });
730
+ return data.execModLogs.map(strip);
731
+ }
732
+ /** Publishes a mod of yours, at its current version, for other grid owners to install. */
733
+ async modPublish(appId, gridId, name, title, description) {
734
+ const data = await this.graphql.request(ExecModPublishDocument, { appId, gridId, name, title, description });
735
+ return strip(data.execModPublish);
736
+ }
737
+ /** The app's listed mods, most installed first. */
738
+ async modListings(appId) {
739
+ const data = await this.graphql.request(ExecModListingsDocument, { appId });
740
+ return data.execModListings.map(strip);
741
+ }
742
+ /** Delists a listing you published; installed copies keep running. */
743
+ async modUnpublish(appId, listingId) {
744
+ const data = await this.graphql.request(ExecModUnpublishDocument, { appId, listingId });
745
+ return data.execModUnpublish;
746
+ }
747
+ /** Installs a listing onto a grid you own as your own mod, switched off. */
748
+ async modInstall(appId, gridId, name, listingId) {
749
+ const data = await this.graphql.request(ExecModInstallDocument, { appId, gridId, name, listingId });
750
+ return strip(data.execModInstall);
751
+ }
752
+ /** The app's mods by grid or owner, or all of them. Requires `view_compute_diagnostics`. */
753
+ async appMods(appId, options = {}) {
754
+ const data = await this.graphql.request(ExecAppModsDocument, { appId, ...options });
755
+ return data.execAppMods.map(strip);
756
+ }
757
+ /** The app's mod switches that are off. Requires `view_compute_diagnostics`. */
758
+ async modSwitches(appId) {
759
+ const data = await this.graphql.request(ExecModSwitchesDocument, { appId });
760
+ return data.execModSwitches.map(strip);
761
+ }
762
+ /**
763
+ * The mods kill ladder: switch off (or on) one mod (`target`: its mod id), a player's mods
764
+ * (their user id), a grid's (its id), a listing's installs (its id), or every mod in the
765
+ * app (no target). Returns the switches that are off. Requires `manage_compute`.
766
+ */
767
+ async modSetSwitch(appId, scope, off, options = {}) {
768
+ const data = await this.graphql.request(ExecModSetSwitchDocument, { appId, scope, off, ...options });
769
+ return data.execModSetSwitch.map(strip);
770
+ }
588
771
  /** A host for this player and its connect token (valid for about a minute). */
589
772
  async endpoint(appId, options = {}) {
590
773
  const data = await this.graphql.request(ExecConnectDocument, {
@@ -606,14 +789,22 @@ export class ExecAPI {
606
789
  }
607
790
  /**
608
791
  * Deploys a new version of the app's nodes and makes it active: the manifest, and
609
- * each distinct module once. Running instances pick it up when they next start.
610
- * Requires the org `manage_compute` permission.
792
+ * each distinct module once. A type gives its module as `wasm`, or names a `crate` of
793
+ * `buildId`, whose modules the platform already holds. Running instances pick it up
794
+ * when they next start. Requires the org `manage_compute` permission.
611
795
  */
612
796
  async deploy(options) {
613
797
  const types = {};
614
798
  const artifacts = new Map();
615
799
  for (const [name, t] of Object.entries(options.types)) {
616
800
  const { wasm, ...spec } = t;
801
+ if (!wasm) {
802
+ if (!spec.crate || !options.buildId) {
803
+ throw new CrowdyError({ message: `type '${name}' needs its wasm, or a crate of the deploy's buildId` });
804
+ }
805
+ types[name] = spec;
806
+ continue;
807
+ }
617
808
  const digest = await sha256Hex(wasm);
618
809
  if (!artifacts.has(digest))
619
810
  artifacts.set(digest, base64(wasm));
@@ -624,6 +815,7 @@ export class ExecAPI {
624
815
  appId: options.appId,
625
816
  manifestJson: JSON.stringify({ root: options.root, types }),
626
817
  artifacts: [...artifacts].map(([digest, wasmBase64]) => ({ digest, wasmBase64 })),
818
+ buildId: options.buildId,
627
819
  },
628
820
  });
629
821
  return data.execDeploy;