@notionhq/custom-blocks 0.0.76 → 0.0.78

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.
Files changed (109) hide show
  1. package/HOST.md +68 -37
  2. package/bin/src/bridge/manifest.js +3 -3
  3. package/dist/bridge/SandboxBridge.d.ts +6 -12
  4. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  5. package/dist/bridge/SandboxBridge.js +123 -71
  6. package/dist/bridge/dataSources/resolve.d.ts +3 -4
  7. package/dist/bridge/dataSources/resolve.d.ts.map +1 -1
  8. package/dist/bridge/dataSources/resolve.js +2 -12
  9. package/dist/bridge/hostState.d.ts +3 -2
  10. package/dist/bridge/hostState.d.ts.map +1 -1
  11. package/dist/bridge/loadManifest.d.ts +4 -4
  12. package/dist/bridge/loadManifest.d.ts.map +1 -1
  13. package/dist/bridge/loadManifest.js +2 -2
  14. package/dist/bridge/manifest.d.ts +3 -3
  15. package/dist/bridge/manifest.js +3 -3
  16. package/dist/bridge/messages/{ready.d.ts → connect.d.ts} +13 -11
  17. package/dist/bridge/messages/connect.d.ts.map +1 -0
  18. package/dist/bridge/messages/{ready.js → connect.js} +14 -13
  19. package/dist/bridge/messages/hostToSandbox.d.ts +24 -79
  20. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  21. package/dist/bridge/messages/hostToSandbox.js +2 -3
  22. package/dist/bridge/messages/init.d.ts +35 -6
  23. package/dist/bridge/messages/init.d.ts.map +1 -1
  24. package/dist/bridge/messages/init.js +8 -4
  25. package/dist/bridge/messages/initResult.d.ts +29 -0
  26. package/dist/bridge/messages/initResult.d.ts.map +1 -0
  27. package/dist/bridge/messages/initResult.js +25 -0
  28. package/dist/bridge/messages/queryDataSource.d.ts +3 -7
  29. package/dist/bridge/messages/queryDataSource.d.ts.map +1 -1
  30. package/dist/bridge/messages/queryDataSource.js +3 -7
  31. package/dist/bridge/messages/queryDataSourceResult.d.ts +4 -240
  32. package/dist/bridge/messages/queryDataSourceResult.d.ts.map +1 -1
  33. package/dist/bridge/messages/queryDataSourceResult.js +4 -22
  34. package/dist/bridge/messages/sandboxToHost.d.ts +21 -7
  35. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  36. package/dist/bridge/messages/sandboxToHost.js +4 -2
  37. package/dist/bridge/sandboxClient.d.ts +2 -4
  38. package/dist/bridge/sandboxClient.d.ts.map +1 -1
  39. package/dist/bridge/sandboxClient.js +2 -5
  40. package/dist/customBlock.d.ts +1 -1
  41. package/dist/customBlock.d.ts.map +1 -1
  42. package/dist/customBlock.js +1 -1
  43. package/dist/host/createCustomBlockHost.d.ts +6 -3
  44. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  45. package/dist/host/createCustomBlockHost.js +227 -58
  46. package/dist/host/lifecycle/initErrors.d.ts +1 -1
  47. package/dist/host/lifecycle/initErrors.d.ts.map +1 -1
  48. package/dist/host/lifecycle/initErrors.js +25 -7
  49. package/dist/host/lifecycle/types.d.ts +6 -4
  50. package/dist/host/lifecycle/types.d.ts.map +1 -1
  51. package/dist/host/messages/invalidSandboxMessage.js +0 -3
  52. package/dist/host/queries/querySubscriptions.d.ts +10 -10
  53. package/dist/host/queries/querySubscriptions.d.ts.map +1 -1
  54. package/dist/host/queries/querySubscriptions.js +35 -19
  55. package/dist/host/queries/types.d.ts +5 -4
  56. package/dist/host/queries/types.d.ts.map +1 -1
  57. package/dist/host.d.ts +5 -3
  58. package/dist/host.d.ts.map +1 -1
  59. package/dist/host.js +2 -1
  60. package/dist/index.d.ts +2 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/init.d.ts +7 -7
  64. package/dist/init.d.ts.map +1 -1
  65. package/dist/init.js +70 -50
  66. package/dist/react/index.d.ts +1 -1
  67. package/dist/react/index.d.ts.map +1 -1
  68. package/dist/react/standalonePreview.d.ts.map +1 -1
  69. package/dist/react/standalonePreview.js +2 -0
  70. package/dist/react/useCustomBlockInit.d.ts +5 -6
  71. package/dist/react/useCustomBlockInit.d.ts.map +1 -1
  72. package/dist/react/useCustomBlockInit.js +3 -19
  73. package/dist/react/useRuntimeState.d.ts +4 -3
  74. package/dist/react/useRuntimeState.d.ts.map +1 -1
  75. package/dist/react/useRuntimeState.js +3 -2
  76. package/dist/version.js +1 -1
  77. package/docs/data-sources.md +7 -5
  78. package/docs/errors.md +5 -1
  79. package/docs/lifecycle.md +19 -31
  80. package/docs/manifest.md +1 -1
  81. package/package.json +1 -1
  82. package/src/bridge/SandboxBridge.ts +151 -77
  83. package/src/bridge/dataSources/resolve.ts +3 -14
  84. package/src/bridge/hostState.ts +3 -2
  85. package/src/bridge/loadManifest.ts +6 -6
  86. package/src/bridge/manifest.ts +3 -3
  87. package/src/bridge/messages/{ready.ts → connect.ts} +15 -14
  88. package/src/bridge/messages/hostToSandbox.ts +2 -6
  89. package/src/bridge/messages/init.ts +24 -10
  90. package/src/bridge/messages/initResult.ts +37 -0
  91. package/src/bridge/messages/queryDataSource.ts +3 -7
  92. package/src/bridge/messages/queryDataSourceResult.ts +4 -24
  93. package/src/bridge/messages/sandboxToHost.ts +4 -2
  94. package/src/bridge/sandboxClient.ts +3 -8
  95. package/src/customBlock.ts +2 -2
  96. package/src/host/createCustomBlockHost.ts +269 -64
  97. package/src/host/lifecycle/initErrors.ts +25 -7
  98. package/src/host/lifecycle/types.ts +13 -4
  99. package/src/host/messages/invalidSandboxMessage.ts +0 -3
  100. package/src/host/queries/querySubscriptions.ts +58 -39
  101. package/src/host/queries/types.ts +10 -11
  102. package/src/host.ts +13 -2
  103. package/src/index.ts +3 -1
  104. package/src/init.ts +73 -60
  105. package/src/react/index.ts +0 -1
  106. package/src/react/standalonePreview.ts +2 -0
  107. package/src/react/useCustomBlockInit.ts +6 -27
  108. package/src/react/useRuntimeState.ts +4 -3
  109. package/dist/bridge/messages/ready.d.ts.map +0 -1
