@foir/sdk 0.8.0 → 0.9.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 CHANGED
@@ -104,8 +104,60 @@ on `dev` would publish unreviewed work to npm.
104
104
 
105
105
  Changes on `dev` that have not been published to npm.
106
106
 
107
- **The next release is BREAKING** — see `foir.raw()` under "Changed". A MINOR,
108
- per the pre-1.0 note above.
107
+ ---
108
+
109
+ ## [0.9.0] — 2026-08-20
110
+
111
+ Everything additive, so a MINOR either way under the pre-1.0 note.
112
+
113
+ ### Added
114
+
115
+ - **`hostedLogoutUrl(config, clientId, returnTo?)` on `./auth`** — the mirror of
116
+ `hostedLoginUrl`, and the only thing that actually signs a customer out on the
117
+ hosted-cookie flow.
118
+
119
+ Login on that flow is symmetric across browser and server; logout was not.
120
+ The apex `foir_customer_session` cookie is the durable credential, and
121
+ disposing of a bearer, clearing a local session, or calling the
122
+ `customerLogout` mutation all leave it in place — the next request
123
+ re-establishes the customer from it. The only surface that ended a session was
124
+ `POST /session/logout`, cookie-authenticated and CSRF-header-gated, which only
125
+ a browser sending `credentials: 'include'` can reach.
126
+
127
+ This is a URL builder: no transport, no CORS surface, no CSRF header, no
128
+ JavaScript required. Server-side logout becomes
129
+ `return redirect(hostedLogoutUrl(config, clientId, returnTo))`. `returnTo`
130
+ must be absolute and on an origin registered for `clientId`.
131
+
132
+ Requires the platform's `end-session` endpoint (TTS-1534). `HostedLoginConfig`
133
+ gains an optional `endSessionPath`, defaulting to `/session/end-session`.
134
+
135
+ - **`Prose`, `Passage`, `ProseSpan` and `RecordAncestor` on `./data`.** The
136
+ shapes a prose field and the record hierarchy resolve to, shipped here
137
+ rather than generated per project because they are identical everywhere —
138
+ the same reason `Image` and `Reference` live here.
139
+
140
+ `Prose<E>` is parameterised on its entries element: `Prose<Passage>` for a
141
+ text-only field, `Prose<Cta | Passage>` for one that admits embeds. `foir
142
+ types` fills the parameter in from the field's own member set.
143
+
144
+ - **`FoirManifestStructural`, and `structurals` on `FoirManifestModel`.** The
145
+ manifest's bucket for a platform-owned object with no model behind it — a
146
+ prose wrapper, an `ancestors` list. Absent on a server that predates it,
147
+ where those fields landed in `unclassifiedFields` and dropped out of the
148
+ selection entirely; widen on absent.
149
+
150
+ ---
151
+
152
+ ## [0.8.0] — 2026-08-20
153
+
154
+ **BREAKING** — see `foir.raw()` under "Changed". A MINOR, per the pre-1.0
155
+ note above.
156
+
157
+ > Filed after the fact. 0.8.0 was published with a version bump and no
158
+ > changelog entry, so everything below sat under "Unreleased" while it was
159
+ > already on npm. Recorded here rather than left there, because a consumer
160
+ > upgrading from 0.7.0 has no other account of what changed.
109
161
 
110
162
  ### Removed
111
163
 
@@ -19,12 +19,34 @@ export interface HostedLoginConfig {
19
19
  cookieName?: string;
20
20
  /** Session-exchange path on the front door. Default `/session/token`. */
21
21
  sessionPath?: string;
22
+ /** RP-initiated logout path on the front door. Default `/session/end-session`. */
23
+ endSessionPath?: string;
22
24
  }
23
25
  /**
24
26
  * Build the redirect URL to the hosted front door, forwarding a best-effort
25
27
  * absolute `returnTo` (honoured if the front door supports it).
26
28
  */
27
29
  export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string): string;
30
+ /**
31
+ * Build the redirect URL that ENDS the hosted session — the mirror of
32
+ * `hostedLoginUrl`, and the only thing that actually signs a customer out on
33
+ * this flow.
34
+ *
35
+ * The cookie is the durable credential. Disposing of a bearer, clearing a
36
+ * local session, or calling the `customerLogout` mutation (which clears
37
+ * upstream provider tokens and nothing else) all leave it in place, and the
38
+ * next request re-establishes the customer from it. This URL points at the
39
+ * front door's end-session endpoint, which revokes the session row and clears
40
+ * the cookie before returning the browser to `returnTo`.
41
+ *
42
+ * A URL builder, deliberately: no transport, no CORS surface, no CSRF header,
43
+ * and no JavaScript required at the consumer. Server-side logout is
44
+ * `return redirect(hostedLogoutUrl(config, clientId, returnTo))`.
45
+ *
46
+ * `returnTo` must be an absolute URL on an origin registered for `clientId`;
47
+ * the platform refuses anything else. Omit it for a bare 204 with no redirect.
48
+ */
49
+ export declare function hostedLogoutUrl(config: HostedLoginConfig, clientId: string, returnTo?: string): string;
28
50
  /** The identity + bearer returned by a successful hosted-session exchange. */
