ncblock 0.0.2 → 0.0.4

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 (54) hide show
  1. package/README.md +15 -242
  2. package/dist/bridge/SandboxBridge.d.ts +64 -0
  3. package/dist/bridge/SandboxBridge.d.ts.map +1 -0
  4. package/dist/{notion.js → bridge/SandboxBridge.js} +80 -337
  5. package/dist/bridge/dataSources/resolve.d.ts +22 -0
  6. package/dist/bridge/dataSources/resolve.d.ts.map +1 -0
  7. package/dist/bridge/dataSources/resolve.js +65 -0
  8. package/dist/bridge/dataSources/resolveProperty.d.ts +36 -0
  9. package/dist/bridge/dataSources/resolveProperty.d.ts.map +1 -0
  10. package/dist/bridge/dataSources/resolveProperty.js +67 -0
  11. package/dist/bridge/hostState.d.ts +57 -0
  12. package/dist/bridge/hostState.d.ts.map +1 -0
  13. package/dist/bridge/hostState.js +65 -0
  14. package/dist/bridge/loadManifest.d.ts +9 -0
  15. package/dist/bridge/loadManifest.d.ts.map +1 -0
  16. package/dist/bridge/loadManifest.js +41 -0
  17. package/dist/bridge/messages/getUser.d.ts +32 -0
  18. package/dist/bridge/messages/getUser.d.ts.map +1 -0
  19. package/dist/bridge/messages/getUser.js +24 -0
  20. package/dist/bridge/messages/hostToSandbox.d.ts +45 -0
  21. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  22. package/dist/bridge/messages/hostToSandbox.js +4 -0
  23. package/dist/bridge/messages/listUsers.d.ts +40 -0
  24. package/dist/bridge/messages/listUsers.d.ts.map +1 -0
  25. package/dist/bridge/messages/listUsers.js +25 -0
  26. package/dist/bridge/messages/sandboxToHost.d.ts +9 -0
  27. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  28. package/dist/bridge/messages/sandboxToHost.js +4 -0
  29. package/dist/bridge/sandboxClient.d.ts +46 -0
  30. package/dist/bridge/sandboxClient.d.ts.map +1 -0
  31. package/dist/bridge/sandboxClient.js +72 -0
  32. package/dist/bridge/users/user.d.ts +36 -0
  33. package/dist/bridge/users/user.d.ts.map +1 -0
  34. package/dist/bridge/users/user.js +19 -0
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/init.d.ts +44 -0
  39. package/dist/init.d.ts.map +1 -0
  40. package/dist/init.js +49 -0
  41. package/dist/react.d.ts +1 -1
  42. package/dist/react.d.ts.map +1 -1
  43. package/dist/react.js +2 -1
  44. package/dist/types.d.ts +20 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/users.d.ts +15 -0
  47. package/dist/users.d.ts.map +1 -0
  48. package/dist/users.js +18 -0
  49. package/package.json +1 -1
  50. package/dist/notion.d.ts +0 -113
  51. package/dist/notion.d.ts.map +0 -1
  52. package/dist/pages.d.ts +0 -23
  53. package/dist/pages.d.ts.map +0 -1
  54. package/dist/pages.js +0 -30
@@ -1,86 +1,18 @@
1
1
  import * as v from "valibot";
2
- import { readIncomingType } from "./bridge/incomingType";
3
- import { manifestSchema } from "./bridge/manifest";
4
- import { hostToSandboxMessageSchema } from "./bridge/messages/hostToSandbox";
5
- import { notionPagePropertyValueSchema } from "./bridge/pages/page";
6
- import { PendingRequests } from "./bridge/pendingRequests";
7
- import { unreachable } from "./utils";
2
+ import { unreachable } from "../utils";
3
+ import { resolveDataSources } from "./dataSources/resolve";
4
+ import { resolvePropertyWriteMapForDataSource } from "./dataSources/resolveProperty";
5
+ import { createEmptyDataSourceQueryState, } from "./hostState";
6
+ import { readIncomingType } from "./incomingType";
7
+ import { hostToSandboxMessageSchema } from "./messages/hostToSandbox";
8
+ import { PendingRequests } from "./pendingRequests";
8
9
  /**
9
10
  * Used to ensure that the host and client are using the same version of the bridge protocol. A
10
11
  * single host needs to support multiple custom blocks built with different versions of the bridge
11
12
  * protocol. Increment this number any time a breaking change is made to the bridge protocol.
12
13
  */
