@foir/sdk 0.7.0 → 0.8.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/CHANGELOG.md +805 -4
- package/README.md +46 -15
- package/dist/auth/auth.d.ts +104 -1
- package/dist/auth/index.d.ts +12 -6
- package/dist/auth/operations.d.ts +85 -64
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth.js +775 -106
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client.js +25 -31
- package/dist/codegen/block-registry.d.ts +13 -19
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -21
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +9 -4
- package/dist/codegen/index.js +7 -11
- package/dist/codegen/item-registry.d.ts +28 -0
- package/dist/codegen/item-registry.js +7 -0
- package/dist/codegen/item-scaffold.d.ts +29 -0
- package/dist/codegen/item-scaffold.js +7 -0
- package/dist/codegen/preset.d.ts +14 -7
- package/dist/codegen/shared.d.ts +20 -9
- package/dist/content/content-renderer.d.ts +90 -24
- package/dist/content/index.d.ts +13 -7
- package/dist/content/richtext.d.ts +66 -18
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +302 -92
- package/dist/data/client.d.ts +97 -25
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +38 -19
- package/dist/data/manifest.d.ts +266 -3
- package/dist/data/schema.d.ts +78 -8
- package/dist/data/selection.d.ts +231 -37
- package/dist/data/subjects.d.ts +221 -0
- package/dist/data/subscription-document.d.ts +75 -0
- package/dist/data/subscriptions.d.ts +181 -0
- package/dist/data/transport.d.ts +255 -37
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1419 -45
- package/dist/graphql/cdn-route.d.ts +12 -28
- package/dist/media/index.d.ts +6 -2
- package/dist/media/media.d.ts +2 -2
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media.js +97 -0
- package/dist/next.js +663 -182
- package/dist/push.js +700 -90
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +7 -1
- package/dist/react.js +1272 -194
- package/dist/realtime.js +1 -1
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +23 -6
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +1 -1
- package/package.json +13 -1
- package/dist/chunk-ADO5FJNM.js +0 -30
package/dist/data/builtins.d.ts
CHANGED
|
@@ -7,24 +7,27 @@
|
|
|
7
7
|
* give four projects four chances to disagree about `ImageValue`.
|
|
8
8
|
*
|
|
9
9
|
* 1. **Value objects.** `GET /v1/manifest` reports a scalar's `type` as a
|
|
10
|
-
* GraphQL named type, and
|
|
11
|
-
* objects rather than leaves: `
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 2. **
|
|
17
|
-
* for
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
10
|
+
* GraphQL named type, and several of the names it can use are
|
|
11
|
+
* platform-owned objects rather than leaves: `Image`, `Video`, `FileValue`,
|
|
12
|
+
* `Richtext`, `GeoPoint`, `Reference`, `CollabInfo`. They are structurally
|
|
13
|
+
* nested but behave as leaves — always present, never lazily hydrated,
|
|
14
|
+
* never `include`-able.
|
|
15
|
+
*
|
|
16
|
+
* 2. **Primitive members.** A flexible field's union members are *model keys*,
|
|
17
|
+
* except for six lowercase tokens — `richtext`, `image`, `video`, `file`,
|
|
18
|
+
* `reference`, `geopoint` — which are members of the real GraphQL union but
|
|
19
|
+
* have **no entry in `models`**. A generator that resolved every member to
|
|
20
|
+
* `models[key]` would skip them (an inexhaustive union, silently wrong at
|
|
21
|
+
* runtime) or emit `never`.
|
|
22
|
+
*
|
|
23
|
+
* # These are ONE set, not two (TTS-1391)
|
|
24
|
+
*
|
|
25
|
+
* They used to be two: a value object for a plain field, and a separate
|
|
26
|
+
* `<Name>Block` wrapper for the same thing inside a flexible field. TTS-1391
|
|
27
|
+
* removed the wrapper — an item IS its value — so a plain `image` field and an
|
|
28
|
+
* `image` inside a flexible field are the SAME GraphQL type, `Image`. There is
|
|
29
|
+
* no `ImageValue` in a real schema and never will be again; the only difference
|
|
30
|
+
* between the two positions is whether `__typename` was selected.
|
|
28
31
|
*
|
|
29
32
|
* ---------------------------------------------------------------------------
|
|
30
33
|
* The obligation that comes with a static table
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
* Field shapes verified against deployed dev's introspection on 2026-07-28 and
|
|
43
46
|
* against `api-public/internal/schema/field_mapping.go`.
|
|
44
47
|
*/
|
|
48
|
+
import type { FoirConnection } from './schema';
|
|
45
49
|
/** A `JSON` scalar. Deliberately not `any`: reading through it needs a check. */
|
|
46
50
|
export type FoirJsonValue = string | number | boolean | null | FoirJsonValue[] | {
|
|
47
51
|
[key: string]: FoirJsonValue;
|
|
@@ -69,8 +73,18 @@ export interface ImageVariantSet {
|
|
|
69
73
|
_3xl: ImageVariant | null;
|
|
70
74
|
_4xl: ImageVariant | null;
|
|
71
75
|
}
|
|
72
|
-
export interface
|
|
73
|
-
|
|
76
|
+
export interface Image {
|
|
77
|
+
/**
|
|
78
|
+
* `String` on the wire, not `String!`.
|
|
79
|
+
*
|
|
80
|
+
* It was non-null here and nullable on the flexible-field member until
|
|
81
|
+
* TTS-1391, which is a §5.3.2 response-shape conflict the moment both are one
|
|
82
|
+
* type. The platform relaxed rather than tightened, because nullable is the
|
|
83
|
+
* honest form — its own public `File` asset type already declares
|
|
84
|
+
* `url: String` — so this follows.
|
|
85
|
+
*/
|
|
86
|
+
__typename?: 'Image';
|
|
87
|
+
url: string | null;
|
|
74
88
|
width: number | null;
|
|
75
89
|
height: number | null;
|
|
76
90
|
alt: string | null;
|
|
@@ -86,8 +100,9 @@ export interface ImageValue {
|
|
|
86
100
|
/** AVIF tier set (smaller, narrower support). The preferred `<picture>` source. */
|
|
87
101
|
avif: ImageVariantSet | null;
|
|
88
102
|
}
|
|
89
|
-
export interface
|
|
90
|
-
|
|
103
|
+
export interface Video {
|
|
104
|
+
__typename?: 'Video';
|
|
105
|
+
url: string | null;
|
|
91
106
|
thumbnailUrl: string | null;
|
|
92
107
|
previewUrl: string | null;
|
|
93
108
|
hlsManifestUrl: string | null;
|
|
@@ -100,28 +115,62 @@ export interface VideoValue {
|
|
|
100
115
|
fileSize: number | null;
|
|
101
116
|
}
|
|
102
117
|
export interface FileValue {
|
|
118
|
+
__typename?: 'FileValue';
|
|
103
119
|
url: string | null;
|
|
104
120
|
filename: string | null;
|
|
105
121
|
mimeType: string | null;
|
|
106
122
|
size: number | null;
|
|
107
123
|
}
|
|
108
|
-
export interface
|
|
124
|
+
export interface GeoPoint {
|
|
125
|
+
__typename?: 'GeoPoint';
|
|
109
126
|
lat: number | null;
|
|
110
127
|
lng: number | null;
|
|
111
128
|
}
|
|
112
|
-
export interface
|
|
129
|
+
export interface Richtext {
|
|
130
|
+
__typename?: 'Richtext';
|
|
113
131
|
html: string | null;
|
|
114
132
|
json: FoirJsonValue | null;
|
|
115
133
|
markdown: string | null;
|
|
116
134
|
}
|
|
117
135
|
/**
|
|
118
|
-
* A
|
|
119
|
-
*
|
|
120
|
-
*
|
|
136
|
+
* A reference whose target model is not pinned by the field's declaration.
|
|
137
|
+
*
|
|
138
|
+
* A reference field that declares ONE `referenceTypes` entry resolves to that
|
|
139
|
+
* model's own type, and one declaring several resolves to a `<Members>Ref`
|
|
140
|
+
* union — so this type is what an undeclared reference resolves to, and what a
|
|
141
|
+
* flexible field's `reference` member is.
|
|
142
|
+
*
|
|
143
|
+
* # What makes it followable
|
|
144
|
+
*
|
|
145
|
+
* `_targetModelKey` names the model and `_targetId` OR `_targetNaturalKey`
|
|
146
|
+
* identifies the row within it: `foir[_targetModelKey].findUnique({ where: { _id:
|
|
147
|
+
* _targetId } })`. Both identifier shapes are live — on the dev database 101 of
|
|
148
|
+
* 193 stored reference values carry an id and 92 carry a natural key and no id
|
|
149
|
+
* — so a consumer must handle either.
|
|
150
|
+
*
|
|
151
|
+
* `_id` and `_type` are NOT here, and their absence is the TTS-1391 correction
|
|
152
|
+
* rather than an omission. They were the wrapper's own metadata: `_id` was the
|
|
153
|
+
* ITEM's id (never the target's, which is why `_targetId` exists separately) and
|
|
154
|
+
* `_type` was the union discriminator, reading `"reference"` on every reference
|
|
155
|
+
* ever stored. The wrapper is gone, so neither field is in the schema, and a
|
|
156
|
+
* consumer selecting them got `undefined` while the types promised a string.
|
|
121
157
|
*/
|
|
122
|
-
export interface
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
export interface Reference<TRecord = FoirUnexpandedRecord> {
|
|
159
|
+
__typename?: 'Reference';
|
|
160
|
+
/** The model to fetch the target from. Null when the target no longer exists. */
|
|
161
|
+
_targetModelKey: string | null;
|
|
162
|
+
/** The target's id, when the reference stored one. */
|
|
163
|
+
_targetId: string | null;
|
|
164
|
+
/** The target's natural key, when the reference names its target that way. */
|
|
165
|
+
_targetNaturalKey: string | null;
|
|
166
|
+
/**
|
|
167
|
+
* The resolved target, over the project's `Record` union — every model in the
|
|
168
|
+
* project. Left as a type parameter so the generated file can name that
|
|
169
|
+
* union; the default is the honest answer for a consumer who has not narrowed
|
|
170
|
+
* it.
|
|
171
|
+
*/
|
|
172
|
+
record: TRecord | null;
|
|
173
|
+
/** A stored SPELLING of the model. Read as an input to `_targetModelKey`. */
|
|
125
174
|
_schema: string | null;
|
|
126
175
|
naturalKey: string | null;
|
|
127
176
|
_preview: FoirJsonValue | null;
|
|
@@ -153,69 +202,54 @@ export interface CollabInfo {
|
|
|
153
202
|
roomName: string;
|
|
154
203
|
}
|
|
155
204
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* own selection set for every union it expands, and it is the only field that
|
|
161
|
-
* cannot be `omit`ed (no omit path can name it). `_type` is the foir block-type
|
|
162
|
-
* token — the model key for a model-derived member, the lowercase primitive
|
|
163
|
-
* token otherwise — and is deliberately NOT protected from `omit` by the
|
|
164
|
-
* server, so a caller who omits it loses the second discriminant while keeping
|
|
165
|
-
* the first.
|
|
166
|
-
*/
|
|
167
|
-
export interface FoirBlockMeta<TType extends string, TTypename extends string> {
|
|
168
|
-
__typename: TTypename;
|
|
169
|
-
_type: TType;
|
|
170
|
-
_id: string | null;
|
|
171
|
-
_key: string | null;
|
|
172
|
-
_label: string | null;
|
|
173
|
-
}
|
|
174
|
-
export interface RichtextBlock extends FoirBlockMeta<'richtext', 'RichtextBlock'>, RichtextValue {
|
|
175
|
-
}
|
|
176
|
-
export interface ImageBlock extends FoirBlockMeta<'image', 'ImageBlock'>, Omit<ImageValue, 'url'> {
|
|
177
|
-
/**
|
|
178
|
-
* Nullable here, `String!` on `ImageValue`. Wrapping a value type as a block
|
|
179
|
-
* drops NonNull (`wrapValueFieldsAsBlock`), and reporting it non-null would
|
|
180
|
-
* be a lie a consumer's generated types cannot recover from.
|
|
181
|
-
*/
|
|
182
|
-
url: string | null;
|
|
183
|
-
}
|
|
184
|
-
export interface VideoBlock extends FoirBlockMeta<'video', 'VideoBlock'>, Omit<VideoValue, 'url'> {
|
|
185
|
-
/** Nullable here, `String!` on `VideoValue` — see `ImageBlock.url`. */
|
|
186
|
-
url: string | null;
|
|
187
|
-
}
|
|
188
|
-
export interface FileBlock extends FoirBlockMeta<'file', 'FileBlock'>, FileValue {
|
|
189
|
-
}
|
|
190
|
-
export interface MediaBlock extends FoirBlockMeta<'media', 'MediaBlock'>, MediaValue {
|
|
191
|
-
}
|
|
192
|
-
export interface TextBlock extends FoirBlockMeta<'text', 'TextBlock'> {
|
|
193
|
-
value: string | null;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* A reference to another record.
|
|
205
|
+
* `Image`, `Video`, `FileValue`, `Richtext`, `GeoPoint` and `Reference` above
|
|
206
|
+
* ARE the members. There is no separate item type, because an item is its
|
|
207
|
+
* value: `... on Image { url }` means the same thing inside a flexible field
|
|
208
|
+
* and at a plain `image` field, and one fragment reads it in both positions.
|
|
197
209
|
*
|
|
198
|
-
* `
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* The
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
+
* `__typename` is optional on each, matching what graphql-codegen emits: it is
|
|
211
|
+
* present whenever selected, and the op compiler always selects it on a union's
|
|
212
|
+
* own selection set — it is the one field no `omit` path can name. Narrowing on
|
|
213
|
+
* it is exact regardless, because the literal types are disjoint.
|
|
214
|
+
*
|
|
215
|
+
* `_type` was the second discriminant and is gone with the envelope. It was
|
|
216
|
+
* never protected from `omit` anyway, so `__typename` was always the one to
|
|
217
|
+
* prefer.
|
|
218
|
+
*
|
|
219
|
+
* The three wrappers below have no replacement, because the things they
|
|
220
|
+
* described no longer exist:
|
|
221
|
+
*
|
|
222
|
+
* - `MediaBlock` — `media` stopped being a primitive at TTS-1391. It was
|
|
223
|
+
* never in the proto FieldType enum, and it is the one primitive name a
|
|
224
|
+
* project had also used as a model key.
|
|
225
|
+
* - `TextBlock` — TTS-1406 removed it. GraphQL requires a union member to be
|
|
226
|
+
* an object type and a text item's value is a bare scalar, which is the
|
|
227
|
+
* whole reason a wrapper existed. An inline model with a text field is how
|
|
228
|
+
* a text slot is authored.
|
|
229
|
+
* - `FlexibleBlock` — there is no fallback member. An item whose `_type`
|
|
230
|
+
* names nothing is nulled out of its list and reported, which is what a
|
|
231
|
+
* stored null already read as.
|
|
210
232
|
*/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
/** @deprecated Renamed to {@link Image}. The wrapper is gone; this IS the image. */
|
|
234
|
+
export type ImageBlock = Image;
|
|
235
|
+
/** @deprecated Renamed to {@link Video}. */
|
|
236
|
+
export type VideoBlock = Video;
|
|
237
|
+
/** @deprecated Renamed to {@link FileValue}. */
|
|
238
|
+
export type FileBlock = FileValue;
|
|
239
|
+
/** @deprecated Renamed to {@link Richtext}. */
|
|
240
|
+
export type RichtextBlock = Richtext;
|
|
241
|
+
/** @deprecated Renamed to {@link GeoPoint}. */
|
|
242
|
+
export type GeoPointValue = GeoPoint;
|
|
243
|
+
/** @deprecated Renamed to {@link Image}. */
|
|
244
|
+
export type ImageValue = Image;
|
|
245
|
+
/** @deprecated Renamed to {@link Video}. */
|
|
246
|
+
export type VideoValue = Video;
|
|
247
|
+
/** @deprecated Renamed to {@link Richtext}. */
|
|
248
|
+
export type RichtextValue = Richtext;
|
|
249
|
+
/** @deprecated Renamed to {@link Reference}. */
|
|
250
|
+
export type ReferenceValue<TRecord = FoirUnexpandedRecord> = Reference<TRecord>;
|
|
251
|
+
/** @deprecated Renamed to {@link Reference}. */
|
|
252
|
+
export type ReferenceBlock<TRecord = FoirUnexpandedRecord> = Reference<TRecord>;
|
|
219
253
|
/**
|
|
220
254
|
* What a nested union or a re-entered object resolves to.
|
|
221
255
|
*
|
|
@@ -225,11 +259,65 @@ export interface FlexibleBlock {
|
|
|
225
259
|
* `tilly_page.blocks → TillyPageBlock.blocks` cycle is reachable in two hops
|
|
226
260
|
* with no `include` and no caller input at all. At a degraded position the
|
|
227
261
|
* response carries `__typename` and nothing else, so that is what the generated
|
|
228
|
-
* type says.
|
|
262
|
+
* type says.
|
|
263
|
+
*
|
|
264
|
+
* # Reading past one
|
|
265
|
+
*
|
|
266
|
+
* A stopping point in front of a UNION can be spent a level on:
|
|
267
|
+
* `foir.product.findMany({ depth: 2 })` expands one more, and the response then
|
|
268
|
+
* carries that union's members here rather than `__typename` alone. A level
|
|
269
|
+
* costs one more expansion at each stopping point inside the expansion — so it
|
|
270
|
+
* compounds where there are several, and costs nothing per union MEMBER — and
|
|
271
|
+
* the call is refused client-side, with the arithmetic, when it would cross the
|
|
272
|
+
* server's field ceiling. See `FoirNamespaceCall.depth`.
|
|
273
|
+
*
|
|
274
|
+
* The TYPE follows the level. A generated composite on the path to a stopping
|
|
275
|
+
* point takes a `<D>` level parameter (see `FoirDepthLevel`), so at `depth: 2`
|
|
276
|
+
* the position resolves to the expanded union — `<Field>Blocks`, the type
|
|
277
|
+
* exported beside the `<Field>BlocksDegraded` named here — rather than to this
|
|
278
|
+
* one. The caller narrows on `__typename` and reads the member's own field; no
|
|
279
|
+
* cast, and no hand-written widening.
|
|
280
|
+
*
|
|
281
|
+
* `D` defaults to `1`, so a call passing no `depth` reaches THIS type at the
|
|
282
|
+
* stopping point exactly as it always did.
|
|
283
|
+
*
|
|
284
|
+
* A stopping point that is a composite CYCLE rather than a union — `menu.items
|
|
285
|
+
* .children` — has no union to expand and stays `__typename` at every depth.
|
|
229
286
|
*/
|
|
230
287
|
export interface FoirDegraded<TTypename extends string = string> {
|
|
231
288
|
__typename: TTypename;
|
|
232
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* The union levels the generated types describe — the type-level half of
|
|
292
|
+
* `depth`.
|
|
293
|
+
*
|
|
294
|
+
* A generated composite on the path to a stopping point takes one of these as
|
|
295
|
+
* a parameter, defaulted to `1`, and the model's shape at each level is named
|
|
296
|
+
* under `FoirModelDefinition.atDepth`. `1` is the complete-by-default document
|
|
297
|
+
* and is what every existing reference to a generated interface resolves to,
|
|
298
|
+
* which is why adding the parameter changed no result type.
|
|
299
|
+
*
|
|
300
|
+
* Four because depth is a budget against the server's 10,000-field ceiling and
|
|
301
|
+
* four is past what a generated module usefully describes: on the live dev
|
|
302
|
+
* project `product.findUnique` measures 318 fields at the default, 468 at 2 and
|
|
303
|
+
* 618 at 3. Those levels all fit since TTS-1448 — they read 1,203 / 4,308 /
|
|
304
|
+
* 13,623 when a union position was charged for every member — so the bound here
|
|
305
|
+
* is about how much generated type surface is worth emitting, not about what
|
|
306
|
+
* the server will run. A call
|
|
307
|
+
* asking for more is not an error: `atDepth` simply has no entry, the result
|
|
308
|
+
* falls back to the default shape, and the caller narrows by hand exactly as
|
|
309
|
+
* they did before. Under-promising is the safe direction, and the wire is
|
|
310
|
+
* unaffected — the printer's budget is a runtime number with no cap here.
|
|
311
|
+
*/
|
|
312
|
+
export type FoirDepthLevel = 1 | 2 | 3 | 4;
|
|
313
|
+
/**
|
|
314
|
+
* `FoirDepthBelow[3] === 2` — one union level spent.
|
|
315
|
+
*
|
|
316
|
+
* Mirrors the printer's `depth - 1` at an expanded stopping point
|
|
317
|
+
* (`document.ts`, `printFields`), and lives beside `FoirDepthLevel` so the two
|
|
318
|
+
* cannot disagree about how many levels exist.
|
|
319
|
+
*/
|
|
320
|
+
export type FoirDepthBelow = [never, never, 1, 2, 3];
|
|
233
321
|
/** A `Record`-union value the compiler did not expand. See `FoirDegraded`. */
|
|
234
322
|
export type FoirUnexpandedRecord = FoirDegraded;
|
|
235
323
|
/**
|
|
@@ -273,6 +361,128 @@ export interface FoirUpdateManyPayload {
|
|
|
273
361
|
count: number;
|
|
274
362
|
ids: string[];
|
|
275
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* What a note is ABOUT — the closed set the platform's write path enforces.
|
|
366
|
+
*
|
|
367
|
+
* On the wire it is `String!`, not a GraphQL enum, so the manifest publishes no
|
|
368
|
+
* `enumValues` for it and `foir types` cannot generate this union. It is here
|
|
369
|
+
* for the same reason the value objects are: platform-owned, identical in every
|
|
370
|
+
* project, and otherwise hand-written once per consumer.
|
|
371
|
+
*
|
|
372
|
+
* It is never a MODEL KEY. Model keys are the project's to choose, so a project
|
|
373
|
+
* with a model keyed `file` would make one value mean two things; a note on a
|
|
374
|
+
* record is `'record'` plus the model key on `subjectModelKey`.
|
|
375
|
+
*
|
|
376
|
+
* This mirrors a platform-side set, and the obligation that comes with a mirror
|
|
377
|
+
* (see the file header) applies: a kind added there widens this union in a
|
|
378
|
+
* minor release, and until then a value outside it is a row no writer could
|
|
379
|
+
* have produced.
|
|
380
|
+
*/
|
|
381
|
+
export type FoirNoteSubjectType = 'record' | 'file' | 'model' | 'operation' | 'schedule' | 'customer';
|
|
382
|
+
/**
|
|
383
|
+
* One file the customer submitted with their note.
|
|
384
|
+
*
|
|
385
|
+
* The file's own facts, and nothing about the platform's handling of it: when
|
|
386
|
+
* the link row was written and which retention clock it starts are not the
|
|
387
|
+
* author's business. What is here is enough to render "you sent this".
|
|
388
|
+
*/
|
|
389
|
+
export interface FoirCustomerNoteAttachment {
|
|
390
|
+
/** The file's id — the same one `createMyNote` was given. */
|
|
391
|
+
fileId: string;
|
|
392
|
+
filename: string;
|
|
393
|
+
mimeType: string;
|
|
394
|
+
/** Size in bytes, as stored. Authoritative; not the number the uploader declared. */
|
|
395
|
+
size: number;
|
|
396
|
+
url: string;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* One note on the customer plane — `foir.ns.notes.myNotes` and
|
|
400
|
+
* `foir.ns.notes.createMyNote`.
|
|
401
|
+
*
|
|
402
|
+
* Deliberately narrower than the note your team sees in the dashboard. There is
|
|
403
|
+
* no visibility, no resolution note, no mentions and no metadata: those are the
|
|
404
|
+
* project's view of a thread, not the author's, and the customer-facing type
|
|
405
|
+
* does not merely omit them from the document — the surface does not carry them.
|
|
406
|
+
*/
|
|
407
|
+
export interface FoirCustomerNote {
|
|
408
|
+
id: string;
|
|
409
|
+
/** The note's prose. */
|
|
410
|
+
body: string | null;
|
|
411
|
+
subjectType: FoirNoteSubjectType;
|
|
412
|
+
subjectId: string;
|
|
413
|
+
/** The record's model key. Present only on a `record` subject. */
|
|
414
|
+
subjectModelKey: string | null;
|
|
415
|
+
/** Set when this note is a reply within a thread. */
|
|
416
|
+
parentNoteId: string | null;
|
|
417
|
+
threadRootId: string | null;
|
|
418
|
+
isResolved: boolean;
|
|
419
|
+
/**
|
|
420
|
+
* The files submitted with this note. Empty when there are none.
|
|
421
|
+
*
|
|
422
|
+
* The one place the customer surface is WIDER than "the author's own prose",
|
|
423
|
+
* and deliberately: an author who attached a screenshot has to be able to see
|
|
424
|
+
* that it arrived, or the only readings left to them are that it failed
|
|
425
|
+
* silently or that it was taken away.
|
|
426
|
+
*/
|
|
427
|
+
attachments: FoirCustomerNoteAttachment[];
|
|
428
|
+
/** ISO-8601. */
|
|
429
|
+
createdAt: string;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* What a note surface has to know about the project before it renders anything
|
|
433
|
+
* — `foir.ns.notes.noteConfig`.
|
|
434
|
+
*
|
|
435
|
+
* Charged `notes:write` rather than `notes:read`, and refused to a caller with
|
|
436
|
+
* no customer session, because the only caller it is useful to is one that can
|
|
437
|
+
* submit. So a composer can read it as its single readiness probe: an answer
|
|
438
|
+
* means this visitor may write, and a refusal names which half is missing.
|
|
439
|
+
*/
|
|
440
|
+
export interface FoirNoteConfig {
|
|
441
|
+
/**
|
|
442
|
+
* Whether this project's note surfaces may OFFER to capture a screenshot.
|
|
443
|
+
*
|
|
444
|
+
* It gates the offer, not the storing. Capture stays opt-in per submission —
|
|
445
|
+
* the browser's own picker is the consent gate and it cannot be bypassed.
|
|
446
|
+
*/
|
|
447
|
+
screenshotsEnabled: boolean;
|
|
448
|
+
}
|
|
449
|
+
/** `myNotes` arguments. Forward-only cursor pagination, like every list read. */
|
|
450
|
+
export interface FoirMyNotesArgs {
|
|
451
|
+
first?: number;
|
|
452
|
+
after?: string;
|
|
453
|
+
/** Narrow to one kind of subject. */
|
|
454
|
+
subjectType?: FoirNoteSubjectType;
|
|
455
|
+
/** Narrow to one subject — a record id, a file id, a model key. */
|
|
456
|
+
subjectId?: string;
|
|
457
|
+
/** Read one thread. Returns only the notes in it this caller can see. */
|
|
458
|
+
threadRootId?: string;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* `myNotes` — the notes this customer wrote, plus the replies your team
|
|
462
|
+
* deliberately sent them. Never another customer's, and never an operator's
|
|
463
|
+
* internal ones.
|
|
464
|
+
*/
|
|
465
|
+
export type FoirMyNotesResult = FoirConnection<FoirCustomerNote>;
|
|
466
|
+
/** `createMyNote` arguments. */
|
|
467
|
+
export interface FoirCreateMyNoteArgs {
|
|
468
|
+
subjectType: FoirNoteSubjectType;
|
|
469
|
+
subjectId: string;
|
|
470
|
+
/** Required when `subjectType` is `'record'`, refused otherwise. */
|
|
471
|
+
subjectModelKey?: string;
|
|
472
|
+
body: string;
|
|
473
|
+
/** Reply within an existing thread. */
|
|
474
|
+
parentNoteId?: string;
|
|
475
|
+
/**
|
|
476
|
+
* Files to submit with this note, already uploaded through
|
|
477
|
+
* `createFileUpload` / `confirmFileUpload`.
|
|
478
|
+
*
|
|
479
|
+
* A file this caller cannot read is REFUSED and the note is not written. That
|
|
480
|
+
* is not a rough edge to code around: a create that accepted the note and
|
|
481
|
+
* dropped the attachment would leave the author believing they sent a
|
|
482
|
+
* screenshot nobody has.
|
|
483
|
+
*/
|
|
484
|
+
attachmentFileIds?: string[];
|
|
485
|
+
}
|
|
276
486
|
export type FoirDeviceType = 'DESKTOP' | 'MOBILE' | 'TABLET';
|
|
277
487
|
export type FoirPlatformType = 'IOS' | 'ANDROID' | 'WEB';
|
|
278
488
|
/**
|
package/dist/data/client.d.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ```ts
|
|
5
5
|
* import { createFoirClient } from '@foir/sdk/data';
|
|
6
|
-
* import
|
|
6
|
+
* import * as generated from './foir.generated';
|
|
7
7
|
*
|
|
8
|
-
* const foir = createFoirClient
|
|
8
|
+
* const foir = createFoirClient({
|
|
9
9
|
* apiUrl: process.env.PLATFORM_API_URL!,
|
|
10
10
|
* apiKey: process.env.PLATFORM_API_KEY!,
|
|
11
|
-
*
|
|
11
|
+
* generated,
|
|
12
12
|
* });
|
|
13
13
|
*
|
|
14
14
|
* const block = await foir.tilly_block.findUnique({
|
|
@@ -18,6 +18,19 @@
|
|
|
18
18
|
* });
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
21
|
+
* One import and one call. `FoirSchema` is inferred from the module rather than
|
|
22
|
+
* restated, and the four runtime exports the client reads are read off it rather
|
|
23
|
+
* than named one at a time at every call site — see `resolveGeneratedConfig`
|
|
24
|
+
* below for why that indirection is worth having.
|
|
25
|
+
*
|
|
26
|
+
* The individual fields (`schemaVersion`, `selections`, `namespaceSelections`,
|
|
27
|
+
* `selectionUnions`) still work, and still win where both are given.
|
|
28
|
+
*
|
|
29
|
+
* The call is the same call it always was. What changed underneath is what goes
|
|
30
|
+
* on the wire: the client loads that op's selection tree, applies
|
|
31
|
+
* `omit`/`pick`/`include`, prints a GraphQL document, hashes it and sends the
|
|
32
|
+
* hash. See `document.ts` for the printer and `transport.ts` for the routing.
|
|
33
|
+
*
|
|
21
34
|
* ---------------------------------------------------------------------------
|
|
22
35
|
* Why this is a Proxy
|
|
23
36
|
* ---------------------------------------------------------------------------
|
|
@@ -29,28 +42,54 @@
|
|
|
29
42
|
* reached. A two-level `Proxy` does that in a few lines and costs one property
|
|
30
43
|
* lookup per call.
|
|
31
44
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
45
|
+
* ---------------------------------------------------------------------------
|
|
46
|
+
* Why `selections` is a config field and not a fetch
|
|
47
|
+
* ---------------------------------------------------------------------------
|
|
48
|
+
*
|
|
49
|
+
* The trees ARE runtime data — that is the one thing the generated module has to
|
|
50
|
+
* carry across the type/value boundary, alongside `foirSchemaVersion`. Passing
|
|
51
|
+
* them in costs nothing and is the intended path.
|
|
52
|
+
*
|
|
53
|
+
* When they are absent the client fetches `GET /v1/manifest` once, at first use,
|
|
54
|
+
* and reads the same trees off it. That is a FALLBACK and never the default,
|
|
55
|
+
* for the reason this file has always given: it puts a round trip in front of
|
|
56
|
+
* the first query on every cold edge invocation, to learn something the
|
|
57
|
+
* generated file already knows. What it buys is that `@foir/sdk/data` still
|
|
58
|
+
* works with zero codegen, which keeps the getting-started path honest.
|
|
59
|
+
*
|
|
60
|
+
* It has to stay reachable, and it must not be reachable by ACCIDENT. Those
|
|
61
|
+
* pull in opposite directions and the split is: a client built with no generated
|
|
62
|
+
* module and no `selections` has chosen the fallback, and says so once; a client
|
|
63
|
+
* built with a `generated` module that cannot serve the printer is refused where
|
|
64
|
+
* it was constructed. There is no input that quietly produces the round trip.
|
|
35
65
|
*
|
|
36
66
|
* ---------------------------------------------------------------------------
|
|
37
67
|
* Why namespaces live under `.ns`
|
|
38
68
|
* ---------------------------------------------------------------------------
|
|
39
69
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* `namespace`. `ns` and `raw` are therefore reserved top-level names.
|
|
70
|
+
* Since there is no runtime schema (above), the client cannot tell whether
|
|
71
|
+
* `foir.search` means a model or a namespace. So the addressing carries it:
|
|
72
|
+
* `foir.<model>.<op>()` addresses a model, `foir.ns.<namespace>.<op>()` a
|
|
73
|
+
* namespace. `ns` and `raw` are therefore reserved top-level names.
|
|
45
74
|
*/
|
|
46
75
|
import type { FoirModelDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirSchemaDefinition } from './schema';
|
|
47
76
|
import type { FoirModelOperation, FoirNamespaceOperation } from './selection';
|
|
48
77
|
import { type FoirEnv } from '../transport/foir-http';
|
|
49
|
-
import { type FoirClientConfig, type
|
|
50
|
-
|
|
78
|
+
import { type FoirClientConfig, type FoirGeneratedModule } from './transport';
|
|
79
|
+
import { type FoirModelSubscriptions, type FoirNamespaceSubscriptions } from './subscriptions';
|
|
80
|
+
/**
|
|
81
|
+
* The ops available on one model for this credential, plus its subscriptions.
|
|
82
|
+
*
|
|
83
|
+
* `onChange` is on every model rather than gated on a generated flag, and that
|
|
84
|
+
* is correct rather than lazy: `recordChanged` charges
|
|
85
|
+
* `records:read:<modelKey>`, which is the SAME scope that put the model in
|
|
86
|
+
* `FoirSchema` at all. A model this credential cannot watch is a model it
|
|
87
|
+
* cannot read, so it is already absent from `S['models']` and the accessor
|
|
88
|
+
* cannot be reached.
|
|
89
|
+
*/
|
|
51
90
|
export type FoirModelClient<S extends FoirSchemaDefinition, M extends FoirModelDefinition> = {
|
|
52
91
|
[OK in keyof M['ops']]: M['ops'][OK] extends FoirOpDefinition ? FoirModelOperation<S, M, M['ops'][OK]> : never;
|
|
53
|
-
};
|
|
92
|
+
} & FoirModelSubscriptions;
|
|
54
93
|
export type FoirNamespaceClient<N extends {
|
|
55
94
|
ops: object;
|
|
56
95
|
}> = {
|
|
@@ -59,21 +98,44 @@ export type FoirNamespaceClient<N extends {
|
|
|
59
98
|
export interface FoirClientCore<S extends FoirSchemaDefinition> {
|
|
60
99
|
/**
|
|
61
100
|
* Non-model root namespaces (`search`, `customers`, `notifications`).
|
|
62
|
-
* Separate accessor because the
|
|
63
|
-
* and the client has no runtime schema to disambiguate with.
|
|
101
|
+
* Separate accessor because the addressing has to distinguish a model from a
|
|
102
|
+
* namespace and the client has no runtime schema to disambiguate with.
|
|
64
103
|
*/
|
|
65
104
|
readonly ns: {
|
|
66
|
-
[NK in keyof S['namespaces']]: FoirNamespaceClient<S['namespaces'][NK]>;
|
|
105
|
+
[NK in keyof S['namespaces']]: FoirNamespaceClient<S['namespaces'][NK]> & FoirNamespaceSubscriptions<NK>;
|
|
67
106
|
};
|
|
68
107
|
/**
|
|
69
|
-
* Untyped passthrough
|
|
108
|
+
* Untyped passthrough — a raw GraphQL document, executed on this credential.
|
|
109
|
+
*
|
|
110
|
+
* It changed meaning with the transport, and deliberately so. It used to take
|
|
111
|
+
* a JSON op because that was the wire; now the wire is GraphQL, and an escape
|
|
112
|
+
* hatch that could only say what the JSON op could say would be an escape from
|
|
113
|
+
* nothing. As a document it reaches everything the credential can reach —
|
|
114
|
+
* including the root fields no model or namespace claims (`designTokens`,
|
|
115
|
+
* `record`, `locales`, `resolvedLocale`, the whole customer-auth mutation
|
|
116
|
+
* set), which the JSON op could not address at all.
|
|
117
|
+
*
|
|
118
|
+
* Returns the whole `data` object rather than a single root field, because a
|
|
119
|
+
* hand-written document may legitimately have several. Routing, the persisted
|
|
120
|
+
* -query handshake and `FoirRequestError` are exactly as on the typed path.
|
|
121
|
+
*
|
|
122
|
+
* ```ts
|
|
123
|
+
* const { pages } = await foir.raw<{ pages: { totalCount: number } }>(
|
|
124
|
+
* 'query Recent($n: Int) { pages(first: $n, orderBy: { createdAt: DESC }) { totalCount } }',
|
|
125
|
+
* { n: 5 },
|
|
126
|
+
* );
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
raw<T = unknown>(document: string, variables?: Record<string, unknown>): Promise<T>;
|
|
130
|
+
/**
|
|
131
|
+
* Close the subscription socket and drop every live subscription.
|
|
70
132
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
133
|
+
* Only needed by a long-lived process that discards a client — a browser tab
|
|
134
|
+
* or an edge invocation just goes away. The socket is lazy and self-closing
|
|
135
|
+
* (it opens on the first subscription and closes when the last is cancelled),
|
|
136
|
+
* so a client that only queries never has one to dispose of.
|
|
75
137
|
*/
|
|
76
|
-
|
|
138
|
+
dispose(): void;
|
|
77
139
|
}
|
|
78
140
|
/**
|
|
79
141
|
* The client, shaped by the credential's manifest. An op that is not in
|
|
@@ -83,12 +145,22 @@ export interface FoirClientCore<S extends FoirSchemaDefinition> {
|
|
|
83
145
|
export type FoirClient<S extends FoirSchemaDefinition> = FoirClientCore<S> & {
|
|
84
146
|
[MK in keyof S['models']]: FoirModelClient<S, S['models'][MK]>;
|
|
85
147
|
};
|
|
86
|
-
export declare function createFoirClient<S extends FoirSchemaDefinition>(
|
|
148
|
+
export declare function createFoirClient<S extends FoirSchemaDefinition = FoirSchemaDefinition>(clientConfig: FoirClientConfig<S>): FoirClient<S>;
|
|
87
149
|
/**
|
|
88
150
|
* `createFoirClient` from the `PLATFORM_API_URL` / `PLATFORM_API_KEY` /
|
|
89
151
|
* `FOIR_PREVIEW` convention `@foir/sdk/client` already uses, so a storefront
|
|
90
152
|
* that has an env wired for GraphQL can add the data client without a second
|
|
91
153
|
* set of variables. Preview picks the `sk_` `PLATFORM_PREVIEW_API_KEY` and the
|
|
92
154
|
* draft schema channel, exactly as `foirRequest` does.
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* import * as generated from './foir.generated';
|
|
158
|
+
* const foir = createFoirClientFromEnv(env, { generated });
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* `generated` is spelled out in the overrides type rather than left inside the
|
|
162
|
+
* `Omit<…>` so `S` is inferred from it here exactly as it is on `createFoirClient`.
|
|
93
163
|
*/
|
|
94
|
-
export declare function createFoirClientFromEnv<S extends FoirSchemaDefinition>(env: FoirEnv, overrides?: Omit<FoirClientConfig, 'apiUrl' | 'apiKey' | 'channel'>
|
|
164
|
+
export declare function createFoirClientFromEnv<S extends FoirSchemaDefinition = FoirSchemaDefinition>(env: FoirEnv, overrides?: Omit<FoirClientConfig, 'apiUrl' | 'apiKey' | 'channel' | 'generated'> & {
|
|
165
|
+
generated?: FoirGeneratedModule<S>;
|
|
166
|
+
}): FoirClient<S>;
|