@arcadible/sdk 1.2.0 → 1.2.2

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.
package/README.md CHANGED
@@ -27,34 +27,31 @@ Each build of a game on [Arcadible](https://arcadible.com) has its own origin, s
27
27
  npm install @arcadible/sdk
28
28
  ```
29
29
 
30
- Use the package in a game with a build step, such as a Vite project. A game without one includes the hosted script instead, which Arcadible serves from every build's own origin, as `arc serve` does locally:
31
-
32
- ```html
33
- <script src="/arcadible/sdk/v1.js"></script>
34
- ```
30
+ The package is for a game with a build step, such as a Vite project. A game without one uses the [hosted script](https://developer.arcadible.com/references/arcadible-sdk#install) instead.
35
31
 
36
32
  ## Save and load
37
33
 
38
34
  ```js
39
35
  import { arcadible } from "@arcadible/sdk";
40
36
 
37
+ // at startup: once the saves have loaded, the player's progress, or a new game.
41
38
  await arcadible.ready;
42
-
43
- const saved = arcadible.saves.readSync("progress");
39
+ const saved = await arcadible.saves.read("progress");
44
40
  const progress = saved ? JSON.parse(saved) : { level: 1 };
45
41
 
42
+ // at a checkpoint:
46
43
  await arcadible.saves.write("progress", JSON.stringify(progress));
47
44
  ```
48
45
 
49
- A save is a name holding one string. `ready` settles once the game's saves have loaded, and never fails; from then on, `readSync`, `listSync`, and `infoSync` answer from memory. Every call that fails rejects (or, for the sync calls, throws) with an `ArcadibleError`, whose `code` says what to do. `arcadible.saves.where` says who can read the saves back: every version of the game, only this build, or only until the page closes.
46
+ A save is a name holding one string. `arcadible.ready` settles once the game's saves have loaded, and never fails; calls made before it wait for it, and from then on `readSync`, `listSync`, and `infoSync` answer at once from memory, for reads during play. Every call that fails rejects (or, for the sync calls, throws) with an `ArcadibleError`, whose `code` says what to do. `arcadible.saves.where` says who can read the saves back: every version of the game, only this build, or only until the page closes.
50
47
 
51
48
  Off Arcadible, such as on your dev server, the package works on its own, with the same rules and limits: saves are kept on the page's own origin, `arcadible.saves.where` is `"build"`, and `arcadible.build` is `null`.
52
49
 
53
50
  ## Learn more
54
51
 
55
- - [Saves](https://developer.arcadible.com/build/saves): adding the SDK, what to save, and other versions' saves.
52
+ - [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk): installing it, the hosted script, `ready` and `build`, and every code.
56
53
  - [`arcadible.saves`](https://developer.arcadible.com/references/arcadible-sdk/saves): every call, what it returns, and when it fails.
57
- - [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk): `ready` and `build`, and every code.
54
+ - [Saves](https://developer.arcadible.com/build/saves): using saves well, across versions, channels, and tabs, and where they're kept.
58
55
  - [Limits](https://developer.arcadible.com/references/limits#saves): how many saves, and how large.
59
56
 
60
57
  ## License
@@ -3,6 +3,7 @@ export type IndexedDBOptions = {
3
3
  factory?: IDBFactory | null;
4
4
  player?: string;
5
5
  persist?: boolean;
6
+ reopenWait?: number;
6
7
  };
7
8
  export declare function indexedDBBackend(options?: IndexedDBOptions): Backend & {
8
9
  close(): void;
@@ -13,6 +13,9 @@ const CHANNEL = "arcadible-saves";
13
13
  // every save is a guest's for One; a signed-in player's sit beside them
14
14
  // later, under their own id, with no migration.
15
15
  const GUEST = "guest";
16
+ // after a failed reopen, how long calls fail at once before it's tried again,
17
+ // so a game saving every frame doesn't try, and fail, every frame.
18
+ const REOPEN_WAIT = 1000;
16
19
  // [function]
17
20
  function settle(request) {
18
21
  return new Promise((resolve, reject) => {
@@ -66,7 +69,7 @@ function isClosed(err) {
66
69
  // refuses to open (a browser blocking site data, some `file://` pages),
67
70
  // they're kept in memory, and `status()` says so. Once it has opened, it
68
71
  // never switches: a lost connection fails calls with `unavailable` until it
69
- // opens again, rather than show an empty copy.
72
+ // opens again, trying at most once a second, rather than show an empty copy.
70
73
  export function indexedDBBackend(options = {}) {
71
74
  const factory = options.factory === undefined
72
75
  ? (globalThis.indexedDB ?? null)
@@ -79,6 +82,9 @@ export function indexedDBBackend(options = {}) {
79
82
  let opened = false;
80
83
  let current = null;
81
84
  let opening = null;
85
+ // whether reopening has failed since it last worked, and when to try again.
86
+ let down = false;
87
+ let retryAt = 0;
82
88
  let asked = false;
83
89
  const keyOf = (scope) => `${player}\u0000${scope.game}\u0000${scope.channel}`;
84
90
  const rangeOf = (scope) => IDBKeyRange.only([player, scope.game, scope.channel]);
@@ -86,6 +92,7 @@ export function indexedDBBackend(options = {}) {
86
92
  if (current === db)
87
93
  current = null;
88
94
  };
95
+ const unreachable = () => new ArcadibleError(Codes.Unavailable, "Saves can't be reached right now. Try again.");
89
96
  const connect = async function () {
90
97
  try {
91
98
  if (!factory)
@@ -103,13 +110,19 @@ export function indexedDBBackend(options = {}) {
103
110
  globalThis.navigator?.storage?.persist?.().catch(() => false);
104
111
  }
105
112
  opened = true;
113
+ down = false;
106
114
  current = db;
107
115
  return db;
108
116
  }
109
117
  catch (err) {
110
118
  if (opened) {
111
- log("warn", "the saves' storage couldn't be opened again.", err);
112
- throw new ArcadibleError(Codes.Unavailable, "Saves can't be reached right now. Try again.");
119
+ // once until it works again: the calls failing meanwhile say so.
120
+ if (!down) {
121
+ log("warn", "the saves' storage couldn't be opened again.", err);
122
+ }
123
+ down = true;
124
+ retryAt = Date.now() + (options.reopenWait ?? REOPEN_WAIT);
125
+ throw unreachable();
113
126
  }
114
127
  log("warn", "this browser won't keep saves here, so they're kept in memory until the page closes.", err);
115
128
  status = "memory";
@@ -125,6 +138,9 @@ export function indexedDBBackend(options = {}) {
125
138
  return Promise.resolve(null);
126
139
  if (current)
127
140
  return Promise.resolve(current);
141
+ if (down && !opening && Date.now() < retryAt) {
142
+ return Promise.reject(unreachable());
143
+ }
128
144
  opening ??= connect();
129
145
  return opening;
130
146
  };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.2.0";
1
+ export declare const VERSION = "1.2.2";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // The client's version, sent in its hello and shown as `arcadible.version`,
2
2
  // for bug reports. It matches package.json (test/unit/protocol.test.ts).
3
- export const VERSION = "1.2.0";
3
+ export const VERSION = "1.2.2";
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  ],
44
44
  "type": "module",
45
45
  "types": "./dist/index.d.ts",
46
- "version": "1.2.0"
46
+ "version": "1.2.2"
47
47
  }