@frontera-sdk/blueprint 1.50.81 → 1.50.83

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": "@frontera-sdk/blueprint",
3
- "version": "1.50.81",
3
+ "version": "1.50.83",
4
4
  "description": "React hooks for reading Blueprint data and invoking governed Actions from inside a Frontera app.",
5
5
  "keywords": [
6
6
  "frontera",
@@ -56,7 +56,7 @@
56
56
  "smoke": "bun run scripts/smoke.ts"
57
57
  },
58
58
  "dependencies": {
59
- "@frontera-sdk/core": "1.50.81"
59
+ "@frontera-sdk/core": "1.50.83"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@tanstack/react-query": "^5.90.21",
@@ -158,6 +158,16 @@ export interface ActionRequestRequester {
158
158
  userId: string | null
159
159
  }
160
160
 
161
+ /** How to read one submitted parameter value: its label, declared kind and whether it is withheld. */
162
+ export interface ActionRequestInputField {
163
+ /** The parameter's own name, for a label. */
164
+ displayName: string
165
+ /** The declared kind (`string`, `number`, `geopoint`, …), or `null` when the parameter is no longer published. */
166
+ kind: string | null
167
+ /** The value is withheld from this reader; `input` holds `"[redacted]"` in its place. */
168
+ hidden: boolean
169
+ }
170
+
161
171
  /**
162
172
  * What a request is asking for, ready to render.
163
173
  *
@@ -172,15 +182,23 @@ export interface ActionRequestRequester {
172
182
  * it agree on what to call it.
173
183
  *
174
184
  * A parameter the Action declares as confidential or restricted arrives as the
175
- * literal string `'[redacted]'`. Show it as withheld rather than as an empty
176
- * value: the parameter WAS supplied, and the approver is simply not the
177
- * audience for it.
185
+ * placeholder string `'[redacted]'` with `inputFields[key].hidden` true. Decide
186
+ * by `hidden`, never by matching the text: a submitted string can read
187
+ * `'[redacted]'` too. Show it as withheld rather than as an empty value: the
188
+ * parameter WAS supplied, and the approver is simply not the audience for it.
178
189
  */
179
190
  export interface ActionRequestIntent {
180
191
  actionApiName: string
181
192
  actionDisplayName: string
182
193
  /** Submitted values, keyed by parameter API name. */
183
194
  input: Record<string, unknown>
195
+ /**
196
+ * How to read each `input` entry, under the same key. Format a value by its
197
+ * declared `kind`, never by its shape (a `json` value can look like a
198
+ * location); `hidden` means the value is withheld from this reader. Absent
199
+ * on replies from a platform release that predates it.
200
+ */
201
+ inputFields?: Record<string, ActionRequestInputField>
184
202
  reason: string | null
185
203
  correlationId: string | null
186
204
  requester: ActionRequestRequester
package/src/hooks.ts CHANGED
@@ -2,15 +2,17 @@ import { createContext, useContext } from 'react'
2
2
  import { useQuery, type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query'
3
3
 
4
4
  import type { BlueprintClient } from './blueprint-client'
5
- import { objectsOf } from './types'
5
+ import { DISTANCE_ROW_KEY, objectsOf } from './types'
6
6
  import type {
7
7
  AggregateRequest,
8
8
  AggregateResponse,
9
9
  BlueprintFilterableProperty,
10
+ BlueprintLocationProperty,
10
11
  BlueprintObjectName,
11
12
  BlueprintRow,
12
13
  BlueprintSortableProperty,
13
14
  MetricQueryRequest,
15
+ NearestRequest,
14
16
  ObjectInstance,
15
17
  QueryRequest,
16
18
  QueryResponse,
@@ -97,6 +99,12 @@ type EffectiveSortable<TLegacy, TObject extends BlueprintObjectName> =
97
99
  ? BlueprintSortableProperty<TObject>
98
100
  : Extract<keyof TLegacy, string>
99
101
 
102
+ /** A location property of the row, or any name for an untyped object type. */
103
+ type EffectiveLocation<TLegacy, TObject extends BlueprintObjectName> =
104
+ string extends EffectiveFilterable<TLegacy, TObject>
105
+ ? string
106
+ : BlueprintLocationProperty<EffectiveRow<TLegacy, TObject>>
107
+
100
108
  type SelectedRow<TRow, TSelect> =
101
109
  undefined extends TSelect
102
110
  ? TRow
@@ -104,15 +112,28 @@ type SelectedRow<TRow, TSelect> =
104
112
  ? Pick<TRow, TSelect[number]>
105
113
  : TRow
106
114
 
115
+ /** `nearest` adds the distance to every row, whatever `select` narrowed. */
116
+ type WithDistance<TRow, TNearest> =
117
+ TNearest extends NearestRequest ? TRow & { [DISTANCE_ROW_KEY]: number } : TRow
118
+
119
+ type ObjectsRow<
120
+ TLegacy,
121
+ TObject extends BlueprintObjectName,
122
+ TSelect,
123
+ TNearest,
124
+ > = WithDistance<SelectedRow<EffectiveRow<TLegacy, TObject>, TSelect>, TNearest>
125
+
107
126
  type ObjectsOptions<
108
127
  TLegacy,
109
128
  TObject extends BlueprintObjectName,
110
129
  TSelect extends readonly Extract<keyof EffectiveRow<TLegacy, TObject>, string>[] | undefined,
111
- > = ReadOptions<QueryResponse<SelectedRow<EffectiveRow<TLegacy, TObject>, TSelect>>> &
112
- Omit<QueryRequest, 'objectSet' | 'select' | 'orderBy'> & {
130
+ TNearest extends NearestRequest<EffectiveLocation<TLegacy, TObject>> | undefined,
131
+ > = ReadOptions<QueryResponse<ObjectsRow<TLegacy, TObject, TSelect, TNearest>>> &
132
+ Omit<QueryRequest, 'objectSet' | 'select' | 'orderBy' | 'nearest'> & {
113
133
  select?: TSelect
114
134
  orderBy?: Array<{ property: EffectiveSortable<TLegacy, TObject>; dir: 'asc' | 'desc' }>
115
135
  where?: TypedWhereNode<EffectiveRow<TLegacy, TObject>, EffectiveFilterable<TLegacy, TObject>>
136
+ nearest?: TNearest
116
137
  }
117
138
 
118
139
  export function useObjects<
@@ -120,18 +141,20 @@ export function useObjects<
120
141
  const TObject extends BlueprintObjectName = BlueprintObjectName,
121
142
  const TSelect extends readonly Extract<keyof EffectiveRow<TLegacy, TObject>, string>[] | undefined =
122
143
  readonly Extract<keyof EffectiveRow<TLegacy, TObject>, string>[] | undefined,
144
+ const TNearest extends NearestRequest<EffectiveLocation<TLegacy, TObject>> | undefined = undefined,
123
145
  >(
124
146
  objectType: TObject,
125
- options: ObjectsOptions<NoInfer<TLegacy>, TObject, TSelect> = {},
126
- ): UseQueryResult<QueryResponse<SelectedRow<EffectiveRow<TLegacy, TObject>, TSelect>>, Error> {
127
- const { select, orderBy, pageSize, pageToken, where, ...queryOptions } = options
128
- return useObjectQuery<SelectedRow<EffectiveRow<TLegacy, TObject>, TSelect>>(
147
+ options: ObjectsOptions<NoInfer<TLegacy>, TObject, TSelect, TNearest> = {},
148
+ ): UseQueryResult<QueryResponse<ObjectsRow<TLegacy, TObject, TSelect, TNearest>>, Error> {
149
+ const { select, orderBy, pageSize, pageToken, where, nearest, ...queryOptions } = options
150
+ return useObjectQuery<ObjectsRow<TLegacy, TObject, TSelect, TNearest>>(
129
151
  {
130
152
  objectSet: objectsOf(objectType, where as WhereNode | undefined),
131
153
  select: select ? [...select] : undefined,
132
154
  orderBy,
133
155
  pageSize,
134
156
  pageToken,
157
+ ...(nearest ? { nearest } : {}),
135
158
  },
136
159
  queryOptions,
137
160
  )
package/src/types.ts CHANGED
@@ -20,6 +20,60 @@ export type ConditionOp =
20
20
  | 'isNull' | 'isNotNull'
21
21
  | 'gt' | 'gte' | 'lt' | 'lte' | 'between'
22
22
  | 'dateRange'
23
+ /** Some element of an array property satisfies every `elementWhere` condition. */
24
+ | 'arrayAny'
25
+ /** A location within `radiusMeters` of the point in `value`. */
26
+ | 'nearby'
27
+ /** A location inside the box in `value`. */
28
+ | 'withinBbox'
29
+
30
+ /** The operators a location property takes, and the only ones it takes. */
31
+ export type LocationConditionOp = 'nearby' | 'withinBbox' | 'isNull' | 'isNotNull'
32
+
33
+ /** Every operator except the location-only ones. */
34
+ export type ScalarConditionOp = Exclude<ConditionOp, 'nearby' | 'withinBbox'>
35
+
36
+ /**
37
+ * A WGS 84 location in decimal degrees: `lat` in -90..90, `lon` in -180..180.
38
+ * Always named, never a `[lat, lon]` pair. This is the value a location
39
+ * property reads as, and the centre of a `nearby` condition.
40
+ */
41
+ export interface BlueprintGeoPoint {
42
+ lat: number
43
+ lon: number
44
+ }
45
+
46
+ /**
47
+ * A latitude/longitude box with inclusive edges. `west > east` means the box
48
+ * crosses the 180° meridian (for example `west: 170, east: -170`). A box that
49
+ * should contain a pole must span every longitude (`west: -180, east: 180`).
50
+ */
51
+ export interface BlueprintGeoBbox {
52
+ west: number
53
+ south: number
54
+ east: number
55
+ north: number
56
+ }
57
+
58
+ /** One condition on an element of an array property (`arrayAny`). */
59
+ export interface ArrayElementCondition {
60
+ /** Field of a struct element; omit when the elements are scalars. */
61
+ field?: string
62
+ op: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'
63
+ value: unknown
64
+ }
65
+
66
+ /**
67
+ * The row key `nearest` adds: the distance in metres from `nearest.from`.
68
+ * Property names start with a lowercase letter, so it never collides with one.
69
+ */
70
+ export const DISTANCE_ROW_KEY = '_distanceMeters'
71
+
72
+ /** Order rows by distance from `from`, closest first. */
73
+ export interface NearestRequest<TProperty extends string = string> {
74
+ property: TProperty
75
+ from: BlueprintGeoPoint
76
+ }
23
77
 
24
78
  export type DatePreset =
25
79
  | 'TODAY' | 'YESTERDAY' | 'LAST_7_DAYS' | 'LAST_30_DAYS' | 'LAST_90_DAYS'
@@ -71,20 +125,45 @@ export type BlueprintSortableProperty<TObject extends BlueprintObjectName> =
71
125
  type PropertyValue<TRow, TProperty extends string> =
72
126
  TProperty extends keyof TRow ? TRow[TProperty] : unknown
73
127
 
128
+ /**
129
+ * The properties of a row that read as a location. Derived from the row type
130
+ * (a generated row types a location as `BlueprintGeoPoint | null`), so it needs
131
+ * nothing from the registry.
132
+ */
133
+ export type BlueprintLocationProperty<TRow> = {
134
+ [K in Extract<keyof TRow, string>]-?: [NonNullable<TRow[K]>] extends [never]
135
+ ? never
136
+ : NonNullable<TRow[K]> extends BlueprintGeoPoint ? K : never
137
+ }[Extract<keyof TRow, string>]
138
+
74
139
  export type TypedPropertyCondition<TRow, TProperty extends string> =
75
140
  TProperty extends unknown
76
141
  ? {
77
142
  property: TProperty
78
- op: ConditionOp
143
+ // An untyped (string-keyed) object type takes every operator; a
144
+ // registered one keeps the location operators for its locations.
145
+ op: string extends TProperty ? ConditionOp : ScalarConditionOp
79
146
  value?: PropertyValue<TRow, TProperty>
80
147
  values?: Array<PropertyValue<TRow, TProperty>>
81
148
  preset?: DatePreset
82
149
  timezone?: string
150
+ elementWhere?: ArrayElementCondition[]
151
+ radiusMeters?: number
83
152
  }
84
153
  : never
85
154
 
155
+ /** A condition on a location property: geo operators and presence only. */
156
+ export type TypedLocationCondition<TProperty extends string> =
157
+ TProperty extends unknown
158
+ ?
159
+ | { property: TProperty; op: 'nearby'; value: BlueprintGeoPoint; radiusMeters: number }
160
+ | { property: TProperty; op: 'withinBbox'; value: BlueprintGeoBbox }
161
+ | { property: TProperty; op: 'isNull' | 'isNotNull' }
162
+ : never
163
+
86
164
  export type TypedWhereNode<TRow, TProperty extends string> =
87
165
  | TypedPropertyCondition<TRow, TProperty>
166
+ | TypedLocationCondition<BlueprintLocationProperty<TRow>>
88
167
  | { and: Array<TypedWhereNode<TRow, TProperty>> }
89
168
  | { or: Array<TypedWhereNode<TRow, TProperty>> }
90
169
  | { not: TypedWhereNode<TRow, TProperty> }
@@ -102,6 +181,13 @@ export interface PropertyCondition {
102
181
  /** `dateRange` only — resolved to [start, end) server-side. */
103
182
  preset?: DatePreset
104
183
  timezone?: string
184
+ /** `arrayAny` only — conditions one element must satisfy together. */
185
+ elementWhere?: ArrayElementCondition[]
186
+ /**
187
+ * `nearby` only — the radius in metres around the point in `value`, measured
188
+ * over the Earth's surface; objects exactly on the circle match.
189
+ */
190
+ radiusMeters?: number
105
191
  }
106
192
 
107
193
  export type WhereNode =
@@ -139,6 +225,13 @@ export interface QueryRequest {
139
225
  * the types sent a parameter guaranteed to fail.
140
226
  */
141
227
  pageToken?: string
228
+ /**
229
+ * Order by distance from `nearest.from` to the location `nearest.property`,
230
+ * closest first, and add `_distanceMeters` to every row. The object set must
231
+ * also filter that location with `nearby` or `withinBbox` (outside any `or`
232
+ * or `not`), and `orderBy` must be omitted.
233
+ */
234
+ nearest?: NearestRequest
142
235
  }
143
236
 
144
237
  /**