@notionhq/workers 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/alpha/builder.d.ts +149 -0
  2. package/dist/alpha/builder.d.ts.map +1 -0
  3. package/dist/alpha/builder.js +260 -0
  4. package/dist/alpha/builder.test.d.ts +2 -0
  5. package/dist/alpha/builder.test.d.ts.map +1 -0
  6. package/dist/alpha/cli/build.d.ts +13 -0
  7. package/dist/alpha/cli/build.d.ts.map +1 -0
  8. package/dist/alpha/cli/build.js +36 -0
  9. package/dist/alpha/cli/build.test.d.ts +2 -0
  10. package/dist/alpha/cli/build.test.d.ts.map +1 -0
  11. package/dist/alpha/cli/codegen.d.ts +10 -0
  12. package/dist/alpha/cli/codegen.d.ts.map +1 -0
  13. package/dist/alpha/cli/codegen.js +41 -0
  14. package/dist/alpha/cli/codegen.test.d.ts +2 -0
  15. package/dist/alpha/cli/codegen.test.d.ts.map +1 -0
  16. package/dist/alpha/cli/discover.d.ts +35 -0
  17. package/dist/alpha/cli/discover.d.ts.map +1 -0
  18. package/dist/alpha/cli/discover.js +39 -0
  19. package/dist/alpha/cli/discover.test.d.ts +2 -0
  20. package/dist/alpha/cli/discover.test.d.ts.map +1 -0
  21. package/dist/alpha/cli/emit-manifest.d.ts +33 -0
  22. package/dist/alpha/cli/emit-manifest.d.ts.map +1 -0
  23. package/dist/alpha/cli/emit-manifest.js +266 -0
  24. package/dist/alpha/cli/emit-manifest.test.d.ts +2 -0
  25. package/dist/alpha/cli/emit-manifest.test.d.ts.map +1 -0
  26. package/dist/alpha/cli/index.d.ts +3 -0
  27. package/dist/alpha/cli/index.d.ts.map +1 -0
  28. package/dist/alpha/cli/index.js +36 -0
  29. package/dist/alpha/context.d.ts +8 -0
  30. package/dist/alpha/context.d.ts.map +1 -0
  31. package/dist/alpha/context.js +33 -0
  32. package/dist/alpha/database.d.ts +90 -0
  33. package/dist/alpha/database.d.ts.map +1 -0
  34. package/dist/alpha/database.js +9 -0
  35. package/dist/alpha/error.d.ts +41 -0
  36. package/dist/alpha/error.d.ts.map +1 -0
  37. package/dist/alpha/error.js +25 -0
  38. package/dist/alpha/icon-names.d.ts +6 -0
  39. package/dist/alpha/icon-names.d.ts.map +1 -0
  40. package/dist/alpha/icon-names.js +0 -0
  41. package/dist/alpha/json-schema.d.ts +117 -0
  42. package/dist/alpha/json-schema.d.ts.map +1 -0
  43. package/dist/alpha/json-schema.js +0 -0
  44. package/dist/alpha/manifest.d.ts +44 -0
  45. package/dist/alpha/manifest.d.ts.map +1 -0
  46. package/dist/alpha/manifest.js +0 -0
  47. package/dist/alpha/output.d.ts +7 -0
  48. package/dist/alpha/output.d.ts.map +1 -0
  49. package/dist/alpha/output.js +8 -0
  50. package/dist/alpha/pacer.d.ts +74 -0
  51. package/dist/alpha/pacer.d.ts.map +1 -0
  52. package/dist/alpha/pacer.js +66 -0
  53. package/dist/alpha/pacer.test.d.ts +2 -0
  54. package/dist/alpha/pacer.test.d.ts.map +1 -0
  55. package/dist/alpha/schedule.d.ts +6 -0
  56. package/dist/alpha/schedule.d.ts.map +1 -0
  57. package/dist/alpha/schedule.js +43 -0
  58. package/dist/alpha/schedule.test.d.ts +2 -0
  59. package/dist/alpha/schedule.test.d.ts.map +1 -0
  60. package/dist/alpha/schema-builder.d.ts +79 -0
  61. package/dist/alpha/schema-builder.d.ts.map +1 -0
  62. package/dist/alpha/schema-builder.js +135 -0
  63. package/dist/alpha/schema.d.ts +185 -0
  64. package/dist/alpha/schema.d.ts.map +1 -0
  65. package/dist/alpha/schema.js +115 -0
  66. package/dist/alpha/sync.d.ts +233 -0
  67. package/dist/alpha/sync.d.ts.map +1 -0
  68. package/dist/alpha/sync.js +70 -0
  69. package/dist/alpha/sync.test.d.ts +2 -0
  70. package/dist/alpha/sync.test.d.ts.map +1 -0
  71. package/dist/alpha/tool.d.ts +132 -0
  72. package/dist/alpha/tool.d.ts.map +1 -0
  73. package/dist/alpha/tool.js +156 -0
  74. package/dist/alpha/tool.test.d.ts +2 -0
  75. package/dist/alpha/tool.test.d.ts.map +1 -0
  76. package/dist/alpha/triggers.d.ts +365 -0
  77. package/dist/alpha/triggers.d.ts.map +1 -0
  78. package/dist/alpha/triggers.generated.d.ts +246 -0
  79. package/dist/alpha/triggers.generated.d.ts.map +1 -0
  80. package/dist/alpha/triggers.generated.js +239 -0
  81. package/dist/alpha/triggers.js +0 -0
  82. package/dist/alpha/types.d.ts +228 -0
  83. package/dist/alpha/types.d.ts.map +1 -0
  84. package/dist/alpha/types.js +0 -0
  85. package/dist/alpha/workflow.d.ts +70 -0
  86. package/dist/alpha/workflow.d.ts.map +1 -0
  87. package/dist/alpha/workflow.js +39 -0
  88. package/dist/alpha/workflow.test.d.ts +2 -0
  89. package/dist/alpha/workflow.test.d.ts.map +1 -0
  90. package/dist/builder.d.ts.map +1 -1
  91. package/dist/builder.js +2 -6
  92. package/dist/capabilities/ai_connector.d.ts.map +1 -1
  93. package/dist/capabilities/automation.d.ts.map +1 -1
  94. package/dist/capabilities/context.d.ts.map +1 -1
  95. package/dist/capabilities/custom-block.d.ts +124 -0
  96. package/dist/capabilities/custom-block.d.ts.map +1 -0
  97. package/dist/capabilities/custom-block.js +22 -0
  98. package/dist/capabilities/oauth.d.ts.map +1 -1
  99. package/dist/capabilities/output.d.ts.map +1 -1
  100. package/dist/capabilities/output.js +2 -4
  101. package/dist/capabilities/stateful-capability.d.ts.map +1 -1
  102. package/dist/capabilities/stateful-capability.js +2 -6
  103. package/dist/capabilities/sync.d.ts.map +1 -1
  104. package/dist/capabilities/sync.js +2 -8
  105. package/dist/capabilities/tool.d.ts.map +1 -1
  106. package/dist/capabilities/tool.js +6 -20
  107. package/dist/capabilities/webhook.d.ts.map +1 -1
  108. package/dist/capabilities/workflow.d.ts +2 -2
  109. package/dist/capabilities/workflow.d.ts.map +1 -1
  110. package/dist/index.d.ts +3 -2
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +1 -7
  113. package/dist/json-schema.d.ts.map +1 -1
  114. package/dist/schema-builder.d.ts.map +1 -1
  115. package/dist/schema.d.ts.map +1 -1
  116. package/dist/types.d.ts.map +1 -1
  117. package/dist/worker.d.ts +41 -1
  118. package/dist/worker.d.ts.map +1 -1
  119. package/dist/worker.js +51 -4
  120. package/package.json +74 -14
  121. package/src/alpha/ajv-formats.d.ts +7 -0
  122. package/src/alpha/builder.test.ts +197 -0
  123. package/src/alpha/builder.ts +338 -0
  124. package/src/alpha/cli/build.test.ts +240 -0
  125. package/src/alpha/cli/build.ts +57 -0
  126. package/src/alpha/cli/codegen.test.ts +31 -0
  127. package/src/alpha/cli/codegen.ts +51 -0
  128. package/src/alpha/cli/discover.test.ts +43 -0
  129. package/src/alpha/cli/discover.ts +81 -0
  130. package/src/alpha/cli/emit-manifest.test.ts +45 -0
  131. package/src/alpha/cli/emit-manifest.ts +409 -0
  132. package/src/alpha/cli/index.ts +41 -0
  133. package/src/alpha/context.ts +61 -0
  134. package/src/alpha/database.ts +102 -0
  135. package/src/alpha/error.ts +49 -0
  136. package/src/alpha/icon-names.ts +890 -0
  137. package/src/alpha/json-schema.ts +180 -0
  138. package/src/alpha/manifest.ts +47 -0
  139. package/src/alpha/output.ts +8 -0
  140. package/src/alpha/pacer.test.ts +81 -0
  141. package/src/alpha/pacer.ts +181 -0
  142. package/src/alpha/schedule.test.ts +31 -0
  143. package/src/alpha/schedule.ts +53 -0
  144. package/src/alpha/schema-builder.ts +193 -0
  145. package/src/alpha/schema.ts +222 -0
  146. package/src/alpha/sync.test.ts +191 -0
  147. package/src/alpha/sync.ts +340 -0
  148. package/src/alpha/tool.test.ts +116 -0
  149. package/src/alpha/tool.ts +319 -0
  150. package/src/alpha/triggers.generated.ts +489 -0
  151. package/src/alpha/triggers.ts +500 -0
  152. package/src/alpha/types.ts +284 -0
  153. package/src/alpha/workflow.test.ts +84 -0
  154. package/src/alpha/workflow.ts +131 -0
  155. package/src/builder.test.ts +28 -60
  156. package/src/builder.ts +3 -10
  157. package/src/capabilities/ai_connector.test.ts +9 -32
  158. package/src/capabilities/ai_connector.ts +5 -17
  159. package/src/capabilities/automation.test.ts +3 -12
  160. package/src/capabilities/automation.ts +3 -11
  161. package/src/capabilities/context.ts +3 -6
  162. package/src/capabilities/custom-block.ts +181 -0
  163. package/src/capabilities/oauth.test.ts +1 -0
  164. package/src/capabilities/oauth.ts +1 -3
  165. package/src/capabilities/output.ts +1 -3
  166. package/src/capabilities/stateful-capability.test.ts +1 -0
  167. package/src/capabilities/stateful-capability.ts +2 -6
  168. package/src/capabilities/sync.test.ts +15 -45
  169. package/src/capabilities/sync.ts +10 -40
  170. package/src/capabilities/tool.test.ts +106 -146
  171. package/src/capabilities/tool.ts +17 -39
  172. package/src/capabilities/webhook.ts +3 -10
  173. package/src/capabilities/workflow.test.ts +10 -31
  174. package/src/capabilities/workflow.ts +5 -12
  175. package/src/index.ts +9 -12
  176. package/src/json-schema.test.ts +2 -4
  177. package/src/json-schema.ts +7 -6
  178. package/src/pacer.test.ts +1 -3
  179. package/src/pacer_internal.ts +1 -5
  180. package/src/schema-builder.test.ts +10 -35
  181. package/src/schema-builder.ts +5 -16
  182. package/src/schema.ts +2 -10
  183. package/src/types.ts +1 -7
  184. package/src/worker.test.ts +147 -0
  185. package/src/worker.ts +67 -35
  186. package/dist/block.d.ts +0 -321
  187. package/dist/block.d.ts.map +0 -1
  188. package/src/block.ts +0 -529
  189. /package/dist/{block.js → alpha/ajv-formats.d.js} +0 -0
