create-ncblock 0.0.1

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 (238) hide show
  1. package/README.md +49 -0
  2. package/bin/cli.js +33 -0
  3. package/package.json +25 -0
  4. package/scripts/init.ts +527 -0
  5. package/scripts/scaffold-assets/AGENTS.md +65 -0
  6. package/scripts/utils/templates.ts +293 -0
  7. package/sdk-version.json +1 -0
  8. package/templates/debug/README.md +36 -0
  9. package/templates/debug/_gitignore +2 -0
  10. package/templates/debug/custom_blocks.json +9 -0
  11. package/templates/debug/dist/assets/index-Cet2SsjS.css +2 -0
  12. package/templates/debug/dist/assets/index-DAzv_fuh.js +9 -0
  13. package/templates/debug/dist/custom_blocks.json +9 -0
  14. package/templates/debug/dist/index.html +16 -0
  15. package/templates/debug/index.html +15 -0
  16. package/templates/debug/node_modules/.bin/browserslist +21 -0
  17. package/templates/debug/node_modules/.bin/esbuild +21 -0
  18. package/templates/debug/node_modules/.bin/jiti +21 -0
  19. package/templates/debug/node_modules/.bin/rollup +21 -0
  20. package/templates/debug/node_modules/.bin/tsc +21 -0
  21. package/templates/debug/node_modules/.bin/tsserver +21 -0
  22. package/templates/debug/node_modules/.bin/tsx +21 -0
  23. package/templates/debug/node_modules/.bin/vite +21 -0
  24. package/templates/debug/node_modules/.vite/deps/_metadata.json +50 -0
  25. package/templates/debug/node_modules/.vite/deps/package.json +3 -0
  26. package/templates/debug/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  27. package/templates/debug/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  28. package/templates/debug/node_modules/.vite/deps/react-dom.js +185 -0
  29. package/templates/debug/node_modules/.vite/deps/react-dom.js.map +1 -0
  30. package/templates/debug/node_modules/.vite/deps/react-dom_client.js +14384 -0
  31. package/templates/debug/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  32. package/templates/debug/node_modules/.vite/deps/react.js +2 -0
  33. package/templates/debug/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  34. package/templates/debug/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  35. package/templates/debug/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  36. package/templates/debug/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  37. package/templates/debug/node_modules/.vite/deps/valibot.js +6623 -0
  38. package/templates/debug/node_modules/.vite/deps/valibot.js.map +1 -0
  39. package/templates/debug/node_modules/.vite-temp/vite.config.ts.timestamp-1778623720803-0bcf523a67aa8.mjs +15 -0
  40. package/templates/debug/package.json +30 -0
  41. package/templates/debug/src/index.css +62 -0
  42. package/templates/debug/src/index.tsx +1963 -0
  43. package/templates/debug/tsconfig.json +17 -0
  44. package/templates/debug/vite.config.ts +8 -0
  45. package/templates/empty/README.md +10 -0
  46. package/templates/empty/_gitignore +2 -0
  47. package/templates/empty/custom_blocks.json +12 -0
  48. package/templates/empty/dist/assets/index-CodJADav.js +9 -0
  49. package/templates/empty/dist/custom_blocks.json +12 -0
  50. package/templates/empty/dist/index.html +15 -0
  51. package/templates/empty/index.html +15 -0
  52. package/templates/empty/node_modules/.bin/esbuild +21 -0
  53. package/templates/empty/node_modules/.bin/jiti +21 -0
  54. package/templates/empty/node_modules/.bin/tsc +21 -0
  55. package/templates/empty/node_modules/.bin/tsserver +21 -0
  56. package/templates/empty/node_modules/.bin/tsx +21 -0
  57. package/templates/empty/node_modules/.bin/vite +21 -0
  58. package/templates/empty/node_modules/.vite/deps/_metadata.json +50 -0
  59. package/templates/empty/node_modules/.vite/deps/package.json +3 -0
  60. package/templates/empty/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  61. package/templates/empty/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  62. package/templates/empty/node_modules/.vite/deps/react-dom.js +185 -0
  63. package/templates/empty/node_modules/.vite/deps/react-dom.js.map +1 -0
  64. package/templates/empty/node_modules/.vite/deps/react-dom_client.js +14384 -0
  65. package/templates/empty/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  66. package/templates/empty/node_modules/.vite/deps/react.js +2 -0
  67. package/templates/empty/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  68. package/templates/empty/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  69. package/templates/empty/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  70. package/templates/empty/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  71. package/templates/empty/node_modules/.vite/deps/valibot.js +6623 -0
  72. package/templates/empty/node_modules/.vite/deps/valibot.js.map +1 -0
  73. package/templates/empty/package.json +28 -0
  74. package/templates/empty/src/index.tsx +12 -0
  75. package/templates/empty/tsconfig.json +17 -0
  76. package/templates/empty/vite.config.ts +7 -0
  77. package/templates/gantt-chart/node_modules/.bin/tsc +21 -0
  78. package/templates/gantt-chart/node_modules/.bin/tsserver +21 -0
  79. package/templates/gantt-chart/node_modules/.bin/vite +21 -0
  80. package/templates/gantt-chart/node_modules/.vite/deps/_metadata.json +50 -0
  81. package/templates/gantt-chart/node_modules/.vite/deps/package.json +3 -0
  82. package/templates/gantt-chart/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  83. package/templates/gantt-chart/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  84. package/templates/gantt-chart/node_modules/.vite/deps/react-dom.js +185 -0
  85. package/templates/gantt-chart/node_modules/.vite/deps/react-dom.js.map +1 -0
  86. package/templates/gantt-chart/node_modules/.vite/deps/react-dom_client.js +14384 -0
  87. package/templates/gantt-chart/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  88. package/templates/gantt-chart/node_modules/.vite/deps/react.js +2 -0
  89. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  90. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  91. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  92. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  93. package/templates/gantt-chart/node_modules/.vite/deps/valibot.js +6623 -0
  94. package/templates/gantt-chart/node_modules/.vite/deps/valibot.js.map +1 -0
  95. package/templates/hello-world/node_modules/.bin/tsc +21 -0
  96. package/templates/hello-world/node_modules/.bin/tsserver +21 -0
  97. package/templates/hello-world/node_modules/.bin/vite +21 -0
  98. package/templates/hello-world/node_modules/.vite/deps/_metadata.json +50 -0
  99. package/templates/hello-world/node_modules/.vite/deps/package.json +3 -0
  100. package/templates/hello-world/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  101. package/templates/hello-world/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  102. package/templates/hello-world/node_modules/.vite/deps/react-dom.js +185 -0
  103. package/templates/hello-world/node_modules/.vite/deps/react-dom.js.map +1 -0
  104. package/templates/hello-world/node_modules/.vite/deps/react-dom_client.js +14384 -0
  105. package/templates/hello-world/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  106. package/templates/hello-world/node_modules/.vite/deps/react.js +2 -0
  107. package/templates/hello-world/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  108. package/templates/hello-world/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  109. package/templates/hello-world/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  110. package/templates/hello-world/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  111. package/templates/hello-world/node_modules/.vite/deps/valibot.js +6623 -0
  112. package/templates/hello-world/node_modules/.vite/deps/valibot.js.map +1 -0
  113. package/templates/interactive-resize/node_modules/.bin/tsc +21 -0
  114. package/templates/interactive-resize/node_modules/.bin/tsserver +21 -0
  115. package/templates/interactive-resize/node_modules/.bin/vite +21 -0
  116. package/templates/interactive-resize/node_modules/.vite/deps/_metadata.json +50 -0
  117. package/templates/interactive-resize/node_modules/.vite/deps/package.json +3 -0
  118. package/templates/interactive-resize/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  119. package/templates/interactive-resize/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  120. package/templates/interactive-resize/node_modules/.vite/deps/react-dom.js +185 -0
  121. package/templates/interactive-resize/node_modules/.vite/deps/react-dom.js.map +1 -0
  122. package/templates/interactive-resize/node_modules/.vite/deps/react-dom_client.js +14384 -0
  123. package/templates/interactive-resize/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  124. package/templates/interactive-resize/node_modules/.vite/deps/react.js +2 -0
  125. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  126. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  127. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  128. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  129. package/templates/interactive-resize/node_modules/.vite/deps/valibot.js +6623 -0
  130. package/templates/interactive-resize/node_modules/.vite/deps/valibot.js.map +1 -0
  131. package/templates/org-chart/node_modules/.bin/tsc +21 -0
  132. package/templates/org-chart/node_modules/.bin/tsserver +21 -0
  133. package/templates/org-chart/node_modules/.bin/vite +21 -0
  134. package/templates/org-chart/node_modules/.vite/deps/_metadata.json +50 -0
  135. package/templates/org-chart/node_modules/.vite/deps/package.json +3 -0
  136. package/templates/org-chart/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  137. package/templates/org-chart/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  138. package/templates/org-chart/node_modules/.vite/deps/react-dom.js +185 -0
  139. package/templates/org-chart/node_modules/.vite/deps/react-dom.js.map +1 -0
  140. package/templates/org-chart/node_modules/.vite/deps/react-dom_client.js +14384 -0
  141. package/templates/org-chart/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  142. package/templates/org-chart/node_modules/.vite/deps/react.js +2 -0
  143. package/templates/org-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  144. package/templates/org-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  145. package/templates/org-chart/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  146. package/templates/org-chart/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  147. package/templates/org-chart/node_modules/.vite/deps/valibot.js +6623 -0
  148. package/templates/org-chart/node_modules/.vite/deps/valibot.js.map +1 -0
  149. package/templates/radar-chart/README.md +55 -0
  150. package/templates/radar-chart/_gitignore +2 -0
  151. package/templates/radar-chart/custom_blocks.json +34 -0
  152. package/templates/radar-chart/dist/assets/index-DOf05oXg.css +2 -0
  153. package/templates/radar-chart/dist/assets/index-DWpNd1qt.js +9 -0
  154. package/templates/radar-chart/dist/custom_blocks.json +34 -0
  155. package/templates/radar-chart/dist/index.html +16 -0
  156. package/templates/radar-chart/index.html +15 -0
  157. package/templates/radar-chart/node_modules/.bin/esbuild +21 -0
  158. package/templates/radar-chart/node_modules/.bin/jiti +21 -0
  159. package/templates/radar-chart/node_modules/.bin/tsc +21 -0
  160. package/templates/radar-chart/node_modules/.bin/tsserver +21 -0
  161. package/templates/radar-chart/node_modules/.bin/tsx +21 -0
  162. package/templates/radar-chart/node_modules/.bin/vite +21 -0
  163. package/templates/radar-chart/node_modules/.vite/deps/_metadata.json +50 -0
  164. package/templates/radar-chart/node_modules/.vite/deps/package.json +3 -0
  165. package/templates/radar-chart/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  166. package/templates/radar-chart/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  167. package/templates/radar-chart/node_modules/.vite/deps/react-dom.js +185 -0
  168. package/templates/radar-chart/node_modules/.vite/deps/react-dom.js.map +1 -0
  169. package/templates/radar-chart/node_modules/.vite/deps/react-dom_client.js +14384 -0
  170. package/templates/radar-chart/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  171. package/templates/radar-chart/node_modules/.vite/deps/react.js +2 -0
  172. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  173. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  174. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  175. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  176. package/templates/radar-chart/node_modules/.vite/deps/valibot.js +6623 -0
  177. package/templates/radar-chart/node_modules/.vite/deps/valibot.js.map +1 -0
  178. package/templates/radar-chart/package.json +30 -0
  179. package/templates/radar-chart/src/index.css +44 -0
  180. package/templates/radar-chart/src/index.tsx +531 -0
  181. package/templates/radar-chart/tsconfig.json +17 -0
  182. package/templates/radar-chart/vite.config.ts +8 -0
  183. package/templates/table-view/README.md +43 -0
  184. package/templates/table-view/_gitignore +2 -0
  185. package/templates/table-view/custom_blocks.json +9 -0
  186. package/templates/table-view/dist/assets/index-Bd8u_e4X.js +12 -0
  187. package/templates/table-view/dist/assets/index-BkZn3aQZ.css +1 -0
  188. package/templates/table-view/dist/custom_blocks.json +9 -0
  189. package/templates/table-view/dist/index.html +16 -0
  190. package/templates/table-view/index.html +15 -0
  191. package/templates/table-view/node_modules/.bin/esbuild +21 -0
  192. package/templates/table-view/node_modules/.bin/jiti +21 -0
  193. package/templates/table-view/node_modules/.bin/rollup +21 -0
  194. package/templates/table-view/node_modules/.bin/tsc +21 -0
  195. package/templates/table-view/node_modules/.bin/tsserver +21 -0
  196. package/templates/table-view/node_modules/.bin/tsx +21 -0
  197. package/templates/table-view/node_modules/.bin/vite +21 -0
  198. package/templates/table-view/node_modules/.vite/deps/@tanstack_react-table.js +2809 -0
  199. package/templates/table-view/node_modules/.vite/deps/@tanstack_react-table.js.map +1 -0
  200. package/templates/table-view/node_modules/.vite/deps/_metadata.json +56 -0
  201. package/templates/table-view/node_modules/.vite/deps/package.json +3 -0
  202. package/templates/table-view/node_modules/.vite/deps/react-D5jdVkJj.js +790 -0
  203. package/templates/table-view/node_modules/.vite/deps/react-D5jdVkJj.js.map +1 -0
  204. package/templates/table-view/node_modules/.vite/deps/react-dom.js +185 -0
  205. package/templates/table-view/node_modules/.vite/deps/react-dom.js.map +1 -0
  206. package/templates/table-view/node_modules/.vite/deps/react-dom_client.js +14384 -0
  207. package/templates/table-view/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  208. package/templates/table-view/node_modules/.vite/deps/react.js +2 -0
  209. package/templates/table-view/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  210. package/templates/table-view/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  211. package/templates/table-view/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  212. package/templates/table-view/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  213. package/templates/table-view/node_modules/.vite/deps/valibot.js +6623 -0
  214. package/templates/table-view/node_modules/.vite/deps/valibot.js.map +1 -0
  215. package/templates/table-view/package.json +31 -0
  216. package/templates/table-view/src/index.css +256 -0
  217. package/templates/table-view/src/index.tsx +1814 -0
  218. package/templates/table-view/src/table-model.ts +663 -0
  219. package/templates/table-view/tsconfig.json +17 -0
  220. package/templates/table-view/vite.config.ts +8 -0
  221. package/templates/us-heatmap/node_modules/.bin/tsc +21 -0
  222. package/templates/us-heatmap/node_modules/.bin/tsserver +21 -0
  223. package/templates/us-heatmap/node_modules/.bin/vite +21 -0
  224. package/templates/us-heatmap/node_modules/.vite/deps/_metadata.json +50 -0
  225. package/templates/us-heatmap/node_modules/.vite/deps/package.json +3 -0
  226. package/templates/us-heatmap/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  227. package/templates/us-heatmap/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  228. package/templates/us-heatmap/node_modules/.vite/deps/react-dom.js +185 -0
  229. package/templates/us-heatmap/node_modules/.vite/deps/react-dom.js.map +1 -0
  230. package/templates/us-heatmap/node_modules/.vite/deps/react-dom_client.js +14384 -0
  231. package/templates/us-heatmap/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  232. package/templates/us-heatmap/node_modules/.vite/deps/react.js +2 -0
  233. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  234. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  235. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  236. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  237. package/templates/us-heatmap/node_modules/.vite/deps/valibot.js +6623 -0
  238. package/templates/us-heatmap/node_modules/.vite/deps/valibot.js.map +1 -0