13
- const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 1;
14
- const MANIFEST_URL = "custom_blocks.json";
15
- /**
16
- * Resolves a public SDK property write map into the ID-keyed bridge shape.
17
- */
18
- function resolvePropertyWriteMapForDataSource(args) {
19
- const { dataSource, properties, operationName } = args;
20
- const resolvedProperties = {};
21
- for (const [identifier, value] of Object.entries(properties)) {
22
- const propertyIdResult = resolvePropertyIdentifierForDataSource({
23
- dataSource,
24
- identifier,
25
- operationName,
26
- });
27
- if (propertyIdResult.status === "error") {
28
- return propertyIdResult;
29
- }
30
- const propertyId = propertyIdResult.propertyId;
31
- if (value.id !== undefined &&
32
- value.id !== identifier &&
33
- value.id !== propertyId) {
34
- return {
35
- status: "error",
36
- error: `Property ${identifier} resolved to ${propertyId} but value id was ${value.id}.`,
37
- };
38
- }
39
- if (resolvedProperties[propertyId] !== undefined) {
40
- return {
41
- status: "error",
42
- error: `Cannot set property ${propertyId} more than once.`,
43
- };
44
- }
45
- const parsedValue = v.safeParse(notionPagePropertyValueSchema, {
46
- ...value,
47
- id: propertyId,
48
- });
49
- if (!parsedValue.success) {
50
- return {
51
- status: "error",
52
- error: `Invalid value for property ${identifier}.`,
53
- };
54
- }
55
- resolvedProperties[propertyId] = parsedValue.output;
56
- }
57
- return { status: "success", properties: resolvedProperties };
58
- }
59
- /**
60
- * Treats matching data-source keys as aliases for property IDs; all other identifiers are raw IDs.
61
- */
62
- function resolvePropertyIdentifierForDataSource(args) {
63
- const { dataSource, identifier, operationName } = args;
64
- if (dataSource !== undefined && identifier in dataSource.propertyIdsByKey) {
65
- const propertyId = dataSource.propertyIdsByKey[identifier];
66
- if (propertyId === undefined) {
67
- return {
68
- status: "error",
69
- error: `${operationName} cannot resolve property key "${identifier}" because it is not bound to a Notion property.`,
70
- };
71
- }
72
- return { status: "success", propertyId };
73
- }
74
- return { status: "success", propertyId: identifier };
75
- }
76
- function createEmptyDataSourceQueryState() {
77
- return {
78
- items: [],
79
- isLoading: false,
80
- hasMore: false,
81
- };
82
- }
83
- class NotionCustomBlockBridge {
14
+ export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 1;
15
+ export class SandboxBridge {
84
16
  constructor() {
85
17
  this.hostState = {
86
18
  status: "uninitialized",
@@ -90,6 +22,8 @@ class NotionCustomBlockBridge {
90
22
  this.nextRequestId = 1;
91
23
  this.pendingCreatePage = new PendingRequests("custom-block-create-page");
92
24
  this.pendingGetPage = new PendingRequests("custom-block-get-page");
25
+ this.pendingGetUser = new PendingRequests("custom-block-get-user");
26
+ this.pendingListUsers = new PendingRequests("custom-block-list-users");
93
27
  this.pendingUpdatePage = new PendingRequests("custom-block-update-page");
94
28
  this.initMessage = new Promise(resolve => {
95
29
  this.resolveInit = resolve;
@@ -205,6 +139,24 @@ class NotionCustomBlockBridge {
205
139
  }
206
140
  return;
207
141
  }
142
+ case "getUserResult": {
143
+ const result = message.status === "success"
144
+ ? { status: "success", user: message.user }
145
+ : { status: "error", error: message.error };
146
+ if (!this.pendingGetUser.resolve(message.requestId, result)) {
147
+ console.warn(`[notion-custom-sdk] getUserResult for unknown requestId ${message.requestId}`);
148
+ }
149
+ return;
150
+ }
151
+ case "listUsersResult": {
152
+ const result = message.status === "success"
153
+ ? { status: "success", list: message.list }
154
+ : { status: "error", error: message.error };
155
+ if (!this.pendingListUsers.resolve(message.requestId, result)) {
156
+ console.warn(`[notion-custom-sdk] listUsersResult for unknown requestId ${message.requestId}`);
157
+ }
158
+ return;
159
+ }
208
160
  case "updatePageResult": {
209
161
  const result = message.status === "success"
210
162
  ? { status: "success", page: message.page }
@@ -270,7 +222,7 @@ class NotionCustomBlockBridge {
270
222
  });
271
223
  });
272
224
  }
273
- async sendReady(manifest) {
225
+ sendReady(manifest) {
274
226
  if (typeof window === "undefined") {
275
227
  return;
276
228
  }
@@ -412,6 +364,31 @@ class NotionCustomBlockBridge {
412
364
  window.parent.postMessage(outbound, "*");
413
365
  });
414
366
  }
367
+ getUser(userId) {
368
+ return new Promise(resolve => {
369
+ const requestId = this.pendingGetUser.allocate(resolve);
370
+ const outbound = {
371
+ type: "getUser",
372
+ requestId,
373
+ userId,
374
+ };
375
+ console.debug("[notion-custom-sdk] outbound postMessage", outbound);
376
+ window.parent.postMessage(outbound, "*");
377
+ });
378
+ }
379
+ listUsers(input = {}) {
380
+ return new Promise(resolve => {
381
+ const requestId = this.pendingListUsers.allocate(resolve);
382
+ const outbound = {
383
+ type: "listUsers",
384
+ requestId,
385
+ startCursor: input.startCursor,
386
+ pageSize: input.pageSize,
387
+ };
388
+ console.debug("[notion-custom-sdk] outbound postMessage", outbound);
389
+ window.parent.postMessage(outbound, "*");
390
+ });
391
+ }
415
392
  updatePage(input) {
416
393
  return new Promise(resolve => {
417
394
  if ((input.properties === undefined ||
@@ -447,6 +424,31 @@ class NotionCustomBlockBridge {
447
424
  window.parent.postMessage(outbound, "*");
448
425
  });
449
426
  }
427
+ /**
428
+ * Updates a page on a known data source, resolving any property keys against the data source's
429
+ * `propertyIdsByKey` before sending the bridge message. Used by the per-row `update` callback
430
+ * returned from {@link getDataSourceQueryView}.
431
+ */
432
+ updateDataSourcePage(args) {
433
+ const { dataSource, pageId, input } = args;
434
+ const resolvedProperties = input.properties === undefined
435
+ ? undefined
436
+ : resolvePropertyWriteMapForDataSource({
437
+ dataSource,
438
+ properties: input.properties,
439
+ operationName: "dataSourcePage.update",
440
+ });
441
+ if (resolvedProperties?.status === "error") {
442
+ return Promise.resolve(resolvedProperties);
443
+ }
444
+ return this.updatePage({
445
+ pageId,
446
+ properties: resolvedProperties?.properties,
447
+ icon: input.icon,
448
+ cover: input.cover,
449
+ archived: input.archived,
450
+ });
451
+ }
450
452
  /**
451
453
  * Translates the public `CreatePageInput["parent"]` into the bridge-native
452
454
  * `CreatePageMessageParent`. The `data_source_key` variant is resolved sandbox-side against
@@ -496,65 +498,6 @@ class NotionCustomBlockBridge {
496
498
  }
497
499
  }
498
500
  }
499
- function resolveDataSources(args) {
500
- if (args.manifest === null) {
501
- return Object.entries(args.dataSourceBindings).map(([key, binding]) => ({
502
- key,
503
- collectionPointer: binding.collectionPointer,
504
- collectionSchema: binding.collectionSchema,
505
- propertyIdsByKey: { ...(binding.propertyIdsByKey ?? {}) },
506
- propertySchemasById: binding.collectionSchema?.propertiesById ?? {},
507
- }));
508
- }
509
- return Object.entries(args.manifest.dataSources).flatMap(([key, manifestDataSource]) => {
510
- const binding = args.dataSourceBindings[key];
511
- const propertySchemasById = binding?.collectionSchema?.propertiesById ?? {};
512
- const bindingPropertyIdsByKey = binding?.propertyIdsByKey ?? {};
513
- const propertyIdsByKey = {};
514
- const manifestProperties = Object.entries(manifestDataSource.properties ?? {});
515
- for (const [propertyKey, manifestProperty] of manifestProperties) {
516
- const propertyId = propertyKey in bindingPropertyIdsByKey
517
- ? bindingPropertyIdsByKey[propertyKey]
518
- : findPropertyIdByManifestProperty(propertySchemasById, {
519
- key: propertyKey,
520
- type: manifestProperty.type,
521
- });
522
- const propertySchema = propertyId === undefined ? undefined : propertySchemasById[propertyId];
523
- propertyIdsByKey[propertyKey] =
524
- propertySchema?.type === manifestProperty.type
525
- ? propertyId
526
- : undefined;
527
- }
528
- return [
529
- {
530
- key,
531
- collectionPointer: binding?.collectionPointer,
532
- collectionSchema: binding?.collectionSchema,
533
- propertyIdsByKey,
534
- propertySchemasById,
535
- },
536
- ];
537
- });
538
- }
539
- function findPropertyIdByManifestProperty(propertySchemasById, manifestProperty) {
540
- // `ManifestProperty.name` is display copy for setup UI. Binding fallback is based
541
- // on the stable semantic key so copy changes don't affect property resolution.
542
- const propertySchemaForKey = propertySchemasById[manifestProperty.key];
543
- if (propertySchemaForKey?.type === manifestProperty.type) {
544
- return manifestProperty.key;
545
- }
546
- const normalizedKey = normalizePropertyName(manifestProperty.key);
547
- for (const [propertyId, propertySchema] of Object.entries(propertySchemasById)) {
548
- if (propertySchema.type === manifestProperty.type &&
549
- normalizePropertyName(propertySchema.name) === normalizedKey) {
550
- return propertyId;
551
- }
552
- }
553
- return undefined;
554
- }
555
- function normalizePropertyName(value) {
556
- return value.trim().toLowerCase();
557
- }
558
501
  function formatInvalidHostReason(incomingType, issues) {
559
502
  const labelled = incomingType
560
503
  ? `host message of type "${incomingType}"`
@@ -571,203 +514,3 @@ function formatInvalidHostReason(incomingType, issues) {
571
514
  const extra = issues.length > 1 ? ` (+${issues.length - 1} more)` : "";
572
515
  return `Could not parse ${labelled}: ${detail}${extra}`;
573
516
  }
574
- /**
575
- * Attempts to load a `custom_blocks.json` manifest co-located with the bundle. Treats
576
- * any non-200 response as "no manifest".
577
- */
578
- async function tryFetchManifest() {
579
- if (typeof fetch !== "function") {
580
- console.warn(`[notion-custom-sdk] no \`fetch\` available; cannot load ${MANIFEST_URL}`);
581
- return null;
582
- }
583
- let response;
584
- try {
585
- response = await fetch(MANIFEST_URL, { credentials: "omit" });
586
- }
587
- catch (error) {
588
- console.warn(`[notion-custom-sdk] no manifest fetched from ${MANIFEST_URL}`, error);
589
- return null;
590
- }
591
- if (!response.ok) {
592
- console.warn(`[notion-custom-sdk] no manifest at ${MANIFEST_URL} (status ${response.status})`);
593
- return null;
594
- }
595
- let json;
596
- try {
597
- json = await response.json();
598
- }
599
- catch (error) {
600
- console.warn(`[notion-custom-sdk] manifest at ${MANIFEST_URL} was not valid JSON`, error);
601
- return null;
602
- }
603
- const parsed = v.safeParse(manifestSchema, json);
604
- if (!parsed.success) {
605
- console.warn(`[notion-custom-sdk] manifest at ${MANIFEST_URL} did not match schema`, parsed.issues);
606
- return null;
607
- }
608
- return parsed.output;
609
- }
610
- const bridge = new NotionCustomBlockBridge();
611
- export function subscribeToCustomBlockHost(listener) {
612
- return bridge.subscribe(listener);
613
- }
614
- export function getCustomBlockHostState() {
615
- return bridge.getHostState();
616
- }
617
- export function queryCustomBlockDataSource(key, limit) {
618
- bridge.queryDataSource(key, limit);
619
- }
620
- /**
621
- * Apply an `init` payload to the bridge directly, bypassing the postMessage
622
- * handshake. Used by the React provider when it needs to seed placeholder
623
- * state (e.g. the standalone preview fallback when not embedded in Notion).
624
- * The bridge applies the payload through the same code path as a real host.
625
- */
626
- export function setMockCustomBlockState(message) {
627
- bridge.setMockState(message);
628
- }
629
- /**
630
- * Thrown by {@link initCustomBlock} when the SDK is loaded in a top-level
631
- * window (no parent frame) — `postMessage` would just hit the same window and
632
- * the handshake can never complete. `<NotionCustomBlock>` catches this
633
- * specifically and falls back to a standalone preview with a warning banner;
634
- * direct callers can `instanceof` it to apply their own policy.
635
- */
636
- export class NotInIframeError extends Error {
637
- constructor(message = NOT_IN_IFRAME_MESSAGE) {
638
- super(message);
639
- this.name = "NotInIframeError";
640
- }
641
- }
642
- const DEFAULT_INIT_TIMEOUT_MS = 2000;
643
- const NOT_IN_IFRAME_MESSAGE = "<NotionCustomBlock> only works inside an iframe — use the dev shell or deploy to Notion.";
644
- let initPromise;
645
- /**
646
- * Performs the SDK ↔ host handshake: loads `custom_blocks.json`, posts
647
- * `ready`, then awaits the host's first `init` message. Resolves with that
648
- * payload; rejects with a
649
- * `TimeoutError` if the host doesn't respond inside `timeoutMs`.
650
- *
651
- * Idempotent: subsequent calls return the same promise as the first and
652
- * ignore any new options. Mount your React tree (or call any SDK hook /
653
- * `subscribeToCustomBlockHost`) only after the returned promise resolves.
654
- *
655
- * @example
656
- * // src/index.tsx
657
- * const initial = await initCustomBlock()
658
- * console.log(initial.theme, initial.context.customBlockId)
659
- * ReactDOM.createRoot(root).render(<App />)
660
- */
661
- export function initCustomBlock(opts = {}) {
662
- initPromise ?? (initPromise = (async () => {
663
- // Standalone tab (no parent frame) — fail fast with a typed error
664
- // rather than letting `awaitInit` time out, since `postMessage` to
665
- // `window.parent` would just hit the same window and never arrive.
666
- // `<NotionCustomBlock>` catches this specifically to render a warning
667
- // banner + children for dev-time previews.
668
- if (typeof window !== "undefined" && window.parent === window) {
669
- throw new NotInIframeError();
670
- }
671
- const manifest = await tryFetchManifest();
672
- await bridge.sendReady(manifest);
673
- const timeoutMs = opts.timeoutMs ?? DEFAULT_INIT_TIMEOUT_MS;
674
- const message = await bridge.awaitInit(AbortSignal.timeout(timeoutMs));
675
- const hostState = bridge.getHostState();
676
- return {
677
- theme: message.theme,
678
- context: message.context,
679
- dataSources: hostState.status === "initialized" ? hostState.dataSources : [],
680
- };
681
- })());
682
- return initPromise;
683
- }
684
- const EMPTY_QUERY_VIEW = {
685
- items: [],
686
- propertySchemasById: {},
687
- propertySchemasByKey: {},
688
- isLoading: false,
689
- hasMore: false,
690
- };
691
- export function getDataSourceQueryView(hostState, key) {
692
- if (hostState.status !== "initialized") {
693
- return EMPTY_QUERY_VIEW;
694
- }
695
- const dataSource = hostState.dataSources.find(entry => entry.key === key);
696
- const queryState = hostState.dataSourceState[key] ?? createEmptyDataSourceQueryState();
697
- if (dataSource === undefined) {
698
- return {
699
- items: [],
700
- collectionSchema: undefined,
701
- propertySchemasById: {},
702
- propertySchemasByKey: {},
703
- isLoading: queryState.isLoading,
704
- hasMore: queryState.hasMore,
705
- error: queryState.error,
706
- };
707
- }
708
- const propertyIdsByKey = dataSource.propertyIdsByKey;
709
- const propertySchemasById = dataSource.propertySchemasById;
710
- const resolvedItems = queryState.items.map(entry => {
711
- const propertiesByKey = {};
712
- for (const [key, propertyId] of Object.entries(propertyIdsByKey)) {
713
- propertiesByKey[key] =
714
- propertyId === undefined ? undefined : entry.propertiesById[propertyId];
715
- }
716
- return {
717
- id: entry.id,
718
- propertiesById: entry.propertiesById,
719
- propertiesByKey,
720
- update: input => updateDataSourcePage({
721
- dataSource,
722
- pageId: entry.id,
723
- input,
724
- }),
725
- };
726
- });
727
- const propertySchemasByKey = {};
728
- for (const [key, propertyId] of Object.entries(propertyIdsByKey)) {
729
- propertySchemasByKey[key] =
730
- propertyId === undefined ? undefined : propertySchemasById[propertyId];
731
- }
732
- return {
733
- items: resolvedItems,
734
- collectionSchema: dataSource.collectionSchema,
735
- propertySchemasById,
736
- propertySchemasByKey,
737
- isLoading: queryState.isLoading,
738
- hasMore: queryState.hasMore,
739
- error: queryState.error,
740
- };
741
- }
742
- function updateDataSourcePage(args) {
743
- const { dataSource, pageId, input } = args;
744
- const resolvedProperties = input.properties === undefined
745
- ? undefined
746
- : resolvePropertyWriteMapForDataSource({
747
- dataSource,
748
- properties: input.properties,
749
- operationName: "dataSourcePage.update",
750
- });
751
- if (resolvedProperties?.status === "error") {
752
- return Promise.resolve(resolvedProperties);
753
- }
754
- return bridge.updatePage({
755
- pageId,
756
- properties: resolvedProperties?.properties,
757
- icon: input.icon,
758
- cover: input.cover,
759
- archived: input.archived,
760
- });
761
- }
762
- export function createPage(input) {
763
- return bridge.createPage(input);
764
- }
765
- export function getPage(pageId) {
766
- return bridge.getPage(pageId);
767
- }
768
- export function updatePage(input) {
769
- return bridge.updatePage(input);
770
- }
771
- export function postCustomBlockResize(height) {
772
- bridge.postResize(height);
773
- }
@@ -0,0 +1,22 @@
1
+ import type { CustomBlockManifest } from "../manifest";
2
+ import type { NotionDataSource, NotionDataSourceBindings } from "./dataSource";
3
+ import type { NotionPropertySchema } from "./propertySchema";
4
+ type ResolveDataSourcesArgs = {
5
+ manifest: CustomBlockManifest | null;
6
+ dataSourceBindings: NotionDataSourceBindings;
7
+ };
8
+ /**
9
+ * Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
10
+ *
11
+ * Combines the host-supplied bindings (collection pointers + schemas) with the
12
+ * manifest's declared data-source keys. When the manifest names a property
13
+ * that isn't in `propertyIdsByKey`, falls back to {@link findPropertyIdByManifestProperty}
14
+ * so renames in Notion still resolve as long as the schema name matches.
15
+ */
16
+ export declare function resolveDataSources(args: ResolveDataSourcesArgs): NotionDataSource[];
17
+ export declare function findPropertyIdByManifestProperty(propertySchemasById: Record<string, NotionPropertySchema>, manifestProperty: {
18
+ key: string;
19
+ type: NotionPropertySchema["type"];
20
+ }): string | undefined;
21
+ export {};
22
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../bridge/dataSources/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAE5D,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,CA+CpB;AAED,wBAAgB,gCAAgC,CAC/C,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,EACzD,gBAAgB,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAA;CAAE,GACnE,MAAM,GAAG,SAAS,CAoBpB"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
3
+ *
4
+ * Combines the host-supplied bindings (collection pointers + schemas) with the
5
+ * manifest's declared data-source keys. When the manifest names a property
6
+ * that isn't in `propertyIdsByKey`, falls back to {@link findPropertyIdByManifestProperty}
7
+ * so renames in Notion still resolve as long as the schema name matches.
8
+ */
9
+ 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
+ return Object.entries(args.manifest.dataSources).map(([key, manifestDataSource]) => {
20
+ const binding = args.dataSourceBindings[key];
21
+ const propertySchemasById = binding?.collectionSchema?.propertiesById ?? {};
22
+ const bindingPropertyIdsByKey = binding?.propertyIdsByKey ?? {};
23
+ const propertyIdsByKey = {};
24
+ const manifestProperties = Object.entries(manifestDataSource.properties ?? {});
25
+ for (const [propertyKey, manifestProperty] of manifestProperties) {
26
+ const propertyId = propertyKey in bindingPropertyIdsByKey
27
+ ? bindingPropertyIdsByKey[propertyKey]
28
+ : findPropertyIdByManifestProperty(propertySchemasById, {
29
+ key: propertyKey,
30
+ type: manifestProperty.type,
31
+ });
32
+ const propertySchema = propertyId === undefined ? undefined : propertySchemasById[propertyId];
33
+ propertyIdsByKey[propertyKey] =
34
+ propertySchema?.type === manifestProperty.type
35
+ ? propertyId
36
+ : undefined;
37
+ }
38
+ return {
39
+ key,
40
+ collectionPointer: binding?.collectionPointer,
41
+ collectionSchema: binding?.collectionSchema,
42
+ propertyIdsByKey,
43
+ propertySchemasById,
44
+ };
45
+ });
46
+ }
47
+ export function findPropertyIdByManifestProperty(propertySchemasById, manifestProperty) {
48
+ // `ManifestProperty.name` is display copy for setup UI. Binding fallback is based
49
+ // on the stable semantic key so copy changes don't affect property resolution.
50
+ const propertySchemaForKey = propertySchemasById[manifestProperty.key];
51
+ if (propertySchemaForKey?.type === manifestProperty.type) {
52
+ return manifestProperty.key;
53
+ }
54
+ const normalizedKey = normalizePropertyName(manifestProperty.key);
55
+ for (const [propertyId, propertySchema] of Object.entries(propertySchemasById)) {
56
+ if (propertySchema.type === manifestProperty.type &&
57
+ normalizePropertyName(propertySchema.name) === normalizedKey) {
58
+ return propertyId;
59
+ }
60
+ }
61
+ return undefined;
62
+ }
63
+ function normalizePropertyName(value) {
64
+ return value.trim().toLowerCase();
65
+ }
@@ -0,0 +1,36 @@
1
+ import type { NotionPagePropertyInputMap, NotionPagePropertyWriteMap } from "../pages/page";
2
+ import type { NotionDataSource } from "./dataSource";
3
+ export type PropertyWriteMapResolutionResult = {
4
+ status: "success";
5
+ properties: NotionPagePropertyWriteMap;
6
+ } | {
7
+ status: "error";
8
+ error: string;
9
+ };
10
+ /**
11
+ * Resolves a public SDK property write map into the ID-keyed bridge shape.
12
+ *
13
+ * Identifiers in `properties` may be raw property IDs or data-source property keys; the latter
14
+ * are looked up in the `dataSource`'s `propertyIdsByKey`. Each value is re-parsed through
15
+ * `notionPagePropertyValueSchema` with its `id` rewritten to the resolved property ID.
16
+ */
17
+ export declare function resolvePropertyWriteMapForDataSource(args: {
18
+ dataSource: NotionDataSource | undefined;
19
+ properties: NotionPagePropertyInputMap;
20
+ operationName: string;
21
+ }): PropertyWriteMapResolutionResult;
22
+ /**
23
+ * Treats matching data-source keys as aliases for property IDs; all other identifiers are raw IDs.
24
+ */
25
+ export declare function resolvePropertyIdentifierForDataSource(args: {
26
+ dataSource: NotionDataSource | undefined;
27
+ identifier: string;
28
+ operationName: string;
29
+ }): {
30
+ status: "success";
31
+ propertyId: string;
32
+ } | {
33
+ status: "error";
34
+ error: string;
35
+ };
36
+ //# sourceMappingURL=resolveProperty.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveProperty.d.ts","sourceRoot":"","sources":["../../../bridge/dataSources/resolveProperty.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,0BAA0B,EAC1B,0BAA0B,EAC1B,MAAM,eAAe,CAAA;AAEtB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,MAAM,MAAM,gCAAgC,GACzC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,0BAA0B,CAAA;CAAE,GAC7D;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;;;;;GAMG;AACH,wBAAgB,oCAAoC,CAAC,IAAI,EAAE;IAC1D,UAAU,EAAE,gBAAgB,GAAG,SAAS,CAAA;IACxC,UAAU,EAAE,0BAA0B,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;CACrB,GAAG,gCAAgC,CA+CnC;AAED;;GAEG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE;IAC5D,UAAU,EAAE,gBAAgB,GAAG,SAAS,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;CACrB,GACE;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAcpC"}
@@ -0,0 +1,67 @@
1
+ import * as v from "valibot";
2
+ import { notionPagePropertyValueSchema } from "../pages/page";
3
+ /**
4
+ * Resolves a public SDK property write map into the ID-keyed bridge shape.
5
+ *
6
+ * Identifiers in `properties` may be raw property IDs or data-source property keys; the latter
7
+ * are looked up in the `dataSource`'s `propertyIdsByKey`. Each value is re-parsed through
8
+ * `notionPagePropertyValueSchema` with its `id` rewritten to the resolved property ID.
9
+ */
10
+ export function resolvePropertyWriteMapForDataSource(args) {
11
+ const { dataSource, properties, operationName } = args;
12
+ const resolvedProperties = {};
13
+ for (const [identifier, value] of Object.entries(properties)) {
14
+ const propertyIdResult = resolvePropertyIdentifierForDataSource({
15
+ dataSource,
16
+ identifier,
17
+ operationName,
18
+ });
19
+ if (propertyIdResult.status === "error") {
20
+ return propertyIdResult;
21
+ }
22
+ const propertyId = propertyIdResult.propertyId;
23
+ if (value.id !== undefined &&
24
+ value.id !== identifier &&
25
+ value.id !== propertyId) {
26
+ return {
27
+ status: "error",
28
+ error: `Property ${identifier} resolved to ${propertyId} but value id was ${value.id}.`,
29
+ };
30
+ }
31
+ if (resolvedProperties[propertyId] !== undefined) {
32
+ return {
33
+ status: "error",
34
+ error: `Cannot set property ${propertyId} more than once.`,
35
+ };
36
+ }
37
+ const parsedValue = v.safeParse(notionPagePropertyValueSchema, {
38
+ ...value,
39
+ id: propertyId,
40
+ });
41
+ if (!parsedValue.success) {
42
+ return {
43
+ status: "error",
44
+ error: `Invalid value for property ${identifier}.`,
45
+ };
46
+ }
47
+ resolvedProperties[propertyId] = parsedValue.output;
48
+ }
49
+ return { status: "success", properties: resolvedProperties };
50
+ }
51
+ /**
52
+ * Treats matching data-source keys as aliases for property IDs; all other identifiers are raw IDs.
53
+ */
54
+ export function resolvePropertyIdentifierForDataSource(args) {
55
+ const { dataSource, identifier, operationName } = args;
56
+ if (dataSource !== undefined && identifier in dataSource.propertyIdsByKey) {
57
+ const propertyId = dataSource.propertyIdsByKey[identifier];
58
+ if (propertyId === undefined) {
59
+ return {
60
+ status: "error",
61
+ error: `${operationName} cannot resolve property key "${identifier}" because it is not bound to a Notion property.`,
62
+ };
63
+ }
64
+ return { status: "success", propertyId };
65
+ }
66
+ return { status: "success", propertyId: identifier };
67
+ }