@auxilium/datalynk-client 1.0.19 → 1.1.0-rc1

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/dist/index.mjs CHANGED
@@ -15,6 +15,31 @@ function clean(obj, undefinedOnly = false) {
15
15
  }
16
16
  return obj;
17
17
  }
18
+ function deepCopy(value) {
19
+ try {
20
+ return structuredClone(value);
21
+ } catch {
22
+ return JSON.parse(JSONSanitize(value));
23
+ }
24
+ }
25
+ function dotNotation(obj, prop, set) {
26
+ if (obj == null || !prop) return void 0;
27
+ return prop.split(/[.[\]]/g).filter((prop2) => prop2.length).reduce((obj2, prop2, i, arr) => {
28
+ if (prop2[0] == '"' || prop2[0] == "'") prop2 = prop2.slice(1, -1);
29
+ if (!(obj2 == null ? void 0 : obj2.hasOwnProperty(prop2))) {
30
+ return void 0;
31
+ }
32
+ return obj2[prop2];
33
+ }, obj);
34
+ }
35
+ function isEqual(a, b) {
36
+ const ta = typeof a, tb = typeof b;
37
+ if (ta != "object" || a == null || (tb != "object" || b == null))
38
+ return ta == "function" && tb == "function" ? a.toString() == b.toString() : a === b;
39
+ const keys = Object.keys(a);
40
+ if (keys.length != Object.keys(b).length) return false;
41
+ return Object.keys(a).every((key) => isEqual(a[key], b[key]));
42
+ }
18
43
  function JSONAttemptParse(json) {
19
44
  try {
20
45
  return JSON.parse(json);
@@ -32,6 +57,214 @@ function JSONSanitize(obj, space) {
32
57
  return value;
33
58
  }, space);
34
59
  }
60
+ class ASet extends Array {
61
+ /** Number of elements in set */
62
+ get size() {
63
+ return this.length;
64
+ }
65
+ /**
66
+ * Array to create set from, duplicate values will be removed
67
+ * @param {T[]} elements Elements which will be added to set
68
+ */
69
+ constructor(elements = []) {
70
+ super();
71
+ if (!!(elements == null ? void 0 : elements["forEach"]))
72
+ elements.forEach((el) => this.add(el));
73
+ }
74
+ /**
75
+ * Add elements to set if unique
76
+ * @param items
77
+ */
78
+ add(...items) {
79
+ items.filter((el) => !this.has(el)).forEach((el) => this.push(el));
80
+ return this;
81
+ }
82
+ /**
83
+ * Remove all elements
84
+ */
85
+ clear() {
86
+ this.splice(0, this.length);
87
+ return this;
88
+ }
89
+ /**
90
+ * Delete elements from set
91
+ * @param items Elements that will be deleted
92
+ */
93
+ delete(...items) {
94
+ items.forEach((el) => {
95
+ const index = this.indexOf(el);
96
+ if (index != -1) this.splice(index, 1);
97
+ });
98
+ return this;
99
+ }
100
+ /**
101
+ * Create list of elements this set has which the comparison set does not
102
+ * @param {ASet<T>} set Set to compare against
103
+ * @return {ASet<T>} Different elements
104
+ */
105
+ difference(set) {
106
+ return new ASet(this.filter((el) => !set.has(el)));
107
+ }
108
+ /**
109
+ * Check if set includes element
110
+ * @param {T} el Element to look for
111
+ * @return {boolean} True if element was found, false otherwise
112
+ */
113
+ has(el) {
114
+ return this.indexOf(el) != -1;
115
+ }
116
+ /**
117
+ * Find index number of element, or -1 if it doesn't exist. Matches by equality not reference
118
+ *
119
+ * @param {T} search Element to find
120
+ * @param {number} fromIndex Starting index position
121
+ * @return {number} Element index number or -1 if missing
122
+ */
123
+ indexOf(search2, fromIndex) {
124
+ return super.findIndex((el) => isEqual(el, search2), fromIndex);
125
+ }
126
+ /**
127
+ * Create list of elements this set has in common with the comparison set
128
+ * @param {ASet<T>} set Set to compare against
129
+ * @return {boolean} Set of common elements
130
+ */
131
+ intersection(set) {
132
+ return new ASet(this.filter((el) => set.has(el)));
133
+ }
134
+ /**
135
+ * Check if this set has no elements in common with the comparison set
136
+ * @param {ASet<T>} set Set to compare against
137
+ * @return {boolean} True if nothing in common, false otherwise
138
+ */
139
+ isDisjointFrom(set) {
140
+ return this.intersection(set).size == 0;
141
+ }
142
+ /**
143
+ * Check if all elements in this set are included in the comparison set
144
+ * @param {ASet<T>} set Set to compare against
145
+ * @return {boolean} True if all elements are included, false otherwise
146
+ */
147
+ isSubsetOf(set) {
148
+ return this.findIndex((el) => !set.has(el)) == -1;
149
+ }
150
+ /**
151
+ * Check if all elements from comparison set are included in this set
152
+ * @param {ASet<T>} set Set to compare against
153
+ * @return {boolean} True if all elements are included, false otherwise
154
+ */
155
+ isSuperset(set) {
156
+ return set.findIndex((el) => !this.has(el)) == -1;
157
+ }
158
+ /**
159
+ * Create list of elements that are only in one set but not both (XOR)
160
+ * @param {ASet<T>} set Set to compare against
161
+ * @return {ASet<T>} New set of unique elements
162
+ */
163
+ symmetricDifference(set) {
164
+ return new ASet([...this.difference(set), ...set.difference(this)]);
165
+ }
166
+ /**
167
+ * Create joined list of elements included in this & the comparison set
168
+ * @param {ASet<T>} set Set join
169
+ * @return {ASet<T>} New set of both previous sets combined
170
+ */
171
+ union(set) {
172
+ return new ASet([...this, ...set]);
173
+ }
174
+ }
175
+ function sortByProp(prop, reverse = false) {
176
+ return function(a, b) {
177
+ const aVal = dotNotation(a, prop);
178
+ const bVal = dotNotation(b, prop);
179
+ if (typeof aVal == "number" && typeof bVal == "number")
180
+ return (reverse ? -1 : 1) * (aVal - bVal);
181
+ if (aVal > bVal) return reverse ? -1 : 1;
182
+ if (aVal < bVal) return reverse ? 1 : -1;
183
+ return 0;
184
+ };
185
+ }
186
+ function makeArray(value) {
187
+ return Array.isArray(value) ? value : [value];
188
+ }
189
+ class Database {
190
+ constructor(database, tables, version2) {
191
+ __publicField2(this, "connection");
192
+ __publicField2(this, "tables");
193
+ this.database = database;
194
+ this.version = version2;
195
+ this.connection = new Promise((resolve, reject) => {
196
+ const req = indexedDB.open(this.database, this.version);
197
+ this.tables = tables.map((t) => {
198
+ t = typeof t == "object" ? t : { name: t };
199
+ return { ...t, name: t.name.toString() };
200
+ });
201
+ const tableNames = new ASet(this.tables.map((t) => t.name));
202
+ req.onerror = () => reject(req.error);
203
+ req.onsuccess = () => {
204
+ const db = req.result;
205
+ if (tableNames.symmetricDifference(new ASet(Array.from(db.objectStoreNames))).length) {
206
+ db.close();
207
+ Object.assign(this, new Database(this.database, this.tables, db.version + 1));
208
+ } else {
209
+ this.version = db.version;
210
+ resolve(db);
211
+ }
212
+ };
213
+ req.onupgradeneeded = () => {
214
+ const db = req.result;
215
+ const existingTables = new ASet(Array.from(db.objectStoreNames));
216
+ existingTables.difference(tableNames).forEach((name) => db.deleteObjectStore(name));
217
+ tableNames.difference(existingTables).forEach((name) => db.createObjectStore(name));
218
+ };
219
+ });
220
+ }
221
+ includes(name) {
222
+ return !!this.tables.find((t) => t.name == name.toString());
223
+ }
224
+ table(name) {
225
+ return new Table(this, name.toString());
226
+ }
227
+ }
228
+ class Table {
229
+ constructor(database, name) {
230
+ this.database = database;
231
+ this.name = name;
232
+ }
233
+ async tx(table, fn2, readonly = false) {
234
+ const db = await this.database.connection;
235
+ const tx = db.transaction(table, readonly ? "readonly" : "readwrite");
236
+ const store = tx.objectStore(table);
237
+ return new Promise((resolve, reject) => {
238
+ const request = fn2(store);
239
+ request.onsuccess = () => resolve(request.result);
240
+ request.onerror = () => reject(request.error);
241
+ });
242
+ }
243
+ add(value, key) {
244
+ return this.tx(this.name, (store) => store.add(value, key));
245
+ }
246
+ count() {
247
+ return this.tx(this.name, (store) => store.count(), true);
248
+ }
249
+ put(key, value) {
250
+ return this.tx(this.name, (store) => store.put(value, key));
251
+ }
252
+ getAll() {
253
+ return this.tx(this.name, (store) => store.getAll(), true);
254
+ }
255
+ getAllKeys() {
256
+ return this.tx(this.name, (store) => store.getAllKeys(), true);
257
+ }
258
+ get(key) {
259
+ return this.tx(this.name, (store) => store.get(key), true);
260
+ }
261
+ delete(key) {
262
+ return this.tx(this.name, (store) => store.delete(key));
263
+ }
264
+ clear() {
265
+ return this.tx(this.name, (store) => store.clear());
266
+ }
267
+ }
35
268
  function contrast(background) {
36
269
  const exploded = background == null ? void 0 : background.match(background.length >= 6 ? /[0-9a-fA-F]{2}/g : /[0-9a-fA-F]/g);
37
270
  if (!exploded || (exploded == null ? void 0 : exploded.length) < 3) return "black";
@@ -1483,6 +1716,33 @@ var BehaviorSubject = function(_super) {
1483
1716
  };
1484
1717
  return BehaviorSubject2;
1485
1718
  }(Subject);
1719
+ var EmptyError = createErrorClass(function(_super) {
1720
+ return function EmptyErrorImpl() {
1721
+ _super(this);
1722
+ this.name = "EmptyError";
1723
+ this.message = "no elements in sequence";
1724
+ };
1725
+ });
1726
+ function lastValueFrom(source, config2) {
1727
+ return new Promise(function(resolve, reject) {
1728
+ var _hasValue = false;
1729
+ var _value;
1730
+ source.subscribe({
1731
+ next: function(value) {
1732
+ _value = value;
1733
+ _hasValue = true;
1734
+ },
1735
+ error: reject,
1736
+ complete: function() {
1737
+ if (_hasValue) {
1738
+ resolve(_value);
1739
+ } else {
1740
+ reject(new EmptyError());
1741
+ }
1742
+ }
1743
+ });
1744
+ });
1745
+ }
1486
1746
  function filter(predicate, thisArg) {
1487
1747
  return operate(function(source, subscriber) {
1488
1748
  var index = 0;
@@ -1512,6 +1772,34 @@ function distinctUntilChanged(comparator, keySelector) {
1512
1772
  function defaultCompare(a, b) {
1513
1773
  return a === b;
1514
1774
  }
1775
+ function skip(count) {
1776
+ return filter(function(_, index) {
1777
+ return count <= index;
1778
+ });
1779
+ }
1780
+ function takeWhile(predicate, inclusive) {
1781
+ return operate(function(source, subscriber) {
1782
+ var index = 0;
1783
+ source.subscribe(createOperatorSubscriber(subscriber, function(value) {
1784
+ var result = predicate(value, index++);
1785
+ (result || inclusive) && subscriber.next(value);
1786
+ !result && subscriber.complete();
1787
+ }));
1788
+ });
1789
+ }
1790
+ async function getTheme(spoke, scope) {
1791
+ let theme = await fetch(`https://${spoke}.auxiliumgroup.com/api/js/auxilium/dijits/templates/login/${spoke}/theme.json`).then((res) => res.json()).catch(() => null);
1792
+ if (scope && theme != null && theme[scope]) theme = { ...theme, ...theme[scope] };
1793
+ return {
1794
+ found: !!theme,
1795
+ background: `https://${spoke}.auxiliumgroup.com/static/js/auxilium/dijits/templates/login/${spoke}/background.jpg`,
1796
+ color: "#c83232",
1797
+ logo: `https://${spoke}.auxiliumgroup.com/static/js/auxilium/dijits/templates/login/${spoke}/logo.png`,
1798
+ title: spoke.toUpperCase(),
1799
+ textColor: "white",
1800
+ ...theme || {}
1801
+ };
1802
+ }
1515
1803
  const _LoginPrompt = class _LoginPrompt {
1516
1804
  constructor(api, spoke, options = {}) {
1517
1805
  __publicField(this, "alert");
@@ -1520,7 +1808,6 @@ const _LoginPrompt = class _LoginPrompt {
1520
1808
  __publicField(this, "password");
1521
1809
  __publicField(this, "persist");
1522
1810
  __publicField(this, "username");
1523
- __publicField(this, "options");
1524
1811
  __publicField(this, "_done");
1525
1812
  /** Promise which resolves once login is complete */
1526
1813
  __publicField(this, "done", new Promise((res) => {
@@ -1528,13 +1815,10 @@ const _LoginPrompt = class _LoginPrompt {
1528
1815
  }));
1529
1816
  this.api = api;
1530
1817
  this.spoke = spoke;
1531
- this.options = {
1532
- title: this.spoke,
1533
- background: "#ffffff",
1534
- color: "#c83232",
1535
- textColor: "#000000",
1536
- ...clean(options, true)
1537
- };
1818
+ this.options = options;
1819
+ this.themeDefaults().then(() => this.render());
1820
+ }
1821
+ async render() {
1538
1822
  this.close();
1539
1823
  document.head.innerHTML += _LoginPrompt.css(this.options);
1540
1824
  const div = document.createElement("div");
@@ -1546,9 +1830,17 @@ const _LoginPrompt = class _LoginPrompt {
1546
1830
  this.password = document.querySelector('#datalynk-login-form input[name="password"]');
1547
1831
  this.persist = document.querySelector('#datalynk-login-form input[name="persist"]');
1548
1832
  this.username = document.querySelector('#datalynk-login-form input[name="username"]');
1549
- if (options.persist === false) this.persist.parentElement.remove();
1833
+ if (this.options.persist === false) this.persist.parentElement.remove();
1550
1834
  this.form.onsubmit = (event) => this.login(event);
1551
1835
  }
1836
+ async themeDefaults() {
1837
+ const theme = await getTheme(this.spoke, "login");
1838
+ this.options = {
1839
+ logoOnly: !this.options.title && !theme.found,
1840
+ ...theme,
1841
+ ...clean(this.options, true)
1842
+ };
1843
+ }
1552
1844
  /** Close the login prompt */
1553
1845
  close() {
1554
1846
  var _a, _b;
@@ -1594,11 +1886,11 @@ __publicField(_LoginPrompt, "css", (options) => `
1594
1886
  @import url('https://fonts.cdnfonts.com/css/ar-blanca');
1595
1887
 
1596
1888
  #datalynk-login {
1597
- --theme-background: ${options.background};
1889
+ --theme-background: ${options.backgroundColor ? options.backgroundColor : `url(${options.background})`};
1598
1890
  --theme-container: #000000cc;
1599
1891
  --theme-glow: ${options.glow || options.color};
1600
1892
  --theme-primary: ${options.color};
1601
- --theme-text: ${options.textColor};;
1893
+ --theme-text: ${options.textColor};
1602
1894
 
1603
1895
  position: fixed;
1604
1896
  left: 0;
@@ -1611,7 +1903,7 @@ __publicField(_LoginPrompt, "css", (options) => `
1611
1903
  font-family: sans-serif;
1612
1904
  z-index: 1000;
1613
1905
  }
1614
-
1906
+
1615
1907
  #datalynk-login .added-links {
1616
1908
  color: var(--theme-text);
1617
1909
  position: fixed;
@@ -1747,12 +2039,18 @@ __publicField(_LoginPrompt, "css", (options) => `
1747
2039
  /** Dynamically create HTML */
1748
2040
  __publicField(_LoginPrompt, "template", (options) => `
1749
2041
  <div id="datalynk-login">
2042
+ <!-- Used to check if image is valid -->
2043
+ ${!options.backgroundColor ? `
2044
+ <img src="${options.background}" onerror="document.querySelector('#datalynk-login').style.backgroundImage = 'url(https://datalynk.auxiliumgroup.com/static/js/auxilium/dijits/templates/login/datalynk/background.jpg)'" style="width: 0; height: 0;"/>
2045
+ ` : ""}
2046
+
1750
2047
  <div class="added-links">
1751
2048
  ${(options.addLinks || []).map((link) => `<a href="${link.url || "#"}" target="_blank">${link.text}</a>`).join(" | ")}
1752
2049
  </div>
1753
2050
  <div class="login-container">
1754
2051
  <div class="login-header">
1755
- ${options.title}
2052
+ ${options.logo ? `<img alt="Logo" src="${options.logo}" class="login-logo" style="height: 100px; width: auto;" onerror="this.style.display='none'" ${options.logoOnly ? `onload="document.querySelector('.login-title').remove()"` : ""}>` : ""}
2053
+ <span class="login-title" style="margin-left: 0.5rem">${options.title}</span>
1756
2054
  </div>
1757
2055
  <div class="login-content">
1758
2056
  <div class="login-body" style="max-width: 300px">
@@ -1798,13 +2096,17 @@ __publicField(_LoginPrompt, "template", (options) => `
1798
2096
  let LoginPrompt = _LoginPrompt;
1799
2097
  class Auth {
1800
2098
  constructor(api) {
2099
+ __publicField(this, "onlinePrompt");
1801
2100
  /** Current user as an observable */
1802
2101
  __publicField(this, "user$", new BehaviorSubject(void 0));
2102
+ var _a;
1803
2103
  this.api = api;
1804
2104
  this.api.token$.subscribe(async (token) => {
1805
2105
  if (token === void 0) return;
1806
2106
  this.user = await this.current(token);
1807
2107
  });
2108
+ if ((_a = this.api.options.offline) == null ? void 0 : _a.length)
2109
+ this.user$.pipe(filter((u) => u !== void 0)).subscribe((u) => localStorage.setItem("datalynk-user", JSON.stringify(u)));
1808
2110
  }
1809
2111
  /** Current user */
1810
2112
  get user() {
@@ -1827,7 +2129,9 @@ class Auth {
1827
2129
  var _a;
1828
2130
  if (!token) return null;
1829
2131
  else if (token == ((_a = this.user) == null ? void 0 : _a.token)) return this.user;
1830
- return this.api.request({ "$/auth/current": {} }, { token }).then((resp) => (resp == null ? void 0 : resp.login) ? resp : null);
2132
+ else if (typeof navigator != "undefined" && !navigator.onLine && typeof localStorage != "undefined" && localStorage.getItem("datalynk-user"))
2133
+ return JSON.parse(localStorage.getItem("datalynk-user"));
2134
+ return this.api.request([{ "$/auth/current": {} }, { "$/env/me": {} }], { token }).then((resp) => resp[0] || resp[1] ? { ...resp[0], ...resp[1] } : null);
1831
2135
  }
1832
2136
  /**
1833
2137
  * Automatically handle sessions by checking localStorage & URL parameters for a token & prompting
@@ -1838,17 +2142,20 @@ class Auth {
1838
2142
  * @return {Promise<void>} Login complete
1839
2143
  */
1840
2144
  async handleLogin(spoke, options) {
1841
- var _a;
2145
+ var _a, _b;
2146
+ if (this.onlinePrompt) window.removeEventListener("online", this.onlinePrompt);
2147
+ if ((_a = this.api.options.offline) == null ? void 0 : _a.length) window.removeEventListener("online", this.onlinePrompt = () => this.handleLogin(spoke, options));
1842
2148
  const urlToken = new URLSearchParams(location.search).get("datalynkToken");
1843
2149
  if (urlToken) {
1844
2150
  this.api.token = urlToken;
1845
2151
  location.href = location.href.replace(/datalynkToken=.*?(&|$)/gm, "");
1846
- } else if (this.api.token) {
1847
- if (((_a = this.api.jwtPayload) == null ? void 0 : _a.realm) != spoke) {
2152
+ } else if (this.api.token && !this.api.expired) {
2153
+ if (((_b = this.api.jwtPayload) == null ? void 0 : _b.realm) != spoke) {
1848
2154
  this.api.token = null;
1849
2155
  location.reload();
1850
2156
  }
1851
2157
  } else {
2158
+ this.api.token = null;
1852
2159
  await this.loginPrompt(spoke, options).done;
1853
2160
  location.reload();
1854
2161
  }
@@ -1953,6 +2260,7 @@ class Auth {
1953
2260
  * @return {Promise<{closed: string, new: string}>}
1954
2261
  */
1955
2262
  logout() {
2263
+ localStorage.removeItem("datalynk-user");
1956
2264
  return this.api.request({ "$/auth/logout": {} }).then((resp) => {
1957
2265
  this.api.token = null;
1958
2266
  return resp;
@@ -1967,11 +2275,13 @@ class Auth {
1967
2275
  * @return {Promise<any>} New session
1968
2276
  */
1969
2277
  reset(login, newPassword, code) {
1970
- return this.api.request({ "$/auth/mobile/rescue": {
1971
- user: login,
1972
- password: newPassword,
1973
- pin: code
1974
- } }).then((resp) => {
2278
+ return this.api.request({
2279
+ "$/auth/mobile/rescue": {
2280
+ user: login,
2281
+ password: newPassword,
2282
+ pin: code
2283
+ }
2284
+ }).then((resp) => {
1975
2285
  if (resp.token) this.api.token = resp.token;
1976
2286
  return resp;
1977
2287
  });
@@ -1990,9 +2300,7 @@ class Auth {
1990
2300
  }
1991
2301
  class Files {
1992
2302
  constructor(api) {
1993
- __publicField(this, "url");
1994
2303
  this.api = api;
1995
- this.url = `${this.api.url}file`;
1996
2304
  }
1997
2305
  associate(fileIds, slice, row, field, execute = true) {
1998
2306
  const req = { [`${execute ? "!" : "$"}/tools/file/update`]: { slice, row, field, ids: fileIds } };
@@ -2007,7 +2315,7 @@ class Files {
2007
2315
  * @return {string} URL file can be viewed at
2008
2316
  */
2009
2317
  get(id, ignoreToken) {
2010
- return `${this.url}?id=${id}${ignoreToken ? "" : `&token=${this.api.token}`}`;
2318
+ return `${this.api.url}file?id=${id}${ignoreToken ? "" : `&token=${this.api.token}`}`;
2011
2319
  }
2012
2320
  /**
2013
2321
  * Upload file(s) to the API & optionally associate them with a row
@@ -2016,25 +2324,23 @@ class Files {
2016
2324
  * @param {{slice: number, row: any, field: string, pk?: string}} associate Row to associate with
2017
2325
  */
2018
2326
  upload(files, associate) {
2019
- let f = [];
2020
- if (files instanceof FileList) f = Array.from(files);
2021
- else f = Array.isArray(files) ? files : [files];
2327
+ let f = files instanceof FileList ? Array.from(files) : makeArray(files);
2022
2328
  return Promise.all(f.map((file) => {
2023
2329
  const data = new FormData();
2024
- data.append("", file, file.name);
2025
- return fetch(this.url, {
2330
+ data.append("uploadedfiles[]", file, file.name);
2331
+ return fetch(`${this.api.url}upload.php`, {
2026
2332
  method: "POST",
2027
2333
  headers: clean({ "Authorization": this.api.token ? `Bearer ${this.api.token}` : "" }),
2028
2334
  body: data
2029
2335
  }).then(async (resp) => {
2030
2336
  const data2 = await resp.json().catch(() => ({}));
2031
2337
  if (!resp.ok || data2["error"]) throw Object.assign(errorFromCode(resp.status, data2.error) || {}, data2);
2032
- return resp;
2338
+ return data2.files.uploadedfiles[0];
2033
2339
  });
2034
2340
  })).then(async (files2) => {
2035
2341
  if (associate) {
2036
2342
  let id = typeof associate.row == "number" ? associate.row : associate.row[associate.pk || "id"];
2037
- if (!id) id = await this.api.slice(associate.slice).insert(associate.row).id();
2343
+ if (!id) id = await this.api.slice(associate.slice).insert(associate.row).id().exec();
2038
2344
  await this.associate(files2.map((f2) => f2.id), associate == null ? void 0 : associate.slice, associate == null ? void 0 : associate.row, associate == null ? void 0 : associate.field);
2039
2345
  }
2040
2346
  return files2;
@@ -2194,23 +2500,13 @@ const Serializer = {
2194
2500
  }
2195
2501
  }
2196
2502
  };
2197
- const _Slice = class _Slice {
2198
- /**
2199
- * An object to aid in constructing requests
2200
- *
2201
- * @param {number} slice Slice ID to interact with
2202
- * @param {Api} api Api to send the requests through
2203
- */
2204
- constructor(slice, api) {
2503
+ class ApiCall {
2504
+ constructor(api, slice) {
2205
2505
  __publicField(this, "operation");
2206
2506
  __publicField(this, "popField");
2207
2507
  __publicField(this, "request", {});
2208
2508
  /** Log response automatically */
2209
2509
  __publicField(this, "debugging");
2210
- /** Unsubscribe from changes, undefined if not subscribed */
2211
- __publicField(this, "unsubscribe");
2212
- /** Cached slice data as an observable */
2213
- __publicField(this, "cache$", new BehaviorSubject([]));
2214
2510
  /**
2215
2511
  * Whitelist and alias fields. Alias of `fields()`
2216
2512
  * @example
@@ -2222,16 +2518,8 @@ const _Slice = class _Slice {
2222
2518
  * @return {Slice<T>}
2223
2519
  */
2224
2520
  __publicField(this, "alias", this.fields);
2225
- this.slice = slice;
2226
2521
  this.api = api;
2227
- }
2228
- /** Cached slice data */
2229
- get cache() {
2230
- return this.cache$.getValue();
2231
- }
2232
- /** Set cached data & alert subscribers */
2233
- set cache(cache) {
2234
- this.cache$.next(cache);
2522
+ this.slice = slice;
2235
2523
  }
2236
2524
  /** Get raw API request */
2237
2525
  get raw() {
@@ -2326,7 +2614,8 @@ const _Slice = class _Slice {
2326
2614
  */
2327
2615
  exec(options) {
2328
2616
  if (!this.operation) throw new Error("No operation chosen");
2329
- return this.api.request(this.raw, options).then((resp) => {
2617
+ const request = this.raw;
2618
+ return this.api.request(request, options).then((resp) => {
2330
2619
  if (this.debugging) console.log(resp);
2331
2620
  return resp;
2332
2621
  });
@@ -2443,9 +2732,9 @@ const _Slice = class _Slice {
2443
2732
  * @param {T} rows Rows to add to slice
2444
2733
  * @returns {this<T>}
2445
2734
  */
2446
- save(...rows) {
2735
+ save(rows) {
2447
2736
  this.operation = "$/slice/multisave";
2448
- this.request.rows = rows;
2737
+ this.request.rows = makeArray(rows);
2449
2738
  return this;
2450
2739
  }
2451
2740
  /**
@@ -2466,30 +2755,6 @@ const _Slice = class _Slice {
2466
2755
  }
2467
2756
  return this;
2468
2757
  }
2469
- /**
2470
- * Synchronize cache with server
2471
- * @example
2472
- * ```ts
2473
- * const slice: Slice = new Slice<T>(Slices.Contact);
2474
- * slice.sync().subscribe((rows: T[]) => {});
2475
- * ```
2476
- * @param {boolean} on Enable/disable events
2477
- * @return {BehaviorSubject<T[]>} Cache which can be subscribed to
2478
- */
2479
- sync(on = true) {
2480
- if (on) {
2481
- new _Slice(this.slice, this.api).select().rows().exec().then((rows) => this.cache = rows);
2482
- if (!this.unsubscribe) this.unsubscribe = this.api.socket.sliceEvents(this.slice, (event) => {
2483
- const ids = [...event.data.new, ...event.data.changed];
2484
- new _Slice(this.slice, this.api).select(ids).rows().exec().then((rows) => this.cache = [...this.cache.filter((c) => c.id != null && !ids.includes(c.id)), ...rows]);
2485
- this.cache = this.cache.filter((v) => v.id && !event.data.lost.includes(v.id));
2486
- });
2487
- return this.cache$;
2488
- } else if (this.unsubscribe) {
2489
- this.unsubscribe();
2490
- this.unsubscribe = null;
2491
- }
2492
- }
2493
2758
  /**
2494
2759
  * Set the request type to update
2495
2760
  * @example
@@ -2532,6 +2797,7 @@ const _Slice = class _Slice {
2532
2797
  Object.entries(field).forEach(([key, value2]) => this.where(key, "==", value2));
2533
2798
  } else {
2534
2799
  const w = raw ? field : [(() => {
2800
+ if (operator == null ? void 0 : operator.startsWith("$")) return operator;
2535
2801
  if (operator == "==") return "$eq";
2536
2802
  if (operator == "!=") return "$neq";
2537
2803
  if (operator == ">") return "$gt";
@@ -2557,9 +2823,270 @@ const _Slice = class _Slice {
2557
2823
  }
2558
2824
  return this;
2559
2825
  }
2560
- };
2561
- __publicField(_Slice, "api");
2562
- let Slice = _Slice;
2826
+ }
2827
+ class Slice {
2828
+ /**
2829
+ * An object to aid in constructing requests
2830
+ *
2831
+ * @param {number} slice Slice ID to interact with
2832
+ * @param {Api} api Api to send the requests through
2833
+ */
2834
+ constructor(slice, api) {
2835
+ __publicField(this, "table");
2836
+ __publicField(this, "info");
2837
+ __publicField(this, "pendingInsert", 0);
2838
+ /** Unsubscribe from changes, undefined if not subscribed */
2839
+ __publicField(this, "unsubscribe");
2840
+ /** Cached slice data as an observable */
2841
+ __publicField(this, "cache$", new BehaviorSubject([]));
2842
+ var _a;
2843
+ this.slice = slice;
2844
+ this.api = api;
2845
+ if (this.offlineEnabled) {
2846
+ this.table = (_a = api.database) == null ? void 0 : _a.table(slice.toString());
2847
+ this.table.getAll().then((resp) => this.cache = resp);
2848
+ this.cache$.pipe(skip(1)).subscribe(async (cache) => {
2849
+ var _a2;
2850
+ await ((_a2 = this.table) == null ? void 0 : _a2.clear());
2851
+ await Promise.all(cache.map((c) => {
2852
+ var _a3;
2853
+ return (_a3 = this.table) == null ? void 0 : _a3.put(c.id, c);
2854
+ }));
2855
+ this.fixIncrement();
2856
+ });
2857
+ this.sync();
2858
+ window.addEventListener("online", async () => {
2859
+ if (this.api.expired) await lastValueFrom(this.api.auth.user$.pipe(skip(1), takeWhile((u) => !u || this.api.expired, true)));
2860
+ this.pushChanges();
2861
+ });
2862
+ }
2863
+ }
2864
+ /** Cached slice data */
2865
+ get cache() {
2866
+ return this.cache$.getValue();
2867
+ }
2868
+ /** Set cached data & alert subscribers */
2869
+ set cache(cache) {
2870
+ this.cache$.next(cache);
2871
+ }
2872
+ /** Is slice offline support enabled */
2873
+ get offlineEnabled() {
2874
+ var _a;
2875
+ return (_a = this.api.database) == null ? void 0 : _a.includes(this.slice.toString());
2876
+ }
2877
+ fixIncrement() {
2878
+ var _a;
2879
+ this.pendingInsert = ((_a = this.cache.toSorted(sortByProp("id")).pop()) == null ? void 0 : _a["id"]) || 0;
2880
+ }
2881
+ execWrapper(call) {
2882
+ const onlineExec = call.exec.bind(call);
2883
+ return () => {
2884
+ if (this.offlineEnabled && navigator && !(navigator == null ? void 0 : navigator.onLine)) {
2885
+ const where = (row, condition) => {
2886
+ if (Array.isArray(condition) ? !condition.length : condition == null) return true;
2887
+ if (!Array.isArray(condition)) return condition;
2888
+ if (condition[0] == "$field") return row[condition[1]];
2889
+ if (condition[0] == "$not") return !where(row, condition.slice(1));
2890
+ if (condition[0] == "$and") return condition.slice(1).filter((v) => where(row, v)).length == condition.length - 1;
2891
+ if (condition[0] == "$or") return !!condition.slice(1).find((v) => where(row, v));
2892
+ if (condition[0] == "$eq") return where(row, condition[1]) == where(row, condition[2]);
2893
+ if (condition[0] == "$neq") return where(row, condition[1]) != where(row, condition[2]);
2894
+ if (condition[0] == "$gt") return where(row, condition[1]) > where(row, condition[2]);
2895
+ if (condition[0] == "$gte") return where(row, condition[1]) >= where(row, condition[2]);
2896
+ if (condition[0] == "$lt") return where(row, condition[1]) < where(row, condition[2]);
2897
+ if (condition[0] == "$lte") return where(row, condition[1]) <= where(row, condition[2]);
2898
+ if (condition[0] == "$mod") return where(row, condition[1]) % where(row, condition[2]);
2899
+ return condition[0];
2900
+ };
2901
+ let request = call.raw, resp = {};
2902
+ if (request["$/slice/delete"]) {
2903
+ const found = this.cache.filter((r) => where(r, request["$/slice/delete"]["where"])).map((r) => r.id);
2904
+ this.cache = this.cache.map((r) => found.includes(r.id) ? { ...r, _sync: "delete" } : r);
2905
+ resp = {
2906
+ affected: found.length,
2907
+ "ignored-keys": [],
2908
+ keys: found,
2909
+ tx: null
2910
+ };
2911
+ } else if (request["$/slice/report"]) {
2912
+ resp["rows"] = deepCopy(this.cache).filter((r) => (r == null ? void 0 : r._sync) != "delete").filter((r) => r && where(r, request["$/slice/report"]["where"]));
2913
+ if (request["order"]) resp["rows"] = resp["rows"].toSorted(sortByProp(request["order"][1][1], request["order"] == "$desc"));
2914
+ if (request["limit"]) resp["rows"] = resp["rows"].slice(0, request["limit"]);
2915
+ if (request["fields"]) {
2916
+ if (request["fields"]["$count"]) resp["rows"] = { count: resp["rows"].length };
2917
+ else resp["rows"] = resp["rows"].map((r) => Object.entries(request["fields"]).reduce((acc, [k, v]) => ({
2918
+ ...acc,
2919
+ [v]: r[k]
2920
+ }), {}));
2921
+ }
2922
+ } else if (request["$/slice/xinsert"]) {
2923
+ const rows = request["$/slice/xinsert"]["rows"].map((r) => ({
2924
+ ...r,
2925
+ id: -++this.pendingInsert,
2926
+ _sync: "insert"
2927
+ }));
2928
+ this.cache = [...this.cache, ...rows];
2929
+ resp = {
2930
+ failed: [],
2931
+ granted: rows.map((r) => Object.keys(r).reduce((acc, key) => ({ ...acc, [key]: true }), {})),
2932
+ "ignored-fields": [],
2933
+ keys: rows.map((r) => r.id),
2934
+ publish: 1,
2935
+ tx: null
2936
+ };
2937
+ } else if (request["$/slice/xupdate"]) {
2938
+ const ids = request["$/slice/xupdate"]["rows"].map((r) => r.id);
2939
+ this.cache = [
2940
+ ...this.cache.filter((c) => !ids.includes(c.id)),
2941
+ ...request["$/slice/xupdate"]["rows"].map((r) => ({ ...r, _sync: "update" }))
2942
+ ].toSorted(sortByProp("id"));
2943
+ resp = {
2944
+ failed: [],
2945
+ granted: request["$/slice/xupdate"]["rows"].map((r) => Object.keys(r).reduce((acc, key) => ({
2946
+ ...acc,
2947
+ [key]: true
2948
+ }), {})),
2949
+ "ignored-fields": [],
2950
+ keys: request["$/slice/xupdate"]["rows"].map((r) => r.id),
2951
+ publish: 1,
2952
+ tx: null
2953
+ };
2954
+ }
2955
+ if (request["$pop"]) {
2956
+ resp = request["$pop"].split(":").reduce((acc, key) => acc[JSONAttemptParse(key)], resp);
2957
+ }
2958
+ return Promise.resolve(resp);
2959
+ } else {
2960
+ return onlineExec();
2961
+ }
2962
+ };
2963
+ }
2964
+ async pushChanges() {
2965
+ if (this.offlineEnabled && navigator && navigator.onLine) {
2966
+ await Promise.allSettled(
2967
+ this.cache.values().map((value) => {
2968
+ if (value._sync == "delete") {
2969
+ return this.delete(value.id).exec();
2970
+ } else if (value._sync == "insert") {
2971
+ return this.insert({ ...value, id: void 0, _sync: void 0 }).exec();
2972
+ } else if (value._sync == "update") {
2973
+ return this.update({
2974
+ ...value,
2975
+ _sync: void 0
2976
+ }).where("_updatedDate", "==", value.modified).exec();
2977
+ }
2978
+ }).filter((r) => !!r)
2979
+ );
2980
+ }
2981
+ }
2982
+ /**
2983
+ * Get slice information
2984
+ * @param reload Ignore cache & reload info
2985
+ * @return {Promise<SliceInfo>}
2986
+ */
2987
+ async getInfo(reload) {
2988
+ var _a, _b, _c;
2989
+ const getType = (field) => {
2990
+ if (field.options) {
2991
+ let t = field.options.split("|").map((o) => `'${o}'`).join(" | ");
2992
+ if (field.type == "control_checkbox") t = `(${t})[]`;
2993
+ return t;
2994
+ }
2995
+ if (field.type == "control_checkbox" || field.type == "boolean") return "boolean";
2996
+ if (field.type == "control_new_date" || field.type == "control_new_time" || field.type == "control_new_datetime" || field.type == "Date")
2997
+ return "Date";
2998
+ if (field.type == "control_number" || field.type == "number") return "number";
2999
+ return "string";
3000
+ };
3001
+ if (this.info && !reload) return Promise.resolve(this.info);
3002
+ this.info = await this.api.request({ "$/slice/fetch": { slice: this.slice } });
3003
+ const fields = ((_c = (_b = (_a = this.info) == null ? void 0 : _a.meta) == null ? void 0 : _b.presentation) == null ? void 0 : _c.fields) ? Object.values(this.info.meta.presentation.fields) : [];
3004
+ this.info.types = fields.filter((value) => value.id != void 0).map((value) => {
3005
+ var _a2;
3006
+ return {
3007
+ key: value.id.toString(),
3008
+ options: (_a2 = value.options) == null ? void 0 : _a2.split("|"),
3009
+ readonly: !!value.readonly || value.id.startsWith("fid") || ["id", "creatorRef", "created", "modifierRef", "modified"].includes(value.id),
3010
+ required: !!value.required,
3011
+ type: getType(value)
3012
+ };
3013
+ });
3014
+ return this.info;
3015
+ }
3016
+ /**
3017
+ * Synchronize cache with server
3018
+ * @example
3019
+ * ```ts
3020
+ * const slice: Slice = new Slice<T>(Slices.Contact);
3021
+ * slice.sync().subscribe((rows: T[]) => {});
3022
+ * ```
3023
+ * @param {boolean} on Enable/disable events
3024
+ * @return {BehaviorSubject<T[]>} Cache which can be subscribed to
3025
+ */
3026
+ sync(on = true) {
3027
+ if (on) {
3028
+ this.pushChanges().then(() => this.select().rows().exec().then((rows) => this.cache = rows));
3029
+ if (!this.unsubscribe) this.unsubscribe = this.api.socket.sliceEvents(this.slice, (event) => {
3030
+ const ids = [...event.data.new, ...event.data.changed];
3031
+ this.select(ids).rows().exec().then((rows) => this.cache = [...this.cache.filter((c) => c.id != null && !ids.includes(c.id)), ...rows]);
3032
+ this.cache = this.cache.filter((v) => v.id && !event.data.lost.includes(v.id));
3033
+ });
3034
+ return this.cache$;
3035
+ } else if (this.unsubscribe) {
3036
+ this.unsubscribe();
3037
+ this.unsubscribe = null;
3038
+ }
3039
+ }
3040
+ // Transaction wrapper =============================================================================================
3041
+ /**
3042
+ * {@inheritDoc ApiCall.count}
3043
+ */
3044
+ count(arg = "id") {
3045
+ const call = new ApiCall(this.api, this.slice);
3046
+ call.exec = this.execWrapper(call);
3047
+ return call.count(arg);
3048
+ }
3049
+ /**
3050
+ * {@inheritDoc ApiCall.delete}
3051
+ */
3052
+ delete(id) {
3053
+ const call = new ApiCall(this.api, this.slice);
3054
+ call.exec = this.execWrapper(call);
3055
+ return call.delete(id);
3056
+ }
3057
+ /**
3058
+ * {@inheritDoc ApiCall.insert}
3059
+ */
3060
+ insert(rows) {
3061
+ const call = new ApiCall(this.api, this.slice);
3062
+ call.exec = this.execWrapper(call);
3063
+ return call.insert(rows);
3064
+ }
3065
+ /**
3066
+ * {@inheritDoc ApiCall.save}
3067
+ */
3068
+ save(rows) {
3069
+ const call = new ApiCall(this.api, this.slice);
3070
+ call.exec = this.execWrapper(call);
3071
+ return call.save(rows);
3072
+ }
3073
+ /**
3074
+ * {@inheritDoc ApiCall.select}
3075
+ */
3076
+ select(id) {
3077
+ const call = new ApiCall(this.api, this.slice);
3078
+ call.exec = this.execWrapper(call);
3079
+ return call.select(id);
3080
+ }
3081
+ /**
3082
+ * {@inheritDoc ApiCall.update}
3083
+ */
3084
+ update(rows) {
3085
+ const call = new ApiCall(this.api, this.slice);
3086
+ call.exec = this.execWrapper(call);
3087
+ return call.update(rows);
3088
+ }
3089
+ }
2563
3090
  class Socket {
2564
3091
  constructor(api, options = {}) {
2565
3092
  __publicField(this, "listeners", []);
@@ -2692,8 +3219,8 @@ class Superuser {
2692
3219
  } });
2693
3220
  }
2694
3221
  }
2695
- const version = "1.0.19";
2696
- class Api {
3222
+ const version = "1.1.0-rc1";
3223
+ const _Api = class _Api {
2697
3224
  /**
2698
3225
  * Connect to Datalynk & send requests
2699
3226
  *
@@ -2702,10 +3229,10 @@ class Api {
2702
3229
  * const api = new Api('https://spoke.auxiliumgroup.com');
2703
3230
  * ```
2704
3231
  *
2705
- * @param {string} url API URL
3232
+ * @param {string} origin API URL
2706
3233
  * @param {ApiOptions} options
2707
3234
  */
2708
- constructor(url, options = {}) {
3235
+ constructor(origin, options = {}) {
2709
3236
  /** Current requests bundle */
2710
3237
  __publicField(this, "bundle", []);
2711
3238
  /** Bundle lifecycle tracking */
@@ -2714,12 +3241,6 @@ class Api {
2714
3241
  __publicField(this, "localStorageKey", "datalynk-token");
2715
3242
  /** Pending requests cache */
2716
3243
  __publicField(this, "pending", {});
2717
- /** API URL */
2718
- __publicField(this, "url");
2719
- /** Package version */
2720
- __publicField(this, "version", version);
2721
- /** API Session token */
2722
- __publicField(this, "token$", new BehaviorSubject(void 0));
2723
3244
  /** Helpers */
2724
3245
  /** Authentication */
2725
3246
  __publicField(this, "auth");
@@ -2731,12 +3252,26 @@ class Api {
2731
3252
  __publicField(this, "socket");
2732
3253
  /** Superuser */
2733
3254
  __publicField(this, "superuser");
2734
- this.options = options;
2735
- this.url = `${new URL(url).origin}/api/`;
3255
+ /** Offline database */
3256
+ __publicField(this, "database");
3257
+ /** Options */
3258
+ __publicField(this, "options");
3259
+ /** Created slices */
3260
+ __publicField(this, "sliceCache", /* @__PURE__ */ new Map());
3261
+ /** API URL */
3262
+ __publicField(this, "url");
3263
+ /** Client library version */
3264
+ __publicField(this, "version", version);
3265
+ /** API Session token */
3266
+ __publicField(this, "token$", new BehaviorSubject(void 0));
3267
+ var _a, _b;
3268
+ this.origin = origin;
3269
+ this.url = `${new URL(origin).origin}/api/`;
2736
3270
  this.options = {
2737
- bundleTime: 100,
3271
+ offline: [],
2738
3272
  origin: typeof location !== "undefined" ? location.host : "Unknown",
2739
3273
  saveSession: true,
3274
+ serviceWorker: "/service.worker.js",
2740
3275
  ...options
2741
3276
  };
2742
3277
  if (this.options.saveSession) {
@@ -2747,12 +3282,32 @@ class Api {
2747
3282
  else localStorage.removeItem(this.localStorageKey);
2748
3283
  });
2749
3284
  }
2750
- Slice.api = this;
3285
+ this.socket = new Socket(this, { url: options.socket });
2751
3286
  this.auth = new Auth(this);
2752
3287
  this.files = new Files(this);
2753
3288
  this.pdf = new Pdf(this);
2754
3289
  this.superuser = new Superuser(this);
2755
- this.socket = new Socket(this, { url: options.socket });
3290
+ if ((_a = this.options.offline) == null ? void 0 : _a.length) {
3291
+ if (typeof indexedDB == "undefined") throw new Error("Cannot enable offline support, indexedDB is not available in this environment");
3292
+ this.database = new Database("datalynk", this.options.offline);
3293
+ (_b = this.options.offline) == null ? void 0 : _b.forEach((id) => this.slice(id));
3294
+ this.cacheUrl();
3295
+ }
3296
+ }
3297
+ /** Get session info from JWT payload */
3298
+ get jwtPayload() {
3299
+ if (!this.token) return null;
3300
+ return decodeJwt(this.token);
3301
+ }
3302
+ /** Is token expired */
3303
+ get expired() {
3304
+ var _a;
3305
+ return (((_a = this.jwtPayload) == null ? void 0 : _a.exp) ?? Infinity) * 1e3 <= Date.now();
3306
+ }
3307
+ /** Logged in spoke */
3308
+ get spoke() {
3309
+ var _a;
3310
+ return (_a = this.jwtPayload) == null ? void 0 : _a.realm;
2756
3311
  }
2757
3312
  get token() {
2758
3313
  return this.token$.getValue();
@@ -2760,11 +3315,6 @@ class Api {
2760
3315
  set token(token) {
2761
3316
  this.token$.next(token);
2762
3317
  }
2763
- /** Get session info from JWT payload */
2764
- get jwtPayload() {
2765
- if (!this.token) return null;
2766
- return decodeJwt(this.token);
2767
- }
2768
3318
  _request(req, options = {}) {
2769
3319
  const token = options.token || this.token;
2770
3320
  return fetch(this.url, {
@@ -2774,14 +3324,31 @@ class Api {
2774
3324
  "Content-Type": "application/json",
2775
3325
  "X-Date-Return-Format": this.options.legacyDates ? void 0 : "ISO8601"
2776
3326
  }),
2777
- body: JSON.stringify(Api.translateTokens(req))
3327
+ body: JSON.stringify(_Api.translateTokens(req))
2778
3328
  }).then(async (resp) => {
2779
3329
  let data = JSONAttemptParse(await resp.text());
2780
3330
  if (!resp.ok || (data == null ? void 0 : data.error)) throw Object.assign(errorFromCode(resp.status, data.error), data);
2781
- if (!options.raw) data = Api.translateTokens(data);
3331
+ if (!options.raw) data = _Api.translateTokens(data);
2782
3332
  return data;
2783
3333
  });
2784
3334
  }
3335
+ async cacheUrl() {
3336
+ if (!this.options.serviceWorker || !("serviceWorker" in navigator)) return;
3337
+ await navigator.serviceWorker.getRegistration(this.options.serviceWorker).then((reg) => reg ?? navigator.serviceWorker.register(this.options.serviceWorker, { scope: "/" }));
3338
+ await navigator.serviceWorker.ready;
3339
+ if (!navigator.serviceWorker.controller) return window.location.reload();
3340
+ }
3341
+ /**
3342
+ * Get list of slices
3343
+ * @return {Promise<number[]>}
3344
+ */
3345
+ getSlices() {
3346
+ return this.request({ "$/tools/action_chain": [
3347
+ { "!/env/me": {} },
3348
+ { "!/slice/permissionsLite": {} },
3349
+ { "!/tools/column": { "col": "id", "rows": { "$_": "1:rows" } } }
3350
+ ] });
3351
+ }
2785
3352
  /**
2786
3353
  * Parses API request/response object for special Datalynk tokens & converts them to native JS objects
2787
3354
  *
@@ -2800,6 +3367,10 @@ class Api {
2800
3367
  val = Serializer.deserialize[index](val[index]);
2801
3368
  } else if (index in Serializer.serialize) {
2802
3369
  val = Serializer.serialize[index](val[index]);
3370
+ } else if (val[index] == "Yes") {
3371
+ val[index] = true;
3372
+ } else if (val[index] == "No") {
3373
+ val[index] = false;
2803
3374
  } else {
2804
3375
  queue.push(`${key}.${index}`);
2805
3376
  }
@@ -2816,7 +3387,7 @@ class Api {
2816
3387
  chain(...requests) {
2817
3388
  const arr = requests.length == 1 && Array.isArray(requests[0]) ? requests[0] : requests;
2818
3389
  return this.request({ "$/tools/action_chain": arr.map((r) => {
2819
- if (!(r instanceof Slice)) return r;
3390
+ if (!(r instanceof ApiCall)) return r;
2820
3391
  const req = r.raw;
2821
3392
  Object.keys(req).forEach((key) => {
2822
3393
  if (key.startsWith("$/")) {
@@ -2835,7 +3406,7 @@ class Api {
2835
3406
  chainMap(request) {
2836
3407
  return this.request({ "$/tools/do": [
2837
3408
  ...Object.entries(request).flatMap(([key, r]) => {
2838
- if (!(r instanceof Slice)) return [key, r];
3409
+ if (!(r instanceof ApiCall)) return [key, r];
2839
3410
  const req = r.raw;
2840
3411
  Object.keys(req).forEach((key2) => {
2841
3412
  if (key2.startsWith("$/")) {
@@ -2916,15 +3487,20 @@ class Api {
2916
3487
  * const unsubscribe = contactsSlice.sync().subscribe(rows => console.log(rows));
2917
3488
  * ```
2918
3489
  *
2919
- * @param {number} slice Slice number the object will target
3490
+ * @param {number} id Slice ID the object will target
2920
3491
  * @returns {Slice<T = any>} Object for making requests & caching rows
2921
3492
  */
2922
- slice(slice) {
2923
- return new Slice(slice, this);
3493
+ slice(id) {
3494
+ if (!this.sliceCache.has(+id)) this.sliceCache.set(+id, new Slice(id, this));
3495
+ return this.sliceCache.get(+id);
2924
3496
  }
2925
- }
3497
+ };
3498
+ /** Client library version */
3499
+ __publicField(_Api, "version", version);
3500
+ let Api = _Api;
2926
3501
  export {
2927
3502
  Api,
3503
+ ApiCall,
2928
3504
  Auth,
2929
3505
  Files,
2930
3506
  LoginPrompt,
@@ -2932,5 +3508,6 @@ export {
2932
3508
  Serializer,
2933
3509
  Slice,
2934
3510
  Socket,
2935
- Superuser
3511
+ Superuser,
3512
+ getTheme
2936
3513
  };