@@ -0,0 +1,197 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { Builder } from "./builder.js";
4
+
5
+ /**
6
+ * Helper to extract the DateValue from a date token.
7
+ * Date tokens have the shape [["\u2023", [["d", dateValue]]]].
8
+ */
9
+ function extractDateValue(token: unknown): any {
10
+ const arr = token as any;
11
+ return arr[0][1][0][1];
12
+ }
13
+
14
+ describe("Builder.date", () => {
15
+ it("creates a date token from YYYY-MM-DD", () => {
16
+ expect(extractDateValue(Builder.date("2024-01-15"))).toEqual({
17
+ type: "date",
18
+ start_date: "2024-01-15",
19
+ });
20
+ });
21
+
22
+ it("rejects non-YYYY-MM-DD strings", () => {
23
+ expect(() => Builder.date("01-15-2024")).toThrow("Expected YYYY-MM-DD");
24
+ expect(() => Builder.date("2024/01/15")).toThrow("Expected YYYY-MM-DD");
25
+ expect(() => Builder.date("2024-1-5")).toThrow("Expected YYYY-MM-DD");
26
+ });
27
+
28
+ it("rejects invalid calendar dates", () => {
29
+ expect(() => Builder.date("2024-13-01")).toThrow();
30
+ // Note: "2024-02-30" is silently coerced by JS Date (rolls to March 1st),
31
+ // so the current validator does not catch it.
32
+ });
33
+ });
34
+
35
+ describe("Builder.dateTime", () => {
36
+ it("parses minimal ISO 8601 (YYYY-MM-DDTHH:mm)", () => {
37
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30"))).toEqual({
38
+ type: "datetime",
39
+ start_date: "2024-01-15",
40
+ start_time: "10:30",
41
+ });
42
+ });
43
+
44
+ it("parses ISO 8601 with seconds", () => {
45
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:45"))).toEqual({
46
+ type: "datetime",
47
+ start_date: "2024-01-15",
48
+ start_time: "10:30",
49
+ });
50
+ });
51
+
52
+ it("parses ISO 8601 with Z suffix", () => {
53
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00Z"))).toEqual({
54
+ type: "datetime",
55
+ start_date: "2024-01-15",
56
+ start_time: "10:30",
57
+ });
58
+ });
59
+
60
+ it("parses ISO 8601 with positive timezone offset", () => {
61
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00+05:30"))).toEqual({
62
+ type: "datetime",
63
+ start_date: "2024-01-15",
64
+ start_time: "10:30",
65
+ });
66
+ });
67
+
68
+ it("parses ISO 8601 with negative timezone offset", () => {
69
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00-08:00"))).toEqual({
70
+ type: "datetime",
71
+ start_date: "2024-01-15",
72
+ start_time: "10:30",
73
+ });
74
+ });
75
+
76
+ it("parses ISO 8601 with milliseconds", () => {
77
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00.000Z"))).toEqual({
78
+ type: "datetime",
79
+ start_date: "2024-01-15",
80
+ start_time: "10:30",
81
+ });
82
+ });
83
+
84
+ it("includes timeZone when provided", () => {
85
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30", "America/New_York"))).toEqual({
86
+ type: "datetime",
87
+ start_date: "2024-01-15",
88
+ start_time: "10:30",
89
+ time_zone: "America/New_York",
90
+ });
91
+ });
92
+
93
+ it("handles midnight", () => {
94
+ expect(extractDateValue(Builder.dateTime("2024-01-15T00:00"))).toEqual({
95
+ type: "datetime",
96
+ start_date: "2024-01-15",
97
+ start_time: "00:00",
98
+ });
99
+ });
100
+
101
+ it("handles end of day", () => {
102
+ expect(extractDateValue(Builder.dateTime("2024-01-15T23:59"))).toEqual({
103
+ type: "datetime",
104
+ start_date: "2024-01-15",
105
+ start_time: "23:59",
106
+ });
107
+ });
108
+
109
+ it("rejects date-only strings", () => {
110
+ expect(() => Builder.dateTime("2024-01-15")).toThrow("Invalid ISO 8601 datetime");
111
+ });
112
+
113
+ it("rejects plain text", () => {
114
+ expect(() => Builder.dateTime("not a date")).toThrow("Invalid ISO 8601 datetime");
115
+ });
116
+
117
+ it("rejects empty string", () => {
118
+ expect(() => Builder.dateTime("")).toThrow("Invalid ISO 8601 datetime");
119
+ });
120
+
121
+ it("rejects date with space instead of T", () => {
122
+ expect(() => Builder.dateTime("2024-01-15 10:30")).toThrow("Invalid ISO 8601 datetime");
123
+ });
124
+ });
125
+
126
+ describe("Builder.dateRange", () => {
127
+ it("creates a date range token", () => {
128
+ expect(extractDateValue(Builder.dateRange("2024-01-15", "2024-01-20"))).toEqual({
129
+ type: "daterange",
130
+ start_date: "2024-01-15",
131
+ end_date: "2024-01-20",
132
+ });
133
+ });
134
+
135
+ it("rejects invalid start date", () => {
136
+ expect(() => Builder.dateRange("bad", "2024-01-20")).toThrow("Expected YYYY-MM-DD");
137
+ });
138
+
139
+ it("rejects invalid end date", () => {
140
+ expect(() => Builder.dateRange("2024-01-15", "bad")).toThrow("Expected YYYY-MM-DD");
141
+ });
142
+ });
143
+
144
+ describe("Builder.dateTimeRange", () => {
145
+ it("creates a datetime range token from ISO strings", () => {
146
+ expect(extractDateValue(Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16T14:00"))).toEqual(
147
+ {
148
+ type: "datetimerange",
149
+ start_date: "2024-01-15",
150
+ start_time: "10:30",
151
+ end_date: "2024-01-16",
152
+ end_time: "14:00",
153
+ },
154
+ );
155
+ });
156
+
157
+ it("parses mixed ISO formats", () => {
158
+ expect(
159
+ extractDateValue(
160
+ Builder.dateTimeRange("2024-01-15T10:30:00Z", "2024-01-16T14:00:00.000+05:00"),
161
+ ),
162
+ ).toEqual({
163
+ type: "datetimerange",
164
+ start_date: "2024-01-15",
165
+ start_time: "10:30",
166
+ end_date: "2024-01-16",
167
+ end_time: "14:00",
168
+ });
169
+ });
170
+
171
+ it("includes timeZone when provided", () => {
172
+ expect(
173
+ extractDateValue(
174
+ Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16T14:00", "Europe/London"),
175
+ ),
176
+ ).toEqual({
177
+ type: "datetimerange",
178
+ start_date: "2024-01-15",
179
+ start_time: "10:30",
180
+ end_date: "2024-01-16",
181
+ end_time: "14:00",
182
+ time_zone: "Europe/London",
183
+ });
184
+ });
185
+
186
+ it("rejects invalid start datetime", () => {
187
+ expect(() => Builder.dateTimeRange("2024-01-15", "2024-01-16T14:00")).toThrow(
188
+ "Invalid ISO 8601 datetime",
189
+ );
190
+ });
191
+
192
+ it("rejects invalid end datetime", () => {
193
+ expect(() => Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16")).toThrow(
194
+ "Invalid ISO 8601 datetime",
195
+ );
196
+ });
197
+ });
@@ -0,0 +1,338 @@
1
+ import type { NoticonName } from "./icon-names.js";
2
+ import type {
3
+ Cover,
4
+ DateValue,
5
+ Icon,
6
+ NoticonColor,
7
+ PeopleValue,
8
+ PlaceValue,
9
+ RelationReference,
10
+ TextValue,
11
+ } from "./types.js";
12
+
13
+ /**
14
+ * Validates a date string is in YYYY-MM-DD format and represents a valid date.
15
+ */
16
+ function validateDateString(dateString: string): void {
17
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
18
+ throw new Error(`Invalid date format: ${dateString}. Expected YYYY-MM-DD format.`);
19
+ }
20
+
21
+ const date = new Date(dateString);
22
+ if (Number.isNaN(date.getTime())) {
23
+ throw new Error(`Invalid date: ${dateString}`);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Internal helper to create a date mention token from a DateValue object.
29
+ */
30
+ function createDateToken(dateValue: DateValue): TextValue {
31
+ return [["‣", [["d", dateValue]]]];
32
+ }
33
+
34
+ /**
35
+ * Parses an ISO 8601 datetime string into date and time components.
36
+ * Accepts formats like "2024-01-15T10:30", "2024-01-15T10:30:00Z",
37
+ * "2024-01-15T10:30:00.000+05:00", etc.
38
+ */
39
+ function parseISODateTime(isoString: string): { date: string; time: string } {
40
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(isoString)) {
41
+ throw new Error(
42
+ `Invalid ISO 8601 datetime: ${isoString}. Expected format like 2024-01-15T10:30 or 2024-01-15T10:30:00Z`,
43
+ );
44
+ }
45
+
46
+ const date = isoString.slice(0, 10);
47
+ const time = isoString.slice(11, 16);
48
+
49
+ if (Number.isNaN(new Date(isoString).getTime())) {
50
+ throw new Error(`Invalid datetime: ${isoString}`);
51
+ }
52
+
53
+ return { date, time };
54
+ }
55
+
56
+ /**
57
+ * Builders for property values in sync changes.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { Builder } from "@notionhq/workers/alpha/builder";
62
+ *
63
+ * const change = {
64
+ * type: "upsert",
65
+ * key: "task-1",
66
+ * properties: {
67
+ * "Task Name": Builder.title("Write docs"),
68
+ * Status: Builder.select("Open"),
69
+ * Due: Builder.date("2026-08-01"),
70
+ * Assignees: Builder.people("alice@example.com"),
71
+ * },
72
+ * };
73
+ * ```
74
+ */
75
+ export const Builder = {
76
+ /**
77
+ * Creates a rich text value.
78
+ */
79
+ richText(content: string): TextValue {
80
+ return [[content]];
81
+ },
82
+
83
+ /**
84
+ * Creates a URL value.
85
+ */
86
+ url(url: string): TextValue {
87
+ return [[url]];
88
+ },
89
+
90
+ /**
91
+ * Creates a title value.
92
+ */
93
+ title(content: string): TextValue {
94
+ return [[content]];
95
+ },
96
+
97
+ /**
98
+ * Creates a text value.
99
+ */
100
+ text(content: string): TextValue {
101
+ return [[content]];
102
+ },
103
+
104
+ /**
105
+ * Creates an email value.
106
+ */
107
+ email(email: string): TextValue {
108
+ return [[email]];
109
+ },
110
+
111
+ /**
112
+ * Creates a phone number value.
113
+ */
114
+ phoneNumber(phone: string): TextValue {
115
+ return [[phone]];
116
+ },
117
+
118
+ /**
119
+ * Creates a checkbox value.
120
+ */
121
+ checkbox(checked: boolean): TextValue {
122
+ return checked ? [["Yes"]] : [["No"]];
123
+ },
124
+
125
+ /**
126
+ * Creates a file URL value.
127
+ * @param fileUrl - The URL of the file
128
+ * @param fileName - Optional display name for the file (defaults to URL)
129
+ */
130
+ file(fileUrl: string, fileName?: string): TextValue {
131
+ return [[fileName ?? fileUrl, [["a", fileUrl]]]];
132
+ },
133
+
134
+ /**
135
+ * Creates a number value.
136
+ */
137
+ number(value: number): TextValue {
138
+ if (Number.isNaN(value)) {
139
+ return [];
140
+ }
141
+ return [[value.toString()]];
142
+ },
143
+
144
+ /**
145
+ * Creates a date value from a date string (YYYY-MM-DD).
146
+ */
147
+ date(dateString: string): TextValue {
148
+ validateDateString(dateString);
149
+
150
+ return createDateToken({
151
+ type: "date",
152
+ start_date: dateString,
153
+ });
154
+ },
155
+
156
+ /**
157
+ * Creates a datetime value from an ISO 8601 datetime string.
158
+ * @param isoString - An ISO 8601 datetime string (e.g., "2024-01-15T10:30", "2024-01-15T10:30:00Z")
159
+ * @param timeZone - Optional IANA timezone name (e.g., "America/New_York")
160
+ */
161
+ dateTime(isoString: string, timeZone?: string): TextValue {
162
+ const { date, time } = parseISODateTime(isoString);
163
+
164
+ const dateValue: DateValue = {
165
+ type: "datetime",
166
+ start_date: date,
167
+ start_time: time,
168
+ };
169
+
170
+ if (timeZone) {
171
+ dateValue.time_zone = timeZone;
172
+ }
173
+
174
+ return createDateToken(dateValue);
175
+ },
176
+
177
+ /**
178
+ * Creates a date range value from date strings.
179
+ */
180
+ dateRange(startDate: string, endDate: string): TextValue {
181
+ validateDateString(startDate);
182
+ validateDateString(endDate);
183
+
184
+ return createDateToken({
185
+ type: "daterange",
186
+ start_date: startDate,
187
+ end_date: endDate,
188
+ });
189
+ },
190
+
191
+ /**
192
+ * Creates a datetime range value from ISO 8601 datetime strings.
193
+ * @param startDateTime - An ISO 8601 datetime string for the range start
194
+ * @param endDateTime - An ISO 8601 datetime string for the range end
195
+ * @param timeZone - Optional IANA timezone name (e.g., "America/New_York")
196
+ */
197
+ dateTimeRange(startDateTime: string, endDateTime: string, timeZone?: string): TextValue {
198
+ const start = parseISODateTime(startDateTime);
199
+ const end = parseISODateTime(endDateTime);
200
+
201
+ const dateValue: DateValue = {
202
+ type: "datetimerange",
203
+ start_date: start.date,
204
+ start_time: start.time,
205
+ end_date: end.date,
206
+ end_time: end.time,
207
+ };
208
+
209
+ if (timeZone) {
210
+ dateValue.time_zone = timeZone;
211
+ }
212
+
213
+ return createDateToken(dateValue);
214
+ },
215
+
216
+ /**
217
+ * Creates a link with custom display text.
218
+ * @param displayText - The text to display
219
+ * @param url - The URL to link to
220
+ */
221
+ link(displayText: string, url: string): TextValue {
222
+ return [[displayText, [["a", url]]]];
223
+ },
224
+
225
+ /**
226
+ * Creates a select value from a single option.
227
+ */
228
+ select(value: string): TextValue {
229
+ return [[value]];
230
+ },
231
+
232
+ /**
233
+ * Creates a multi-select value from multiple options.
234
+ * @param values - Array of option names to select
235
+ */
236
+ multiSelect(...values: string[]): TextValue {
237
+ if (values.length === 0) {
238
+ return [];
239
+ }
240
+ return [[values.join(",")]];
241
+ },
242
+
243
+ /**
244
+ * Creates a status value from a status option name.
245
+ */
246
+ status(value: string): TextValue {
247
+ return [[value]];
248
+ },
249
+
250
+ /**
251
+ * Creates a people value from email addresses.
252
+ * @param emails - Array of email addresses for people to include
253
+ */
254
+ people(...emails: string[]): PeopleValue {
255
+ return emails.map((email) => ({ email }));
256
+ },
257
+
258
+ /**
259
+ * Creates a place value for a geographic location.
260
+ * @param value - The place value with lat/lon coordinates and optional name/address
261
+ */
262
+ place(value: PlaceValue): PlaceValue {
263
+ if (typeof value.lat !== "number" || typeof value.lon !== "number") {
264
+ throw new Error("Place value must have numeric lat and lon coordinates");
265
+ }
266
+ return value;
267
+ },
268
+
269
+ /**
270
+ * Creates a relation reference from a primary key of a related record.
271
+ * Use an array of relation references as the property value.
272
+ *
273
+ * @param primaryKey - The primary key of the related record
274
+ * @example
275
+ * ```typescript
276
+ * // Single relation
277
+ * { Project: [Builder.relation("project-123")] }
278
+ *
279
+ * // Multiple relations
280
+ * { Projects: [Builder.relation("project-123"), Builder.relation("project-456")] }
281
+ * ```
282
+ */
283
+ relation(primaryKey: string): RelationReference {
284
+ return { type: "primaryKey", value: primaryKey };
285
+ },
286
+
287
+ /**
288
+ * Creates an emoji icon.
289
+ * @param emoji - An emoji string (e.g., "🎯", "✨", "🚀")
290
+ */
291
+ emojiIcon(emoji: string): Icon {
292
+ return {
293
+ type: "emoji",
294
+ value: emoji,
295
+ };
296
+ },
297
+
298
+ /**
299
+ * Creates a Notion icon with a specific color.
300
+ * @param icon - The name of the Notion icon (e.g., "checkmark", "pizza", "rocket")
301
+ * @param color - The color variant (defaults to "gray")
302
+ */
303
+ notionIcon(icon: NoticonName, color: NoticonColor = "gray"): Icon {
304
+ return {
305
+ type: "notion",
306
+ icon,
307
+ color,
308
+ };
309
+ },
310
+
311
+ /**
312
+ * Creates an image icon from an external URL.
313
+ * @param url - The URL of the image (e.g., "https://example.com/icon.png")
314
+ */
315
+ imageIcon(url: string): Icon {
316
+ return {
317
+ type: "image",
318
+ url,
319
+ };
320
+ },
321
+
322
+ /**
323
+ * Creates an image cover from an external URL.
324
+ * @param url - The URL of the image (e.g., "https://example.com/cover.png")
325
+ * @param position - Vertical image position, from 0 (top) to 1 (bottom). Defaults to 0.5.
326
+ */
327
+ imageCover(url: string, position = 0.5): Cover {
328
+ if (!Number.isFinite(position) || position < 0 || position > 1) {
329
+ throw new Error(`Invalid cover position: ${position}. Expected a number between 0 and 1.`);
330
+ }
331
+
332
+ return {
333
+ type: "image",
334
+ url,
335
+ position,
336
+ };
337
+ },
338
+ };