@@ -0,0 +1,663 @@
1
+ import type {
2
+ NotionDataSourcePage,
3
+ NotionDateReminder,
4
+ NotionDateTimeReminder,
5
+ NotionDateValue,
6
+ NotionNoReminder,
7
+ NotionPageId,
8
+ NotionPropertySchema,
9
+ NotionRecordPointer,
10
+ } from "ncblock"
11
+
12
+ export type TableRow = NotionDataSourcePage
13
+
14
+ export type ColumnKind =
15
+ | "id"
16
+ | "text"
17
+ | "number"
18
+ | "boolean"
19
+ | "date"
20
+ | "list"
21
+ | "relation"
22
+ | "empty"
23
+
24
+ export type ColumnConfig = {
25
+ key: string
26
+ label: string
27
+ kind: ColumnKind
28
+ }
29
+
30
+ export type ColumnKindSummary = {
31
+ kind: SupportedColumnKind
32
+ count: number
33
+ }
34
+
35
+ type LoadedRelationEntry = NotionRecordPointer & {
36
+ title?: string | string[]
37
+ name?: string | string[]
38
+ label?: string | string[]
39
+ pointer?: unknown
40
+ page?: unknown
41
+ record?: unknown
42
+ }
43
+
44
+ const DATE_LABEL_FORMATTER = new Intl.DateTimeFormat("en-US", {
45
+ month: "short",
46
+ day: "numeric",
47
+ year: "numeric",
48
+ timeZone: "UTC",
49
+ })
50
+
51
+ const NUMBER_FORMATTER = new Intl.NumberFormat("en-US", {
52
+ maximumFractionDigits: 2,
53
+ })
54
+
55
+ const PREFERRED_PRIMARY_KEYS = ["title", "name", "task", "label"]
56
+ const UUID_LIKE_PATTERN = /^[0-9a-f-]{8,}$/i
57
+
58
+ export const SUPPORTED_COLUMN_KINDS = [
59
+ "text",
60
+ "number",
61
+ "boolean",
62
+ "date",
63
+ "list",
64
+ "relation",
65
+ ] as const
66
+
67
+ export type SupportedColumnKind = (typeof SUPPORTED_COLUMN_KINDS)[number]
68
+
69
+ function relationEntries(
70
+ ...entries: LoadedRelationEntry[]
71
+ ): NotionRecordPointer[] {
72
+ return entries
73
+ }
74
+
75
+ function makeSampleRow(
76
+ id: string,
77
+ properties: TableRow["propertiesByKey"],
78
+ ): TableRow {
79
+ return {
80
+ id: id as NotionPageId,
81
+ propertiesById: properties,
82
+ propertiesByKey: properties,
83
+ update: async () => ({
84
+ status: "error",
85
+ error: "Sample rows are read-only",
86
+ }),
87
+ }
88
+ }
89
+
90
+ export const SAMPLE_ITEMS: TableRow[] = [
91
+ makeSampleRow("task-001", {
92
+ title: "Project kickoff",
93
+ status: "In progress",
94
+ priority: "High",
95
+ progress: 32,
96
+ done: false,
97
+ timeline: {
98
+ type: "datetime",
99
+ start_date: "2026-04-14",
100
+ start_time: "09:00",
101
+ time_zone: "America/Los_Angeles",
102
+ },
103
+ tags: ["Planning", "Launch"],
104
+ url: "https://www.notion.so",
105
+ email: "ops@example.com",
106
+ phone: "+1 555 0100",
107
+ owner: relationEntries({
108
+ id: "person-ava-chen",
109
+ table: "collection",
110
+ title: "Ava Chen",
111
+ }),
112
+ relatedTasks: relationEntries(
113
+ {
114
+ id: "task-002",
115
+ table: "collection",
116
+ title: "Design sprint",
117
+ },
118
+ {
119
+ id: "7787acb2-4c73-4c3f-9e84-6f7c9784f093",
120
+ table: "collection",
121
+ },
122
+ ),
123
+ }),
124
+ makeSampleRow("task-002", {
125
+ title: "Design sprint",
126
+ status: "Review",
127
+ priority: "Medium",
128
+ progress: 78,
129
+ done: false,
130
+ timeline: {
131
+ type: "daterange",
132
+ start_date: "2026-04-15",
133
+ end_date: "2026-04-18",
134
+ },
135
+ tags: ["Design", "Stakeholder"],
136
+ url: "https://figma.com/file/example",
137
+ email: "design@example.com",
138
+ phone: "+1 555 0101",
139
+ owner: relationEntries({
140
+ id: "person-nina-patel",
141
+ table: "collection",
142
+ name: "Nina Patel",
143
+ }),
144
+ relatedTasks: relationEntries({
145
+ id: "task-003",
146
+ table: "collection",
147
+ title: "Build handoff",
148
+ }),
149
+ }),
150
+ makeSampleRow("task-003", {
151
+ title: "Build handoff",
152
+ status: "Done",
153
+ priority: "Low",
154
+ progress: 100,
155
+ done: true,
156
+ timeline: {
157
+ type: "datetimerange",
158
+ start_date: "2026-04-18",
159
+ start_time: "13:00",
160
+ end_date: "2026-04-22",
161
+ end_time: "15:00",
162
+ time_zone: "America/New_York",
163
+ },
164
+ tags: ["Engineering", "Release"],
165
+ url: "https://github.com/makenotion/custom-sdk",
166
+ email: "eng@example.com",
167
+ phone: "+1 555 0102",
168
+ owner: relationEntries({
169
+ id: "person-marcus-lee",
170
+ table: "collection",
171
+ title: "Marcus Lee",
172
+ }),
173
+ relatedTasks: relationEntries({
174
+ id: "task-001",
175
+ table: "collection",
176
+ }),
177
+ }),
178
+ makeSampleRow("task-004", {
179
+ title: "Launch day",
180
+ status: "Scheduled",
181
+ priority: "High",
182
+ progress: 0,
183
+ done: false,
184
+ timeline: {
185
+ type: "date",
186
+ start_date: "2026-04-24",
187
+ },
188
+ tags: ["Launch"],
189
+ url: "https://status.example.com",
190
+ email: "support@example.com",
191
+ phone: "+1 555 0103",
192
+ owner: relationEntries({
193
+ id: "person-river-wu",
194
+ table: "collection",
195
+ title: "River Wu",
196
+ }),
197
+ relatedTasks: relationEntries({
198
+ id: "0d27b3b7-d111-4f2c-88b5-7cc6a7fd4f68",
199
+ table: "collection",
200
+ }),
201
+ }),
202
+ ]
203
+
204
+ function isRecord(value: unknown): value is Record<string, unknown> {
205
+ return value !== null && typeof value === "object" && !Array.isArray(value)
206
+ }
207
+
208
+ export function isDateValue(value: unknown): value is NotionDateValue {
209
+ if (!isRecord(value) || typeof value.type !== "string") {
210
+ return false
211
+ }
212
+
213
+ switch (value.type) {
214
+ case "date":
215
+ return typeof value.start_date === "string"
216
+ case "daterange":
217
+ return (
218
+ typeof value.start_date === "string" &&
219
+ typeof value.end_date === "string"
220
+ )
221
+ case "datetime":
222
+ return (
223
+ typeof value.start_date === "string" &&
224
+ typeof value.start_time === "string" &&
225
+ typeof value.time_zone === "string"
226
+ )
227
+ case "datetimerange":
228
+ return (
229
+ typeof value.start_date === "string" &&
230
+ typeof value.start_time === "string" &&
231
+ typeof value.end_date === "string" &&
232
+ typeof value.end_time === "string" &&
233
+ typeof value.time_zone === "string"
234
+ )
235
+ default:
236
+ return false
237
+ }
238
+ }
239
+
240
+ export function isStringArray(value: unknown): value is string[] {
241
+ return Array.isArray(value) && value.every(entry => typeof entry === "string")
242
+ }
243
+
244
+ export function extractRecordId(value: unknown): string | undefined {
245
+ if (typeof value === "string" && value.length > 0) {
246
+ return value
247
+ }
248
+ if (!isRecord(value)) {
249
+ return undefined
250
+ }
251
+ if (typeof value.id === "string" && value.id.length > 0) {
252
+ return value.id
253
+ }
254
+ if (typeof value.row_id === "string" && value.row_id.length > 0) {
255
+ return value.row_id
256
+ }
257
+ if (typeof value.page_id === "string" && value.page_id.length > 0) {
258
+ return value.page_id
259
+ }
260
+ if (isRecord(value.pointer)) {
261
+ return extractRecordId(value.pointer)
262
+ }
263
+ return undefined
264
+ }
265
+
266
+ function coerceInlineText(value: unknown): string | undefined {
267
+ if (typeof value === "string") {
268
+ return value.trim().length > 0 ? value : undefined
269
+ }
270
+ if (isStringArray(value)) {
271
+ const joined = value
272
+ .map(part => part.trim())
273
+ .filter(Boolean)
274
+ .join("")
275
+ return joined.length > 0 ? joined : undefined
276
+ }
277
+ return undefined
278
+ }
279
+
280
+ function extractRelationTitle(value: unknown, depth = 0): string | undefined {
281
+ if (!isRecord(value) || depth > 2) {
282
+ return undefined
283
+ }
284
+
285
+ for (const key of ["title", "name", "label"]) {
286
+ const title = coerceInlineText(value[key])
287
+ if (title) {
288
+ return title
289
+ }
290
+ }
291
+
292
+ for (const key of ["pointer", "page", "record"]) {
293
+ const nested = extractRelationTitle(value[key], depth + 1)
294
+ if (nested) {
295
+ return nested
296
+ }
297
+ }
298
+
299
+ return undefined
300
+ }
301
+
302
+ export function isRelationArray(value: unknown): value is Array<unknown> {
303
+ if (!Array.isArray(value) || value.length === 0) {
304
+ return false
305
+ }
306
+ return value.every(entry => {
307
+ if (typeof entry === "string") {
308
+ return UUID_LIKE_PATTERN.test(entry)
309
+ }
310
+ return extractRecordId(entry) !== undefined
311
+ })
312
+ }
313
+
314
+ function parseIsoDateToDayStartMs(value: string): number {
315
+ const [year, month, day] = value.split("-").map(Number)
316
+ return Date.UTC(year, month - 1, day)
317
+ }
318
+
319
+ function parseTimeToMinutes(value: string): number {
320
+ const [hours, minutes] = value.split(":").map(Number)
321
+ return hours * 60 + minutes
322
+ }
323
+
324
+ function formatIsoDate(value: string): string {
325
+ return DATE_LABEL_FORMATTER.format(new Date(parseIsoDateToDayStartMs(value)))
326
+ }
327
+
328
+ function formatReminder(
329
+ reminder:
330
+ | NotionDateReminder
331
+ | NotionDateTimeReminder
332
+ | NotionNoReminder
333
+ | undefined,
334
+ ): string | undefined {
335
+ if (!reminder) {
336
+ return undefined
337
+ }
338
+ if (reminder.unit === "none") {
339
+ return "No reminder"
340
+ }
341
+ if ("time" in reminder) {
342
+ const unitLabel = reminder.value === 1 ? reminder.unit : `${reminder.unit}s`
343
+ return `${reminder.value} ${unitLabel} before at ${reminder.time}`
344
+ }
345
+ const unitLabel = reminder.value === 1 ? reminder.unit : `${reminder.unit}s`
346
+ return `${reminder.value} ${unitLabel} before`
347
+ }
348
+
349
+ function getDateSortValue(value: NotionDateValue): number {
350
+ switch (value.type) {
351
+ case "date":
352
+ return parseIsoDateToDayStartMs(value.start_date)
353
+ case "daterange":
354
+ return parseIsoDateToDayStartMs(value.start_date)
355
+ case "datetime":
356
+ return (
357
+ parseIsoDateToDayStartMs(value.start_date) +
358
+ parseTimeToMinutes(value.start_time) * 60 * 1000
359
+ )
360
+ case "datetimerange":
361
+ return (
362
+ parseIsoDateToDayStartMs(value.start_date) +
363
+ parseTimeToMinutes(value.start_time) * 60 * 1000
364
+ )
365
+ }
366
+ }
367
+
368
+ export function formatNumberValue(value: number): string {
369
+ return NUMBER_FORMATTER.format(value)
370
+ }
371
+
372
+ export function formatDateValue(value: NotionDateValue): {
373
+ primary: string
374
+ secondary?: string
375
+ } {
376
+ const reminder = formatReminder(value.reminder)
377
+ switch (value.type) {
378
+ case "date":
379
+ return {
380
+ primary: formatIsoDate(value.start_date),
381
+ secondary: reminder,
382
+ }
383
+ case "daterange":
384
+ return {
385
+ primary: `${formatIsoDate(value.start_date)} -> ${formatIsoDate(value.end_date)}`,
386
+ secondary: reminder,
387
+ }
388
+ case "datetime":
389
+ return {
390
+ primary: `${formatIsoDate(value.start_date)} ${value.start_time}`,
391
+ secondary: [value.time_zone, reminder].filter(Boolean).join(" · "),
392
+ }
393
+ case "datetimerange":
394
+ return {
395
+ primary: `${formatIsoDate(value.start_date)} ${value.start_time} -> ${formatIsoDate(value.end_date)} ${value.end_time}`,
396
+ secondary: [value.time_zone, reminder].filter(Boolean).join(" · "),
397
+ }
398
+ }
399
+ }
400
+
401
+ export function getRelationDisplay(entry: unknown): {
402
+ id?: string
403
+ label: string
404
+ } {
405
+ const id = extractRecordId(entry)
406
+ const label = extractRelationTitle(entry) ?? id ?? "Unknown relation"
407
+ return { id, label }
408
+ }
409
+
410
+ export function getSearchText(value: unknown): string {
411
+ if (value === undefined || value === null) {
412
+ return ""
413
+ }
414
+ if (typeof value === "string" || typeof value === "number") {
415
+ return String(value)
416
+ }
417
+ if (typeof value === "boolean") {
418
+ return value ? "true checked yes" : "false unchecked no"
419
+ }
420
+ if (isDateValue(value)) {
421
+ const formatted = formatDateValue(value)
422
+ return [formatted.primary, formatted.secondary].filter(Boolean).join(" ")
423
+ }
424
+ if (isStringArray(value)) {
425
+ return value.join(" ")
426
+ }
427
+ if (isRelationArray(value)) {
428
+ return value
429
+ .map(entry => {
430
+ const relation = getRelationDisplay(entry)
431
+ return [relation.label, relation.id].filter(Boolean).join(" ")
432
+ })
433
+ .join(" ")
434
+ }
435
+ if (Array.isArray(value)) {
436
+ return value.map(entry => getSearchText(entry)).join(" ")
437
+ }
438
+ if (isRecord(value)) {
439
+ return Object.values(value)
440
+ .map(entry => getSearchText(entry))
441
+ .join(" ")
442
+ }
443
+ return String(value)
444
+ }
445
+
446
+ function getSortValue(value: unknown): number | string {
447
+ if (value === undefined || value === null) {
448
+ return ""
449
+ }
450
+ if (typeof value === "number") {
451
+ return value
452
+ }
453
+ if (typeof value === "boolean") {
454
+ return value ? 1 : 0
455
+ }
456
+ if (typeof value === "string") {
457
+ return value.toLowerCase()
458
+ }
459
+ if (isDateValue(value)) {
460
+ return getDateSortValue(value)
461
+ }
462
+ if (isStringArray(value)) {
463
+ return value.join(" ").toLowerCase()
464
+ }
465
+ if (isRelationArray(value)) {
466
+ return value
467
+ .map(entry => getRelationDisplay(entry).label.toLowerCase())
468
+ .join(" ")
469
+ }
470
+ return JSON.stringify(value)
471
+ }
472
+
473
+ export function compareValues(left: unknown, right: unknown): number {
474
+ const leftValue = getSortValue(left)
475
+ const rightValue = getSortValue(right)
476
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
477
+ return leftValue - rightValue
478
+ }
479
+ return String(leftValue).localeCompare(String(rightValue), undefined, {
480
+ numeric: true,
481
+ sensitivity: "base",
482
+ })
483
+ }
484
+
485
+ export function humanizeKey(key: string): string {
486
+ return key
487
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
488
+ .replace(/[_-]+/g, " ")
489
+ .replace(/\s+/g, " ")
490
+ .trim()
491
+ .replace(/^./, first => first.toUpperCase())
492
+ }
493
+
494
+ export function collectColumnKeys(
495
+ items: TableRow[],
496
+ propertySchemasById: Record<string, NotionPropertySchema | undefined> = {},
497
+ ): string[] {
498
+ const seen = new Set<string>()
499
+ const ordered: string[] = []
500
+
501
+ const register = (key: string) => {
502
+ if (key === "id" || seen.has(key)) {
503
+ return
504
+ }
505
+ seen.add(key)
506
+ ordered.push(key)
507
+ }
508
+
509
+ for (const key of Object.keys(propertySchemasById)) {
510
+ if (propertySchemasById[key] !== undefined) {
511
+ register(key)
512
+ }
513
+ }
514
+
515
+ for (const item of items) {
516
+ for (const key of Object.keys(item.propertiesById)) {
517
+ register(key)
518
+ }
519
+ }
520
+
521
+ const preferred = PREFERRED_PRIMARY_KEYS.filter(key => seen.has(key))
522
+ const remainder = ordered.filter(key => !preferred.includes(key))
523
+ return ["id", ...preferred, ...remainder]
524
+ }
525
+
526
+ export function getRowProperty(item: TableRow, key: string): unknown {
527
+ return item.propertiesById[key]
528
+ }
529
+
530
+ function columnKindFromPropertySchema(
531
+ schema: NotionPropertySchema | undefined,
532
+ ): ColumnKind | undefined {
533
+ switch (schema?.type) {
534
+ case "title":
535
+ case "rich_text":
536
+ case "url":
537
+ case "email":
538
+ case "phone_number":
539
+ case "select":
540
+ case "status":
541
+ return "text"
542
+ case "number":
543
+ return "number"
544
+ case "checkbox":
545
+ return "boolean"
546
+ case "date":
547
+ return "date"
548
+ case "multi_select":
549
+ return "list"
550
+ case "people":
551
+ case "relation":
552
+ return "relation"
553
+ default:
554
+ return undefined
555
+ }
556
+ }
557
+
558
+ export function inferColumnKind(
559
+ key: string,
560
+ items: TableRow[],
561
+ propertySchemasById: Record<string, NotionPropertySchema | undefined> = {},
562
+ ): ColumnKind {
563
+ if (key === "id") {
564
+ return "id"
565
+ }
566
+
567
+ const schemaKind = columnKindFromPropertySchema(propertySchemasById[key])
568
+ if (schemaKind !== undefined) {
569
+ return schemaKind
570
+ }
571
+
572
+ for (const item of items) {
573
+ const value = getRowProperty(item, key)
574
+ if (value === undefined) {
575
+ continue
576
+ }
577
+ if (isDateValue(value)) {
578
+ return "date"
579
+ }
580
+ if (isRelationArray(value)) {
581
+ return "relation"
582
+ }
583
+ if (isStringArray(value)) {
584
+ return "list"
585
+ }
586
+ if (typeof value === "boolean") {
587
+ return "boolean"
588
+ }
589
+ if (typeof value === "number") {
590
+ return "number"
591
+ }
592
+ if (typeof value === "string") {
593
+ return "text"
594
+ }
595
+ }
596
+
597
+ return "empty"
598
+ }
599
+
600
+ export function getColumnWidth(kind: ColumnKind): number {
601
+ switch (kind) {
602
+ case "id":
603
+ return 180
604
+ case "number":
605
+ return 132
606
+ case "boolean":
607
+ return 148
608
+ case "date":
609
+ return 260
610
+ case "list":
611
+ return 280
612
+ case "relation":
613
+ return 320
614
+ case "empty":
615
+ return 180
616
+ case "text":
617
+ default:
618
+ return 240
619
+ }
620
+ }
621
+
622
+ export function getColumnConfigs(
623
+ items: TableRow[],
624
+ propertySchemasById: Record<string, NotionPropertySchema | undefined> = {},
625
+ ): ColumnConfig[] {
626
+ return collectColumnKeys(items, propertySchemasById).map(key => ({
627
+ key,
628
+ label: key === "id" ? "ID" : (propertySchemasById[key]?.name ?? key),
629
+ kind: inferColumnKind(key, items, propertySchemasById),
630
+ }))
631
+ }
632
+
633
+ export function getPrimaryColumnId(columnConfigs: ColumnConfig[]): string {
634
+ return (
635
+ columnConfigs.find(config => PREFERRED_PRIMARY_KEYS.includes(config.key))
636
+ ?.key ??
637
+ columnConfigs[0]?.key ??
638
+ "id"
639
+ )
640
+ }
641
+
642
+ export function summarizeColumnKinds(
643
+ columnConfigs: ColumnConfig[],
644
+ ): ColumnKindSummary[] {
645
+ const counts = new Map<SupportedColumnKind, number>()
646
+
647
+ for (const config of columnConfigs) {
648
+ if (
649
+ config.kind === "id" ||
650
+ config.kind === "empty" ||
651
+ !SUPPORTED_COLUMN_KINDS.includes(config.kind as SupportedColumnKind)
652
+ ) {
653
+ continue
654
+ }
655
+ const kind = config.kind as SupportedColumnKind
656
+ counts.set(kind, (counts.get(kind) ?? 0) + 1)
657
+ }
658
+
659
+ return SUPPORTED_COLUMN_KINDS.map(kind => ({
660
+ kind,
661
+ count: counts.get(kind) ?? 0,
662
+ })).filter(entry => entry.count > 0)
663
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noUnusedLocals": true,
8
+ "noUnusedParameters": true,
9
+ "types": ["vite/client"],
10
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
11
+ "skipLibCheck": true,
12
+ "esModuleInterop": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx"
15
+ },
16
+ "include": ["src", "vite.config.ts"]
17
+ }
@@ -0,0 +1,8 @@
1
+ import tailwindcss from "@tailwindcss/vite"
2
+ import react from "@vitejs/plugin-react"
3
+ import { notionCustomBlock } from "ncblock/vite"
4
+ import { defineConfig } from "vite"
5
+
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss(), notionCustomBlock()],
8
+ })
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/nsarkar/work/custom/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/bin/node_modules:/Users/nsarkar/work/custom/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/node_modules:/Users/nsarkar/work/custom/node_modules/.pnpm/typescript@6.0.3/node_modules:/Users/nsarkar/work/custom/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/nsarkar/work/custom/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/bin/node_modules:/Users/nsarkar/work/custom/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/node_modules:/Users/nsarkar/work/custom/node_modules/.pnpm/typescript@6.0.3/node_modules:/Users/nsarkar/work/custom/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi