@definite-app/data-apps 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@definite-app/data-apps",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Build source-authored React data apps that compile to a single HTML file with DuckDB WASM and Perspective.js.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -8,10 +8,39 @@ type DatasetKind = "table" | "database";
8
8
  type DataAppErrorSeverity = "warning" | "error" | "fatal";
9
9
  type DataAppErrorPhase = "bridge" | "resolve" | "fetch" | "duckdb" | "perspective" | "render" | "uncaught";
10
10
 
11
+ /**
12
+ * Logged-in viewer. Null in publicMode (no authenticated session).
13
+ * Mirrors BridgeUser in wax/src/utils/fi/dataBridge.ts.
14
+ */
15
+ export type DefiniteUser = {
16
+ id: string;
17
+ email: string;
18
+ role: "guest" | "viewer" | "analyst" | "admin" | "embedded_viewer" | null;
19
+ avatar: string | null;
20
+ createdAt: string | null;
21
+ updatedAt: string | null;
22
+ lastSignInAt: string | null;
23
+ };
24
+
25
+ /**
26
+ * Active team. Null in publicMode.
27
+ * Mirrors BridgeTeam in wax/src/utils/fi/dataBridge.ts.
28
+ */
29
+ export type DefiniteTeam = {
30
+ id: string;
31
+ domain: string;
32
+ plan: "trial" | "free" | "standard" | "enterprise" | null;
33
+ createdAt: string | null;
34
+ updatedAt: string | null;
35
+ trialEndAt: string | null;
36
+ };
37
+
11
38
  type DefiniteContext = {
12
39
  publicMode: boolean;
13
40
  driveFile: string | null;
14
41
  appVersion: "v1" | "v2";
42
+ user: DefiniteUser | null;
43
+ team: DefiniteTeam | null;
15
44
  };
16
45
 
17
46
  type ResourceCacheDetails = {
@@ -235,10 +264,15 @@ function getEmbeddedBridge(): DefiniteBridge | null {
235
264
  return { ...resolved, kind: "json" };
236
265
  },
237
266
  async getContext() {
267
+ // Embedded mode resolves identity server-side via the Bearer token; the
268
+ // runtime doesn't have direct access to those fields, so we surface null
269
+ // until the embed endpoint widens its handshake.
238
270
  return {
239
271
  publicMode: false,
240
272
  driveFile: ctx.driveFile,
241
273
  appVersion: "v2",
274
+ user: null,
275
+ team: null,
242
276
  } satisfies DefiniteContext;
243
277
  },
244
278
  reportError(error) {
@@ -486,6 +520,8 @@ function getPreviewBridge(): DefiniteBridge | null {
486
520
  publicMode: false,
487
521
  driveFile: "preview://data-apps-v2",
488
522
  appVersion: "v2",
523
+ user: null,
524
+ team: null,
489
525
  ...(previewData.context ?? {}),
490
526
  } satisfies DefiniteContext;
491
527
  },
@@ -1383,6 +1419,36 @@ async function loadJsonResource<T>(
1383
1419
  return { data: value, cache };
1384
1420
  }
1385
1421
 
1422
+ // React hook for reading the host-injected viewer context. Returns null until
1423
+ // the bridge resolves; in the wax-injected case (the common path) this is
1424
+ // effectively synchronous because `window.Definite._context` is already set
1425
+ // when the iframe script runs, so the initial render already has a value.
1426
+ export function useDefiniteContext(): DefiniteContext | null {
1427
+ const [ctx, setCtx] = useState<DefiniteContext | null>(() => {
1428
+ // Synchronous fast path: wax injects `_context` into `window.Definite`
1429
+ // before any app code runs, so we can read it on first render.
1430
+ const injected = (window as unknown as { Definite?: { _context?: DefiniteContext } }).Definite?._context;
1431
+ return injected ?? null;
1432
+ });
1433
+
1434
+ useEffect(() => {
1435
+ let cancelled = false;
1436
+ getBridge()
1437
+ .getContext()
1438
+ .then((next) => {
1439
+ if (!cancelled) setCtx(next);
1440
+ })
1441
+ .catch((err) => {
1442
+ console.error("[data-apps] useDefiniteContext: getContext() failed", err);
1443
+ });
1444
+ return () => {
1445
+ cancelled = true;
1446
+ };
1447
+ }, []);
1448
+
1449
+ return ctx;
1450
+ }
1451
+
1386
1452
  export function useDataset(key: string, opts?: { mode?: DataAppMode }): DatasetHandle {
1387
1453
  const mode = opts?.mode ?? "auto";
1388
1454
  const [state, setState] = useState<DatasetHandle>({
@@ -3117,9 +3183,24 @@ export function EChart(props: {
3117
3183
  const instanceRef = useRef<any>(null);
3118
3184
  const onClickRef = useRef(props.onClick);
3119
3185
  onClickRef.current = props.onClick;
3120
- const serializedOption = useMemo(() => JSON.stringify(props.option), [props.option]);
3186
+ // Change-detection key ONLY. JSON.stringify drops function values, so the
3187
+ // serialized string must never be parsed back into the object handed to
3188
+ // ECharts -- a JSON round-trip silently deletes every axisLabel/tooltip/
3189
+ // label `formatter` function, leaving only string-template formatters.
3190
+ const optionKey = useMemo(() => JSON.stringify(props.option), [props.option]);
3191
+ const optionRef = useRef(props.option);
3192
+ optionRef.current = props.option;
3121
3193
  const echartsTheme = (props.theme ?? "dark") === "light" ? "light" : "dark";
3122
3194
 
3195
+ // Apply the live option object (functions intact) to an ECharts instance.
3196
+ const applyOption = (instance: any) => {
3197
+ const opt = optionRef.current;
3198
+ instance.setOption(
3199
+ opt.backgroundColor ? opt : { ...opt, backgroundColor: "transparent" },
3200
+ true,
3201
+ );
3202
+ };
3203
+
3123
3204
  // Init / re-init on theme change
3124
3205
  useEffect(() => {
3125
3206
  const el = containerRef.current;
@@ -3134,9 +3215,7 @@ export function EChart(props: {
3134
3215
  instanceRef.current = instance;
3135
3216
 
3136
3217
  try {
3137
- const opt = JSON.parse(serializedOption);
3138
- if (!opt.backgroundColor) opt.backgroundColor = "transparent";
3139
- instance.setOption(opt, true);
3218
+ applyOption(instance);
3140
3219
  } catch (e) {
3141
3220
  console.error("[EChart] setOption failed", e);
3142
3221
  }
@@ -3159,13 +3238,11 @@ export function EChart(props: {
3159
3238
  useEffect(() => {
3160
3239
  if (!instanceRef.current) return;
3161
3240
  try {
3162
- const opt = JSON.parse(serializedOption);
3163
- if (!opt.backgroundColor) opt.backgroundColor = "transparent";
3164
- instanceRef.current.setOption(opt, true);
3241
+ applyOption(instanceRef.current);
3165
3242
  } catch (e) {
3166
3243
  console.error("[EChart] setOption update failed", e);
3167
3244
  }
3168
- }, [serializedOption]);
3245
+ }, [optionKey]);
3169
3246
 
3170
3247
  return (
3171
3248
  <div