@client-compose/client 0.6.0 → 0.7.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/testing.d.ts CHANGED
@@ -19,6 +19,15 @@ export interface RecordedScreenRequest extends RecordedRequest {
19
19
  client: ClientContext | undefined;
20
20
  islands: Capabilities;
21
21
  }
22
+ /** An island request (spec §4.5): a page of one island, or the island resolved again. */
23
+ export interface RecordedIslandRequest extends RecordedRequest {
24
+ screen: string;
25
+ key: string;
26
+ version: number;
27
+ params: Record<string, string>;
28
+ cursor: string | undefined;
29
+ client: ClientContext | undefined;
30
+ }
22
31
  export interface RecordedActionRequest extends RecordedRequest {
23
32
  action: string;
24
33
  version: number;
@@ -31,6 +40,8 @@ export interface FakeGatewayHandlers {
31
40
  screens?: Record<string, (request: RecordedScreenRequest) => FakeResponse>;
32
41
  /** By action name. */
33
42
  actions?: Record<string, (request: RecordedActionRequest) => FakeResponse>;
43
+ /** Island requests, by `"{screen}/{key}"`. */
44
+ islands?: Record<string, (request: RecordedIslandRequest) => FakeResponse>;
34
45
  /** Anything else, e.g. uploads to storage: return undefined to leave a request to the others. */
35
46
  other?: (request: RecordedRequest) => FakeResponse | undefined;
36
47
  }
@@ -40,6 +51,7 @@ export interface FakeGateway {
40
51
  /** Every request received, in order. */
41
52
  requests: RecordedRequest[];
42
53
  screenRequests(): RecordedScreenRequest[];
54
+ islandRequests(): RecordedIslandRequest[];
43
55
  actionRequests(): RecordedActionRequest[];
44
56
  }
45
57
  /** A composer whose screens and actions are the handlers given. */
@@ -53,12 +65,17 @@ export interface InstalledFakeGateway extends FakeGateway {
53
65
  * app rendered in a test). Call `restore()` after the test.
54
66
  */
55
67
  export declare function installFakeGateway(handlers?: FakeGatewayHandlers): InstalledFakeGateway;
56
- /** An island in a screen response, typed by its generated descriptor. */
57
- export declare function island<P>(key: string, descriptor: IslandDescriptor<P>, props: P): IslandEntry<P>;
68
+ /**
69
+ * An island in a screen or island response, typed by its generated descriptor. Give `next` for an
70
+ * island with more pages (spec §4.5).
71
+ */
72
+ export declare function island<P>(key: string, descriptor: IslandDescriptor<P>, props: P, next?: string): IslandEntry<P>;
58
73
  /** An island that failed on the composer (spec §3.4). */
59
74
  export declare function islandError(key: string, descriptor: IslandDescriptor<unknown>, error: IslandError): IslandEntry;
60
75
  /** A screen response. */
61
76
  export declare function screen(id: string, islands: IslandEntry<unknown>[], meta?: ScreenMeta): FakeResponse;
77
+ /** An island response (spec §4.5): a page of one island, or the island resolved again. */
78
+ export declare function islandResponse(screen: string, entry: IslandEntry<unknown>): FakeResponse;
62
79
  /** A screen request that fails, e.g. `screenError(404, "not_found")` or `screenError(401, "unauthenticated")`. */
63
80
  export declare function screenError(status: number, code: string): FakeResponse;
64
81
  /** An action that succeeded, with its result typed by the descriptor. */
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
package/dist/testing.js CHANGED
@@ -5,6 +5,7 @@
5
5
  // const gateway = createFakeGateway({
6
6
  // screens: { profile: () => screen("profile", [island("header", ProfileHeaderV1, { name: "Ada" })]) },
7
7
  // actions: { follow: () => actionOk(FollowUserV1, { followers: 8 }) },
8
+ // islands: { "profile/posts": (r) => islandResponse("profile", island("posts", PostsV1, …, "page:3")) },
8
9
  // });
9
10
  // // pass gateway.fetch as the client's fetch, then assert on gateway.actionRequests()
10
11
  //
@@ -12,13 +13,15 @@
12
13
  import { PROTOCOL_VERSION, } from "@client-compose/protocol";
13
14
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14
15
  function strings(value) {
15
- if (!isRecord(value))
16
+ if (!isRecord(value)) {
16
17
  return {};
18
+ }
17
19
  return Object.fromEntries(Object.entries(value).filter((e) => typeof e[1] === "string"));
18
20
  }
19
21
  function capabilities(value) {
20
- if (!isRecord(value))
22
+ if (!isRecord(value)) {
21
23
  return {};
24
+ }
22
25
  return Object.fromEntries(Object.entries(value).map(([type, versions]) => [
23
26
  type,
24
27
  Array.isArray(versions) ? versions.filter((v) => typeof v === "number") : [],
@@ -27,13 +30,19 @@ function capabilities(value) {
27
30
  function clientContext(value) {
28
31
  if (!isRecord(value) ||
29
32
  typeof value.platform !== "string" ||
30
- typeof value.appVersion !== "string")
33
+ typeof value.appVersion !== "string") {
31
34
  return undefined;
32
- return { ...value, platform: value.platform, appVersion: value.appVersion };
35
+ }
36
+ return {
37
+ ...value,
38
+ platform: value.platform,
39
+ appVersion: value.appVersion,
40
+ };
33
41
  }
34
42
  function parseBody(body) {
35
- if (typeof body !== "string")
43
+ if (typeof body !== "string") {
36
44
  return body;
45
+ }
37
46
  try {
38
47
  return JSON.parse(body);
39
48
  }
@@ -42,8 +51,9 @@ function parseBody(body) {
42
51
  }
43
52
  }
44
53
  function urlOf(input) {
45
- if (typeof input === "string")
54
+ if (typeof input === "string") {
46
55
  return input;
56
+ }
47
57
  return input instanceof URL ? input.href : input.url;
48
58
  }
49
59
  function headersOf(init) {
@@ -51,10 +61,15 @@ function headersOf(init) {
51
61
  new Headers(init).forEach((value, name) => (out[name] = value));
52
62
  return out;
53
63
  }
64
+ function isIslandURL(url) {
65
+ return /\/screens\/[^/]+\/islands\/[^/?]+/.test(url);
66
+ }
54
67
  function asScreenRequest(request) {
55
68
  const { body } = request;
56
- if (!request.url.includes("/screens/") || !isRecord(body) || typeof body.screen !== "string")
69
+ const isScreen = request.url.includes("/screens/") && !isIslandURL(request.url);
70
+ if (!isScreen || !isRecord(body) || typeof body.screen !== "string") {
57
71
  return undefined;
72
+ }
58
73
  return {
59
74
  ...request,
60
75
  screen: body.screen,
@@ -63,10 +78,29 @@ function asScreenRequest(request) {
63
78
  islands: capabilities(body.islands),
64
79
  };
65
80
  }
81
+ function asIslandRequest(request) {
82
+ const { body } = request;
83
+ if (!isIslandURL(request.url) || !isRecord(body)) {
84
+ return undefined;
85
+ }
86
+ if (typeof body.screen !== "string" || typeof body.key !== "string") {
87
+ return undefined;
88
+ }
89
+ return {
90
+ ...request,
91
+ screen: body.screen,
92
+ key: body.key,
93
+ version: typeof body.version === "number" ? body.version : 0,
94
+ params: strings(body.params),
95
+ cursor: typeof body.cursor === "string" ? body.cursor : undefined,
96
+ client: clientContext(body.client),
97
+ };
98
+ }
66
99
  function asActionRequest(request) {
67
100
  const { body } = request;
68
- if (!request.url.includes("/actions/") || !isRecord(body) || typeof body.action !== "string")
101
+ if (!request.url.includes("/actions/") || !isRecord(body) || typeof body.action !== "string") {
69
102
  return undefined;
103
+ }
70
104
  return {
71
105
  ...request,
72
106
  action: body.action,
@@ -94,22 +128,31 @@ export function createFakeGateway(handlers = {}) {
94
128
  };
95
129
  requests.push(request);
96
130
  const other = handlers.other?.(request);
97
- if (other)
131
+ if (other) {
98
132
  return respond(other);
133
+ }
134
+ const islandRequest = asIslandRequest(request);
135
+ const islandHandler = islandRequest && handlers.islands?.[`${islandRequest.screen}/${islandRequest.key}`];
136
+ if (islandRequest && islandHandler) {
137
+ return respond(islandHandler(islandRequest));
138
+ }
99
139
  const screenRequest = asScreenRequest(request);
100
140
  const screenHandler = screenRequest && handlers.screens?.[screenRequest.screen];
101
- if (screenRequest && screenHandler)
141
+ if (screenRequest && screenHandler) {
102
142
  return respond(screenHandler(screenRequest));
143
+ }
103
144
  const actionRequest = asActionRequest(request);
104
145
  const actionHandler = actionRequest && handlers.actions?.[actionRequest.action];
105
- if (actionRequest && actionHandler)
146
+ if (actionRequest && actionHandler) {
106
147
  return respond(actionHandler(actionRequest));
148
+ }
107
149
  throw new Error(`fake gateway: no handler for ${request.method} ${request.url} ${JSON.stringify(request.body)}`);
108
150
  }
109
151
  return {
110
152
  fetch: fakeFetch,
111
153
  requests,
112
154
  screenRequests: () => requests.flatMap((r) => asScreenRequest(r) ?? []),
155
+ islandRequests: () => requests.flatMap((r) => asIslandRequest(r) ?? []),
113
156
  actionRequests: () => requests.flatMap((r) => asActionRequest(r) ?? []),
114
157
  };
115
158
  }
@@ -129,19 +172,51 @@ export function installFakeGateway(handlers = {}) {
129
172
  };
130
173
  }
131
174
  // ---- Responses ----
132
- /** An island in a screen response, typed by its generated descriptor. */
133
- export function island(key, descriptor, props) {
134
- return { key, type: descriptor.type, version: descriptor.version, status: "ok", props };
175
+ /**
176
+ * An island in a screen or island response, typed by its generated descriptor. Give `next` for an
177
+ * island with more pages (spec §4.5).
178
+ */
179
+ export function island(key, descriptor, props, next) {
180
+ return {
181
+ key,
182
+ type: descriptor.type,
183
+ version: descriptor.version,
184
+ status: "ok",
185
+ props,
186
+ ...(next !== undefined && { next }),
187
+ };
135
188
  }
136
189
  /** An island that failed on the composer (spec §3.4). */
137
190
  export function islandError(key, descriptor, error) {
138
- return { key, type: descriptor.type, version: descriptor.version, status: "error", error };
191
+ return {
192
+ key,
193
+ type: descriptor.type,
194
+ version: descriptor.version,
195
+ status: "error",
196
+ error,
197
+ };
139
198
  }
140
199
  /** A screen response. */
141
200
  export function screen(id, islands, meta) {
142
201
  return {
143
202
  status: 200,
144
- body: { protocol: PROTOCOL_VERSION, screen: id, islands, ...(meta && { meta }) },
203
+ body: {
204
+ protocol: PROTOCOL_VERSION,
205
+ screen: id,
206
+ islands,
207
+ ...(meta && { meta }),
208
+ },
209
+ };
210
+ }
211
+ /** An island response (spec §4.5): a page of one island, or the island resolved again. */
212
+ export function islandResponse(screen, entry) {
213
+ return {
214
+ status: 200,
215
+ body: {
216
+ protocol: PROTOCOL_VERSION,
217
+ screen,
218
+ island: entry,
219
+ },
145
220
  };
146
221
  }
147
222
  /** A screen request that fails, e.g. `screenError(404, "not_found")` or `screenError(401, "unauthenticated")`. */
@@ -150,13 +225,36 @@ export function screenError(status, code) {
150
225
  }
151
226
  /** An action that succeeded, with its result typed by the descriptor. */
152
227
  export function actionOk(action, result, effects) {
153
- const base = { protocol: PROTOCOL_VERSION, action: action.action, version: action.version };
154
- return { status: 200, body: { ...base, status: "ok", result, ...(effects && { effects }) } };
228
+ const base = {
229
+ protocol: PROTOCOL_VERSION,
230
+ action: action.action,
231
+ version: action.version,
232
+ };
233
+ return {
234
+ status: 200,
235
+ body: {
236
+ ...base,
237
+ status: "ok",
238
+ result,
239
+ ...(effects && { effects }),
240
+ },
241
+ };
155
242
  }
156
243
  /** An action that ended with one of its declared errors. */
157
244
  export function actionError(action, error) {
158
- const base = { protocol: PROTOCOL_VERSION, action: action.action, version: action.version };
159
- return { status: 200, body: { ...base, status: "error", error } };
245
+ const base = {
246
+ protocol: PROTOCOL_VERSION,
247
+ action: action.action,
248
+ version: action.version,
249
+ };
250
+ return {
251
+ status: 200,
252
+ body: {
253
+ ...base,
254
+ status: "error",
255
+ error,
256
+ },
257
+ };
160
258
  }
161
259
  /** An action the composer didn't perform, e.g. `actionFailure(401, "unauthenticated")` (spec §5.4). */
162
260
  export function actionFailure(status, code) {
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,8FAA8F;AAC9F,yFAAyF;AACzF,EAAE;AACF,0CAA0C;AAC1C,6GAA6G;AAC7G,6EAA6E;AAC7E,+GAA+G;AAC/G,UAAU;AACV,2FAA2F;AAC3F,EAAE;AACF,0FAA0F;AAE1F,OAAO,EASL,gBAAgB,GAEjB,MAAM,0BAA0B,CAAC;AAgElC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CACpE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEvE,SAAS,OAAO,CAAC,KAAc;IAC7B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAyB,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CACrF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;QAC9C,IAAI;QACJ,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;KAC1F,CAAC,CACH,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IACE,CAAC,QAAQ,CAAC,KAAK,CAAC;QAChB,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,EACpC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,GAAG,KAAK;QACR,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,UAAU;KACZ,CAAC;AACrB,CAAC;AAED,SAAS,SAAS,CAAC,IAAa;IAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,KAAwB;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;AACvD,CAAC;AAED,SAAS,SAAS,CAAC,IAA6B;IAC9C,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,mCAAmC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,eAAe,CAAC,OAAwB;IAC/C,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChF,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACpE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;QAC5B,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAClC,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAAwB;IAC/C,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACzB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,OAAO,EAAE,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;QAC5B,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QACjE,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;KACnC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAAwB;IAC/C,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC5D,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAClC,cAAc,EAAE,OAAO,IAAI,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;KAC1F,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,QAAsB;IACrC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QACjD,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,iBAAiB,CAAC,WAAgC,EAAE;IAClE,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,KAAK,UAAU,SAAS,CAAC,KAAwB,EAAE,IAAkB;QACnE,MAAM,OAAO,GAAoB;YAC/B,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC;YACjB,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK;YAC7B,OAAO,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC;YACjC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC;SAC5B,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEvB,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,aAAa,GACjB,aAAa,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,IAAI,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC;QACtF,IAAI,aAAa,IAAI,aAAa,EAAE,CAAC;YACnC,OAAO,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,aAAa,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAChF,IAAI,aAAa,IAAI,aAAa,EAAE,CAAC;YACnC,OAAO,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,aAAa,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAChF,IAAI,aAAa,IAAI,aAAa,EAAE,CAAC;YACnC,OAAO,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gCAAgC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAChG,CAAC;IACJ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,QAAQ;QACR,cAAc,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvE,cAAc,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvE,cAAc,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;KACxE,CAAC;AACJ,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAgC,EAAE;IACnE,MAAM,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;IAClC,UAAU,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IACjC,OAAO;QACL,GAAG,OAAO;QACV,OAAO,EAAE,GAAG,EAAE;YACZ,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,sBAAsB;AAEtB;;;GAGG;AACH,MAAM,UAAU,MAAM,CACpB,GAAW,EACX,UAA+B,EAC/B,KAAQ,EACR,IAAa;IAEb,OAAO;QACL,GAAG;QACH,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,MAAM,EAAE,IAAI;QACZ,KAAK;QACL,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,WAAW,CACzB,GAAW,EACX,UAAqC,EACrC,KAAkB;IAElB,OAAO;QACL,GAAG;QACH,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,MAAM,EAAE,OAAO;QACf,KAAK;KACN,CAAC;AACJ,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,MAAM,CACpB,EAAU,EACV,OAA+B,EAC/B,IAAiB;IAEjB,OAAO;QACL,MAAM,EAAE,GAAG;QACX,IAAI,EAAE;YACJ,QAAQ,EAAE,gBAAgB;YAC1B,MAAM,EAAE,EAAE;YACV,OAAO;YACP,GAAG,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC;SACtB;KACF,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,KAA2B;IACxE,OAAO;QACL,MAAM,EAAE,GAAG;QACX,IAAI,EAAE;YACJ,QAAQ,EAAE,gBAAgB;YAC1B,MAAM;YACN,MAAM,EAAE,KAAK;SACd;KACF,CAAC;AACJ,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAY;IACtD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC3E,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CACtB,MAAiC,EACjC,MAAS,EACT,OAAuB;IAEvB,MAAM,IAAI,GAAG;QACX,QAAQ,EAAE,gBAAgB;QAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC;IACF,OAAO;QACL,MAAM,EAAE,GAAG;QACX,IAAI,EAAE;YACJ,GAAG,IAAI;YACP,MAAM,EAAE,IAAI;YACZ,MAAM;YACN,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;SAC5B;KACF,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,WAAW,CACzB,MAAiC,EACjC,KAAQ;IAER,MAAM,IAAI,GAAG;QACX,QAAQ,EAAE,gBAAgB;QAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC;IACF,OAAO;QACL,MAAM,EAAE,GAAG;QACX,IAAI,EAAE;YACJ,GAAG,IAAI;YACP,MAAM,EAAE,OAAO;YACf,KAAK;SACN;KACF,CAAC;AACJ,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,IAAY;IACxD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC3E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@client-compose/client",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Framework-agnostic Client Compose client: island registry, screen requests and render planning.",
5
5
  "keywords": [
6
6
  "client-compose",
@@ -44,10 +44,10 @@
44
44
  "LICENSE"
45
45
  ],
46
46
  "dependencies": {
47
- "@client-compose/protocol": "0.3.0"
47
+ "@client-compose/protocol": "0.4.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@client-compose/conformance": "0.6.0"
50
+ "@client-compose/conformance": "0.7.0"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public"
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  type DeclaredError,
11
11
  type IslandDescriptor,
12
12
  type IslandEntry,
13
+ type IslandRequest,
14
+ type IslandResponse,
13
15
  PROTOCOL_VERSION,
14
16
  type ScreenRequest,
15
17
  type ScreenResponse,
@@ -31,13 +33,25 @@ export class IslandRegistry<R> {
31
33
  versionOrRender: number | IslandRenderer<R, P>,
32
34
  maybeRender?: IslandRenderer<R, P>,
33
35
  ): this {
34
- const [type, version, render] =
35
- typeof islandOrType === "string"
36
- ? [islandOrType, versionOrRender as number, maybeRender!]
37
- : [islandOrType.type, islandOrType.version, versionOrRender as IslandRenderer<R, P>];
38
- if (!this.renderers.has(type)) this.renderers.set(type, new Map());
39
- // The registry hands each renderer the props of its own island type and version.
40
- this.renderers.get(type)!.set(version, render as IslandRenderer<R>);
36
+ if (typeof islandOrType !== "string") {
37
+ if (typeof versionOrRender === "number") {
38
+ throw new Error("register(descriptor, render): pass the renderer, not a version");
39
+ }
40
+ return this.add(islandOrType.type, islandOrType.version, versionOrRender);
41
+ }
42
+ if (typeof versionOrRender !== "number" || !maybeRender) {
43
+ throw new Error("register(type, version, render): pass a version and a renderer");
44
+ }
45
+ return this.add(islandOrType, versionOrRender, maybeRender);
46
+ }
47
+
48
+ private add<P>(type: string, version: number, render: IslandRenderer<R, P>): this {
49
+ const versions = this.renderers.get(type) ?? new Map<number, IslandRenderer<R>>();
50
+ // The registry hands each renderer the props of its own island type and version, so the
51
+ // renderer's props type holds. TypeScript can't see that through one map of every island.
52
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
53
+ versions.set(version, render as IslandRenderer<R>);
54
+ this.renderers.set(type, versions);
41
55
  return this;
42
56
  }
43
57
 
@@ -61,7 +75,13 @@ export function buildRequest(
61
75
  client: ClientContext,
62
76
  params?: Record<string, string>,
63
77
  ): ScreenRequest {
64
- return { protocol: PROTOCOL_VERSION, screen, params, client, islands: registry.capabilities() };
78
+ return {
79
+ protocol: PROTOCOL_VERSION,
80
+ screen,
81
+ params,
82
+ client,
83
+ islands: registry.capabilities(),
84
+ };
65
85
  }
66
86
 
67
87
  export interface PlannedIsland<R> {
@@ -88,7 +108,9 @@ export function plan<R>(
88
108
  for (const entry of response.islands) {
89
109
  if (entry.status === "error") {
90
110
  const fallback = options.onError?.(entry);
91
- if (fallback !== undefined) out.push({ key: entry.key, render: () => fallback });
111
+ if (fallback !== undefined) {
112
+ out.push({ key: entry.key, render: () => fallback });
113
+ }
92
114
  continue;
93
115
  }
94
116
  if (entry.status !== "ok") {
@@ -105,6 +127,41 @@ export function plan<R>(
105
127
  return out;
106
128
  }
107
129
 
130
+ // ---- Reading what the composer answered ----
131
+
132
+ function isRecord(value: unknown): value is Record<string, unknown> {
133
+ return typeof value === "object" && value !== null && !Array.isArray(value);
134
+ }
135
+
136
+ /** The body as JSON, or undefined if it isn't JSON. */
137
+ function parseJson(text: string): unknown {
138
+ try {
139
+ return JSON.parse(text);
140
+ } catch {
141
+ return undefined;
142
+ }
143
+ }
144
+
145
+ function isIslandEntry(value: unknown): value is IslandEntry {
146
+ if (!isRecord(value)) {
147
+ return false;
148
+ }
149
+ const named = typeof value.key === "string" && typeof value.type === "string";
150
+ return named && typeof value.version === "number" && typeof value.status === "string";
151
+ }
152
+
153
+ function isScreenResponse(value: unknown): value is ScreenResponse {
154
+ if (!isRecord(value) || typeof value.screen !== "string") {
155
+ return false;
156
+ }
157
+ return Array.isArray(value.islands) && value.islands.every(isIslandEntry);
158
+ }
159
+
160
+ /** An action's outcome: what a client needs of one is its status (the client fixtures, spec §8.2). */
161
+ function isActionResponse(value: unknown): value is ActionResponse {
162
+ return isRecord(value) && (value.status === "ok" || value.status === "error");
163
+ }
164
+
108
165
  export class ScreenFetchError extends Error {
109
166
  readonly status: number;
110
167
  readonly body: string;
@@ -121,13 +178,27 @@ export class ScreenFetchError extends Error {
121
178
 
122
179
  function errorCode(body: string): string | undefined {
123
180
  try {
124
- const code = (JSON.parse(body) as { error?: { code?: unknown } }).error?.code;
181
+ const parsed: unknown = JSON.parse(body);
182
+ const error = isRecord(parsed) ? parsed.error : undefined;
183
+ const code = isRecord(error) ? error.code : undefined;
125
184
  return typeof code === "string" ? code : undefined;
126
185
  } catch {
127
186
  return undefined;
128
187
  }
129
188
  }
130
189
 
190
+ /**
191
+ * The caller's headers, given any way fetch takes them, sent as JSON unless they say otherwise.
192
+ * (Spreading a `Headers` object would drop them all.)
193
+ */
194
+ function jsonHeaders(init: HeadersInit | undefined): Headers {
195
+ const headers = new Headers(init);
196
+ if (!headers.has("content-type")) {
197
+ headers.set("content-type", "application/json");
198
+ }
199
+ return headers;
200
+ }
201
+
131
202
  export async function fetchScreen(
132
203
  baseUrl: string,
133
204
  request: ScreenRequest,
@@ -137,11 +208,99 @@ export async function fetchScreen(
137
208
  const res = await fetchImpl(`${baseUrl}/screens/${encodeURIComponent(request.screen)}`, {
138
209
  ...init,
139
210
  method: "POST",
140
- headers: { "content-type": "application/json", ...init.headers },
211
+ headers: jsonHeaders(init.headers),
141
212
  body: JSON.stringify(request),
142
213
  });
143
- if (!res.ok) throw new ScreenFetchError(res.status, await res.text());
144
- return (await res.json()) as ScreenResponse;
214
+ const text = await res.text();
215
+ if (!res.ok) {
216
+ throw new ScreenFetchError(res.status, text);
217
+ }
218
+ const response = parseJson(text);
219
+ if (!isScreenResponse(response)) {
220
+ throw new ScreenFetchError(res.status, text);
221
+ }
222
+ return response;
223
+ }
224
+
225
+ // ---- Island requests (spec §4.5) ----
226
+
227
+ /**
228
+ * A request for one island of a screen: the page `cursor` asks for (an entry's `next`), or the
229
+ * island from its first page without one. `params` are the screen's own.
230
+ */
231
+ export function buildIslandRequest(
232
+ screen: string,
233
+ params: Record<string, string> | undefined,
234
+ entry: { key: string; version: number },
235
+ client: ClientContext,
236
+ cursor?: string,
237
+ ): IslandRequest {
238
+ return {
239
+ protocol: PROTOCOL_VERSION,
240
+ screen,
241
+ key: entry.key,
242
+ version: entry.version,
243
+ params,
244
+ client,
245
+ ...(cursor !== undefined && { cursor }),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * POST an island request. Resolves with the island's entry: its props (and `next`, if it has more
251
+ * pages) or the error it failed with. Throws IslandFetchError for a non-2xx response (e.g.
252
+ * `invalid_cursor`, `unknown_island`, `unauthenticated`), or an answer that isn't this island.
253
+ */
254
+ export async function fetchIsland(
255
+ baseUrl: string,
256
+ request: IslandRequest,
257
+ init: RequestInit = {},
258
+ fetchImpl: typeof fetch = fetch,
259
+ ): Promise<IslandEntry> {
260
+ const path = `${encodeURIComponent(request.screen)}/islands/${encodeURIComponent(request.key)}`;
261
+ const res = await fetchImpl(`${baseUrl}/screens/${path}`, {
262
+ ...init,
263
+ method: "POST",
264
+ headers: jsonHeaders(init.headers),
265
+ body: JSON.stringify(request),
266
+ });
267
+ const text = await res.text();
268
+ if (!res.ok) {
269
+ throw new IslandFetchError(res.status, text);
270
+ }
271
+ const response = parseJson(text);
272
+ if (!isIslandResponse(response) || !isIslandAsked(response.island, request)) {
273
+ throw new IslandFetchError(res.status, text);
274
+ }
275
+ return response.island;
276
+ }
277
+
278
+ /**
279
+ * An island request failed: a non-2xx response such as `invalid_cursor`, `unknown_island` or
280
+ * `unauthenticated`, or an answer that isn't the island asked for. An island that failed on the
281
+ * composer isn't thrown: it's an entry with `status: "error"`.
282
+ */
283
+ export class IslandFetchError extends Error {
284
+ readonly status: number;
285
+ readonly body: string;
286
+ /** The protocol error code from the response body (e.g. "invalid_cursor"), if there was one. */
287
+ readonly code: string | undefined;
288
+
289
+ constructor(status: number, body: string) {
290
+ super(`Island request failed with status ${status}`);
291
+ this.status = status;
292
+ this.body = body;
293
+ this.code = errorCode(body);
294
+ }
295
+ }
296
+
297
+ function isIslandResponse(value: unknown): value is IslandResponse {
298
+ return isRecord(value) && typeof value.screen === "string" && isIslandEntry(value.island);
299
+ }
300
+
301
+ /** Whether the answer is the island the request asked for, at the version it asked for. */
302
+ function isIslandAsked(island: IslandEntry, request: IslandRequest): boolean {
303
+ return island.key === request.key && island.version === request.version;
145
304
  }
146
305
 
147
306
  // ---- Actions (spec §5) ----
@@ -166,8 +325,11 @@ export class ActionFetchError extends Error {
166
325
 
167
326
  /** A fresh idempotency key: one per user intent, reused when retrying it (spec §5.2). */
168
327
  export function newIdempotencyKey(): string {
169
- const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;
170
- if (c?.randomUUID) return c.randomUUID();
328
+ // React Native's Hermes may have no crypto, or crypto without randomUUID.
329
+ const c: Partial<Pick<Crypto, "randomUUID">> | undefined = globalThis.crypto;
330
+ if (c?.randomUUID) {
331
+ return c.randomUUID();
332
+ }
171
333
  // React Native's Hermes has no crypto.randomUUID. Uniqueness, not secrecy, is what matters here.
172
334
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
173
335
  const r = (Math.random() * 16) | 0;
@@ -205,16 +367,18 @@ export async function fetchAction(
205
367
  const res = await fetchImpl(`${baseUrl}/actions/${encodeURIComponent(request.action)}`, {
206
368
  ...init,
207
369
  method: "POST",
208
- headers: { "content-type": "application/json", ...init.headers },
370
+ headers: jsonHeaders(init.headers),
209
371
  body: JSON.stringify(request),
210
372
  });
211
373
  const text = await res.text();
212
- if (!res.ok) throw new ActionFetchError(res.status, text);
213
- try {
214
- return JSON.parse(text) as ActionResponse;
215
- } catch {
374
+ if (!res.ok) {
216
375
  throw new ActionFetchError(res.status, text);
217
376
  }
377
+ const response = parseJson(text);
378
+ if (!isActionResponse(response)) {
379
+ throw new ActionFetchError(res.status, text);
380
+ }
381
+ return response;
218
382
  }
219
383
 
220
384
  /**
@@ -226,12 +390,16 @@ export function checkActionResponse<I, R, E extends DeclaredError>(
226
390
  action: ActionDescriptor<I, R, E>,
227
391
  response: unknown,
228
392
  ): ActionResponse<R, E> {
229
- const r = response as { status?: unknown; error?: { code?: unknown } } | null;
230
- const declared =
231
- r?.status === "error" &&
232
- typeof r.error?.code === "string" &&
233
- action.errors.includes(r.error.code);
234
- if (r?.status === "ok" || declared) return response as ActionResponse<R, E>;
393
+ const error = isRecord(response) ? response.error : undefined;
394
+ const code = isRecord(error) ? error.code : undefined;
395
+ const status = isRecord(response) ? response.status : undefined;
396
+ const declared = status === "error" && typeof code === "string" && action.errors.includes(code);
397
+ if (isActionResponse(response) && (status === "ok" || declared)) {
398
+ // The result's and error's types are the action's: generated from its manifest, which the
399
+ // composer checks its outcomes against. They exist only at the type level.
400
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
401
+ return response as ActionResponse<R, E>;
402
+ }
235
403
  throw new ActionFetchError(200, JSON.stringify(response));
236
404
  }
237
405