29
51
  export interface HostedSession {
30
52
  token: string;
@@ -14,7 +14,7 @@ export { customerLogin, customerRegister, customerRequestOTP, customerLoginOTP,
14
14
  export type { FoirAuthTokens, FoirCustomer, FoirUser, SessionAdapter, RequireAuthOptions, RequireAdminOptions, } from './auth';
15
15
  export { AUTH_OPS } from './ops';
16
16
  export type { AuthOpName, AuthOpOptions } from './ops';
17
- export { hostedLoginUrl, fetchHostedSession } from './hosted';
17
+ export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession } from './hosted';
18
18
  export type { HostedLoginConfig, HostedSession } from './hosted';
19
19
  export { createFoirSessionClient } from './session-client';
20
20
  export type { FoirSessionClient, FoirSessionConfig, FoirSessionUser, } from './session-client';
package/dist/auth.js CHANGED
@@ -1111,6 +1111,7 @@ async function requireAdmin(adapter, options) {
1111
1111
  // src/auth/hosted.ts
1112
1112
  var DEFAULT_COOKIE_NAME = "foir_customer_session";
1113
1113
  var DEFAULT_SESSION_PATH = "/session/token";
1114
+ var DEFAULT_END_SESSION_PATH = "/session/end-session";
1114
1115
  function hostedLoginUrl(config, returnTo) {
1115
1116
  const dest = new URL(config.loginOrigin);
1116
1117
  if (returnTo && returnTo !== "/") {
@@ -1118,6 +1119,15 @@ function hostedLoginUrl(config, returnTo) {
1118
1119
  }
1119
1120
  return dest.toString();
1120
1121
  }
1122
+ function hostedLogoutUrl(config, clientId, returnTo) {
1123
+ const path = config.endSessionPath ?? DEFAULT_END_SESSION_PATH;
1124
+ const dest = new URL(path, config.loginOrigin);
1125
+ dest.searchParams.set("client_id", clientId);
1126
+ if (returnTo) {
1127
+ dest.searchParams.set("post_logout_redirect_uri", returnTo);
1128
+ }
1129
+ return dest.toString();
1130
+ }
1121
1131
  async function fetchHostedSession(request, config) {
1122
1132
  const cookieName = config.cookieName ?? DEFAULT_COOKIE_NAME;
1123
1133
  const sessionPath = config.sessionPath ?? DEFAULT_SESSION_PATH;
@@ -1313,6 +1323,7 @@ export {
1313
1323
  getSessionCustomer,
1314
1324
  getSessionToken,
1315
1325
  hostedLoginUrl,
1326
+ hostedLogoutUrl,
1316
1327
  loginWithOTP,
1317
1328
  loginWithPassword,
1318
1329
  myProviderToken,
@@ -132,6 +132,73 @@ export interface Richtext {
132
132
  json: FoirJsonValue | null;
133
133
  markdown: string | null;
134
134
  }
135
+ /**
136
+ * One run of styled text inside a passage.
137
+ *
138
+ * `marks` are named — `strong`, `em`, `code`, or a key into the passage's
139
+ * `markDefs` for anything that carries data (a link, and nothing else today).
140
+ * The bitmask the old editor stored is gone.
141
+ */
142
+ export interface ProseSpan {
143
+ __typename?: 'ProseSpan';
144
+ text: string | null;
145
+ marks: Array<string | null> | null;
146
+ }
147
+ /**
148
+ * A text entry in a prose flow — a paragraph, a heading, a quote, a list item.
149
+ *
150
+ * `kind` is the semantic shape and `token` is the design-token override that
151
+ * beats it when set. `list` + `level` carry list nesting flat rather than as a
152
+ * tree. A soft break inside a passage is a `\n` in a span's text, not a
153
+ * separate entry.
154
+ *
155
+ * `markDefs` stays an open JSON leaf by declaration: def types are extensible,
156
+ * and a typed object over an open shape would drop what it does not know.
157
+ */
158
+ export interface Passage {
159
+ __typename?: 'Passage';
160
+ _key: string | null;
161
+ kind: string | null;
162
+ token: string | null;
163
+ align: string | null;
164
+ list: string | null;
165
+ level: number | null;
166
+ language: string | null;
167
+ spans: Array<ProseSpan | null> | null;
168
+ markDefs: FoirJsonValue | null;
169
+ }
170
+ /**
171
+ * A prose field's read projection.
172
+ *
173
+ * `entries` is the typed reading and the one that keeps prose structured —
174
+ * `Passage` for a text-only field, and a union of `Passage` with the field's
175
+ * admitted embeds for one that places models. `html` and `markdown` are the
176
+ * passages only: an embed is omitted from both BY DECLARATION rather than
177
+ * rendered as invented markup, so a consumer that wants the embeds reads
178
+ * `entries`. `json` is the declared flow verbatim — the lossless escape hatch
179
+ * where an entry nobody recognises always survives.
180
+ *
181
+ * `E` is the entries element, filled in per field by `foir types`.
182
+ */
183
+ export interface Prose<E = Passage> {
184
+ html: string | null;
185
+ markdown: string | null;
186
+ json: FoirJsonValue | null;
187
+ entries: Array<E | null> | null;
188
+ }
189
+ /**
190
+ * One ancestor of a hierarchy record, root-first.
191
+ *
192
+ * Identity only, by design: a child's public URL already exposes every
193
+ * ancestor's slug, so the walk never widens content visibility. Anything more
194
+ * than identity goes through the `parent` reference like any other read.
195
+ */
196
+ export interface RecordAncestor {
197
+ __typename?: 'RecordAncestor';
198
+ _id: string;
199
+ _naturalKey: string | null;
200
+ _modelKey: string | null;
201
+ }
135
202
  /**
136
203
  * A reference whose target model is not pinned by the field's declaration.
137
204
  *
@@ -44,12 +44,12 @@
44
44
  export { createFoirClient, createFoirClientFromEnv } from './client';
45
45
  export type { FoirClient, FoirClientCore, FoirModelClient, FoirNamespaceClient } from './client';
46
46
  export { fetchFoirManifest } from './manifest';
47
- export type { FoirManifest, FoirManifestComposite, FoirManifestModel, FoirManifestNamespace, FoirManifestOp, FoirManifestOpPayload, FoirManifestRelation, FoirManifestScalar, FoirManifestSelection, FoirManifestSelectionField, FoirManifestSelectionMember, FoirManifestSelectionUnion, FoirManifestSubscription, FoirManifestSubscriptionArgScope, FoirManifestUnclassifiedField, } from './manifest';
47
+ export type { FoirManifest, FoirManifestComposite, FoirManifestModel, FoirManifestNamespace, FoirManifestOp, FoirManifestOpPayload, FoirManifestRelation, FoirManifestScalar, FoirManifestSelection, FoirManifestSelectionField, FoirManifestSelectionMember, FoirManifestSelectionUnion, FoirManifestStructural, FoirManifestSubscription, FoirManifestSubscriptionArgScope, FoirManifestUnclassifiedField, } from './manifest';
48
48
  export { planFoirModelCall, planFoirNamespaceCall } from './document';
49
49
  export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
50
50
  export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
51
51
  export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
52
- export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Reference, Richtext, Video, } from './builtins';
52
+ export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
53
53
  /**
54
54
  * Pre-TTS-1391 spellings. The `<Name>Block` wrappers and the `<Name>Value`
55
55
  * split are the same thing as the types above — an item IS its value — so each
@@ -139,6 +139,23 @@ export interface FoirManifestOpPayload {
139
139
  */
140
140
  error?: string;
141
141
  }
142
+ /**
143
+ * A platform-owned structural field: a prose wrapper, or the hierarchy walk's
144
+ * `ancestors`. Nested and always returned, like a composite — but there is no
145
+ * model behind it, so the entry names its TYPE (`Prose`, `ProseCta`,
146
+ * `RecordAncestor`) and the shape is `builtins.ts`'s.
147
+ *
148
+ * ABSENT on a server that predates the bucket, where these fields landed in
149
+ * `unclassifiedFields` and dropped out of the selection entirely. Widen on
150
+ * absent, as everywhere else in this file.
151
+ */
152
+ export interface FoirManifestStructural {
153
+ key: string;
154
+ type: string;
155
+ list: boolean;
156
+ nullable: boolean;
157
+ deprecated?: boolean;
158
+ }
142
159
  /** A built field the projection has no bucket for. Reported rather than dropped. */
143
160
  export interface FoirManifestUnclassifiedField {
144
161
  key: string;
@@ -172,12 +189,14 @@ export interface FoirManifestModel {
172
189
  scalars: FoirManifestScalar[];
173
190
  relations: FoirManifestRelation[];
174
191
  composites: FoirManifestComposite[];
192
+ /** Platform-owned structural fields — prose wrappers, `ancestors`. */
193
+ structurals?: FoirManifestStructural[];
175
194
  /**
176
- * Block-union field key → member model keys. Members that are built-in block
177
- * primitives appear under their lowercase token (`text`, `image`, `video`,
178
- * `file`, `richtext`, `media`, `reference`, `flexible`) and have NO entry in
179
- * `models` — they are platform-owned, identical in every project, and typed
180
- * statically by `builtins.ts`.
195
+ * Union field key → member model keys. Members that are platform primitives
196
+ * appear under their lowercase token (`richtext`, `image`, `video`, `file`,
197
+ * `reference`, `geopoint`) and have NO entry in `models` — they are
198
+ * platform-owned, identical in every project, and typed statically by
199
+ * `builtins.ts`.
181
200
  */
182
201
  unions?: Record<string, string[]>;
183
202
  /** Op names of this model's typed lookups. Each also appears in `ops`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {