@foir/sdk 0.16.0 → 0.17.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
@@ -108,6 +108,28 @@ nobody asking for `@foir/sdk` gets it early. The push that carries the commit to
108
108
 
109
109
  Changes on `dev` that have not been published to npm.
110
110
 
111
+ ## [0.17.0] — 2026-10-02
112
+
113
+ Published under npm's `next` tag from `dev`, because it reads a field the
114
+ platform serves from the next promotion; `latest` moves to it then.
115
+
116
+ ### Added
117
+
118
+ - **An app is told when a project cannot email its customers**
119
+ (`@foir/sdk/app`). A project with no App base URL used to accept a
120
+ notification's email and drop it. The platform now refuses a notification
121
+ that carries an email, and an opt-in to a category confirmed by email, with
122
+ the reason as data. `appCustomerEmailUnavailableOf(error)` reads it:
123
+ `{ reason: 'NO_APP_BASE_URL' }`, or `null` for any other failure.
124
+ - `install.config()` answers `customerEmailUnavailable`: the same reason ahead
125
+ of time, or `null` when the project can send. `AppCustomerEmailUnavailableReason`
126
+ is the type.
127
+
128
+ ### Changed
129
+
130
+ - `install.config()` asks the platform for `customerEmailUnavailable`, a field
131
+ a platform older than this release does not have.
132
+
111
133
  ## [0.16.0] — 2026-10-02
112
134
 
113
135
  Published under npm's `next` tag from `dev`, as 0.15.0 was; `latest` moves to
@@ -28,6 +28,7 @@
28
28
  */
29
29
  import type { FoirDispatchCredential } from '../data/transport';
30
30
  import { type FoirRetryPolicy } from '../transport/retry';
31
+ import type { AppCustomerEmailUnavailableReason } from './errors';
31
32
  type Json = null | boolean | number | string | Json[] | {
32
33
  [key: string]: Json;
33
34
  };
@@ -95,6 +96,13 @@ export interface AppInstallConfig {
95
96
  settings: JsonObject;
96
97
  sources: AppBinding[];
97
98
  sinks: AppBinding[];
99
+ /**
100
+ * Why the project cannot send a customer an email at the level this install
101
+ * sits at, or null when it can. While it is set, a notification that carries
102
+ * an email and an opt-in to a category confirmed by email are refused
103
+ * (`appCustomerEmailUnavailableOf`), so say so before one is.
104
+ */
105
+ customerEmailUnavailable: AppCustomerEmailUnavailableReason | null;
98
106
  }
99
107
  /**
100
108
  * The status an install reports. Leaving out `subscriptionHealth`,
@@ -34,6 +34,26 @@ export declare function appCustomerUnlinkedOf(error: unknown): {
34
34
  model: string;
35
35
  identityProvider: string;
36
36
  } | null;
37
+ /**
38
+ * Why a project cannot send a customer an email. `NO_APP_BASE_URL`: no App base
39
+ * URL is set for the project, or for the customer's organization. A reason
40
+ * this build does not know arrives as the platform spelled it.
41
+ */
42
+ export type AppCustomerEmailUnavailableReason = 'NO_APP_BASE_URL' | (string & {});
43
+ /** The reason as the platform sent it, or null when it sent none. */
44
+ export declare function customerEmailUnavailableReasonOf(value: unknown): AppCustomerEmailUnavailableReason | null;
45
+ /**
46
+ * A request refused because it would send a customer an email the project
47
+ * cannot send, or null for any other failure. Nothing was written and nothing
48
+ * was queued. An opt-in to a category confirmed by email is refused this way,
49
+ * and so is a notification that carries an email.
50
+ *
51
+ * It is about the project, not the customer: the project's operator fixes it,
52
+ * and the same request goes once they have.
53
+ */
54
+ export declare function appCustomerEmailUnavailableOf(error: unknown): {
55
+ reason: AppCustomerEmailUnavailableReason;
56
+ } | null;
37
57
  /**
38
58
  * Whether a read failed because the token is not served the field it asked
39
59
  * for: the field is not on the schema this token's capabilities reach. That is
@@ -22,8 +22,8 @@
22
22
  */
23
23
  export { createAppClient } from './client';
24
24
  export type { AppBinding, AppClient, AppClientConfig, AppConsentChange, AppCustomer, AppCustomerEraseResult, AppCustomerList, AppCustomerSaveResult, AppFieldMapping, AppInstallConfig, AppNotification, AppOperationStart, AppRecord, AppRecordList, AppStartedOperation, AppStatusReport, AppSyncDirection, AppUpsertResult, AppWhoami, } from './client';
25
- export { appCustomerUnlinkedOf, appLookupConflictOf, isAppFieldWithheld } from './errors';
26
- export type { AppLookupConflict } from './errors';
25
+ export { appCustomerEmailUnavailableOf, appCustomerUnlinkedOf, appLookupConflictOf, isAppFieldWithheld } from './errors';
26
+ export type { AppCustomerEmailUnavailableReason, AppLookupConflict } from './errors';
27
27
  export { appModelFieldName, appModelTypeName, appSelectorEnumValue } from './naming';
28
28
  export { RECORD_KEY_RULE, recordKeyRefusal } from './record-key';
29
29
  export { readAppDispatch, readCredentialProbe } from './dispatch';
package/dist/app.js CHANGED
@@ -251,6 +251,13 @@ function appCustomerUnlinkedOf(error) {
251
251
  if (typeof model !== "string" || typeof identityProvider !== "string") return null;
252
252
  return { model, identityProvider };
253
253
  }
254
+ function customerEmailUnavailableReasonOf(value) {
255
+ return typeof value === "string" && value !== "" ? value : null;
256
+ }
257
+ function appCustomerEmailUnavailableOf(error) {
258
+ const reason = customerEmailUnavailableReasonOf(extensionOf(error, "customerEmailUnavailable")?.reason);
259
+ return reason ? { reason } : null;
260
+ }
254
261
  function isAppFieldWithheld(error, field) {
255
262
  if (!(error instanceof FoirRequestError)) return false;
256
263
  return (error.prunedSelections ?? []).some((selection) => {
@@ -490,7 +497,7 @@ function createAppClient(config) {
490
497
  "integrationConfig",
491
498
  `query AppInstallConfig {
492
499
  integrationConfig {
493
- name enabled middlewareUrl targetModels webhookUrl config
500
+ name enabled middlewareUrl targetModels webhookUrl config customerEmailUnavailable
494
501
  sources { key model naturalKey mode fields { appField modelField direction } }
495
502
  sinks { key model naturalKey fields { appField modelField direction } }
496
503
  }
@@ -506,7 +513,8 @@ function createAppClient(config) {
506
513
  webhookUrl: got.webhookUrl ?? "",
507
514
  settings: got.config ?? {},
508
515
  sources: bindingsOf(got.sources),
509
- sinks: bindingsOf(got.sinks)
516
+ sinks: bindingsOf(got.sinks),
517
+ customerEmailUnavailable: customerEmailUnavailableReasonOf(got.customerEmailUnavailable)
510
518
  };
511
519
  },
512
520
  async credentials() {
@@ -1145,6 +1153,7 @@ export {
1145
1153
  FoirRequestError,
1146
1154
  MAX_APP_MANIFEST_BYTES,
1147
1155
  RECORD_KEY_RULE,
1156
+ appCustomerEmailUnavailableOf,
1148
1157
  appCustomerUnlinkedOf,
1149
1158
  appLookupConflictOf,
1150
1159
  appModelFieldName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {