@databricks/appkit-ui 0.57.0 → 0.59.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.
- package/dist/js/format/index.d.ts +38 -0
- package/dist/js/format/index.d.ts.map +1 -0
- package/dist/js/format/index.js +160 -0
- package/dist/js/format/index.js.map +1 -0
- package/dist/js/index.d.ts +5 -1
- package/dist/js/index.js +3 -1
- package/dist/js/metric-filter/index.d.ts +40 -0
- package/dist/js/metric-filter/index.d.ts.map +1 -0
- package/dist/js/metric-filter/index.js +56 -0
- package/dist/js/metric-filter/index.js.map +1 -0
- package/dist/react/charts/base.d.ts +19 -2
- package/dist/react/charts/base.d.ts.map +1 -1
- package/dist/react/charts/base.js +28 -8
- package/dist/react/charts/base.js.map +1 -1
- package/dist/react/charts/index.d.ts +4 -3
- package/dist/react/charts/normalize.d.ts.map +1 -1
- package/dist/react/charts/normalize.js +6 -6
- package/dist/react/charts/normalize.js.map +1 -1
- package/dist/react/charts/options.d.ts +31 -2
- package/dist/react/charts/options.d.ts.map +1 -1
- package/dist/react/charts/options.js +150 -13
- package/dist/react/charts/options.js.map +1 -1
- package/dist/react/charts/types.d.ts +54 -1
- package/dist/react/charts/types.d.ts.map +1 -1
- package/dist/react/charts/types.js.map +1 -1
- package/dist/react/charts/utils.d.ts +8 -9
- package/dist/react/charts/utils.d.ts.map +1 -1
- package/dist/react/charts/utils.js +135 -28
- package/dist/react/charts/utils.js.map +1 -1
- package/dist/react/hooks/analytics-sse.js +117 -0
- package/dist/react/hooks/analytics-sse.js.map +1 -0
- package/dist/react/hooks/index.d.ts +4 -1
- package/dist/react/hooks/types.d.ts +90 -4
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +14 -74
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-metric-view.d.ts +39 -0
- package/dist/react/hooks/use-metric-view.d.ts.map +1 -0
- package/dist/react/hooks/use-metric-view.js +178 -0
- package/dist/react/hooks/use-metric-view.js.map +1 -0
- package/dist/react/index.d.ts +9 -5
- package/dist/react/index.js +5 -3
- package/dist/react/lib/format.js +3 -1
- package/dist/react/lib/format.js.map +1 -1
- package/dist/shared/src/index.d.ts +2 -0
- package/dist/shared/src/metric-filter.d.ts +36 -0
- package/dist/shared/src/metric-filter.d.ts.map +1 -0
- package/dist/shared/src/metric-metadata.d.ts +15 -0
- package/dist/shared/src/metric-metadata.d.ts.map +1 -0
- package/docs/development/type-generation.md +7 -6
- package/docs/plugins/ai-search.md +10 -0
- package/docs/plugins/analytics.md +287 -12
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { MetricFilter, MetricFilterOperatorName, MetricOrderBy, MetricOrderDirection, MetricPredicate } from "../../shared/src/metric-filter.js";
|
|
2
|
+
import { MetricViewColumnDisplay } from "../../shared/src/metric-metadata.js";
|
|
3
|
+
import "../../shared/src/index.js";
|
|
4
|
+
import "../../js/index.js";
|
|
1
5
|
import { Table } from "apache-arrow";
|
|
2
6
|
|
|
3
7
|
//#region src/react/hooks/types.d.ts
|
|
@@ -197,11 +201,93 @@ type InferServingResponse<K> = K extends AugmentedRegistry<ServingEndpointRegist
|
|
|
197
201
|
type InferServingRequest<K> = K extends AugmentedRegistry<ServingEndpointRegistry> ? ServingEndpointRegistry[K] extends {
|
|
198
202
|
request: infer Req;
|
|
199
203
|
} ? Req : Record<string, unknown> : Record<string, unknown>;
|
|
204
|
+
/** Metric view registry for type-safe metric keys */
|
|
205
|
+
interface MetricRegistry {}
|
|
206
|
+
type MetricKey = AugmentedRegistry<MetricRegistry> extends never ? string : AugmentedRegistry<MetricRegistry>;
|
|
207
|
+
type InferMeasureKeys<K> = K extends AugmentedRegistry<MetricRegistry> ? MetricRegistry[K] extends {
|
|
208
|
+
measureKeys: infer M;
|
|
209
|
+
} ? M : string : string;
|
|
210
|
+
type InferDimensionKeys<K> = K extends AugmentedRegistry<MetricRegistry> ? MetricRegistry[K] extends {
|
|
211
|
+
dimensionKeys: infer D;
|
|
212
|
+
} ? D : string : string;
|
|
213
|
+
type InferTimeGrains<K> = K extends AugmentedRegistry<MetricRegistry> ? MetricRegistry[K] extends {
|
|
214
|
+
timeGrains: infer G;
|
|
215
|
+
} ? G : string : string;
|
|
200
216
|
/**
|
|
201
|
-
*
|
|
202
|
-
* `
|
|
217
|
+
* Infers the full row shape (every measure + dimension) from registry
|
|
218
|
+
* otherwise a total `Record<string, unknown>`.
|
|
203
219
|
*/
|
|
204
|
-
|
|
220
|
+
type InferMetricRow<K> = K extends AugmentedRegistry<MetricRegistry> ? MetricRegistry[K] extends {
|
|
221
|
+
measures: infer Meas;
|
|
222
|
+
dimensions: infer Dim;
|
|
223
|
+
} ? Meas & Dim : Record<string, unknown> : Record<string, unknown>;
|
|
224
|
+
type PickMetricRow<K, M extends ReadonlyArray<PropertyKey>, D extends ReadonlyArray<PropertyKey>> = K extends AugmentedRegistry<MetricRegistry> ? MetricRegistry[K] extends {
|
|
225
|
+
measures: infer Meas;
|
|
226
|
+
dimensions: infer Dim;
|
|
227
|
+
} ? Pick<Meas, Extract<M[number], keyof Meas>> & Pick<Dim, Extract<D[number], keyof Dim>> : Record<string, unknown> : Record<string, unknown>;
|
|
228
|
+
type MetricDimensionMeta<K> = K extends AugmentedRegistry<MetricRegistry> ? MetricRegistry[K] extends {
|
|
229
|
+
metadata: {
|
|
230
|
+
dimensions: infer DM;
|
|
231
|
+
};
|
|
232
|
+
} ? DM : never : never;
|
|
233
|
+
/**
|
|
234
|
+
* The dimension keys of K that are TEMPORAL — i.e. carry a `time_grain` tuple in
|
|
235
|
+
* the generated metadata. Only these can be a `timeDimension`.
|
|
236
|
+
* Degrades to `string` for an unknown key.
|
|
237
|
+
*/
|
|
238
|
+
type InferTimeDimensionKeys<K> = K extends AugmentedRegistry<MetricRegistry> ? { [P in keyof MetricDimensionMeta<K>]: MetricDimensionMeta<K>[P] extends {
|
|
239
|
+
time_grain: unknown;
|
|
240
|
+
} ? P : never }[keyof MetricDimensionMeta<K>] : string;
|
|
241
|
+
/**
|
|
242
|
+
* The valid grains for the SELECTED temporal dimensions `D` of K — the union of
|
|
243
|
+
* each selected temporal dimension's `time_grain` tuple. In practice grains are
|
|
244
|
+
* type-driven (all `timestamp` dims share one set, all `date` dims another), so
|
|
245
|
+
* the union is exactly the grains applicable to the query. Falls back to the
|
|
246
|
+
* metric's whole `timeGrains` union (or `string`) for an unknown/degraded key.
|
|
247
|
+
*/
|
|
248
|
+
type GrainsForSelectedTimeDims<K, D extends ReadonlyArray<PropertyKey>> = K extends AugmentedRegistry<MetricRegistry> ? { [P in Extract<D[number], keyof MetricDimensionMeta<K>>]: MetricDimensionMeta<K>[P] extends {
|
|
249
|
+
time_grain: infer G extends readonly unknown[];
|
|
250
|
+
} ? G[number] : never }[Extract<D[number], keyof MetricDimensionMeta<K>>] : string;
|
|
251
|
+
/**
|
|
252
|
+
* Options for configuring a `useMetricView` query.
|
|
253
|
+
*
|
|
254
|
+
* Generic over the selected measure tuple `M` and dimension tuple `D` so the
|
|
255
|
+
* returned row shape ({@link PickMetricRow}) narrows to exactly the columns the
|
|
256
|
+
* query asked for. `timeDimension` must be a SELECTED, TEMPORAL dimension, and
|
|
257
|
+
* `timeGrain` is correlated to the grains valid for those dimensions — so
|
|
258
|
+
* bucketing a non-temporal dimension is a type error. `orderBy` is constrained
|
|
259
|
+
* to SELECTED measures and dimensions, so ordering by an unselected column is a
|
|
260
|
+
* compile-time error.
|
|
261
|
+
*/
|
|
262
|
+
type MetricViewTimeOptions<K extends MetricKey, D extends ReadonlyArray<InferDimensionKeys<K>>> = {
|
|
263
|
+
timeGrain?: undefined;
|
|
264
|
+
timeDimension?: Extract<D[number], InferTimeDimensionKeys<K>>;
|
|
265
|
+
} | {
|
|
266
|
+
timeGrain: GrainsForSelectedTimeDims<K, D>;
|
|
267
|
+
timeDimension: Extract<D[number], InferTimeDimensionKeys<K>>;
|
|
268
|
+
};
|
|
269
|
+
type UseMetricViewOptions<K extends MetricKey = MetricKey, M extends ReadonlyArray<InferMeasureKeys<K>> = ReadonlyArray<InferMeasureKeys<K>>, D extends ReadonlyArray<InferDimensionKeys<K>> = ReadonlyArray<InferDimensionKeys<K>>> = {
|
|
270
|
+
measures: M;
|
|
271
|
+
dimensions?: D;
|
|
272
|
+
filter?: MetricFilter;
|
|
273
|
+
orderBy?: ReadonlyArray<MetricOrderBy<M[number] | D[number]>>;
|
|
274
|
+
limit?: number; /** Whether to automatically start the metric query. Default is true. */
|
|
275
|
+
autoStart?: boolean;
|
|
276
|
+
} & MetricViewTimeOptions<K, D>;
|
|
277
|
+
interface UseMetricViewResult<T = Record<string, unknown>[]> {
|
|
278
|
+
data: T | null;
|
|
279
|
+
loading: boolean;
|
|
280
|
+
error: string | null;
|
|
281
|
+
/** Structured upstream error code, mirroring useAnalyticsQuery. */
|
|
282
|
+
errorCode: string | null;
|
|
283
|
+
/** Per-column display metadata for the queried columns, carried in the SSE result payload. `undefined` when the server injected no metadata (dormant / unknown key). */
|
|
284
|
+
metadata: Record<string, MetricViewColumnDisplay> | undefined;
|
|
285
|
+
/**
|
|
286
|
+
* Latest warehouse status emitted while waiting for the SQL warehouse to
|
|
287
|
+
* reach RUNNING. `null` until the current request receives a status event.
|
|
288
|
+
*/
|
|
289
|
+
warehouseStatus: WarehouseStatus | null;
|
|
290
|
+
}
|
|
205
291
|
//#endregion
|
|
206
|
-
export { AiSearchClientConfig, AiSearchIndexSummary, AiSearchQueryType, AiSearchRequest, AiSearchResponse, AiSearchResult, AnalyticsFormat, InferParams, InferResultByFormat, InferRowType, InferServingChunk, InferServingRequest, InferServingResponse, MetricRegistry, PluginRegistry, QueryKey, QueryRegistry, ServingAlias, ServingEndpointRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult, WarehouseState, WarehouseStatus };
|
|
292
|
+
export { AiSearchClientConfig, AiSearchIndexSummary, AiSearchQueryType, AiSearchRequest, AiSearchResponse, AiSearchResult, AnalyticsFormat, GrainsForSelectedTimeDims, InferDimensionKeys, InferMeasureKeys, InferMetricRow, InferParams, InferResultByFormat, InferRowType, InferServingChunk, InferServingRequest, InferServingResponse, InferTimeDimensionKeys, InferTimeGrains, MetricKey, MetricRegistry, PickMetricRow, PluginRegistry, QueryKey, QueryRegistry, ServingAlias, ServingEndpointRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult, UseMetricViewOptions, UseMetricViewResult, WarehouseState, WarehouseStatus };
|
|
207
293
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/hooks/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/hooks/types.ts"],"mappings":";;;;;;;;;;;;AAcA;;KAAY,eAAA,yIAkBZ;;;;;;;;;;;UAAiB,eAAA,cACF,MAAA,oBAA0B,MAAA,2BAC/B,KAAA;EAKC;;;;EAAA,SAAA,SAAA,GAAY,IAAA;AAAA;;UAQN,wBAAA,WACL,eAAA;EAAV;EAGA,MAAA,GAAS,CAAA;EAAT;EAGA,iBAAA;EAAA;EAGA,SAAA;AAAA;;AAQF;;;;KAAY,cAAA;AAkBZ;;;;;;;;;AAOA;AAPA,UAAiB,eAAA;EACf,KAAA,EAAO,cAAA;EA0ByB;EAxBhC,SAAA;AAAA;;UAIe,uBAAA;EAMf;EAJA,IAAA,EAAM,CAAA;EAkBN;EAhBA,OAAA;EAgBgC;EAdhC,KAAA;EAmCe;;;;;;;EA3Bf,SAAA;EA+BE;;;AAKJ;;EA9BE,eAAA,EAAiB,eAAA;AAAA;;;;;;;;;;;;;;;;AAmCnB;;;UAdiB,aAAA;EAAA,CACd,GAAA;IACC,IAAA;IACA,UAAA,EAAY,MAAA;IACZ,MAAA;EAAA;AAAA;;KAKQ,iBAAA,0BACE,CAAA,mBAAoB,CAAA,WAAY,CAAA,GAAI,CAAA,CAAE,CAAA;;KAIxC,QAAA,GAAW,iBAAA,CAAkB,aAAA,2BAErC,iBAAA,CAAkB,aAAA;;;;;KAMV,WAAA,SAAoB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACxD,aAAA,CAAc,CAAA;EAAa,MAAA;AAAA,IACzB,CAAA,GACA,CAAA,GACF,CAAA;;;;;KAMQ,YAAA,MAAkB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACtD,aAAA,CAAc,CAAA;EAAa,MAAA,EAAQ,KAAA;AAAA,IACjC,CAAA,SAAU,MAAA,oBACR,CAAA,GACA,MAAA,oBACF,MAAA,oBACF,MAAA;;;;;;KAOQ,mBAAA,iBAAoC,eAAA,IAAmB,CAAA,oCAG/D,eAAA,CAAgB,YAAA,CAAa,CAAA,KAC7B,WAAA,CAAY,CAAA,EAAG,CAAA;;;;KAKP,WAAA,MAAiB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACrD,aAAA,CAAc,CAAA;EAAa,UAAA;AAAA,IACzB,CAAA,GACA,MAAA,oBACF,MAAA;AAAA,UAEa,cAAA;EAAA,CACd,GAAA,WAAc,MAAA;AAAA;AAAA,KAYL,iBAAA;;UAGK,oBAAA;EACf,KAAA;EACA,SAAA,EAAW,iBAAA;EACX,UAAA;AAAA;;UAIe,oBAAA;EACf,OAAA,EAAS,oBAAA;AAAA;AAAA,UAGM,eAAA;EACf,SAAA;EACA,WAAA;EACA,OAAA;EACA,UAAA;EACA,SAAA,GAAY,iBAAA;EACZ,OAAA,GAAU,MAAA;EACV,QAAA;AAAA;AAAA,UAGe,cAAA,WACL,MAAA,oBAA0B,MAAA;EAEpC,KAAA;EACA,IAAA,EAAM,CAAA;AAAA;AAAA,UAGS,gBAAA,WACL,MAAA,oBAA0B,MAAA;EAEpC,OAAA,EAAS,cAAA,CAAe,CAAA;EACxB,UAAA;EACA,WAAA;EACA,SAAA,EAAW,iBAAA;EACX,aAAA;AAAA;;;;;;;;;;;;;;;UAsBe,uBAAA;;KAGL,YAAA,GACV,iBAAA,CAAkB,uBAAA,2BAEd,iBAAA,CAAkB,uBAAA;;KAGZ,iBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,KAAA;AAAA,IACnC,CAAA;;KAKI,oBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,QAAA;AAAA,IACnC,CAAA;;KAKI,mBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,OAAA;AAAA,IACnC,GAAA,GACA,MAAA,oBACF,MAAA;;UAQW,cAAA;AAAA,KAEL,SAAA,GAAY,iBAAA,CAAkB,cAAA,2BAEtC,iBAAA,CAAkB,cAAA;AAAA,KAEV,gBAAA,MAAsB,CAAA,SAAU,iBAAA,CAAkB,cAAA,IAC1D,cAAA,CAAe,CAAA;EAAa,WAAA;AAAA,IAC1B,CAAA;AAAA,KAIM,kBAAA,MAAwB,CAAA,SAAU,iBAAA,CAAkB,cAAA,IAC5D,cAAA,CAAe,CAAA;EAAa,aAAA;AAAA,IAC1B,CAAA;AAAA,KAIM,eAAA,MAAqB,CAAA,SAAU,iBAAA,CAAkB,cAAA,IACzD,cAAA,CAAe,CAAA;EAAa,UAAA;AAAA,IAC1B,CAAA;;AAnIN;;;KA2IY,cAAA,MAAoB,CAAA,SAAU,iBAAA,CAAkB,cAAA,IACxD,cAAA,CAAe,CAAA;EACb,QAAA;EACA,UAAA;AAAA,IAEA,IAAA,GAAO,GAAA,GACP,MAAA,oBACF,MAAA;AAAA,KAEQ,aAAA,cAEA,aAAA,CAAc,WAAA,aACd,aAAA,CAAc,WAAA,KACtB,CAAA,SAAU,iBAAA,CAAkB,cAAA,IAC5B,cAAA,CAAe,CAAA;EACb,QAAA;EACA,UAAA;AAAA,IAEA,IAAA,CAAK,IAAA,EAAM,OAAA,CAAQ,CAAA,gBAAiB,IAAA,KAClC,IAAA,CAAK,GAAA,EAAK,OAAA,CAAQ,CAAA,gBAAiB,GAAA,KACrC,MAAA,oBACF,MAAA;AAAA,KAEC,mBAAA,MAAyB,CAAA,SAAU,iBAAA,CAAkB,cAAA,IACtD,cAAA,CAAe,CAAA;EAAa,QAAA;IAAY,UAAA;EAAA;AAAA,IACtC,EAAA;;;;;AA7IN;KAsJY,sBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,cAAA,kBAEV,mBAAA,CAAoB,CAAA,IAAK,mBAAA,CAAoB,CAAA,EAAG,CAAA;EAC1D,UAAA;AAAA,IAEE,CAAA,iBAEE,mBAAA,CAAoB,CAAA;;;;;;;;KAUtB,yBAAA,cAEA,aAAA,CAAc,WAAA,KACtB,CAAA,SAAU,iBAAA,CAAkB,cAAA,YAEpB,OAAA,CACJ,CAAA,gBACM,mBAAA,CAAoB,CAAA,KACxB,mBAAA,CAAoB,CAAA,EAAG,CAAA;EACzB,UAAA;AAAA,IAEE,CAAA,mBAEJ,OAAA,CAAQ,CAAA,gBAAiB,mBAAA,CAAoB,CAAA;AAvKnD;;;;;;;;;;;AAAA,KAgMK,qBAAA,WACO,SAAA,YACA,aAAA,CAAc,kBAAA,CAAmB,CAAA;EAGvC,SAAA;EACA,aAAA,GAAgB,OAAA,CAAQ,CAAA,UAAW,sBAAA,CAAuB,CAAA;AAAA;EAG1D,SAAA,EAAW,yBAAA,CAA0B,CAAA,EAAG,CAAA;EACxC,aAAA,EAAe,OAAA,CAAQ,CAAA,UAAW,sBAAA,CAAuB,CAAA;AAAA;AAAA,KAGnD,oBAAA,WACA,SAAA,GAAY,SAAA,YACZ,aAAA,CAAc,gBAAA,CAAiB,CAAA,KAAM,aAAA,CAC7C,gBAAA,CAAiB,CAAA,cAET,aAAA,CAAc,kBAAA,CAAmB,CAAA,KAAM,aAAA,CAC/C,kBAAA,CAAmB,CAAA;EAGrB,QAAA,EAAU,CAAA;EACV,UAAA,GAAa,CAAA;EACb,MAAA,GAAS,YAAA;EACT,OAAA,GAAU,aAAA,CAAc,aAAA,CAAc,CAAA,WAAY,CAAA;EAClD,KAAA,WA7M4B;EA+M5B,SAAA;AAAA,IACE,qBAAA,CAAsB,CAAA,EAAG,CAAA;AAAA,UAEZ,mBAAA,KAAwB,MAAA;EACvC,IAAA,EAAM,CAAA;EACN,OAAA;EACA,KAAA;EAxNwB;EA0NxB,SAAA;EAxNA;EA0NA,QAAA,EAAU,MAAA,SAAe,uBAAA;EAzNd;;;;EA8NX,eAAA,EAAiB,eAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-analytics-query.d.ts","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"use-analytics-query.d.ts","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"mappings":";;;;;AA2MA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,iBAAA,wBAEJ,QAAA,GAAW,QAAA,YACX,eAAA,gBAAA,CAEV,QAAA,EAAU,CAAA,EACV,UAAA,GAAa,WAAA,CAAY,CAAA,UACzB,OAAA,GAAS,wBAAA,CAAyB,CAAA,IACjC,uBAAA,CAAwB,mBAAA,CAAoB,CAAA,EAAG,CAAA,EAAG,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArrowClient } from "../../js/arrow/arrow-client.js";
|
|
2
2
|
import { connectSSE } from "../../js/sse/connect-sse.js";
|
|
3
|
+
import { GENERIC_LOAD_ERROR, getDevMode, handleAnalyticsSseError, handleAnalyticsSseMessage, userFacingFetchError } from "./analytics-sse.js";
|
|
3
4
|
import { useAnalyticsWarehousePublisher } from "./use-analytics-warehouse-status.js";
|
|
4
5
|
import { useQueryHMR } from "./use-query-hmr.js";
|
|
5
6
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
@@ -24,55 +25,6 @@ function useStableParams(value) {
|
|
|
24
25
|
if (!shallowEqualParams(ref.current, value)) ref.current = value;
|
|
25
26
|
return ref.current;
|
|
26
27
|
}
|
|
27
|
-
function getDevMode() {
|
|
28
|
-
const dev = new URL(window.location.href).searchParams.get("dev");
|
|
29
|
-
return dev ? `?dev=${dev}` : "";
|
|
30
|
-
}
|
|
31
|
-
const GENERIC_LOAD_ERROR = "Unable to load data, please try again";
|
|
32
|
-
/** Map a fetch/SSE transport error to a user-facing message. */
|
|
33
|
-
function userFacingFetchError(error) {
|
|
34
|
-
if (error instanceof Error) {
|
|
35
|
-
if (error.name === "AbortError") return "Request timed out, please try again";
|
|
36
|
-
if (error.message.includes("Failed to fetch")) return "Network error. Please check your connection.";
|
|
37
|
-
}
|
|
38
|
-
return GENERIC_LOAD_ERROR;
|
|
39
|
-
}
|
|
40
|
-
function isWarehouseStatusPayload(value) {
|
|
41
|
-
return typeof value === "object" && value !== null && typeof value.state === "string";
|
|
42
|
-
}
|
|
43
|
-
async function handleAnalyticsSseMessage(parsed, ctx) {
|
|
44
|
-
if (parsed.type === "warehouse_status") {
|
|
45
|
-
if (!isWarehouseStatusPayload(parsed.status)) {
|
|
46
|
-
ctx.setLoading(false);
|
|
47
|
-
ctx.setError(GENERIC_LOAD_ERROR);
|
|
48
|
-
ctx.unpublishWarehouseStatus();
|
|
49
|
-
console.error("[useAnalyticsQuery] Malformed warehouse_status event", parsed);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
ctx.setWarehouseStatus(parsed.status);
|
|
53
|
-
ctx.publishWarehouseStatus(parsed.status);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (parsed.type === "result") {
|
|
57
|
-
ctx.setLoading(false);
|
|
58
|
-
ctx.setData(Array.isArray(parsed.data) ? parsed.data : []);
|
|
59
|
-
ctx.unpublishWarehouseStatus();
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (parsed.type === "error" || parsed.error || parsed.code) {
|
|
63
|
-
const errorMsg = parsed.error || parsed.message || "Unable to execute query";
|
|
64
|
-
ctx.setLoading(false);
|
|
65
|
-
ctx.setError(errorMsg);
|
|
66
|
-
ctx.unpublishWarehouseStatus();
|
|
67
|
-
if (typeof parsed.errorCode === "string") ctx.setErrorCode(parsed.errorCode);
|
|
68
|
-
if (parsed.code) console.error(`[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
console.error("[useAnalyticsQuery] Unrecognized SSE payload", parsed);
|
|
72
|
-
ctx.setLoading(false);
|
|
73
|
-
ctx.setError(GENERIC_LOAD_ERROR);
|
|
74
|
-
ctx.unpublishWarehouseStatus();
|
|
75
|
-
}
|
|
76
28
|
/**
|
|
77
29
|
* Fetch the real column names for a statement from the fallback endpoint,
|
|
78
30
|
* used when a very wide schema's names didn't fit in the response header.
|
|
@@ -231,40 +183,28 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
231
183
|
return;
|
|
232
184
|
}
|
|
233
185
|
const sseContext = {
|
|
186
|
+
source: "useAnalyticsQuery",
|
|
187
|
+
resource: { queryKey },
|
|
188
|
+
defaultExecutionError: "Unable to execute query",
|
|
189
|
+
unpublishOnMalformedMessage: false,
|
|
190
|
+
signal: abortController.signal,
|
|
191
|
+
abort: () => abortController.abort(),
|
|
234
192
|
setLoading,
|
|
235
193
|
setError,
|
|
236
194
|
setErrorCode,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
195
|
+
onWarehouseStatus: (status) => {
|
|
196
|
+
setWarehouseStatus(status);
|
|
197
|
+
publishWarehouseStatus(status);
|
|
198
|
+
},
|
|
199
|
+
onResult: (message) => setData(message.data),
|
|
240
200
|
unpublishWarehouseStatus
|
|
241
201
|
};
|
|
242
202
|
connectSSE({
|
|
243
203
|
url: urlSuffix,
|
|
244
204
|
payload,
|
|
245
205
|
signal: abortController.signal,
|
|
246
|
-
onMessage:
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
await handleAnalyticsSseMessage(JSON.parse(message.data), sseContext);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
console.warn("[useAnalyticsQuery] Malformed message received", error);
|
|
252
|
-
setLoading(false);
|
|
253
|
-
setError(GENERIC_LOAD_ERROR);
|
|
254
|
-
abortController.abort();
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
onError: (error) => {
|
|
258
|
-
if (abortController.signal.aborted) return;
|
|
259
|
-
setLoading(false);
|
|
260
|
-
unpublishWarehouseStatus();
|
|
261
|
-
if (error instanceof Error) console.error("[useAnalyticsQuery] Error", {
|
|
262
|
-
queryKey,
|
|
263
|
-
error: error.message,
|
|
264
|
-
stack: error.stack
|
|
265
|
-
});
|
|
266
|
-
setError(userFacingFetchError(error));
|
|
267
|
-
}
|
|
206
|
+
onMessage: (message) => handleAnalyticsSseMessage(message.data, sseContext),
|
|
207
|
+
onError: (error) => handleAnalyticsSseError(error, sseContext)
|
|
268
208
|
});
|
|
269
209
|
}, [
|
|
270
210
|
queryKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-analytics-query.js","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { ArrowClient, connectSSE } from \"@/js\";\nimport type {\n AnalyticsFormat,\n InferParams,\n InferResultByFormat,\n QueryKey,\n UseAnalyticsQueryOptions,\n UseAnalyticsQueryResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\n/** Shallow equality for plain-object query parameters (primitive values only). */\nfunction shallowEqualParams(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false;\n if (\n !Object.is(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n return true;\n}\n\n/** Keep structurally-equal params referentially stable across renders. */\nfunction useStableParams<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!shallowEqualParams(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n\nfunction getDevMode(): string {\n const dev = new URL(window.location.href).searchParams.get(\"dev\");\n return dev ? `?dev=${dev}` : \"\";\n}\n\nconst GENERIC_LOAD_ERROR = \"Unable to load data, please try again\";\n\n/** Map a fetch/SSE transport error to a user-facing message. */\nfunction userFacingFetchError(error: unknown): string {\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n return \"Request timed out, please try again\";\n }\n if (error.message.includes(\"Failed to fetch\")) {\n return \"Network error. Please check your connection.\";\n }\n }\n return GENERIC_LOAD_ERROR;\n}\n\ninterface AnalyticsQuerySseContext<ResultType> {\n setLoading: (loading: boolean) => void;\n setError: (error: string | null) => void;\n setErrorCode: (code: string | null) => void;\n setData: (data: ResultType | null) => void;\n setWarehouseStatus: (status: WarehouseStatus | null) => void;\n publishWarehouseStatus: (status: WarehouseStatus | null) => void;\n unpublishWarehouseStatus: () => void;\n}\n\nfunction isWarehouseStatusPayload(value: unknown): value is WarehouseStatus {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as WarehouseStatus).state === \"string\"\n );\n}\n\nasync function handleAnalyticsSseMessage<ResultType>(\n parsed: Record<string, unknown>,\n ctx: AnalyticsQuerySseContext<ResultType>,\n): Promise<void> {\n if (parsed.type === \"warehouse_status\") {\n if (!isWarehouseStatusPayload(parsed.status)) {\n ctx.setLoading(false);\n ctx.setError(GENERIC_LOAD_ERROR);\n ctx.unpublishWarehouseStatus();\n console.error(\n \"[useAnalyticsQuery] Malformed warehouse_status event\",\n parsed,\n );\n return;\n }\n ctx.setWarehouseStatus(parsed.status);\n ctx.publishWarehouseStatus(parsed.status);\n return;\n }\n\n // JSON result. The SSE wire schema is intentionally loose (`data` is an\n // optional array of unknown values), so a structural check is enough here —\n // no need to ship a schema validator (zod, ~60 KB gz) to the browser just\n // to read our own same-origin server's messages. Missing or non-array\n // `data` normalizes to [] so `undefined` never bleeds into the hook's\n // `T | null` state.\n if (parsed.type === \"result\") {\n ctx.setLoading(false);\n ctx.setData((Array.isArray(parsed.data) ? parsed.data : []) as ResultType);\n ctx.unpublishWarehouseStatus();\n return;\n }\n\n // NOTE: ARROW_STREAM no longer flows over SSE — the server streams the\n // raw Arrow IPC bytes back as the query response body, handled by\n // `fetchArrowDirect` instead of this SSE handler.\n\n if (parsed.type === \"error\" || parsed.error || parsed.code) {\n const errorMsg =\n (parsed.error as string | undefined) ||\n (parsed.message as string | undefined) ||\n \"Unable to execute query\";\n ctx.setLoading(false);\n ctx.setError(errorMsg);\n ctx.unpublishWarehouseStatus();\n // Propagate the upstream structured code so UI consumers can branch on\n // a stable identifier (e.g. format-switch on\n // RESULT_TOO_LARGE_FOR_JSON_FALLBACK or ARROW_DELIVERY_UNSUPPORTED)\n // instead of parsing the human-readable message.\n if (typeof parsed.errorCode === \"string\") {\n ctx.setErrorCode(parsed.errorCode);\n }\n if (parsed.code) {\n console.error(\n `[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`,\n );\n }\n return;\n }\n\n // Not a warehouse-status, result, or error event — surface a generic error\n // rather than silently dropping an unrecognized payload.\n console.error(\"[useAnalyticsQuery] Unrecognized SSE payload\", parsed);\n ctx.setLoading(false);\n ctx.setError(GENERIC_LOAD_ERROR);\n ctx.unpublishWarehouseStatus();\n}\n\ninterface ArrowDirectContext {\n url: string;\n payload: string;\n signal: AbortSignal;\n setLoading: (loading: boolean) => void;\n setError: (error: string | null) => void;\n setErrorCode: (code: string | null) => void;\n setData: (data: unknown) => void;\n unpublishWarehouseStatus: () => void;\n}\n\n/**\n * Fetch the real column names for a statement from the fallback endpoint,\n * used when a very wide schema's names didn't fit in the response header.\n * Returns undefined on any failure so decoding falls back to the raw Arrow\n * schema names.\n */\nasync function fetchArrowColumns(\n statementId: string,\n signal: AbortSignal,\n): Promise<string[] | undefined> {\n try {\n const res = await fetch(\n `/api/analytics/columns/${encodeURIComponent(statementId)}`,\n { signal },\n );\n if (!res.ok) return undefined;\n const body = (await res.json()) as { columns?: unknown };\n return Array.isArray(body.columns) ? (body.columns as string[]) : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Fetch an ARROW_STREAM query result as raw Arrow IPC bytes directly from\n * the query endpoint (no SSE, no second /arrow-result request) and decode\n * it into a Table. The server streams the bytes back as the POST response\n * body; errors before the first byte arrive as a JSON `{ error, errorCode }`.\n */\nasync function fetchArrowDirect(ctx: ArrowDirectContext): Promise<void> {\n try {\n const response = await fetch(ctx.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: ctx.payload,\n signal: ctx.signal,\n });\n if (ctx.signal.aborted) return;\n\n if (!response.ok) {\n let message = GENERIC_LOAD_ERROR;\n let code: string | null = null;\n try {\n const body = (await response.json()) as {\n error?: string;\n errorCode?: string;\n };\n if (body.error) message = body.error;\n if (typeof body.errorCode === \"string\") code = body.errorCode;\n } catch {\n // Non-JSON error body — keep the generic message.\n }\n ctx.setLoading(false);\n ctx.setError(message);\n if (code) ctx.setErrorCode(code);\n ctx.unpublishWarehouseStatus();\n return;\n }\n\n const buffer = await response.arrayBuffer();\n if (ctx.signal.aborted) return;\n // Databricks encodes ARROW_STREAM columns positionally (col_0, …); the\n // server sends the real manifest names so we can relabel the decoded\n // Table (charts look columns up by name). Normally inline in the\n // `X-Appkit-Arrow-Columns` header; for very wide schemas the header\n // carries only a statement-id reference and we fetch the names.\n let columnNames: string[] | undefined;\n const header = response.headers.get(\"X-Appkit-Arrow-Columns\");\n if (header) {\n try {\n columnNames = JSON.parse(decodeURIComponent(header));\n } catch {\n // Malformed header — fall back to the raw Arrow schema names.\n }\n } else {\n const ref = response.headers.get(\"X-Appkit-Arrow-Columns-Ref\");\n if (ref) {\n columnNames = await fetchArrowColumns(ref, ctx.signal);\n }\n }\n const table = await ArrowClient.processArrowBuffer(\n new Uint8Array(buffer),\n columnNames,\n );\n ctx.setData(table);\n ctx.setLoading(false);\n ctx.unpublishWarehouseStatus();\n } catch (error) {\n if (ctx.signal.aborted) return;\n ctx.setLoading(false);\n ctx.unpublishWarehouseStatus();\n ctx.setError(userFacingFetchError(error));\n }\n}\n\n/**\n * Subscribe to an analytics query and return its latest result. JSON_ARRAY\n * results stream over SSE (with warehouse-readiness progress); ARROW_STREAM\n * results are fetched as raw Arrow bytes directly from the query endpoint.\n * Integration hook between client and analytics plugin.\n *\n * The return type is automatically inferred based on the format:\n * - `format: \"JSON_ARRAY\"` (default): Returns typed array from QueryRegistry\n * - `format: \"ARROW_STREAM\"`: Returns TypedArrowTable with row type preserved\n *\n * Note: User context execution is determined by query file naming:\n * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation)\n * - `queryKey.sql`: Executes as service principal\n *\n * @param queryKey - Analytics query identifier\n * @param parameters - Query parameters (type-safe based on QueryRegistry)\n * @param options - Analytics query settings including format\n * @returns Query result state with format-appropriate data type\n *\n * @example JSON_ARRAY format (default)\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params);\n * // data: Array<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n *\n * @example ARROW_STREAM format\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params, { format: \"ARROW_STREAM\" });\n * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n */\nexport function useAnalyticsQuery<\n T = unknown,\n K extends QueryKey = QueryKey,\n F extends AnalyticsFormat = \"JSON_ARRAY\",\n>(\n queryKey: K,\n parameters?: InferParams<K> | null,\n options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,\n): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {\n const format = options?.format ?? \"JSON_ARRAY\";\n const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;\n const autoStart = options?.autoStart ?? true;\n\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/query/${encodeURIComponent(queryKey)}${devMode}`;\n\n type ResultType = InferResultByFormat<T, K, F>;\n const [data, setData] = useState<ResultType | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, queryKey);\n\n if (!queryKey || queryKey.trim().length === 0) {\n throw new Error(\n \"useAnalyticsQuery: 'queryKey' must be a non-empty string.\",\n );\n }\n\n const stableParameters = useStableParams(parameters);\n\n const payload = useMemo(() => {\n try {\n const serialized = JSON.stringify({\n parameters: stableParameters,\n format,\n });\n const sizeInBytes = new Blob([serialized]).size;\n if (sizeInBytes > maxParametersSize) {\n throw new Error(\n \"useAnalyticsQuery: Parameters size exceeds the maximum allowed size\",\n );\n }\n\n return serialized;\n } catch (error) {\n console.error(\"useAnalyticsQuery: Failed to serialize parameters\", error);\n return null;\n }\n }, [stableParameters, format, maxParametersSize]);\n\n const start = useCallback(() => {\n if (payload === null) {\n setError(\"Failed to serialize query parameters\");\n return;\n }\n\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setErrorCode(null);\n setData(null);\n setWarehouseStatus(null);\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n // ARROW_STREAM: the server streams raw Arrow IPC bytes back on the query\n // response body (no SSE). Fetch and decode directly.\n if (format === \"ARROW_STREAM\") {\n void fetchArrowDirect({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n setLoading,\n setError,\n setErrorCode,\n setData: (table) => setData(table as ResultType),\n unpublishWarehouseStatus,\n });\n return;\n }\n\n const sseContext: AnalyticsQuerySseContext<ResultType> = {\n setLoading,\n setError,\n setErrorCode,\n setData,\n setWarehouseStatus,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n // Drop late envelopes from a stream whose controller was already\n // aborted (React StrictMode unmount→remount). Mirrors onError below.\n if (abortController.signal.aborted) return;\n try {\n const parsed = JSON.parse(message.data) as Record<string, unknown>;\n await handleAnalyticsSseMessage(parsed, sseContext);\n } catch (error) {\n // A `JSON.parse` failure (or any other thrown error inside the\n // SSE message handler) used to leave the hook permanently in\n // `loading=true` with no error surfaced — the UI would just\n // spin forever. Clear loading and report a user-facing error\n // so the consumer can render a retry affordance.\n //\n // We also abort the SSE connection: if the upstream is\n // emitting un-parseable frames, leaving the stream open just\n // re-fires the same failure on the next message. Closing\n // forces the consumer into a clean retry path.\n console.warn(\"[useAnalyticsQuery] Malformed message received\", error);\n setLoading(false);\n setError(GENERIC_LOAD_ERROR);\n abortController.abort();\n }\n },\n onError: (error) => {\n if (abortController.signal.aborted) return;\n setLoading(false);\n unpublishWarehouseStatus();\n\n if (error instanceof Error) {\n console.error(\"[useAnalyticsQuery] Error\", {\n queryKey,\n error: error.message,\n stack: error.stack,\n });\n }\n setError(userFacingFetchError(error));\n },\n });\n }, [\n queryKey,\n payload,\n urlSuffix,\n format,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n if (autoStart) {\n start();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n unpublishWarehouseStatus();\n };\n }, [start, autoStart, unpublishWarehouseStatus]);\n\n useQueryHMR(queryKey, start);\n\n return { data, loading, error, errorCode, warehouseStatus };\n}\n"],"mappings":";;;;;;;;AAsBA,SAAS,mBAAmB,GAAY,GAAqB;AAC3D,KAAI,OAAO,GAAG,GAAG,EAAE,CAAE,QAAO;AAC5B,KACE,MAAM,QACN,MAAM,QACN,OAAO,MAAM,YACb,OAAO,MAAM,SAEb,QAAO;CAET,MAAM,QAAQ,OAAO,KAAK,EAA6B;CACvD,MAAM,QAAQ,OAAO,KAAK,EAA6B;AACvD,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,MAAK,MAAM,OAAO,OAAO;AACvB,MAAI,CAAC,OAAO,OAAO,GAAG,IAAI,CAAE,QAAO;AACnC,MACE,CAAC,OAAO,GACL,EAA8B,MAC9B,EAA8B,KAChC,CAED,QAAO;;AAGX,QAAO;;;AAIT,SAAS,gBAAmB,OAAa;CACvC,MAAM,MAAM,OAAU,MAAM;AAC5B,KAAI,CAAC,mBAAmB,IAAI,SAAS,MAAM,CACzC,KAAI,UAAU;AAEhB,QAAO,IAAI;;AAGb,SAAS,aAAqB;CAC5B,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,KAAK,CAAC,aAAa,IAAI,MAAM;AACjE,QAAO,MAAM,QAAQ,QAAQ;;AAG/B,MAAM,qBAAqB;;AAG3B,SAAS,qBAAqB,OAAwB;AACpD,KAAI,iBAAiB,OAAO;AAC1B,MAAI,MAAM,SAAS,aACjB,QAAO;AAET,MAAI,MAAM,QAAQ,SAAS,kBAAkB,CAC3C,QAAO;;AAGX,QAAO;;AAaT,SAAS,yBAAyB,OAA0C;AAC1E,QACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAA0B,UAAU;;AAIhD,eAAe,0BACb,QACA,KACe;AACf,KAAI,OAAO,SAAS,oBAAoB;AACtC,MAAI,CAAC,yBAAyB,OAAO,OAAO,EAAE;AAC5C,OAAI,WAAW,MAAM;AACrB,OAAI,SAAS,mBAAmB;AAChC,OAAI,0BAA0B;AAC9B,WAAQ,MACN,wDACA,OACD;AACD;;AAEF,MAAI,mBAAmB,OAAO,OAAO;AACrC,MAAI,uBAAuB,OAAO,OAAO;AACzC;;AASF,KAAI,OAAO,SAAS,UAAU;AAC5B,MAAI,WAAW,MAAM;AACrB,MAAI,QAAS,MAAM,QAAQ,OAAO,KAAK,GAAG,OAAO,OAAO,EAAE,CAAgB;AAC1E,MAAI,0BAA0B;AAC9B;;AAOF,KAAI,OAAO,SAAS,WAAW,OAAO,SAAS,OAAO,MAAM;EAC1D,MAAM,WACH,OAAO,SACP,OAAO,WACR;AACF,MAAI,WAAW,MAAM;AACrB,MAAI,SAAS,SAAS;AACtB,MAAI,0BAA0B;AAK9B,MAAI,OAAO,OAAO,cAAc,SAC9B,KAAI,aAAa,OAAO,UAAU;AAEpC,MAAI,OAAO,KACT,SAAQ,MACN,6BAA6B,OAAO,KAAK,aAAa,WACvD;AAEH;;AAKF,SAAQ,MAAM,gDAAgD,OAAO;AACrE,KAAI,WAAW,MAAM;AACrB,KAAI,SAAS,mBAAmB;AAChC,KAAI,0BAA0B;;;;;;;;AAoBhC,eAAe,kBACb,aACA,QAC+B;AAC/B,KAAI;EACF,MAAM,MAAM,MAAM,MAChB,0BAA0B,mBAAmB,YAAY,IACzD,EAAE,QAAQ,CACX;AACD,MAAI,CAAC,IAAI,GAAI,QAAO;EACpB,MAAM,OAAQ,MAAM,IAAI,MAAM;AAC9B,SAAO,MAAM,QAAQ,KAAK,QAAQ,GAAI,KAAK,UAAuB;SAC5D;AACN;;;;;;;;;AAUJ,eAAe,iBAAiB,KAAwC;AACtE,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,IAAI,KAAK;GACpC,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,IAAI;GACV,QAAQ,IAAI;GACb,CAAC;AACF,MAAI,IAAI,OAAO,QAAS;AAExB,MAAI,CAAC,SAAS,IAAI;GAChB,IAAI,UAAU;GACd,IAAI,OAAsB;AAC1B,OAAI;IACF,MAAM,OAAQ,MAAM,SAAS,MAAM;AAInC,QAAI,KAAK,MAAO,WAAU,KAAK;AAC/B,QAAI,OAAO,KAAK,cAAc,SAAU,QAAO,KAAK;WAC9C;AAGR,OAAI,WAAW,MAAM;AACrB,OAAI,SAAS,QAAQ;AACrB,OAAI,KAAM,KAAI,aAAa,KAAK;AAChC,OAAI,0BAA0B;AAC9B;;EAGF,MAAM,SAAS,MAAM,SAAS,aAAa;AAC3C,MAAI,IAAI,OAAO,QAAS;EAMxB,IAAI;EACJ,MAAM,SAAS,SAAS,QAAQ,IAAI,yBAAyB;AAC7D,MAAI,OACF,KAAI;AACF,iBAAc,KAAK,MAAM,mBAAmB,OAAO,CAAC;UAC9C;OAGH;GACL,MAAM,MAAM,SAAS,QAAQ,IAAI,6BAA6B;AAC9D,OAAI,IACF,eAAc,MAAM,kBAAkB,KAAK,IAAI,OAAO;;EAG1D,MAAM,QAAQ,MAAM,YAAY,mBAC9B,IAAI,WAAW,OAAO,EACtB,YACD;AACD,MAAI,QAAQ,MAAM;AAClB,MAAI,WAAW,MAAM;AACrB,MAAI,0BAA0B;UACvB,OAAO;AACd,MAAI,IAAI,OAAO,QAAS;AACxB,MAAI,WAAW,MAAM;AACrB,MAAI,0BAA0B;AAC9B,MAAI,SAAS,qBAAqB,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmC7C,SAAgB,kBAKd,UACA,YACA,UAAuC,EAAE,EACc;CACvD,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,oBAAoB,SAAS,qBAAqB,MAAM;CAC9D,MAAM,YAAY,SAAS,aAAa;CAExC,MAAM,UAAU,YAAY;CAC5B,MAAM,YAAY,wBAAwB,mBAAmB,SAAS,GAAG;CAGzE,MAAM,CAAC,MAAM,WAAW,SAA4B,KAAK;CACzD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAAwB,KAAK;CACvD,MAAM,CAAC,WAAW,gBAAgB,SAAwB,KAAK;CAC/D,MAAM,CAAC,iBAAiB,sBACtB,SAAiC,KAAK;CACxC,MAAM,qBAAqB,OAA+B,KAAK;CAG/D,MAAM,EACJ,SAAS,wBACT,WAAW,6BACT,+BAJgB,OAAO,EAIqB,SAAS;AAEzD,KAAI,CAAC,YAAY,SAAS,MAAM,CAAC,WAAW,EAC1C,OAAM,IAAI,MACR,4DACD;CAGH,MAAM,mBAAmB,gBAAgB,WAAW;CAEpD,MAAM,UAAU,cAAc;AAC5B,MAAI;GACF,MAAM,aAAa,KAAK,UAAU;IAChC,YAAY;IACZ;IACD,CAAC;AAEF,OADoB,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,OACzB,kBAChB,OAAM,IAAI,MACR,sEACD;AAGH,UAAO;WACA,OAAO;AACd,WAAQ,MAAM,qDAAqD,MAAM;AACzE,UAAO;;IAER;EAAC;EAAkB;EAAQ;EAAkB,CAAC;CAEjD,MAAM,QAAQ,kBAAkB;AAC9B,MAAI,YAAY,MAAM;AACpB,YAAS,uCAAuC;AAChD;;AAGF,qBAAmB,SAAS,OAAO;AAEnC,aAAW,KAAK;AAChB,WAAS,KAAK;AACd,eAAa,KAAK;AAClB,UAAQ,KAAK;AACb,qBAAmB,KAAK;AACxB,yBAAuB,KAAK;EAE5B,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;AAI7B,MAAI,WAAW,gBAAgB;AAC7B,GAAK,iBAAiB;IACpB,KAAK;IACL;IACA,QAAQ,gBAAgB;IACxB;IACA;IACA;IACA,UAAU,UAAU,QAAQ,MAAoB;IAChD;IACD,CAAC;AACF;;EAGF,MAAM,aAAmD;GACvD;GACA;GACA;GACA;GACA;GACA;GACA;GACD;AAED,aAAW;GACT,KAAK;GACL;GACA,QAAQ,gBAAgB;GACxB,WAAW,OAAO,YAAY;AAG5B,QAAI,gBAAgB,OAAO,QAAS;AACpC,QAAI;AAEF,WAAM,0BADS,KAAK,MAAM,QAAQ,KAAK,EACC,WAAW;aAC5C,OAAO;AAWd,aAAQ,KAAK,kDAAkD,MAAM;AACrE,gBAAW,MAAM;AACjB,cAAS,mBAAmB;AAC5B,qBAAgB,OAAO;;;GAG3B,UAAU,UAAU;AAClB,QAAI,gBAAgB,OAAO,QAAS;AACpC,eAAW,MAAM;AACjB,8BAA0B;AAE1B,QAAI,iBAAiB,MACnB,SAAQ,MAAM,6BAA6B;KACzC;KACA,OAAO,MAAM;KACb,OAAO,MAAM;KACd,CAAC;AAEJ,aAAS,qBAAqB,MAAM,CAAC;;GAExC,CAAC;IACD;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,iBAAgB;AACd,MAAI,UACF,QAAO;AAGT,eAAa;AACX,sBAAmB,SAAS,OAAO;AACnC,6BAA0B;;IAE3B;EAAC;EAAO;EAAW;EAAyB,CAAC;AAEhD,aAAY,UAAU,MAAM;AAE5B,QAAO;EAAE;EAAM;EAAS;EAAO;EAAW;EAAiB"}
|
|
1
|
+
{"version":3,"file":"use-analytics-query.js","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { ArrowClient, connectSSE } from \"@/js\";\nimport {\n type AnalyticsSseHandlerContext,\n GENERIC_LOAD_ERROR,\n getDevMode,\n handleAnalyticsSseError,\n handleAnalyticsSseMessage,\n userFacingFetchError,\n} from \"./analytics-sse\";\nimport type {\n AnalyticsFormat,\n InferParams,\n InferResultByFormat,\n QueryKey,\n UseAnalyticsQueryOptions,\n UseAnalyticsQueryResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\n/** Shallow equality for plain-object query parameters (primitive values only). */\nfunction shallowEqualParams(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false;\n if (\n !Object.is(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n return true;\n}\n\n/** Keep structurally-equal params referentially stable across renders. */\nfunction useStableParams<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!shallowEqualParams(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n\ninterface ArrowDirectContext {\n url: string;\n payload: string;\n signal: AbortSignal;\n setLoading: (loading: boolean) => void;\n setError: (error: string | null) => void;\n setErrorCode: (code: string | null) => void;\n setData: (data: unknown) => void;\n unpublishWarehouseStatus: () => void;\n}\n\n/**\n * Fetch the real column names for a statement from the fallback endpoint,\n * used when a very wide schema's names didn't fit in the response header.\n * Returns undefined on any failure so decoding falls back to the raw Arrow\n * schema names.\n */\nasync function fetchArrowColumns(\n statementId: string,\n signal: AbortSignal,\n): Promise<string[] | undefined> {\n try {\n const res = await fetch(\n `/api/analytics/columns/${encodeURIComponent(statementId)}`,\n { signal },\n );\n if (!res.ok) return undefined;\n const body = (await res.json()) as { columns?: unknown };\n return Array.isArray(body.columns) ? (body.columns as string[]) : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Fetch an ARROW_STREAM query result as raw Arrow IPC bytes directly from\n * the query endpoint (no SSE, no second /arrow-result request) and decode\n * it into a Table. The server streams the bytes back as the POST response\n * body; errors before the first byte arrive as a JSON `{ error, errorCode }`.\n */\nasync function fetchArrowDirect(ctx: ArrowDirectContext): Promise<void> {\n try {\n const response = await fetch(ctx.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: ctx.payload,\n signal: ctx.signal,\n });\n if (ctx.signal.aborted) return;\n\n if (!response.ok) {\n let message = GENERIC_LOAD_ERROR;\n let code: string | null = null;\n try {\n const body = (await response.json()) as {\n error?: string;\n errorCode?: string;\n };\n if (body.error) message = body.error;\n if (typeof body.errorCode === \"string\") code = body.errorCode;\n } catch {\n // Non-JSON error body — keep the generic message.\n }\n ctx.setLoading(false);\n ctx.setError(message);\n if (code) ctx.setErrorCode(code);\n ctx.unpublishWarehouseStatus();\n return;\n }\n\n const buffer = await response.arrayBuffer();\n if (ctx.signal.aborted) return;\n // Databricks encodes ARROW_STREAM columns positionally (col_0, …); the\n // server sends the real manifest names so we can relabel the decoded\n // Table (charts look columns up by name). Normally inline in the\n // `X-Appkit-Arrow-Columns` header; for very wide schemas the header\n // carries only a statement-id reference and we fetch the names.\n let columnNames: string[] | undefined;\n const header = response.headers.get(\"X-Appkit-Arrow-Columns\");\n if (header) {\n try {\n columnNames = JSON.parse(decodeURIComponent(header));\n } catch {\n // Malformed header — fall back to the raw Arrow schema names.\n }\n } else {\n const ref = response.headers.get(\"X-Appkit-Arrow-Columns-Ref\");\n if (ref) {\n columnNames = await fetchArrowColumns(ref, ctx.signal);\n }\n }\n const table = await ArrowClient.processArrowBuffer(\n new Uint8Array(buffer),\n columnNames,\n );\n ctx.setData(table);\n ctx.setLoading(false);\n ctx.unpublishWarehouseStatus();\n } catch (error) {\n if (ctx.signal.aborted) return;\n ctx.setLoading(false);\n ctx.unpublishWarehouseStatus();\n ctx.setError(userFacingFetchError(error));\n }\n}\n\n/**\n * Subscribe to an analytics query and return its latest result. JSON_ARRAY\n * results stream over SSE (with warehouse-readiness progress); ARROW_STREAM\n * results are fetched as raw Arrow bytes directly from the query endpoint.\n * Integration hook between client and analytics plugin.\n *\n * The return type is automatically inferred based on the format:\n * - `format: \"JSON_ARRAY\"` (default): Returns typed array from QueryRegistry\n * - `format: \"ARROW_STREAM\"`: Returns TypedArrowTable with row type preserved\n *\n * Note: User context execution is determined by query file naming:\n * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation)\n * - `queryKey.sql`: Executes as service principal\n *\n * @param queryKey - Analytics query identifier\n * @param parameters - Query parameters (type-safe based on QueryRegistry)\n * @param options - Analytics query settings including format\n * @returns Query result state with format-appropriate data type\n *\n * @example JSON_ARRAY format (default)\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params);\n * // data: Array<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n *\n * @example ARROW_STREAM format\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params, { format: \"ARROW_STREAM\" });\n * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n */\nexport function useAnalyticsQuery<\n T = unknown,\n K extends QueryKey = QueryKey,\n F extends AnalyticsFormat = \"JSON_ARRAY\",\n>(\n queryKey: K,\n parameters?: InferParams<K> | null,\n options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,\n): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {\n const format = options?.format ?? \"JSON_ARRAY\";\n const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;\n const autoStart = options?.autoStart ?? true;\n\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/query/${encodeURIComponent(queryKey)}${devMode}`;\n\n type ResultType = InferResultByFormat<T, K, F>;\n const [data, setData] = useState<ResultType | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, queryKey);\n\n if (!queryKey || queryKey.trim().length === 0) {\n throw new Error(\n \"useAnalyticsQuery: 'queryKey' must be a non-empty string.\",\n );\n }\n\n const stableParameters = useStableParams(parameters);\n\n const payload = useMemo(() => {\n try {\n const serialized = JSON.stringify({\n parameters: stableParameters,\n format,\n });\n const sizeInBytes = new Blob([serialized]).size;\n if (sizeInBytes > maxParametersSize) {\n throw new Error(\n \"useAnalyticsQuery: Parameters size exceeds the maximum allowed size\",\n );\n }\n\n return serialized;\n } catch (error) {\n console.error(\"useAnalyticsQuery: Failed to serialize parameters\", error);\n return null;\n }\n }, [stableParameters, format, maxParametersSize]);\n\n const start = useCallback(() => {\n if (payload === null) {\n setError(\"Failed to serialize query parameters\");\n return;\n }\n\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setErrorCode(null);\n setData(null);\n setWarehouseStatus(null);\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n // ARROW_STREAM: the server streams raw Arrow IPC bytes back on the query\n // response body (no SSE). Fetch and decode directly.\n if (format === \"ARROW_STREAM\") {\n void fetchArrowDirect({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n setLoading,\n setError,\n setErrorCode,\n setData: (table) => setData(table as ResultType),\n unpublishWarehouseStatus,\n });\n return;\n }\n\n const sseContext: AnalyticsSseHandlerContext = {\n source: \"useAnalyticsQuery\",\n resource: { queryKey },\n defaultExecutionError: \"Unable to execute query\",\n unpublishOnMalformedMessage: false,\n signal: abortController.signal,\n abort: () => abortController.abort(),\n setLoading,\n setError,\n setErrorCode,\n onWarehouseStatus: (status) => {\n setWarehouseStatus(status);\n publishWarehouseStatus(status);\n },\n onResult: (message) => setData(message.data as ResultType),\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: (message) =>\n handleAnalyticsSseMessage(message.data, sseContext),\n onError: (error) => handleAnalyticsSseError(error, sseContext),\n });\n }, [\n queryKey,\n payload,\n urlSuffix,\n format,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n if (autoStart) {\n start();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n unpublishWarehouseStatus();\n };\n }, [start, autoStart, unpublishWarehouseStatus]);\n\n useQueryHMR(queryKey, start);\n\n return { data, loading, error, errorCode, warehouseStatus };\n}\n"],"mappings":";;;;;;;;;AA8BA,SAAS,mBAAmB,GAAY,GAAqB;AAC3D,KAAI,OAAO,GAAG,GAAG,EAAE,CAAE,QAAO;AAC5B,KACE,MAAM,QACN,MAAM,QACN,OAAO,MAAM,YACb,OAAO,MAAM,SAEb,QAAO;CAET,MAAM,QAAQ,OAAO,KAAK,EAA6B;CACvD,MAAM,QAAQ,OAAO,KAAK,EAA6B;AACvD,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,MAAK,MAAM,OAAO,OAAO;AACvB,MAAI,CAAC,OAAO,OAAO,GAAG,IAAI,CAAE,QAAO;AACnC,MACE,CAAC,OAAO,GACL,EAA8B,MAC9B,EAA8B,KAChC,CAED,QAAO;;AAGX,QAAO;;;AAIT,SAAS,gBAAmB,OAAa;CACvC,MAAM,MAAM,OAAU,MAAM;AAC5B,KAAI,CAAC,mBAAmB,IAAI,SAAS,MAAM,CACzC,KAAI,UAAU;AAEhB,QAAO,IAAI;;;;;;;;AAoBb,eAAe,kBACb,aACA,QAC+B;AAC/B,KAAI;EACF,MAAM,MAAM,MAAM,MAChB,0BAA0B,mBAAmB,YAAY,IACzD,EAAE,QAAQ,CACX;AACD,MAAI,CAAC,IAAI,GAAI,QAAO;EACpB,MAAM,OAAQ,MAAM,IAAI,MAAM;AAC9B,SAAO,MAAM,QAAQ,KAAK,QAAQ,GAAI,KAAK,UAAuB;SAC5D;AACN;;;;;;;;;AAUJ,eAAe,iBAAiB,KAAwC;AACtE,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,IAAI,KAAK;GACpC,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,IAAI;GACV,QAAQ,IAAI;GACb,CAAC;AACF,MAAI,IAAI,OAAO,QAAS;AAExB,MAAI,CAAC,SAAS,IAAI;GAChB,IAAI,UAAU;GACd,IAAI,OAAsB;AAC1B,OAAI;IACF,MAAM,OAAQ,MAAM,SAAS,MAAM;AAInC,QAAI,KAAK,MAAO,WAAU,KAAK;AAC/B,QAAI,OAAO,KAAK,cAAc,SAAU,QAAO,KAAK;WAC9C;AAGR,OAAI,WAAW,MAAM;AACrB,OAAI,SAAS,QAAQ;AACrB,OAAI,KAAM,KAAI,aAAa,KAAK;AAChC,OAAI,0BAA0B;AAC9B;;EAGF,MAAM,SAAS,MAAM,SAAS,aAAa;AAC3C,MAAI,IAAI,OAAO,QAAS;EAMxB,IAAI;EACJ,MAAM,SAAS,SAAS,QAAQ,IAAI,yBAAyB;AAC7D,MAAI,OACF,KAAI;AACF,iBAAc,KAAK,MAAM,mBAAmB,OAAO,CAAC;UAC9C;OAGH;GACL,MAAM,MAAM,SAAS,QAAQ,IAAI,6BAA6B;AAC9D,OAAI,IACF,eAAc,MAAM,kBAAkB,KAAK,IAAI,OAAO;;EAG1D,MAAM,QAAQ,MAAM,YAAY,mBAC9B,IAAI,WAAW,OAAO,EACtB,YACD;AACD,MAAI,QAAQ,MAAM;AAClB,MAAI,WAAW,MAAM;AACrB,MAAI,0BAA0B;UACvB,OAAO;AACd,MAAI,IAAI,OAAO,QAAS;AACxB,MAAI,WAAW,MAAM;AACrB,MAAI,0BAA0B;AAC9B,MAAI,SAAS,qBAAqB,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmC7C,SAAgB,kBAKd,UACA,YACA,UAAuC,EAAE,EACc;CACvD,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,oBAAoB,SAAS,qBAAqB,MAAM;CAC9D,MAAM,YAAY,SAAS,aAAa;CAExC,MAAM,UAAU,YAAY;CAC5B,MAAM,YAAY,wBAAwB,mBAAmB,SAAS,GAAG;CAGzE,MAAM,CAAC,MAAM,WAAW,SAA4B,KAAK;CACzD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAAwB,KAAK;CACvD,MAAM,CAAC,WAAW,gBAAgB,SAAwB,KAAK;CAC/D,MAAM,CAAC,iBAAiB,sBACtB,SAAiC,KAAK;CACxC,MAAM,qBAAqB,OAA+B,KAAK;CAG/D,MAAM,EACJ,SAAS,wBACT,WAAW,6BACT,+BAJgB,OAAO,EAIqB,SAAS;AAEzD,KAAI,CAAC,YAAY,SAAS,MAAM,CAAC,WAAW,EAC1C,OAAM,IAAI,MACR,4DACD;CAGH,MAAM,mBAAmB,gBAAgB,WAAW;CAEpD,MAAM,UAAU,cAAc;AAC5B,MAAI;GACF,MAAM,aAAa,KAAK,UAAU;IAChC,YAAY;IACZ;IACD,CAAC;AAEF,OADoB,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,OACzB,kBAChB,OAAM,IAAI,MACR,sEACD;AAGH,UAAO;WACA,OAAO;AACd,WAAQ,MAAM,qDAAqD,MAAM;AACzE,UAAO;;IAER;EAAC;EAAkB;EAAQ;EAAkB,CAAC;CAEjD,MAAM,QAAQ,kBAAkB;AAC9B,MAAI,YAAY,MAAM;AACpB,YAAS,uCAAuC;AAChD;;AAGF,qBAAmB,SAAS,OAAO;AAEnC,aAAW,KAAK;AAChB,WAAS,KAAK;AACd,eAAa,KAAK;AAClB,UAAQ,KAAK;AACb,qBAAmB,KAAK;AACxB,yBAAuB,KAAK;EAE5B,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;AAI7B,MAAI,WAAW,gBAAgB;AAC7B,GAAK,iBAAiB;IACpB,KAAK;IACL;IACA,QAAQ,gBAAgB;IACxB;IACA;IACA;IACA,UAAU,UAAU,QAAQ,MAAoB;IAChD;IACD,CAAC;AACF;;EAGF,MAAM,aAAyC;GAC7C,QAAQ;GACR,UAAU,EAAE,UAAU;GACtB,uBAAuB;GACvB,6BAA6B;GAC7B,QAAQ,gBAAgB;GACxB,aAAa,gBAAgB,OAAO;GACpC;GACA;GACA;GACA,oBAAoB,WAAW;AAC7B,uBAAmB,OAAO;AAC1B,2BAAuB,OAAO;;GAEhC,WAAW,YAAY,QAAQ,QAAQ,KAAmB;GAC1D;GACD;AAED,aAAW;GACT,KAAK;GACL;GACA,QAAQ,gBAAgB;GACxB,YAAY,YACV,0BAA0B,QAAQ,MAAM,WAAW;GACrD,UAAU,UAAU,wBAAwB,OAAO,WAAW;GAC/D,CAAC;IACD;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,iBAAgB;AACd,MAAI,UACF,QAAO;AAGT,eAAa;AACX,sBAAmB,SAAS,OAAO;AACnC,6BAA0B;;IAE3B;EAAC;EAAO;EAAW;EAAyB,CAAC;AAEhD,aAAY,UAAU,MAAM;AAE5B,QAAO;EAAE;EAAM;EAAS;EAAO;EAAW;EAAiB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { InferDimensionKeys, InferMeasureKeys, MetricKey, PickMetricRow, UseMetricViewOptions, UseMetricViewResult } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/react/hooks/use-metric-view.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to a Unity Catalog Metric View and return its latest result.
|
|
6
|
+
*
|
|
7
|
+
* @param key - Metric view identifier
|
|
8
|
+
* @param options - Measures (required) plus optional dimensions, filter,
|
|
9
|
+
* orderBy, timeGrain/timeDimension, and limit
|
|
10
|
+
* @returns Metric result state with typed rows, display metadata, and warehouse readiness
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* `orderBy` and `limit` interact. With `limit`, the route completes the ordering
|
|
14
|
+
* with the remaining dimensions so the capped rows are the *same* rows on every
|
|
15
|
+
* run — pass `orderBy` to choose WHICH rows (top-N), since the completion only
|
|
16
|
+
* makes the result stable, not ranked. Without `limit`, `orderBy` is presentation
|
|
17
|
+
* ordering over the full result and gets no completion.
|
|
18
|
+
*
|
|
19
|
+
* When a request refetches with the same metric key, measures, and dimensions,
|
|
20
|
+
* the previous `data` and `metadata` remain available while `loading` is true.
|
|
21
|
+
* Changing any of those result-shaping fields clears the previous result.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const { data, metadata } = useMetricView("orders", {
|
|
26
|
+
* measures: ["revenue"],
|
|
27
|
+
* dimensions: ["region"],
|
|
28
|
+
* filter: { member: "region", operator: "in", values: ["EMEA", "APAC"] },
|
|
29
|
+
* orderBy: [{ field: "revenue", direction: "DESC" }],
|
|
30
|
+
* limit: 10,
|
|
31
|
+
* });
|
|
32
|
+
* // JSON_ARRAY preserves SQL scalar cells as strings and nullable columns as null:
|
|
33
|
+
* // data: Array<{ revenue: string | null; region: string | null }> | null
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function useMetricView<K extends MetricKey = MetricKey, const M extends ReadonlyArray<InferMeasureKeys<K>> = ReadonlyArray<InferMeasureKeys<K>>, const D extends ReadonlyArray<InferDimensionKeys<K>> = readonly []>(key: K, options: UseMetricViewOptions<K, M, D>): UseMetricViewResult<PickMetricRow<K, M, D>[]>;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { useMetricView };
|
|
39
|
+
//# sourceMappingURL=use-metric-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-metric-view.d.ts","names":[],"sources":["../../../src/react/hooks/use-metric-view.ts"],"mappings":";;;;;AAqEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,aAAA,WACJ,SAAA,GAAY,SAAA,kBACN,aAAA,CAAc,gBAAA,CAAiB,CAAA,KAAM,aAAA,CACnD,gBAAA,CAAiB,CAAA,oBAEH,aAAA,CAAc,kBAAA,CAAmB,CAAA,iBAAA,CAEjD,GAAA,EAAK,CAAA,EACL,OAAA,EAAS,oBAAA,CAAqB,CAAA,EAAG,CAAA,EAAG,CAAA,IACnC,mBAAA,CAAoB,aAAA,CAAc,CAAA,EAAG,CAAA,EAAG,CAAA"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { connectSSE } from "../../js/sse/connect-sse.js";
|
|
2
|
+
import { getDevMode, handleAnalyticsSseError, handleAnalyticsSseMessage } from "./analytics-sse.js";
|
|
3
|
+
import { useAnalyticsWarehousePublisher } from "./use-analytics-warehouse-status.js";
|
|
4
|
+
import { useQueryHMR } from "./use-query-hmr.js";
|
|
5
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
6
|
+
|
|
7
|
+
//#region src/react/hooks/use-metric-view.ts
|
|
8
|
+
function asMetricMetadata(value) {
|
|
9
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to a Unity Catalog Metric View and return its latest result.
|
|
13
|
+
*
|
|
14
|
+
* @param key - Metric view identifier
|
|
15
|
+
* @param options - Measures (required) plus optional dimensions, filter,
|
|
16
|
+
* orderBy, timeGrain/timeDimension, and limit
|
|
17
|
+
* @returns Metric result state with typed rows, display metadata, and warehouse readiness
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* `orderBy` and `limit` interact. With `limit`, the route completes the ordering
|
|
21
|
+
* with the remaining dimensions so the capped rows are the *same* rows on every
|
|
22
|
+
* run — pass `orderBy` to choose WHICH rows (top-N), since the completion only
|
|
23
|
+
* makes the result stable, not ranked. Without `limit`, `orderBy` is presentation
|
|
24
|
+
* ordering over the full result and gets no completion.
|
|
25
|
+
*
|
|
26
|
+
* When a request refetches with the same metric key, measures, and dimensions,
|
|
27
|
+
* the previous `data` and `metadata` remain available while `loading` is true.
|
|
28
|
+
* Changing any of those result-shaping fields clears the previous result.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const { data, metadata } = useMetricView("orders", {
|
|
33
|
+
* measures: ["revenue"],
|
|
34
|
+
* dimensions: ["region"],
|
|
35
|
+
* filter: { member: "region", operator: "in", values: ["EMEA", "APAC"] },
|
|
36
|
+
* orderBy: [{ field: "revenue", direction: "DESC" }],
|
|
37
|
+
* limit: 10,
|
|
38
|
+
* });
|
|
39
|
+
* // JSON_ARRAY preserves SQL scalar cells as strings and nullable columns as null:
|
|
40
|
+
* // data: Array<{ revenue: string | null; region: string | null }> | null
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function useMetricView(key, options) {
|
|
44
|
+
const autoStart = options.autoStart ?? true;
|
|
45
|
+
const devMode = getDevMode();
|
|
46
|
+
const urlSuffix = `/api/analytics/metric/${encodeURIComponent(key)}${devMode}`;
|
|
47
|
+
const [result, setResult] = useState(null);
|
|
48
|
+
const [loading, setLoading] = useState(false);
|
|
49
|
+
const [error, setError] = useState(null);
|
|
50
|
+
const [errorCode, setErrorCode] = useState(null);
|
|
51
|
+
const [warehouseStatus, setWarehouseStatus] = useState(null);
|
|
52
|
+
const abortControllerRef = useRef(null);
|
|
53
|
+
const activeRequestKeyRef = useRef(null);
|
|
54
|
+
const effectLeaseRef = useRef(0);
|
|
55
|
+
const requestedShapeRef = useRef(null);
|
|
56
|
+
const { publish: publishWarehouseStatus, unpublish: unpublishWarehouseStatus } = useAnalyticsWarehousePublisher(useId(), key);
|
|
57
|
+
if (!key || key.trim().length === 0) throw new Error("useMetricView: 'key' must be a non-empty string.");
|
|
58
|
+
const payload = useMemo(() => {
|
|
59
|
+
const body = { measures: options.measures };
|
|
60
|
+
if (options.dimensions !== void 0) body.dimensions = options.dimensions;
|
|
61
|
+
if (options.filter !== void 0) body.filter = options.filter;
|
|
62
|
+
if (options.timeGrain !== void 0) body.timeGrain = options.timeGrain;
|
|
63
|
+
if (options.timeDimension !== void 0) body.timeDimension = options.timeDimension;
|
|
64
|
+
if (options.orderBy !== void 0) body.orderBy = options.orderBy;
|
|
65
|
+
if (options.limit !== void 0) body.limit = options.limit;
|
|
66
|
+
return JSON.stringify(body);
|
|
67
|
+
}, [
|
|
68
|
+
options.measures,
|
|
69
|
+
options.dimensions,
|
|
70
|
+
options.filter,
|
|
71
|
+
options.timeGrain,
|
|
72
|
+
options.timeDimension,
|
|
73
|
+
options.orderBy,
|
|
74
|
+
options.limit
|
|
75
|
+
]);
|
|
76
|
+
const resultShape = JSON.stringify({
|
|
77
|
+
key,
|
|
78
|
+
measures: options.measures,
|
|
79
|
+
dimensions: options.dimensions ?? []
|
|
80
|
+
});
|
|
81
|
+
const requestKey = `${urlSuffix}\0${payload}`;
|
|
82
|
+
const isCurrentShape = result?.shape === resultShape;
|
|
83
|
+
const data = isCurrentShape ? result.data : null;
|
|
84
|
+
const metadata = isCurrentShape ? result.metadata : void 0;
|
|
85
|
+
const start = useCallback(() => {
|
|
86
|
+
abortControllerRef.current?.abort();
|
|
87
|
+
setLoading(true);
|
|
88
|
+
setError(null);
|
|
89
|
+
setErrorCode(null);
|
|
90
|
+
setWarehouseStatus(null);
|
|
91
|
+
if (requestedShapeRef.current !== resultShape) {
|
|
92
|
+
requestedShapeRef.current = resultShape;
|
|
93
|
+
setResult(null);
|
|
94
|
+
}
|
|
95
|
+
publishWarehouseStatus(null);
|
|
96
|
+
const abortController = new AbortController();
|
|
97
|
+
abortControllerRef.current = abortController;
|
|
98
|
+
activeRequestKeyRef.current = requestKey;
|
|
99
|
+
const sseContext = {
|
|
100
|
+
source: "useMetricView",
|
|
101
|
+
resource: { key },
|
|
102
|
+
defaultExecutionError: "Unable to execute metric query",
|
|
103
|
+
unpublishOnMalformedMessage: true,
|
|
104
|
+
signal: abortController.signal,
|
|
105
|
+
abort: () => abortController.abort(),
|
|
106
|
+
setLoading,
|
|
107
|
+
setError,
|
|
108
|
+
setErrorCode,
|
|
109
|
+
onWarehouseStatus: (status) => {
|
|
110
|
+
setWarehouseStatus(status);
|
|
111
|
+
publishWarehouseStatus(status);
|
|
112
|
+
},
|
|
113
|
+
onResult: (message) => {
|
|
114
|
+
setError(null);
|
|
115
|
+
setErrorCode(null);
|
|
116
|
+
setResult({
|
|
117
|
+
shape: resultShape,
|
|
118
|
+
data: message.data,
|
|
119
|
+
metadata: asMetricMetadata(message.payload.metadata)
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
unpublishWarehouseStatus
|
|
123
|
+
};
|
|
124
|
+
connectSSE({
|
|
125
|
+
url: urlSuffix,
|
|
126
|
+
payload,
|
|
127
|
+
signal: abortController.signal,
|
|
128
|
+
onMessage: (message) => handleAnalyticsSseMessage(message.data, sseContext),
|
|
129
|
+
onError: (error) => handleAnalyticsSseError(error, sseContext)
|
|
130
|
+
});
|
|
131
|
+
}, [
|
|
132
|
+
key,
|
|
133
|
+
payload,
|
|
134
|
+
requestKey,
|
|
135
|
+
resultShape,
|
|
136
|
+
urlSuffix,
|
|
137
|
+
publishWarehouseStatus,
|
|
138
|
+
unpublishWarehouseStatus
|
|
139
|
+
]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const lease = ++effectLeaseRef.current;
|
|
142
|
+
if (autoStart) {
|
|
143
|
+
const activeController = abortControllerRef.current;
|
|
144
|
+
if (activeRequestKeyRef.current !== requestKey || !activeController || activeController.signal.aborted) start();
|
|
145
|
+
} else {
|
|
146
|
+
abortControllerRef.current?.abort();
|
|
147
|
+
activeRequestKeyRef.current = null;
|
|
148
|
+
unpublishWarehouseStatus();
|
|
149
|
+
}
|
|
150
|
+
return () => {
|
|
151
|
+
const controller = abortControllerRef.current;
|
|
152
|
+
queueMicrotask(() => {
|
|
153
|
+
if (effectLeaseRef.current !== lease) return;
|
|
154
|
+
controller?.abort();
|
|
155
|
+
if (abortControllerRef.current === controller) activeRequestKeyRef.current = null;
|
|
156
|
+
unpublishWarehouseStatus();
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
}, [
|
|
160
|
+
start,
|
|
161
|
+
autoStart,
|
|
162
|
+
requestKey,
|
|
163
|
+
unpublishWarehouseStatus
|
|
164
|
+
]);
|
|
165
|
+
useQueryHMR(key, start);
|
|
166
|
+
return {
|
|
167
|
+
data,
|
|
168
|
+
loading,
|
|
169
|
+
error,
|
|
170
|
+
errorCode,
|
|
171
|
+
metadata,
|
|
172
|
+
warehouseStatus
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
export { useMetricView };
|
|
178
|
+
//# sourceMappingURL=use-metric-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-metric-view.js","names":[],"sources":["../../../src/react/hooks/use-metric-view.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { MetricViewColumnDisplay } from \"shared\";\nimport { connectSSE } from \"@/js\";\nimport {\n type AnalyticsSseHandlerContext,\n getDevMode,\n handleAnalyticsSseError,\n handleAnalyticsSseMessage,\n} from \"./analytics-sse\";\nimport type {\n InferDimensionKeys,\n InferMeasureKeys,\n MetricKey,\n PickMetricRow,\n UseMetricViewOptions,\n UseMetricViewResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\nfunction asMetricMetadata(\n value: unknown,\n): Record<string, MetricViewColumnDisplay> | undefined {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return value as Record<string, MetricViewColumnDisplay>;\n }\n return undefined;\n}\n\n/**\n * Subscribe to a Unity Catalog Metric View and return its latest result.\n *\n * @param key - Metric view identifier\n * @param options - Measures (required) plus optional dimensions, filter,\n * orderBy, timeGrain/timeDimension, and limit\n * @returns Metric result state with typed rows, display metadata, and warehouse readiness\n *\n * @remarks\n * `orderBy` and `limit` interact. With `limit`, the route completes the ordering\n * with the remaining dimensions so the capped rows are the *same* rows on every\n * run — pass `orderBy` to choose WHICH rows (top-N), since the completion only\n * makes the result stable, not ranked. Without `limit`, `orderBy` is presentation\n * ordering over the full result and gets no completion.\n *\n * When a request refetches with the same metric key, measures, and dimensions,\n * the previous `data` and `metadata` remain available while `loading` is true.\n * Changing any of those result-shaping fields clears the previous result.\n *\n * @example\n * ```typescript\n * const { data, metadata } = useMetricView(\"orders\", {\n * measures: [\"revenue\"],\n * dimensions: [\"region\"],\n * filter: { member: \"region\", operator: \"in\", values: [\"EMEA\", \"APAC\"] },\n * orderBy: [{ field: \"revenue\", direction: \"DESC\" }],\n * limit: 10,\n * });\n * // JSON_ARRAY preserves SQL scalar cells as strings and nullable columns as null:\n * // data: Array<{ revenue: string | null; region: string | null }> | null\n * ```\n */\nexport function useMetricView<\n K extends MetricKey = MetricKey,\n const M extends ReadonlyArray<InferMeasureKeys<K>> = ReadonlyArray<\n InferMeasureKeys<K>\n >,\n const D extends ReadonlyArray<InferDimensionKeys<K>> = readonly [],\n>(\n key: K,\n options: UseMetricViewOptions<K, M, D>,\n): UseMetricViewResult<PickMetricRow<K, M, D>[]> {\n const autoStart = options.autoStart ?? true;\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/metric/${encodeURIComponent(key)}${devMode}`;\n\n type Rows = PickMetricRow<K, M, D>[];\n const [result, setResult] = useState<{\n shape: string;\n data: Rows;\n metadata: Record<string, MetricViewColumnDisplay> | undefined;\n } | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n const activeRequestKeyRef = useRef<string | null>(null);\n const effectLeaseRef = useRef(0);\n const requestedShapeRef = useRef<string | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, key);\n\n if (!key || key.trim().length === 0) {\n throw new Error(\"useMetricView: 'key' must be a non-empty string.\");\n }\n\n // Stringify to compare request by value; prevent re-firing on every render.\n const payload = useMemo(() => {\n const body: {\n measures: ReadonlyArray<unknown>;\n dimensions?: ReadonlyArray<unknown>;\n filter?: unknown;\n timeGrain?: unknown;\n timeDimension?: unknown;\n orderBy?: ReadonlyArray<unknown>;\n limit?: number;\n } = { measures: options.measures };\n if (options.dimensions !== undefined) body.dimensions = options.dimensions;\n if (options.filter !== undefined) body.filter = options.filter;\n if (options.timeGrain !== undefined) body.timeGrain = options.timeGrain;\n if (options.timeDimension !== undefined)\n body.timeDimension = options.timeDimension;\n if (options.orderBy !== undefined) body.orderBy = options.orderBy;\n if (options.limit !== undefined) body.limit = options.limit;\n return JSON.stringify(body);\n }, [\n options.measures,\n options.dimensions,\n options.filter,\n options.timeGrain,\n options.timeDimension,\n options.orderBy,\n options.limit,\n ]);\n\n // Detect shape changes: clear rows when metric/measures/dimensions change,\n // but keep stale rows during filter/order/limit/time-grain revalidations.\n const resultShape = JSON.stringify({\n key,\n measures: options.measures,\n dimensions: options.dimensions ?? [],\n });\n const requestKey = `${urlSuffix}\\0${payload}`;\n\n // Return stale rows only if shape is current; hide during shape transitions.\n const isCurrentShape = result?.shape === resultShape;\n const data = isCurrentShape ? result.data : null;\n const metadata = isCurrentShape ? result.metadata : undefined;\n\n const start = useCallback(() => {\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setErrorCode(null);\n setWarehouseStatus(null);\n if (requestedShapeRef.current !== resultShape) {\n requestedShapeRef.current = resultShape;\n setResult(null);\n }\n // Register an empty slot to clear stale status from the prior run.\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n activeRequestKeyRef.current = requestKey;\n\n const sseContext: AnalyticsSseHandlerContext = {\n source: \"useMetricView\",\n resource: { key },\n defaultExecutionError: \"Unable to execute metric query\",\n unpublishOnMalformedMessage: true,\n signal: abortController.signal,\n abort: () => abortController.abort(),\n setLoading,\n setError,\n setErrorCode,\n onWarehouseStatus: (status) => {\n setWarehouseStatus(status);\n publishWarehouseStatus(status);\n },\n onResult: (message) => {\n setError(null);\n setErrorCode(null);\n setResult({\n shape: resultShape,\n data: message.data as Rows,\n metadata: asMetricMetadata(message.payload.metadata),\n });\n },\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: (message) =>\n handleAnalyticsSseMessage(message.data, sseContext),\n onError: (error) => handleAnalyticsSseError(error, sseContext),\n });\n }, [\n key,\n payload,\n requestKey,\n resultShape,\n urlSuffix,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n const lease = ++effectLeaseRef.current;\n\n if (autoStart) {\n // React Strict Mode runs an effect's setup/cleanup/setup sequence on\n // mount. Keep the still-active identical stream for the second setup so\n // development sends one POST, not an immediately-aborted duplicate.\n // A real request change still starts synchronously and aborts the prior\n // controller in start().\n const activeController = abortControllerRef.current;\n if (\n activeRequestKeyRef.current !== requestKey ||\n !activeController ||\n activeController.signal.aborted\n ) {\n start();\n }\n } else {\n abortControllerRef.current?.abort();\n activeRequestKeyRef.current = null;\n unpublishWarehouseStatus();\n }\n\n return () => {\n // Defer teardown by one microtask so Strict Mode's immediate second\n // setup can claim the same request. On a genuine unmount there is no new\n // lease, so the stream is still cancelled before later async work runs.\n const controller = abortControllerRef.current;\n queueMicrotask(() => {\n if (effectLeaseRef.current !== lease) return;\n controller?.abort();\n if (abortControllerRef.current === controller) {\n activeRequestKeyRef.current = null;\n }\n unpublishWarehouseStatus();\n });\n };\n }, [start, autoStart, requestKey, unpublishWarehouseStatus]);\n\n useQueryHMR(key, start);\n\n return { data, loading, error, errorCode, metadata, warehouseStatus };\n}\n"],"mappings":"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"}
|