@audiotool/nexus 0.0.10 → 0.0.12

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.
@@ -29,22 +29,10 @@ export type AudiotoolClient = {
29
29
  * @returns Promise resolving to a SyncedDocument instance
30
30
  */
31
31
  createSyncedDocument: (opts: {
32
- /** Run in online mode, which means the document state is synced in real time. */
33
- mode: "online";
34
32
  /** The project to sync to; this can be anything containing a project's UUID, e.g. the URL of the studio
35
33
  * when the project is open in the browser.
36
34
  */
37
35
  project: string;
38
- } | {
39
- /** Run in offline mode, i.e. not synced to any particular project.
40
- *
41
- * All changes are discarded on reload.
42
- */
43
- mode: "offline";
44
- /** Whether transaction validation is on. If off, fewer transaction errors will occur,
45
- * but the document might not get accepted by the backend if connecting to a project.
46
- */
47
- validated: boolean;
48
36
  }) => Promise<SyncedDocument>;
49
37
  /**
50
38
  * Collection of Audiotool API service clients.
@@ -41,8 +41,12 @@ export interface NexusGateway {
41
41
  * This function must indicate that the current document state was loaded by returning at least
42
42
  * one transaction. If the document state is empty, that transaction can be empty. The studio
43
43
  * will show the loading spinner until the first transaction is received.
44
+ *
45
+ * After the first transaction, the gateway can return "done" to indicate that no further transactions
46
+ * will be returned, which the caller can use as a signal to stop polling. This is useful in situations
47
+ * where it's known that no synchronization transactions will ever be returned, such as when running in offline mode.
44
48
  */
45
- synchronize(): ReadonlyTransaction[];
49
+ synchronize(): ReadonlyTransaction[] | "done";
46
50
  /** This becomes true when the gateway is blocked in some way from syncing with upstream state.
47
51
  * The document should be locked in this case to prevent data loss.
48
52
  */
@@ -28,4 +28,6 @@
28
28
  */
29
29
  export { createAudiotoolClient, type AudiotoolClient, } from '../audiotool-client';
30
30
  export type { SyncedDocument } from '../synced-document';
31
- export { getLoginStatus, type LoginStatus } from '../login-status';
31
+ export { getLoginStatus, type LoggedInStatus, type LoggedOutStatus, type LoginStatus, } from '../login-status';
32
+ export { createOfflineDocument } from '../synced-document';
33
+ export type { OfflineDocument } from '../synced-document';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var jt = (i) => {
5
5
  var Gt = (i, e, t) => e in i ? Wt(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
6
6
  var a = (i, e, t) => Gt(i, typeof e != "symbol" ? e + "" : e, t), At = (i, e, t) => e.has(i) || jt("Cannot " + t);
7
7
  var s = (i, e, t) => (At(i, e, "read from private field"), t ? t.call(i) : e.get(i)), h = (i, e, t) => e.has(i) ? jt("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(i) : e.set(i, t), g = (i, e, t, n) => (At(i, e, "write to private field"), n ? n.call(i, t) : e.set(i, t), t), y = (i, e, t) => (At(i, e, "access private method"), t);
8
- import { aS as entityMessageToTypeKey, aT as mustUnpackEntity, aU as packEntity, aV as getEntityTypeKeyFromProtoName, aW as unpackEntity, aX as Preset, aY as anyEntityToTypeKey, aZ as createRetryingPromiseClient, a_ as createAuthorizedKeepaliveTransport, aR as createAudiotoolAPI, a$ as extractUuid, n as neverThrowingFetch } from "./audiotool-api-D9u-oGp3.js";
8
+ import { aS as runningInBrowser, aT as entityMessageToTypeKey, aU as mustUnpackEntity, aV as packEntity, aW as getEntityTypeKeyFromProtoName, aX as unpackEntity, aY as Preset, aZ as anyEntityToTypeKey, a_ as createRetryingPromiseClient, a$ as createAuthorizedKeepaliveTransport, aR as createAudiotoolAPI, b0 as extractUuid, n as neverThrowingFetch } from "./audiotool-api-CjHbqgj8.js";
9
9
  import { Message, proto3, protoInt64, Any, ScalarType, MethodKind } from "@bufbuild/protobuf";
10
10
  import { P as Pointer, E as Empty, e as entityMessageTypes } from "./types-Cztu157p.js";
11
11
  import { t as throw_, a as assert, b as asyncInterval, s as sleep } from "./lang-K-8hAzE4.js";
@@ -1276,7 +1276,7 @@ a(dt, "runtime", proto3), a(dt, "typeName", "audiotool.document.v1.GetTimeRespon
1276
1276
  }
1277
1277
  ]));
1278
1278
  let GetTimeResponse = dt;
1279
- const wasmUrl = "/document_validator.wasm", wasmJsUrl = "/wasm_exec.js", runningInNode = typeof process < "u";
1279
+ const wasmUrl = "/document_validator.wasm", wasmJsUrl = "/wasm_exec.js";
1280
1280
  let wasmDocumentStateBuilderCache;
1281
1281
  const getWasmDocumentState = async () => {
1282
1282
  if (wasmDocumentStateBuilderCache !== void 0)
@@ -1305,14 +1305,14 @@ const getWasmDocumentState = async () => {
1305
1305
  const d = c();
1306
1306
  let u = !1;
1307
1307
  return {
1308
- applyTransaction(f) {
1308
+ applyTransaction(m) {
1309
1309
  assert(
1310
1310
  !u,
1311
1311
  "tried applying a transaction after document state was terminated"
1312
1312
  );
1313
- const m = d.applyTransaction(f.toBinary());
1314
- return m instanceof Object && "error" in m ? m.error : new Transaction({
1315
- modifications: m.rollbacks.map((p) => Modification.fromBinary(p))
1313
+ const f = d.applyTransaction(m.toBinary());
1314
+ return f instanceof Object && "error" in f ? f.error : new Transaction({
1315
+ modifications: f.rollbacks.map((p) => Modification.fromBinary(p))
1316
1316
  });
1317
1317
  },
1318
1318
  terminate() {
@@ -1321,7 +1321,7 @@ const getWasmDocumentState = async () => {
1321
1321
  };
1322
1322
  }), (await i)();
1323
1323
  }, executeWrapperJs = async () => {
1324
- if (!runningInNode)
1324
+ if (runningInBrowser)
1325
1325
  return import("https://cdn.audiotool.com/website-assets/document-service/5b85be5b151fe1c949845fe34b15f24ab059c736//wasm_exec.js");
1326
1326
  const path = await import("node:path"), url = await import("node:url"), fs = await import("node:fs");
1327
1327
  let filePath;
@@ -1339,7 +1339,7 @@ const getWasmDocumentState = async () => {
1339
1339
  )
1340
1340
  ), await promise;
1341
1341
  }, loadWasm = async () => {
1342
- if (!runningInNode)
1342
+ if (runningInBrowser)
1343
1343
  return await fetch(
1344
1344
  "https://cdn.audiotool.com/website-assets/document-service/5b85be5b151fe1c949845fe34b15f24ab059c736//document_validator.wasm.gz"
1345
1345
  ).then(
@@ -1392,22 +1392,22 @@ class NexusStateConsolidator {
1392
1392
  if (t.size === 0)
1393
1393
  return [];
1394
1394
  const l = s(this, b).findIndex(
1395
- ([m]) => t.has(m.id)
1395
+ ([f]) => t.has(f.id)
1396
1396
  );
1397
1397
  if (l === -1)
1398
1398
  throw new Error(
1399
1399
  "Invariant violation: expected to find rejected transaction in #pending, but didn't. This is a bug."
1400
1400
  );
1401
- const d = s(this, b).splice(l), u = d.map(([, m]) => {
1402
- if (!(s(this, I).applyTransaction(m) instanceof Transaction))
1401
+ const d = s(this, b).splice(l), u = d.map(([, f]) => {
1402
+ if (!(s(this, I).applyTransaction(f) instanceof Transaction))
1403
1403
  throw new Error("error applying reversal of transaction");
1404
- return m;
1405
- }).reverse(), f = d.filter(([m]) => !t.has(m.id)).map(([m]) => {
1406
- const p = s(this, I).applyTransaction(m);
1404
+ return f;
1405
+ }).reverse(), m = d.filter(([f]) => !t.has(f.id)).map(([f]) => {
1406
+ const p = s(this, I).applyTransaction(f);
1407
1407
  if (p instanceof Transaction)
1408
- return [m, p];
1409
- }).filter((m) => m !== void 0);
1410
- return s(this, b).push(...f), [...u, ...f.map(([m]) => m)];
1408
+ return [f, p];
1409
+ }).filter((f) => f !== void 0);
1410
+ return s(this, b).push(...m), [...u, ...m.map(([f]) => f)];
1411
1411
  }
1412
1412
  const c = s(this, b).map((l) => l[1].clone()).reverse();
1413
1413
  return c.forEach((l) => {
@@ -1420,7 +1420,7 @@ class NexusStateConsolidator {
1420
1420
  throw new Error(`Error applying incoming transaction: ${d}`);
1421
1421
  }), g(this, b, s(this, b).filter(([l]) => {
1422
1422
  const d = t.has(l.id), u = e.some(
1423
- (f) => f.id === l.id
1423
+ (m) => m.id === l.id
1424
1424
  );
1425
1425
  return !d && !u;
1426
1426
  }).map(([l]) => {
@@ -1488,7 +1488,7 @@ const combinedValueNotifiersWithAnd = (...i) => {
1488
1488
  }
1489
1489
  )[Symbol.asyncIterator]());
1490
1490
  let u = d();
1491
- const f = 1e3;
1491
+ const m = 1e3;
1492
1492
  return {
1493
1493
  nextTransactionIterator: async function* () {
1494
1494
  for (; ; )
@@ -1525,7 +1525,7 @@ const combinedValueNotifiersWithAnd = (...i) => {
1525
1525
  case Code.Unavailable:
1526
1526
  case Code.Unknown: {
1527
1527
  if (n.setValue(!1), await sleep(
1528
- f + Math.random() * f * 0.1,
1528
+ m + Math.random() * m * 0.1,
1529
1529
  // pass in master abort controller for early termination
1530
1530
  r.signal
1531
1531
  ), r.signal.aborted)
@@ -1571,8 +1571,8 @@ const combinedValueNotifiersWithAnd = (...i) => {
1571
1571
  );
1572
1572
  if (d instanceof Error)
1573
1573
  throw new Error("error sending transaction", { cause: d });
1574
- l.forEach(([u, f]) => {
1575
- f(d.errors[u.id] ?? void 0);
1574
+ l.forEach(([u, m]) => {
1575
+ m(d.errors[u.id] ?? void 0);
1576
1576
  });
1577
1577
  }
1578
1578
  })(), {
@@ -1629,41 +1629,43 @@ const combinedValueNotifiersWithAnd = (...i) => {
1629
1629
  }
1630
1630
  };
1631
1631
  }, createCollabGateway = (i, e, t, n) => {
1632
- const o = new ValueNotifier(!1), r = createDocumentServiceConnection(
1632
+ const o = createDocumentServiceConnection(
1633
1633
  i,
1634
1634
  t
1635
- ), c = new NexusStateConsolidator(e), l = [], d = [], u = /* @__PURE__ */ new Set();
1635
+ ), r = new NexusStateConsolidator(e), c = [], l = [], d = /* @__PURE__ */ new Set();
1636
1636
  (async () => {
1637
- for await (const p of r.receiveNextTransaction)
1638
- l.push(p);
1639
- })(), r.connectionOk.subscribe((p) => {
1640
- o.setValue(!p);
1641
- });
1642
- let m = !1;
1637
+ for await (const p of o.receiveNextTransaction)
1638
+ c.push(p);
1639
+ })();
1640
+ const m = new ValueNotifier(!0);
1641
+ o.connectionOk.subscribe((p) => {
1642
+ m.setValue(!p);
1643
+ }, !0);
1644
+ let f = !1;
1643
1645
  return {
1644
- blocked: o,
1646
+ blocked: m,
1645
1647
  send: (p) => {
1646
- if (m)
1648
+ if (f)
1647
1649
  throw new Error(
1648
1650
  "tried sending a transaction after gateway was terminated"
1649
1651
  );
1650
- p = p.clone(), p.id = crypto.randomUUID(), d.push(p), r.sendNextTransaction(p).then((w) => {
1651
- w !== void 0 && u.add(p.id);
1652
+ p = p.clone(), p.id = crypto.randomUUID(), l.push(p), o.sendNextTransaction(p).then((w) => {
1653
+ w !== void 0 && d.add(p.id);
1652
1654
  });
1653
1655
  },
1654
1656
  synchronize: () => {
1655
- if (m)
1657
+ if (f)
1656
1658
  return [];
1657
- l.length > 0;
1658
- const p = c.consolidate(
1659
- l,
1660
- u,
1661
- d
1659
+ c.length > 0;
1660
+ const p = r.consolidate(
1661
+ c,
1662
+ d,
1663
+ l
1662
1664
  );
1663
- return l.length = 0, u.clear(), d.length = 0, p;
1665
+ return c.length = 0, d.clear(), l.length = 0, p;
1664
1666
  },
1665
1667
  terminate: async () => {
1666
- m = !0, await r.terminate(), l.length = 0, u.clear(), d.length = 0, o.terminate(), e.terminate();
1668
+ f = !0, await o.terminate(), c.length = 0, d.clear(), l.length = 0, m.terminate(), e.terminate();
1667
1669
  }
1668
1670
  };
1669
1671
  }, createWasmNexusValidator = async () => {
@@ -1902,11 +1904,11 @@ const createNexusFields = (i, e, t, n) => {
1902
1904
  );
1903
1905
  else {
1904
1906
  const u = l.map(
1905
- (f, m) => createField(
1907
+ (m, f) => createField(
1906
1908
  i,
1907
1909
  e,
1908
- c.withAppendedFieldNumber(m),
1909
- f,
1910
+ c.withAppendedFieldNumber(f),
1911
+ m,
1910
1912
  r
1911
1913
  )
1912
1914
  );
@@ -2053,50 +2055,50 @@ const createNexusFields = (i, e, t, n) => {
2053
2055
  },
2054
2056
  ...(i == null ? void 0 : i.callbacks) ?? {}
2055
2057
  }, o = /* @__PURE__ */ new Map(), r = (u) => {
2056
- var f;
2057
- return ((f = e.get(u)) == null ? void 0 : f.entityType) ?? o.get(u);
2058
+ var m;
2059
+ return ((m = e.get(u)) == null ? void 0 : m.entityType) ?? o.get(u);
2058
2060
  }, c = (u) => {
2059
- const f = createEntity(
2061
+ const m = createEntity(
2060
2062
  r,
2061
2063
  mustUnpackEntity(
2062
2064
  u.entity ?? throw_("received empty create modification")
2063
2065
  )
2064
2066
  );
2065
- e.set(f.id, f), visitPointers$1(f, (m, p) => {
2066
- addSourceTarget(t, m, p), n.onStartPointingTo(m, p);
2067
- }), n.onCreate(f);
2067
+ e.set(m.id, m), visitPointers$1(m, (f, p) => {
2068
+ addSourceTarget(t, f, p), n.onStartPointingTo(f, p);
2069
+ }), n.onCreate(m);
2068
2070
  }, l = (u) => {
2069
- const f = u.entityId, m = e.get(f) ?? throw_("can't find deleted entity");
2070
- e.delete(f), visitPointers$1(m, (p, w) => {
2071
+ const m = u.entityId, f = e.get(m) ?? throw_("can't find deleted entity");
2072
+ e.delete(m), visitPointers$1(f, (p, w) => {
2071
2073
  removeSourceTarget(t, p, w), n.onStopPointingTo(p, w);
2072
- }), n.onDelete(m);
2074
+ }), n.onDelete(f);
2073
2075
  }, d = (u) => {
2074
- const f = u.field ?? throw_("received update without pointer"), m = NexusLocation.fromPointerMessage(r, f), p = e.get(m.entityId) ?? throw_("can't find updated entity"), w = extractPbUpdateValue(u.value, r);
2075
- applyUpdate(p, m, w, {
2076
+ const m = u.field ?? throw_("received update without pointer"), f = NexusLocation.fromPointerMessage(r, m), p = e.get(f.entityId) ?? throw_("can't find updated entity"), w = extractPbUpdateValue(u.value, r);
2077
+ applyUpdate(p, f, w, {
2076
2078
  onStopPointingTo: (x, E) => {
2077
2079
  removeSourceTarget(t, x, E) || throw_(), n.onStopPointingTo(x, E);
2078
2080
  },
2079
2081
  onStartPointingTo: (x, E) => {
2080
2082
  addSourceTarget(t, x, E), n.onStartPointingTo(x, E);
2081
2083
  }
2082
- }), n.onUpdate(m, w);
2084
+ }), n.onUpdate(f, w);
2083
2085
  };
2084
2086
  return {
2085
2087
  entities: e,
2086
2088
  references: t,
2087
2089
  applyModification(u) {
2088
- const f = u.modification;
2089
- switch (f.case) {
2090
+ const m = u.modification;
2091
+ switch (m.case) {
2090
2092
  case "create": {
2091
- c(f.value);
2093
+ c(m.value);
2092
2094
  break;
2093
2095
  }
2094
2096
  case "delete": {
2095
- l(f.value);
2097
+ l(m.value);
2096
2098
  break;
2097
2099
  }
2098
2100
  case "update": {
2099
- d(f.value);
2101
+ d(m.value);
2100
2102
  break;
2101
2103
  }
2102
2104
  }
@@ -2104,11 +2106,11 @@ const createNexusFields = (i, e, t, n) => {
2104
2106
  getStats() {
2105
2107
  return {
2106
2108
  entities: e.size,
2107
- references: t.values().reduce((u, f) => u + f.length, 0)
2109
+ references: t.values().reduce((u, m) => u + m.length, 0)
2108
2110
  };
2109
2111
  },
2110
- _addEntityTypeForId(u, f) {
2111
- o.set(u, f);
2112
+ _addEntityTypeForId(u, m) {
2113
+ o.set(u, m);
2112
2114
  }
2113
2115
  };
2114
2116
  }, TerminableBuilder = {
@@ -2297,7 +2299,7 @@ let NexusEventManager = bt;
2297
2299
  const sizeOf = (i) => i.values().reduce((e, t) => e + t.size, 0), getOrDefault = (i, e) => i.get(e) ?? i.set(e, /* @__PURE__ */ new Set()).get(e) ?? throw_(), mockNexusGateway = () => {
2298
2300
  let i = !1, e = !1;
2299
2301
  return {
2300
- synchronize: () => e ? [] : (e = !0, [new Transaction()]),
2302
+ synchronize: () => e ? "done" : (e = !0, [new Transaction()]),
2301
2303
  send: () => {
2302
2304
  if (i)
2303
2305
  throw new Error("Gateway terminated");
@@ -2767,15 +2769,15 @@ const filterByType = (i, e) => {
2767
2769
  e.forEach((d) => n.set(d.entity.id, d));
2768
2770
  const o = /* @__PURE__ */ new Map(), r = [];
2769
2771
  [...n.values()].forEach(({ entity: d, overwrites: u }) => {
2770
- const f = entityToConstructorType(d), m = createDefaultEntityMessage(d.entityType);
2771
- m.id = t.get(d.id) ?? throw_();
2772
- const p = mapConstructorLocations(f, (w) => {
2772
+ const m = entityToConstructorType(d), f = createDefaultEntityMessage(d.entityType);
2773
+ f.id = t.get(d.id) ?? throw_();
2774
+ const p = mapConstructorLocations(m, (w) => {
2773
2775
  const x = t.get(w.entityId);
2774
- return x !== void 0 ? (r.push([m.id, x]), new NexusLocation(x, w.entityType, [
2776
+ return x !== void 0 ? (r.push([f.id, x]), new NexusLocation(x, w.entityType, [
2775
2777
  ...w.fieldIndex
2776
2778
  ])) : w;
2777
2779
  });
2778
- updateEntityMessageWithConstructor(m, p), u !== void 0 && updateEntityMessageWithConstructor(m, u), o.set(m.id, [d.entityType, m]);
2780
+ updateEntityMessageWithConstructor(f, p), u !== void 0 && updateEntityMessageWithConstructor(f, u), o.set(f.id, [d.entityType, f]);
2779
2781
  });
2780
2782
  const c = toposort(r).reverse(), l = [...o.keys()].filter(
2781
2783
  (d) => !c.includes(d)
@@ -3306,12 +3308,16 @@ class NexusDocument {
3306
3308
  resolve: r
3307
3309
  } = Promise.withResolvers(), c = asyncInterval(async () => {
3308
3310
  const u = (s(this, M) ?? throw_()).synchronize();
3311
+ if (u === "done")
3312
+ throw new Error(
3313
+ "Gateway returned 'done' before returning the initial transaction."
3314
+ );
3309
3315
  if (u.length === 0)
3310
3316
  return;
3311
- const f = u.flatMap((m) => m.modifications).length;
3317
+ const m = u.flatMap((f) => f.modifications).length;
3312
3318
  await this._applyIncomingTransactions(u, {
3313
3319
  _takeTransactionLock: !1
3314
- }), c.terminate(), r(f > 0);
3320
+ }), c.terminate(), r(m > 0);
3315
3321
  }, s(this, Nt)), l = await o;
3316
3322
  if ((e == null ? void 0 : e.templateTransaction) !== void 0)
3317
3323
  if (l)
@@ -3319,15 +3325,19 @@ class NexusDocument {
3319
3325
  "Could not apply template transaction to document: Project state from backend is not empty."
3320
3326
  );
3321
3327
  else {
3322
- const u = await e.templateTransaction, f = await this.createTransaction(void 0, !1);
3323
- u.modifications.forEach((m) => f._addModification(m)), f.send();
3328
+ const u = await e.templateTransaction, m = await this.createTransaction(void 0, !1);
3329
+ u.modifications.forEach((f) => m._addModification(f)), m.send();
3324
3330
  }
3325
3331
  n.release();
3326
3332
  const d = asyncInterval(async (u) => {
3327
3333
  if (u.aborted)
3328
3334
  return;
3329
- const f = await s(this, S).acquire(), m = (s(this, M) ?? throw_()).synchronize();
3330
- await this._applyIncomingTransactions(m, { _takeTransactionLock: !1 }), f.release();
3335
+ const m = await s(this, S).acquire(), f = (s(this, M) ?? throw_()).synchronize();
3336
+ if (f === "done") {
3337
+ d.terminate(), m.release();
3338
+ return;
3339
+ }
3340
+ await this._applyIncomingTransactions(f, { _takeTransactionLock: !1 }), m.release();
3331
3341
  }, s(this, Nt));
3332
3342
  g(this, Bt, () => d.terminate());
3333
3343
  }
@@ -3558,8 +3568,19 @@ const DocumentService = {
3558
3568
  kind: MethodKind.Unary
3559
3569
  }
3560
3570
  }
3571
+ }, createSyncedDocument = (i, e, t, n) => ({
3572
+ connected: e,
3573
+ createTransaction: () => i.createTransaction(),
3574
+ modify: (o) => i.modify(o),
3575
+ queryEntities: i.queryEntitiesWithoutLock,
3576
+ events: i.events,
3577
+ start: async () => i.takeTransactions({ validator: t, gateway: n }),
3578
+ stop: async () => i.terminate()
3579
+ }), createOfflineDocument = async (i) => {
3580
+ const e = (i == null ? void 0 : i.validated) ?? !0 ? await createWasmNexusValidator() : mockNexusValidator(), t = mockNexusGateway(), n = new NexusDocument(), o = new ValueNotifier(!0), r = createSyncedDocument(n, o, e, t);
3581
+ return await r.start(), r;
3561
3582
  }, createOnlineDocument = async (i, e, t) => {
3562
- var u;
3583
+ var m;
3563
3584
  let n = createWasmNexusValidator();
3564
3585
  const o = await i.projectService.openSession({
3565
3586
  projectName: e
@@ -3568,31 +3589,18 @@ const DocumentService = {
3568
3589
  throw new Error("Couldn't open session", { cause: o });
3569
3590
  const r = createRetryingPromiseClient(
3570
3591
  DocumentService,
3571
- createAuthorizedKeepaliveTransport({
3572
- baseUrl: ((u = o.session) == null ? void 0 : u.documentServiceUrl) ?? throw_("backend returned no document service url"),
3592
+ await createAuthorizedKeepaliveTransport({
3593
+ baseUrl: ((m = o.session) == null ? void 0 : m.documentServiceUrl) ?? throw_("backend returned no document service url"),
3573
3594
  useBinaryFormat: !0,
3574
3595
  getToken: t
3575
3596
  })
3576
- );
3577
- await n;
3578
- const c = createCollabGateway(
3597
+ ), c = await n, l = createCollabGateway(
3579
3598
  r,
3580
3599
  await getWasmDocumentState(),
3581
3600
  e
3582
- ), l = new NexusDocument(), d = new ValueNotifier(!1);
3583
- return c.blocked.subscribe((f) => d.setValue(!f)), createSyncedDocument(l, d, await n, c);
3584
- }, createOfflineDocument = async (i) => {
3585
- const e = i ? await createWasmNexusValidator() : mockNexusValidator(), t = mockNexusGateway(), n = new NexusDocument(), o = new ValueNotifier(!0);
3586
- return createSyncedDocument(n, o, e, t);
3587
- }, createSyncedDocument = (i, e, t, n) => ({
3588
- connected: e,
3589
- createTransaction: () => i.createTransaction(),
3590
- modify: (o) => i.modify(o),
3591
- queryEntities: i.queryEntitiesWithoutLock,
3592
- events: i.events,
3593
- start: async () => i.takeTransactions({ validator: t, gateway: n }),
3594
- stop: async () => i.terminate()
3595
- }), createAudiotoolClient = async ({
3601
+ ), d = new NexusDocument(), u = new ValueNotifier(!1);
3602
+ return l.blocked.subscribe((f) => u.setValue(!f), !0), createSyncedDocument(d, u, c, l);
3603
+ }, createAudiotoolClient = async ({
3596
3604
  authorization: i
3597
3605
  }) => {
3598
3606
  const e = async () => {
@@ -3607,12 +3615,9 @@ const DocumentService = {
3607
3615
  }, t = await createAudiotoolAPI(e);
3608
3616
  return {
3609
3617
  api: t,
3610
- createSyncedDocument: async (n) => {
3611
- if (n.mode === "online") {
3612
- const o = extractProjectName(n.project);
3613
- return await createOnlineDocument(t, o, e);
3614
- } else
3615
- return await createOfflineDocument(n.validated);
3618
+ createSyncedDocument: async ({ project: n }) => {
3619
+ const o = extractProjectName(n);
3620
+ return await createOnlineDocument(t, o, e);
3616
3621
  }
3617
3622
  };
3618
3623
  }, extractProjectName = (i) => {
@@ -3643,8 +3648,8 @@ const DocumentService = {
3643
3648
  };
3644
3649
  const d = r.get("code");
3645
3650
  if (d != null) {
3646
- const m = r.get("state"), p = localStorage.getItem(n.state);
3647
- if (m == null || p == null || m !== p)
3651
+ const f = r.get("state"), p = localStorage.getItem(n.state);
3652
+ if (f == null || p == null || f !== p)
3648
3653
  return {
3649
3654
  loggedIn: !1,
3650
3655
  error: toError(
@@ -3693,26 +3698,26 @@ const DocumentService = {
3693
3698
  }
3694
3699
  localStorage.removeItem(n.state);
3695
3700
  const u = await getOrFetchValidToken(n, i);
3696
- let f;
3701
+ let m;
3697
3702
  if (typeof u == "string") {
3698
- const m = async () => {
3699
- if (f == null) {
3700
- f = (async () => await getOrFetchValidToken(n, i) ?? new Error("User not logged in."))();
3701
- const w = await f;
3702
- return f = void 0, w;
3703
+ const f = async () => {
3704
+ if (m == null) {
3705
+ m = (async () => await getOrFetchValidToken(n, i) ?? new Error("User not logged in."))();
3706
+ const w = await m;
3707
+ return m = void 0, w;
3703
3708
  }
3704
- return await f;
3709
+ return await m;
3705
3710
  };
3706
3711
  let p;
3707
3712
  return {
3708
3713
  loggedIn: !0,
3709
- getToken: m,
3714
+ getToken: f,
3710
3715
  getUserName: async () => {
3711
3716
  if (p !== void 0)
3712
3717
  return await p;
3713
3718
  const { promise: w, resolve: x } = Promise.withResolvers();
3714
3719
  p = w;
3715
- const E = await m();
3720
+ const E = await f();
3716
3721
  if (E instanceof Error)
3717
3722
  return x(E), await p;
3718
3723
  const gt = await neverThrowingFetch(
@@ -3782,8 +3787,8 @@ const DocumentService = {
3782
3787
  return;
3783
3788
  let n = !0;
3784
3789
  {
3785
- const m = localStorage.getItem(i.expiresAt);
3786
- n = m == null ? !0 : Date.now() >= parseInt(m) - 6e4;
3790
+ const f = localStorage.getItem(i.expiresAt);
3791
+ n = f == null ? !0 : Date.now() >= parseInt(f) - 6e4;
3787
3792
  }
3788
3793
  if (!n)
3789
3794
  return t;
@@ -3807,13 +3812,14 @@ const DocumentService = {
3807
3812
  return new Error(`Error during refresh token request: ${r.name}`, {
3808
3813
  cause: r.message
3809
3814
  });
3810
- const { error: c, error_description: l, access_token: d, refresh_token: u, expires_in: f } = await r.json();
3815
+ const { error: c, error_description: l, access_token: d, refresh_token: u, expires_in: m } = await r.json();
3811
3816
  return c ? toError(c, l) : (localStorage.setItem(i.accessToken, d), localStorage.setItem(i.refreshToken, u), localStorage.setItem(
3812
3817
  i.expiresAt,
3813
- (Date.now() + f * 1e3).toString()
3818
+ (Date.now() + m * 1e3).toString()
3814
3819
  ), d);
3815
3820
  };
3816
3821
  export {
3817
3822
  createAudiotoolClient,
3823
+ createOfflineDocument,
3818
3824
  getLoginStatus
3819
3825
  };
@@ -1,23 +1,4 @@
1
1
  export type LoggedInStatus = {
2
- /** The app is authorized to make actions on a user's behalf. */
3
- loggedIn: true;
4
- /** Simple utility to get the current user display name / user name. The result of this function is cached. */
5
- getUserInfo(): Promise<{
6
- name: string;
7
- displayName: string;
8
- } | Error>;
9
- /** Log the current user out and reload the page. */
10
- logout: () => void;
11
- /** Get the current authentication token. Might refresh the token if need be, but most often
12
- * just returns the token.
13
- */
14
- getToken: () => Promise<string | Error>;
15
- };
16
- /**
17
- * The current authentication status of the user in this tab. Either logged in or logged out.
18
- * To change the authentication status, call login or logout on this object.
19
- */
20
- export type LoginStatus = {
21
2
  /** The app is authorized to make actions on a user's behalf. */
22
3
  loggedIn: true;
23
4
  /** Simple utility to get the current user name. The result of this function is cached. */
@@ -28,7 +9,8 @@ export type LoginStatus = {
28
9
  * just returns the token.
29
10
  */
30
11
  getToken: () => Promise<string | Error>;
31
- } | {
12
+ };
13
+ export type LoggedOutStatus = {
32
14
  /** The app is not authorized to make actions on a user's behalf. Call login to authorize.*/
33
15
  loggedIn: false;
34
16
  /** If an error is the reason the user is logged out, this is set. Otherwise, the user just hasn't logged in yet. */
@@ -36,6 +18,11 @@ export type LoginStatus = {
36
18
  /** start the authorization flow by redirecting the user to the login page of audiotool. */
37
19
  login: () => Promise<void>;
38
20
  };
21
+ /**
22
+ * The current authentication status of the user in this tab. Either logged in or logged out.
23
+ * To change the authentication status, call login or logout on this object.
24
+ */
25
+ export type LoginStatus = LoggedInStatus | LoggedOutStatus;
39
26
  /**
40
27
  * This function allows to let arbitrary users use your app by letting them login/logout using the audiotool accounts system.
41
28
  *
@@ -1,8 +1,11 @@
1
+ import { AudiotoolAPI } from './api/audiotool-api';
2
+ import { NexusGateway } from './document/backend/gateway';
3
+ import { NexusValidator } from './document/backend/validator';
4
+ import { NexusDocument } from './document/document';
1
5
  import { NexusEventManager } from './document/event-manager';
2
6
  import { EntityQuery } from './document/query/entity';
3
7
  import { SafeTransactionBuilder, TransactionBuilder } from './document/transaction-builder';
4
8
  import { ValueNotifier } from './utils/observable-notifier-value';
5
- import { AudiotoolAPI } from './api/audiotool-api';
6
9
 
7
10
  /**
8
11
  * An Audiotool project document that synchronizes in real-time with the backend.
@@ -150,6 +153,26 @@ export type SyncedDocument = {
150
153
  **/
151
154
  stop: () => Promise<void>;
152
155
  };
153
- /** Create an online document. */
156
+ /** Create a SyncedDocument wrapper from a NexusDocument. */
157
+ export declare const createSyncedDocument: (document: NexusDocument, connected: ValueNotifier<boolean>, validator: NexusValidator, gateway: NexusGateway) => SyncedDocument;
158
+ /** An offline version of a {@link SyncedDocument} - starts out completely empty,
159
+ * and all changes are discarded on reload/shutdown.
160
+ *
161
+ * Doesn't have start/stop methods since no syncing is happening.
162
+ *
163
+ * Can be safely thrown away after usage.
164
+ */
165
+ export type OfflineDocument = Omit<SyncedDocument, "start" | "stop">;
166
+ /**
167
+ * Create a nexus document that is not synced to the backend; all changes are discarded on reload/shutdown.
168
+ *
169
+ * The returned document is ready to be modified immediately and can be thrown away without calling start/stop.
170
+ *
171
+ * To create a document that is synced with a state from the backend/DAW, use {@link AudiotoolClient.createSyncedDocument}.
172
+ * */
173
+ export declare const createOfflineDocument: (opts?: {
174
+ /** Whether validation is enabled. Turning that off results in fewer transaction errors, but can lead to invalid states. */
175
+ validated?: boolean;
176
+ }) => Promise<OfflineDocument>;
177
+ /** Create a SyncedDocument wrapper that's connected to the backend. */
154
178
  export declare const createOnlineDocument: (api: AudiotoolAPI, projectName: string, getToken: () => Promise<string>) => Promise<SyncedDocument>;
155
- export declare const createOfflineDocument: (validated: boolean) => Promise<SyncedDocument>;
@@ -22,7 +22,7 @@ export declare const createAuthorizedKeepaliveTransport: ({ baseUrl, useBinaryFo
22
22
  useBinaryFormat: boolean;
23
23
  typeRegistry?: IMessageTypeRegistry;
24
24
  getToken: () => Promise<string>;
25
- }) => KeepaliveTransport;
25
+ }) => Promise<KeepaliveTransport>;
26
26
  /** Takes the `keepalive` parameter out of `options`, packs it into the
27
27
  * keepalive header.
28
28
  */
@@ -0,0 +1,4 @@
1
+ /** True if we're not in bun, deno, or node.js. */
2
+ export declare const runningInBrowser: boolean;
3
+ /** True if we're in node.js */
4
+ export declare const runningInNodeJs: boolean;