@@ -6,14 +6,13 @@ import { resolvePropertyWriteMapForDataSource } from "./dataSources/resolvePrope
6
6
  import { createEmptyDataSourceQueryState, } from "./hostState.js";
7
7
  import { readIncomingType } from "./incomingType.js";
8
8
  import { hostToSandboxMessageSchema } from "./messages/hostToSandbox.js";
9
- import { CustomBlockInitError } from "./messages/init.js";
9
+ import { CustomBlockInitializationError, initMessageSchema, } from "./messages/init.js";
10
10
  import { PendingRequests } from "./pendingRequests.js";
11
11
  /**
12
12
  * Used to ensure that the host and client are using the same version of the bridge protocol. A
13
13
  * single host needs to support multiple custom blocks built with different versions of the bridge
14
14
  * protocol. Increment this number any time a breaking change is made to the bridge protocol.
15
15
  */
16
- // TODO(custom-blocks): Update when bumping bridge protocol version to 3.
17
16
  export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 2;
18
17
  export class SandboxBridge {
19
18
  constructor() {
@@ -30,13 +29,14 @@ export class SandboxBridge {
30
29
  this.pendingGetUser = new PendingRequests("custom-block-get-user");
31
30
  this.pendingListUsers = new PendingRequests("custom-block-list-users");
32
31
  this.pendingUpdatePage = new PendingRequests("custom-block-update-page");
33
- this.hasSentReady = false;
32
+ this.hasSentConnect = false;
33
+ this.hasReceivedInit = false;
34
34
  this.latestDataSourceBindings = {};
35
+ this.isMockState = false;
35
36
  this.initMessage = new Promise((resolve, reject) => {
36
37
  this.resolveInit = resolve;
37
38
  this.rejectInit = reject;
38
39
  });
39
- this.manifest = null;
40
40
  this.notify = () => {
41
41
  for (const listener of this.listeners) {
42
42
  listener();
@@ -81,7 +81,16 @@ export class SandboxBridge {
81
81
  // `init` is the only message valid before initialization. Handle it up
82
82
  // front so every later case can assume `status === "initialized"`.
83
83
  if (message.type === "init") {
84
- this.applyInit(message);
84
+ if (message.initializationId !== this.initializationId) {
85
+ console.warn(`[custom-blocks-sdk] ignoring init for unknown initializationId ${message.initializationId}`);
86
+ return;
87
+ }
88
+ if (this.hasReceivedInit) {
89
+ console.warn("[custom-blocks-sdk] ignoring duplicate init message");
90
+ return;
91
+ }
92
+ this.hasReceivedInit = true;
93
+ this.applyInit(message, true);
85
94
  return;
86
95
  }
87
96
  // Alias to keep TS's narrowed `InitializedHostState` across the switch
@@ -129,10 +138,12 @@ export class SandboxBridge {
129
138
  const dataSources = reuseDataSourcesForUnchangedBindings({
130
139
  previousDataSources: hostState.dataSources,
131
140
  previousBindings: this.latestDataSourceBindings,
132
- nextDataSources: resolveDataSources({
133
- manifest: this.manifest,
134
- dataSourceBindings: nextBindings,
135
- }),
141
+ nextDataSources: this.isMockState
142
+ ? resolveMockDataSources(nextBindings)
143
+ : resolveDataSources({
144
+ manifest: hostState.manifest,
145
+ dataSourceBindings: nextBindings,
146
+ }),
136
147
  nextBindings,
137
148
  });
138
149
  this.latestDataSourceBindings = nextBindings;
@@ -198,30 +209,21 @@ export class SandboxBridge {
198
209
  return;
199
210
  }
200
211
  case "queryDataSourceResult": {
201
- const queryEntry = Object.entries(hostState.dataSourceState).find(([, state]) => state.latestSnapshotId === message.snapshotId);
212
+ const queryEntry = Object.entries(hostState.dataSourceState).find(([, state]) => state.subscriptionId === message.subscriptionId);
202
213
  if (queryEntry === undefined) {
203
214
  return;
204
215
  }
205
216
  const [key, currentState] = queryEntry;
206
- if (currentState.latestRequestId !== message.requestId) {
207
- return;
208
- }
209
- const queryResult = "status" in message
210
- ? message.status === "error"
211
- ? {
212
- items: [],
213
- hasMore: false,
214
- error: message.error,
215
- }
216
- : {
217
- items: message.items,
218
- hasMore: message.hasMore,
219
- error: undefined,
220
- }
217
+ const queryResult = message.status === "error"
218
+ ? {
219
+ items: [],
220
+ hasMore: false,
221
+ error: message.error,
222
+ }
221
223
  : {
222
224
  items: message.items,
223
225
  hasMore: message.hasMore,
224
- error: message.error,
226
+ error: undefined,
225
227
  };
226
228
  this.hostState = {
227
229
  ...hostState,
@@ -232,10 +234,7 @@ export class SandboxBridge {
232
234
  isLoading: false,
233
235
  hasMore: queryResult.hasMore,
234
236
  error: queryResult.error,
235
- // Keep the request ID so later host-pushed refreshes for the
236
- // same subscription still match.
237
- latestRequestId: message.requestId,
238
- latestSnapshotId: message.snapshotId,
237
+ subscriptionId: message.subscriptionId,
239
238
  latestLimit: currentState.latestLimit,
240
239
  },
241
240
  },
@@ -248,7 +247,7 @@ export class SandboxBridge {
248
247
  }
249
248
  }
250
249
  };
251
- // `ready` is sent later by `initCustomBlock` (after the manifest fetch
250
+ // `connect` is sent later by `initCustomBlock` (after the manifest fetch
252
251
  // resolves). Top-level / no-iframe rejection is handled there too, so
253
252
  // the constructor just attaches the listener.
254
253
  if (typeof window !== "undefined") {
@@ -286,42 +285,46 @@ export class SandboxBridge {
286
285
  }
287
286
  const onAbort = () => reject(signal.reason);
288
287
  signal.addEventListener("abort", onAbort, { once: true });
289
- this.initMessage.then(message => {
288
+ this.initMessage.then(() => {
290
289
  signal.removeEventListener("abort", onAbort);
291
- resolve(message);
290
+ resolve();
292
291
  }, err => {
293
292
  signal.removeEventListener("abort", onAbort);
294
293
  reject(err);
295
294
  });
296
295
  });
297
296
  }
298
- sendReady(manifestResult) {
297
+ sendConnect(manifestResult) {
299
298
  if (typeof window === "undefined") {
300
299
  return;
301
300
  }
302
- if (this.hasSentReady) {
303
- console.warn("[custom-blocks-sdk] ignoring duplicate ready message");
301
+ if (this.hasSentConnect) {
302
+ console.warn("[custom-blocks-sdk] ignoring duplicate connect message");
304
303
  return;
305
304
  }
306
305
  const { manifest, error } = manifestResult;
307
- this.hasSentReady = true;
308
- this.manifest = manifest;
309
- const readyMessage = error !== undefined
306
+ this.hasSentConnect = true;
307
+ const initializationId = `custom-block-initialization-${this.nextRequestId}`;
308
+ this.nextRequestId += 1;
309
+ this.initializationId = initializationId;
310
+ const connectMessage = error !== undefined
310
311
  ? {
311
- type: "ready",
312
+ type: "connect",
313
+ initializationId,
312
314
  status: "error",
313
315
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
314
316
  sdkVersion: CUSTOM_BLOCKS_SDK_VERSION,
315
317
  error,
316
318
  }
317
319
  : {
318
- type: "ready",
320
+ type: "connect",
321
+ initializationId,
319
322
  status: "success",
320
323
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
321
324
  sdkVersion: CUSTOM_BLOCKS_SDK_VERSION,
322
- manifest,
325
+ ...(manifest !== null ? { manifest } : {}),
323
326
  };
324
- this.postToHost(readyMessage);
327
+ this.postToHost(connectMessage);
325
328
  }
326
329
  postToHost(message) {
327
330
  console.debug("[custom-blocks-sdk] outbound postMessage", message);
@@ -335,15 +338,6 @@ export class SandboxBridge {
335
338
  getHostState() {
336
339
  return this.hostState;
337
340
  }
338
- /**
339
- * The author-declared manifest loaded from `custom_blocks.json` and forwarded
340
- * to the host in `ready`. `null` when it failed to load/parse and the host
341
- * should reject init via `ready.status: "error"`. Static for the lifetime
342
- * of the sandbox.
343
- */
344
- getManifest() {
345
- return this.manifest;
346
- }
347
341
  /**
348
342
  * Apply an `init` payload as if it had arrived from the host. Lets callers
349
343
  * seed the bridge directly (e.g. the React provider's standalone preview
@@ -351,9 +345,13 @@ export class SandboxBridge {
351
345
  * of why it's being seeded.
352
346
  */
353
347
  setMockState(message) {
354
- this.applyInit(message);
348
+ this.isMockState = true;
349
+ this.applyInit(v.parse(initMessageSchema, message), false);
355
350
  }
356
- applyInit(message) {
351
+ applyInit(message, postResult) {
352
+ if (postResult) {
353
+ this.isMockState = false;
354
+ }
357
355
  if (message.status === "error") {
358
356
  // The host couldn't construct block location for this block (most commonly the parent record
359
357
  // failed to resolve). Surface the failure through the `awaitInit` promise so callers see
@@ -363,7 +361,7 @@ export class SandboxBridge {
363
361
  // back over the bridge since the host has already given up.
364
362
  console.error(`[custom-blocks-sdk] host reported init error (${message.error.code}): ${message.error.message}`);
365
363
  if (this.rejectInit) {
366
- this.rejectInit(new CustomBlockInitError(message.error));
364
+ this.rejectInit(new CustomBlockInitializationError(message.error));
367
365
  this.resolveInit = undefined;
368
366
  this.rejectInit = undefined;
369
367
  }
@@ -371,10 +369,28 @@ export class SandboxBridge {
371
369
  }
372
370
  const { blockId, parent, page } = message;
373
371
  this.latestDataSourceBindings = message.dataSources.bindings;
374
- const dataSources = resolveDataSources({
375
- manifest: this.manifest,
376
- dataSourceBindings: this.latestDataSourceBindings,
377
- });
372
+ const dataSources = postResult
373
+ ? resolveDataSources({
374
+ manifest: message.manifest,
375
+ dataSourceBindings: this.latestDataSourceBindings,
376
+ })
377
+ : resolveMockDataSources(this.latestDataSourceBindings);
378
+ const bindingError = getInitBindingError(message.manifest, dataSources);
379
+ if (postResult && bindingError !== undefined) {
380
+ const result = {
381
+ type: "initResult",
382
+ initializationId: message.initializationId,
383
+ status: "error",
384
+ error: bindingError,
385
+ };
386
+ this.postToHost(result);
387
+ if (this.rejectInit) {
388
+ this.rejectInit(new CustomBlockInitializationError(bindingError));
389
+ this.resolveInit = undefined;
390
+ this.rejectInit = undefined;
391
+ }
392
+ return;
393
+ }
378
394
  this.hostState = {
379
395
  status: "initialized",
380
396
  theme: message.theme,
@@ -382,15 +398,24 @@ export class SandboxBridge {
382
398
  parent,
383
399
  page,
384
400
  currentUser: message.currentUser,
401
+ manifest: message.manifest,
385
402
  dataSources,
386
403
  dataSourceState: {},
387
404
  };
388
405
  this.notify();
406
+ if (postResult) {
407
+ const result = {
408
+ type: "initResult",
409
+ initializationId: message.initializationId,
410
+ status: "success",
411
+ };
412
+ this.postToHost(result);
413
+ }
389
414
  // Resolve the awaitInit promise once. Subsequent `init` messages
390
415
  // (the host shouldn't send these, but be tolerant) update state but
391
416
  // don't re-resolve.
392
417
  if (this.resolveInit) {
393
- this.resolveInit(message);
418
+ this.resolveInit();
394
419
  this.resolveInit = undefined;
395
420
  this.rejectInit = undefined;
396
421
  }
@@ -418,16 +443,14 @@ export class SandboxBridge {
418
443
  return;
419
444
  }
420
445
  const limit = resolveDataSourceQueryLimit(options.limit);
421
- const snapshotId = makeDataSourceSnapshotId({
446
+ const subscriptionId = makeDataSourceSubscriptionId({
422
447
  key,
423
448
  });
424
449
  if (currentState.isLoading &&
425
- currentState.latestSnapshotId === snapshotId &&
450
+ currentState.subscriptionId === subscriptionId &&
426
451
  currentState.latestLimit === limit) {
427
452
  return;
428
453
  }
429
- const requestId = `custom-block-query-${this.nextRequestId}`;
430
- this.nextRequestId += 1;
431
454
  this.hostState = {
432
455
  ...this.hostState,
433
456
  dataSourceState: {
@@ -436,8 +459,7 @@ export class SandboxBridge {
436
459
  ...currentState,
437
460
  isLoading: true,
438
461
  error: undefined,
439
- latestRequestId: requestId,
440
- latestSnapshotId: snapshotId,
462
+ subscriptionId,
441
463
  latestLimit: limit,
442
464
  },
443
465
  },
@@ -445,8 +467,7 @@ export class SandboxBridge {
445
467
  this.notify();
446
468
  const outbound = {
447
469
  type: "queryDataSource",
448
- requestId,
449
- snapshotId,
470
+ subscriptionId,
450
471
  dataSourceId: dataSource.collectionPointer.id,
451
472
  limit,
452
473
  };
@@ -464,8 +485,7 @@ export class SandboxBridge {
464
485
  ...currentState,
465
486
  isLoading: false,
466
487
  error,
467
- latestRequestId: undefined,
468
- latestSnapshotId: undefined,
488
+ subscriptionId: undefined,
469
489
  latestLimit: undefined,
470
490
  },
471
491
  },
@@ -708,10 +728,42 @@ function resolveDataSourceQueryLimit(limit) {
708
728
  }
709
729
  return limit;
710
730
  }
711
- function makeDataSourceSnapshotId(args) {
731
+ function makeDataSourceSubscriptionId(args) {
712
732
  const { key } = args;
713
733
  return `data-source:${encodeURIComponent(key)}`;
714
734
  }
735
+ function getInitBindingError(manifest, dataSources) {
736
+ for (const [dataSourceKey, manifestDataSource] of Object.entries(manifest.dataSources)) {
737
+ const dataSource = dataSources.find(entry => entry.key === dataSourceKey);
738
+ if (dataSource?.collectionPointer === undefined ||
739
+ dataSource.collectionSchema === undefined) {
740
+ return {
741
+ code: "invalid_init_bindings",
742
+ message: `Host did not provide a complete binding for data source "${dataSourceKey}".`,
743
+ isRetryable: false,
744
+ };
745
+ }
746
+ for (const propertyKey of Object.keys(manifestDataSource.properties ?? {})) {
747
+ if (dataSource.propertyIdsByKey[propertyKey] === undefined) {
748
+ return {
749
+ code: "invalid_init_bindings",
750
+ message: `Host did not provide a valid binding for property "${dataSourceKey}.${propertyKey}".`,
751
+ isRetryable: false,
752
+ };
753
+ }
754
+ }
755
+ }
756
+ return undefined;
757
+ }
758
+ function resolveMockDataSources(bindings) {
759
+ return Object.entries(bindings).map(([key, binding]) => ({
760
+ key,
761
+ collectionPointer: binding.collectionPointer,
762
+ collectionSchema: binding.collectionSchema,
763
+ propertyIdsByKey: { ...(binding.propertyIdsByKey ?? {}) },
764
+ propertySchemasById: binding.collectionSchema?.propertiesById ?? {},
765
+ }));
766
+ }
715
767
  function formatInvalidHostReason(incomingType, issues) {
716
768
  const labelled = incomingType
717
769
  ? `host message of type "${incomingType}"`
@@ -1,16 +1,15 @@
1
1
  import type { CustomBlockManifest } from "../manifest.js";
2
2
  import type { NotionDataSource, NotionDataSourceBindings } from "./dataSource.js";
3
3
  type ResolveDataSourcesArgs = {
4
- manifest: CustomBlockManifest | null;
4
+ manifest: CustomBlockManifest;
5
5
  dataSourceBindings: NotionDataSourceBindings;
6
6
  };
7
7
  /**
8
8
  * Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
9
9
  *
10
10
  * Combines the host-supplied bindings (collection pointers + schemas) with the
11
- * manifest's declared data-source keys. Property mappings are host-owned: the
12
- * SDK validates the supplied property id against the manifest and live schema,
13
- * but does not infer a missing mapping.
11
+ * manifest's declared data-source keys. The host owns every mapping; the SDK
12
+ * validates supplied IDs but never infers omitted bindings.
14
13
  */
15
14
  export declare function resolveDataSources(args: ResolveDataSourcesArgs): NotionDataSource[];
16
15
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EACX,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,kBAAkB,EAAE,wBAAwB,CAAA;CAC5C,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,sBAAsB,GAC1B,gBAAgB,EAAE,CAyCpB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EACX,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,kBAAkB,EAAE,wBAAwB,CAAA;CAC5C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,sBAAsB,GAC1B,gBAAgB,EAAE,CA+BpB"}
@@ -2,20 +2,10 @@
2
2
  * Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
3
3
  *
4
4
  * Combines the host-supplied bindings (collection pointers + schemas) with the
5
- * manifest's declared data-source keys. Property mappings are host-owned: the
6
- * SDK validates the supplied property id against the manifest and live schema,
7
- * but does not infer a missing mapping.
5
+ * manifest's declared data-source keys. The host owns every mapping; the SDK
6
+ * validates supplied IDs but never infers omitted bindings.
8
7
  */
9
8
  export function resolveDataSources(args) {
10
- if (args.manifest === null) {
11
- return Object.entries(args.dataSourceBindings).map(([key, binding]) => ({
12
- key,
13
- collectionPointer: binding.collectionPointer,
14
- collectionSchema: binding.collectionSchema,
15
- propertyIdsByKey: { ...(binding.propertyIdsByKey ?? {}) },
16
- propertySchemasById: binding.collectionSchema?.propertiesById ?? {},
17
- }));
18
- }
19
9
  return Object.entries(args.manifest.dataSources).map(([key, manifestDataSource]) => {
20
10
  const binding = args.dataSourceBindings[key];
21
11
  const propertySchemasById = binding?.collectionSchema?.propertiesById ?? {};
@@ -2,6 +2,7 @@ import type { NotionDataSource } from "./dataSources/dataSource.js";
2
2
  import type { NotionDataSourcePage, NotionDataSourcePageBridge, NotionDataSourcePageUpdateInput, NotionDataSourcePageUpdateResult } from "./dataSources/dataSourcePage.js";
3
3
  import type { NotionPropertySchema } from "./dataSources/propertySchema.js";
4
4
  import type { NotionBlockId } from "./ids.js";
5
+ import type { CustomBlockManifest } from "./manifest.js";
5
6
  import type { CustomBlockQueryDataSourceErrorInfo } from "./messages/queryDataSourceResult.js";
6
7
  import type { CustomBlockPage, NotionPageId } from "./pages/page.js";
7
8
  import type { NotionParent } from "./parent.js";
@@ -19,6 +20,7 @@ export type InitializedHostState = {
19
20
  parent: NotionParent;
20
21
  page: CustomBlockPage;
21
22
  currentUser: NotionUser;
23
+ manifest: CustomBlockManifest;
22
24
  dataSources: NotionDataSource[];
23
25
  dataSourceState: Record<string, DataSourceQueryState>;
24
26
  };
@@ -28,8 +30,7 @@ export type DataSourceQueryState = {
28
30
  isLoading: boolean;
29
31
  hasMore: boolean;
30
32
  error?: CustomBlockQueryDataSourceErrorInfo;
31
- latestRequestId?: string;
32
- latestSnapshotId?: string;
33
+ subscriptionId?: string;
33
34
  latestLimit?: number;
34
35
  };
35
36
  export declare function createEmptyDataSourceQueryState(): DataSourceQueryState;
@@ -1 +1 @@
1
- {"version":3,"file":"hostState.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/hostState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EACX,oBAAoB,EACpB,0BAA0B,EAC1B,+BAA+B,EAC/B,gCAAgC,EAChC,MAAM,iCAAiC,CAAA;AAExC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,qCAAqC,CAAA;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG,oBAAoB,CAAA;AAEhF,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,eAAe,CAAA;IACvB,KAAK,EAAE,WAAW,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,MAAM,EAAE,aAAa,CAAA;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,WAAW,EAAE,UAAU,CAAA;IACvB,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;CACrD,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,iGAAiG;IACjG,KAAK,EAAE,0BAA0B,EAAE,CAAA;IACnC,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,mCAAmC,CAAA;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,wBAAgB,+BAA+B,IAAI,oBAAoB,CAMtE;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IACjC,KAAK,EAAE,oBAAoB,EAAE,CAAA;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IACvD,mBAAmB,EAAE;QAAE,CAAC,UAAU,EAAE,MAAM,GAAG,oBAAoB,CAAA;KAAE,CAAA;IACnE,gBAAgB,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;IACvD,oBAAoB,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAA;KAAE,CAAA;IACzE,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,mCAAmC,CAAA;CAC3C,CAAA;AAWD;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,UAAU,EAAE,gBAAgB,CAAA;IAC5B,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,+BAA+B,CAAA;CACtC,KAAK,OAAO,CAAC,gCAAgC,CAAC,CAAA;AAE/C,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,oBAAoB,EAC/B,GAAG,EAAE,MAAM,EACX,oBAAoB,EAAE,sBAAsB,GAC1C,mBAAmB,CAgErB"}
1
+ {"version":3,"file":"hostState.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/hostState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EACX,oBAAoB,EACpB,0BAA0B,EAC1B,+BAA+B,EAC/B,gCAAgC,EAChC,MAAM,iCAAiC,CAAA;AAExC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,qCAAqC,CAAA;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG,oBAAoB,CAAA;AAEhF,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,eAAe,CAAA;IACvB,KAAK,EAAE,WAAW,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,MAAM,EAAE,aAAa,CAAA;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,WAAW,EAAE,UAAU,CAAA;IACvB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;CACrD,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,iGAAiG;IACjG,KAAK,EAAE,0BAA0B,EAAE,CAAA;IACnC,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,mCAAmC,CAAA;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,wBAAgB,+BAA+B,IAAI,oBAAoB,CAMtE;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IACjC,KAAK,EAAE,oBAAoB,EAAE,CAAA;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IACvD,mBAAmB,EAAE;QAAE,CAAC,UAAU,EAAE,MAAM,GAAG,oBAAoB,CAAA;KAAE,CAAA;IACnE,gBAAgB,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;IACvD,oBAAoB,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAAA;KAAE,CAAA;IACzE,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,mCAAmC,CAAA;CAC3C,CAAA;AAWD;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,UAAU,EAAE,gBAAgB,CAAA;IAC5B,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,+BAA+B,CAAA;CACtC,KAAK,OAAO,CAAC,gCAAgC,CAAC,CAAA;AAE/C,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,oBAAoB,EAC/B,GAAG,EAAE,MAAM,EACX,oBAAoB,EAAE,sBAAsB,GAC1C,mBAAmB,CAgErB"}
@@ -1,20 +1,20 @@
1
1
  import type { CustomBlockErrorInfo } from "./errors.js";
2
2
  import { type CustomBlockManifest } from "./manifest.js";
3
- export type CustomBlockReadyErrorCode = "manifest_unavailable" | "manifest_invalid" | (string & {});
4
- export type CustomBlockReadyError = CustomBlockErrorInfo<CustomBlockReadyErrorCode>;
3
+ export type CustomBlockConnectErrorCode = "manifest_unavailable" | "manifest_invalid" | (string & {});
4
+ export type CustomBlockConnectError = CustomBlockErrorInfo<CustomBlockConnectErrorCode>;
5
5
  export type ManifestLoadResult = {
6
6
  manifest: CustomBlockManifest | null;
7
7
  error?: undefined;
8
8
  } | {
9
9
  manifest: null;
10
- error: CustomBlockReadyError;
10
+ error: CustomBlockConnectError;
11
11
  };
12
12
  /**
13
13
  * Attempts to load a `custom_blocks.json` manifest co-located with the bundle.
14
14
  * A missing manifest means the block has no declared data requirements.
15
15
  * Other failures return a structured error so the host can reject the manifest.
16
16
  * The SDK validates the manifest locally for author feedback; the host still
17
- * validates the ready message as the iframe trust boundary.
17
+ * validates the connect message as the iframe trust boundary.
18
18
  */
19
19
  export declare function loadManifest(): Promise<ManifestLoadResult>;
20
20
  //# sourceMappingURL=loadManifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loadManifest.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/loadManifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,KAAK,mBAAmB,EAAkB,MAAM,eAAe,CAAA;AAIxE,MAAM,MAAM,yBAAyB,GAClC,sBAAsB,GACtB,kBAAkB,GAClB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,MAAM,MAAM,qBAAqB,GAChC,oBAAoB,CAAC,yBAAyB,CAAC,CAAA;AAEhD,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;CAChB,GACD;IACA,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,EAAE,qBAAqB,CAAA;CAC3B,CAAA;AAEJ;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAkEhE"}
1
+ {"version":3,"file":"loadManifest.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/loadManifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,KAAK,mBAAmB,EAAkB,MAAM,eAAe,CAAA;AAIxE,MAAM,MAAM,2BAA2B,GACpC,sBAAsB,GACtB,kBAAkB,GAClB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,MAAM,MAAM,uBAAuB,GAClC,oBAAoB,CAAC,2BAA2B,CAAC,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;CAChB,GACD;IACA,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,EAAE,uBAAuB,CAAA;CAC7B,CAAA;AAEJ;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAkEhE"}
@@ -6,7 +6,7 @@ const MANIFEST_URL = "custom_blocks.json";
6
6
  * A missing manifest means the block has no declared data requirements.
7
7
  * Other failures return a structured error so the host can reject the manifest.
8
8
  * The SDK validates the manifest locally for author feedback; the host still
9
- * validates the ready message as the iframe trust boundary.
9
+ * validates the connect message as the iframe trust boundary.
10
10
  */
11
11
  export async function loadManifest() {
12
12
  if (typeof fetch !== "function") {
@@ -28,7 +28,7 @@ export async function loadManifest() {
28
28
  manifest: null,
29
29
  // Consider this retryable because it may have just been a network issue. Because `init`
30
30
  // errors are considered terminal, the host should "retry" by reloading the iframe and
31
- // sending another `ready` message.
31
+ // sending another `connect` message.
32
32
  error: { code: "manifest_unavailable", message, isRetryable: true },
33
33
  };
34
34
  }
@@ -1,9 +1,9 @@
1
1
  import * as v from "valibot";
2
2
  /**
3
3
  * User-authored manifest declaring the data sources the custom block expects.
4
- * Lives at `custom_blocks.json` in the project root and is forwarded to the host with
5
- * the bridge `ready` message so the host can pre-bind data sources, surface
6
- * configuration UI, etc. The `notionCustomBlock` Vite plugin from
4
+ * Lives at `custom_blocks.json` in the project root. The sandbox may send it in
5
+ * `connect`, and the host returns the authoritative manifest in `init`. The
6
+ * `notionCustomBlock` Vite plugin from
7
7
  * `@notionhq/custom-blocks/vite` wires the JSON file into the dev server and
8
8
  * the build output.
9
9
  */
@@ -2,9 +2,9 @@ import * as v from "valibot";
2
2
  import { notionPropertyTypeSchema } from "./dataSources/propertySchema.js";
3
3
  /**
4
4
  * User-authored manifest declaring the data sources the custom block expects.
5
- * Lives at `custom_blocks.json` in the project root and is forwarded to the host with
6
- * the bridge `ready` message so the host can pre-bind data sources, surface
7
- * configuration UI, etc. The `notionCustomBlock` Vite plugin from
5
+ * Lives at `custom_blocks.json` in the project root. The sandbox may send it in
6
+ * `connect`, and the host returns the authoritative manifest in `init`. The
7
+ * `notionCustomBlock` Vite plugin from
8
8
  * `@notionhq/custom-blocks/vite` wires the JSON file into the dev server and
9
9
  * the build output.
10
10
  */
@@ -1,18 +1,19 @@
1
1
  import * as v from "valibot";
2
2
  /**
3
- * First message the sandbox sends after mount, kicking off the bridge handshake. The host replies
4
- * with `init`.
3
+ * First message the sandbox sends after mount. It identifies the sandbox and starts the bridge
4
+ * initialization exchange. The host echoes `initializationId` in its `init` response.
5
5
  */
6
- export declare const readyMessageSchema: v.UnionSchema<[v.ObjectSchema<{
7
- readonly type: v.LiteralSchema<"ready", undefined>;
6
+ export declare const connectMessageSchema: v.VariantSchema<"status", [v.ObjectSchema<{
7
+ readonly type: v.LiteralSchema<"connect", undefined>;
8
+ readonly initializationId: v.StringSchema<undefined>;
8
9
  readonly bridgeProtocolVersion: v.NumberSchema<undefined>;
9
10
  readonly sdkVersion: v.StringSchema<undefined>;
10
11
  readonly status: v.LiteralSchema<"success", undefined>;
11
12
  /**
12
- * The data sources and settings the custom block expects, or null when the
13
- * block has no manifest and no declared data requirements.
13
+ * The data sources and settings the custom block expects. Hosts may omit
14
+ * this when they already have an authoritative manifest.
14
15
  */
15
- readonly manifest: v.UnionSchema<[v.ObjectSchema<{
16
+ readonly manifest: v.OptionalSchema<v.ObjectSchema<{
16
17
  readonly version: v.LiteralSchema<1, undefined>;
17
18
  readonly dataSources: v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
18
19
  readonly name: v.StringSchema<undefined>;
@@ -30,9 +31,10 @@ export declare const readyMessageSchema: v.UnionSchema<[v.ObjectSchema<{
30
31
  readonly type: v.PicklistSchema<readonly ["title", "rich_text", "number", "checkbox", "url", "email", "phone_number", "select", "multi_select", "status", "date", "people", "files", "unique_id", "relation", "place", "formula", "rollup", "button", "verification", "last_visited_time", "location", "created_time", "last_edited_time", "created_by", "last_edited_by"], undefined>;
31
32
  }, undefined>, undefined>, {}>;
32
33
  }, undefined>, undefined>;
33
- }, undefined>, v.NullSchema<undefined>], undefined>;
34
+ }, undefined>, undefined>;
34
35
  }, undefined>, v.ObjectSchema<{
35
- readonly type: v.LiteralSchema<"ready", undefined>;
36
+ readonly type: v.LiteralSchema<"connect", undefined>;
37
+ readonly initializationId: v.StringSchema<undefined>;
36
38
  readonly bridgeProtocolVersion: v.NumberSchema<undefined>;
37
39
  readonly sdkVersion: v.StringSchema<undefined>;
38
40
  readonly status: v.LiteralSchema<"error", undefined>;
@@ -42,5 +44,5 @@ export declare const readyMessageSchema: v.UnionSchema<[v.ObjectSchema<{
42
44
  readonly isRetryable: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
43
45
  }, undefined>;
44
46
  }, undefined>], undefined>;
45
- export type ReadyMessage = v.InferOutput<typeof readyMessageSchema>;
46
- //# sourceMappingURL=ready.d.ts.map
47
+ export type ConnectMessage = v.InferOutput<typeof connectMessageSchema>;
48
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/messages/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA2B5B;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;IAG/B;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BASH,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAA"}