@lotics/app-sdk 0.118.1 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -174,6 +174,30 @@ function transportErrorMessage(status, parsed) {
174
174
  return status < 500 || authored ? jsonMessage : gatewayErrorMessage(status);
175
175
  }
176
176
 
177
+ // ../shared/src/utils.ts
178
+ function decimalPlaces(value) {
179
+ if (Number.isInteger(value)) return 0;
180
+ const [mantissa = "", exponent = "0"] = String(value).split("e");
181
+ const fraction = mantissa.split(".")[1]?.length ?? 0;
182
+ return Math.max(0, fraction - Number(exponent));
183
+ }
184
+ function sumDecimal(values, divisor = 1) {
185
+ const float = values.reduce((total2, value) => total2 + value, 0) / divisor;
186
+ if (!values.every(Number.isFinite)) return float;
187
+ const factor = 10 ** values.reduce((places, value) => Math.max(places, decimalPlaces(value)), 0);
188
+ const scaled = values.map((value) => Math.round(value * factor));
189
+ const total = scaled.reduce((sum, value) => sum + value, 0);
190
+ const exact = Number.isSafeInteger(total) && scaled.every((one, index) => Number.isSafeInteger(one) && one / factor === values[index]);
191
+ return exact ? total / (factor * divisor) : float;
192
+ }
193
+ function sample(arr) {
194
+ if (arr.length === 0) return void 0;
195
+ return arr[Math.floor(Math.random() * arr.length)];
196
+ }
197
+ function assertNever(value) {
198
+ throw new Error(`Unhandled union member: ${JSON.stringify(value)}`);
199
+ }
200
+
177
201
  // ../shared/src/schemas/read_row.ts
178
202
  var RECORD_ID_COLUMN = "__source_record_id";
179
203
 
@@ -205,7 +229,7 @@ function aggregateRows(rows, aggregate) {
205
229
  ...Object.fromEntries(by.map((column) => [column, first2?.[column] ?? null])),
206
230
  ...day === void 0 ? {} : { [AGGREGATE_DAY_COLUMN]: dayOf(first2?.[day]) },
207
231
  [AGGREGATE_COUNT_COLUMN]: members.length,
208
- ...sum === void 0 ? {} : { [AGGREGATE_SUM_COLUMN]: figures.length === 0 ? null : figures.reduce((total, value) => total + value, 0) }
232
+ ...sum === void 0 ? {} : { [AGGREGATE_SUM_COLUMN]: figures.length === 0 ? null : sumDecimal(figures) }
209
233
  };
210
234
  });
211
235
  }
@@ -1200,6 +1224,9 @@ async function standaloneUpload(file, fidelity, place) {
1200
1224
 
1201
1225
  export {
1202
1226
  __export,
1227
+ sumDecimal,
1228
+ sample,
1229
+ assertNever,
1203
1230
  AGGREGATE_COUNT_COLUMN,
1204
1231
  AGGREGATE_SUM_COLUMN,
1205
1232
  aggregateRows,
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  aggregateRows,
7
7
  anyRecordingLive,
8
8
  applyPatch,
9
+ assertNever,
9
10
  decodeAll,
10
11
  encodePatch,
11
12
  getUrlParams,
@@ -20,12 +21,14 @@ import {
20
21
  rpc,
21
22
  rpcAgentRun,
22
23
  rpcAgentRunContinue,
24
+ sample,
23
25
  setUrlParams,
24
26
  subscribeQueriesChanged,
25
27
  subscribeRecordings,
26
28
  subscribeUrlParams,
29
+ sumDecimal,
27
30
  urlParam
28
- } from "./chunk-XL5LZFGB.js";
31
+ } from "./chunk-UFWVZOJE.js";
29
32
 
30
33
  // src/mount.tsx
31
34
  import { createRoot } from "react-dom/client";
@@ -1723,15 +1726,6 @@ var customRandom = (alphabet, defaultSize, getRandom) => {
1723
1726
  };
1724
1727
  var customAlphabet = (alphabet, size2 = 21) => customRandom(alphabet, size2 | 0, random);
1725
1728
 
1726
- // ../shared/src/utils.ts
1727
- function sample(arr) {
1728
- if (arr.length === 0) return void 0;
1729
- return arr[Math.floor(Math.random() * arr.length)];
1730
- }
1731
- function assertNever(value) {
1732
- throw new Error(`Unhandled union member: ${JSON.stringify(value)}`);
1733
- }
1734
-
1735
1729
  // ../shared/src/expression_array.ts
1736
1730
  var expression_array_exports = {};
1737
1731
  __export(expression_array_exports, {
@@ -1790,6 +1784,9 @@ function getField(obj, field) {
1790
1784
  }
1791
1785
  return value;
1792
1786
  }
1787
+ function addends(values2) {
1788
+ return values2.map((value) => typeof value === "number" ? value : 0);
1789
+ }
1793
1790
  function countInto(result, key) {
1794
1791
  defineOwn(result, key, (Object.hasOwn(result, key) ? result[key] : 0) + 1);
1795
1792
  }
@@ -1847,14 +1844,10 @@ function sumBy(arr, fieldOrCb) {
1847
1844
  if (typeof fieldOrCb !== "string") {
1848
1845
  throw new Error("sumBy: second arg must be a function or field path string");
1849
1846
  }
1850
- return arr.reduce((sum2, item) => {
1851
- const val = getField(item, fieldOrCb);
1852
- return sum2 + (typeof val === "number" ? val : 0);
1853
- }, 0);
1847
+ return sumDecimal(addends(arr.map((item) => getField(item, fieldOrCb))));
1854
1848
  }
1855
1849
  async function sumByCallback(arr, cb) {
1856
- const values2 = await callEach(arr, cb);
1857
- return values2.reduce((sum2, val) => sum2 + (typeof val === "number" ? val : 0), 0);
1850
+ return sumDecimal(addends(await callEach(arr, cb)));
1858
1851
  }
1859
1852
  function meanBy(arr, fieldOrCb) {
1860
1853
  if (!Array.isArray(arr)) {
@@ -1865,19 +1858,13 @@ function meanBy(arr, fieldOrCb) {
1865
1858
  }
1866
1859
  if (isCallback(fieldOrCb)) {
1867
1860
  assertCallbackInput("meanBy", arr);
1868
- return callEach(arr, fieldOrCb).then(
1869
- (vals) => vals.reduce((acc, v) => acc + (typeof v === "number" ? v : 0), 0) / arr.length
1870
- );
1861
+ return callEach(arr, fieldOrCb).then((values2) => sumDecimal(addends(values2), arr.length));
1871
1862
  }
1872
1863
  const field = fieldOrCb;
1873
1864
  if (typeof field !== "string") {
1874
1865
  throw new Error("meanBy: second arg must be a function or field path string");
1875
1866
  }
1876
- const sum2 = arr.reduce((acc, item) => {
1877
- const val = getField(item, field);
1878
- return acc + (typeof val === "number" ? val : 0);
1879
- }, 0);
1880
- return sum2 / arr.length;
1867
+ return sumDecimal(addends(arr.map((item) => getField(item, field))), arr.length);
1881
1868
  }
1882
1869
  function minBy(arr, fieldOrCb) {
1883
1870
  if (!Array.isArray(arr)) {
@@ -2842,9 +2829,7 @@ function mean(arr) {
2842
2829
  if (present.length === 0) {
2843
2830
  return null;
2844
2831
  }
2845
- const numArr = present;
2846
- const total = numArr.reduce((a, b) => a + b, 0);
2847
- const result = total / numArr.length;
2832
+ const result = present.every((item) => typeof item === "number") ? sumDecimal(present, present.length) : NaN;
2848
2833
  if (isNaN(result)) {
2849
2834
  throw new Error("mean: array contains non-numeric values");
2850
2835
  }
@@ -2923,7 +2908,11 @@ function sum(arr) {
2923
2908
  if (!Array.isArray(arr)) {
2924
2909
  throw new Error("sum: expected array, got " + typeof arr);
2925
2910
  }
2926
- return arr.reduce((a, b) => a + b, 0);
2911
+ const present = arr.filter((item) => item != null);
2912
+ if (!present.every((item) => typeof item === "number")) {
2913
+ throw new Error("sum: array contains non-numeric values");
2914
+ }
2915
+ return sumDecimal(present);
2927
2916
  }
2928
2917
  function abs(n) {
2929
2918
  if (n == null) return null;
@@ -21239,19 +21228,17 @@ function aggregateNumberField(values2, operation) {
21239
21228
  }
21240
21229
  switch (operation) {
21241
21230
  case "sum": {
21242
- const sum2 = numericValues.reduce((acc, val) => acc + val, 0);
21243
- return { result: sum2 };
21231
+ return { result: sumDecimal(numericValues) };
21244
21232
  }
21245
21233
  case "avg": {
21246
- const sum2 = numericValues.reduce((acc, val) => acc + val, 0);
21247
- const average = sum2 / numericValues.length;
21234
+ const average = sumDecimal(numericValues, numericValues.length);
21248
21235
  return { result: average };
21249
21236
  }
21250
21237
  case "median": {
21251
21238
  const sorted = [...numericValues].sort((a, b) => a - b);
21252
21239
  const middle = Math.floor(sorted.length / 2);
21253
21240
  if (sorted.length % 2 === 0) {
21254
- const median = (sorted[middle - 1] + sorted[middle]) / 2;
21241
+ const median = sumDecimal([sorted[middle - 1], sorted[middle]], 2);
21255
21242
  return { result: median };
21256
21243
  } else {
21257
21244
  return { result: sorted[middle] };
@@ -21268,7 +21255,7 @@ function aggregateNumberField(values2, operation) {
21268
21255
  case "range": {
21269
21256
  const min3 = Math.min(...numericValues);
21270
21257
  const max3 = Math.max(...numericValues);
21271
- const range2 = max3 - min3;
21258
+ const range2 = sumDecimal([max3, -min3]);
21272
21259
  return { result: range2 };
21273
21260
  }
21274
21261
  default:
@@ -28984,6 +28971,7 @@ function useAppContext() {
28984
28971
  memberId: data?.member_id ?? null,
28985
28972
  groupIds: data?.group_ids ?? null,
28986
28973
  absences: data?.absences ?? null,
28974
+ openableApps: data?.openable_apps ?? null,
28987
28975
  commentsEnabled: data?.comments_enabled ?? false,
28988
28976
  recordingEnabled: data?.recording_enabled === true,
28989
28977
  timezone: data?.timezone,
package/dist/router.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  isEmbedded,
3
3
  postHostNotification,
4
4
  setUrlParams
5
- } from "./chunk-XL5LZFGB.js";
5
+ } from "./chunk-UFWVZOJE.js";
6
6
 
7
7
  // src/router.tsx
8
8
  import { useEffect } from "react";
package/dist/rpc.d.ts CHANGED
@@ -77,6 +77,12 @@ export interface AppContext {
77
77
  /** Who in the organization is away, or will be, and who stands in — absent from an older host and for a visitor from
78
78
  * outside the organization. */
79
79
  absences?: AppAbsence[];
80
+ /** The apps of the workspace the viewer can open, each by its id and its name — absent from an older host,
81
+ * for a visitor and until the host has listed them. */
82
+ openable_apps?: {
83
+ id: string;
84
+ name: string;
85
+ }[];
80
86
  }
81
87
  /** Whether a Lotics host embeds the app, rather than it standing alone on its own origin. */
82
88
  export declare function isEmbedded(): boolean;
package/dist/viewer.d.ts CHANGED
@@ -14,6 +14,11 @@ export declare function useAppContext(): {
14
14
  groupIds: readonly string[] | null;
15
15
  /** Who in the organization is away, or will be, and who stands in; `null` where the host did not say. */
16
16
  absences: readonly AppAbsence[] | null;
17
+ /** The apps the viewer can open, by id and name; `null` where the host did not say. */
18
+ openableApps: readonly {
19
+ id: string;
20
+ name: string;
21
+ }[] | null;
17
22
  commentsEnabled: boolean;
18
23
  recordingEnabled: boolean;
19
24
  timezone: string | undefined;
@@ -261,6 +261,11 @@ host did not say (a visitor from outside the organization, an older host). Both
261
261
  context, read once as the app opens. Display only: the workflow body re-checks who may act
262
262
  (`current_member_stands_in_for(...)`).
263
263
 
264
+ `openableApps` lists the workspace's apps the viewer can open, each by its `id` and its `name` — what a
265
+ way through to a sibling app (`openApp`) is offered by. `null` where the host did not say (standalone, a
266
+ render, an older host, or a page that had not listed its apps when the app opened). Display only: the
267
+ app opened checks the viewer itself.
268
+
264
269
  ## The workspace's zone at the root
265
270
 
266
271
  An app's root draws nothing until `useAppContext()` has `settled` — where it failed, it draws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.118.1",
3
+ "version": "0.119.0",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through — typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {