@project-ajax/sdk 0.0.74 → 0.0.76

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.
@@ -1,7 +1,8 @@
1
+ import type { CapabilityContext } from "./context.js";
1
2
  /**
2
- * Context provided to automation execute functions
3
+ * Event provided to automation execute functions
3
4
  */
4
- export interface AutomationContext {
5
+ export interface AutomationEvent {
5
6
  /**
6
7
  * The ID of the page that triggered the automation (if applicable)
7
8
  */
@@ -54,10 +55,11 @@ export interface AutomationConfiguration {
54
55
  description: string;
55
56
  /**
56
57
  * The function that executes when the automation is triggered
57
- * @param context - Context about the automation trigger, including page data if applicable
58
+ * @param event - Event data about the automation trigger, including page data if applicable
59
+ * @param context - The capability execution context (Notion client, etc.)
58
60
  * @returns A promise that resolves when the automation completes
59
61
  */
60
- execute: (context: AutomationContext) => Promise<void> | void;
62
+ execute: (event: AutomationEvent, context: CapabilityContext) => Promise<void> | void;
61
63
  }
62
64
  /**
63
65
  * Result returned from automation execution
@@ -81,6 +83,6 @@ export declare function createAutomationCapability(key: string, config: Automati
81
83
  title: string;
82
84
  description: string;
83
85
  };
84
- handler(context: AutomationContext): Promise<void>;
86
+ handler(event: AutomationEvent): Promise<void>;
85
87
  };
86
88
  //# sourceMappingURL=automation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"automation.d.ts","sourceRoot":"","sources":["../../src/capabilities/automation.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9D;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAC5C,OAAO,0BAA0B,CACjC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,uBAAuB;;;;;;;qBASP,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;EAezD"}
1
+ {"version":3,"file":"automation.d.ts","sourceRoot":"","sources":["../../src/capabilities/automation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGtD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,OAAO,EAAE,CACR,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,iBAAiB,KACtB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAC5C,OAAO,0BAA0B,CACjC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,uBAAuB;;;;;;;mBAST,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;EAgBrD"}
@@ -1,4 +1,5 @@
1
1
  import { ExecutionError } from "../error.js";
2
+ import { createCapabilityContext } from "./context.js";
2
3
  function createAutomationCapability(key, config) {
3
4
  return {
4
5
  _tag: "automation",
@@ -7,9 +8,10 @@ function createAutomationCapability(key, config) {
7
8
  title: config.title,
8
9
  description: config.description
9
10
  },
10
- async handler(context) {
11
+ async handler(event) {
11
12
  try {
12
- await config.execute(context);
13
+ const capabilityContext = createCapabilityContext();
14
+ await config.execute(event, capabilityContext);
13
15
  process.stdout.write(
14
16
  `
15
17
  <output>${JSON.stringify({ status: "success" })}</output>
@@ -0,0 +1,7 @@
1
+ import { Client } from "@notionhq/client";
2
+ export type CapabilityContext = {
3
+ /** Notion API SDK client for this execution. */
4
+ notion: Client;
5
+ };
6
+ export declare function createCapabilityContext(): CapabilityContext;
7
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/capabilities/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG1C,MAAM,MAAM,iBAAiB,GAAG;IAC/B,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,uBAAuB,IAAI,iBAAiB,CAc3D"}
@@ -0,0 +1,15 @@
1
+ import { Client } from "@notionhq/client";
2
+ function createCapabilityContext() {
3
+ const options = {};
4
+ if (process.env.NOTION_API_BASE_URL) {
5
+ options.baseUrl = process.env.NOTION_API_BASE_URL;
6
+ }
7
+ if (process.env.NOTION_API_TOKEN) {
8
+ options.auth = process.env.NOTION_API_TOKEN;
9
+ }
10
+ const notion = new Client(options);
11
+ return { notion };
12
+ }
13
+ export {
14
+ createCapabilityContext
15
+ };
@@ -1,5 +1,7 @@
1
+ import { type PacerEntry } from "../pacer_internal.js";
1
2
  import type { PropertyConfiguration, PropertySchema, Schema } from "../schema.js";
2
3
  import type { Icon, PeopleValue, PlaceValue, RelationValue, Schedule, SyncSchedule, TextValue } from "../types.js";
4
+ import type { CapabilityContext } from "./context.js";
3
5
  /**
4
6
  * Maps a property configuration to its corresponding value type.
5
7
  */
@@ -69,7 +71,7 @@ export type SyncChange<PK extends string, S extends PropertySchema<PK>> = SyncCh
69
71
  /**
70
72
  * Result returned from the sync execute function.
71
73
  */
72
- export type SyncExecutionResult<PK extends string, Context = unknown> = {
74
+ export type SyncExecutionResult<PK extends string, State = unknown> = {
73
75
  /**
74
76
  * The batch of changes to apply in this execution.
75
77
  * Can include upserts (create/update) and deletes.
@@ -77,23 +79,23 @@ export type SyncExecutionResult<PK extends string, Context = unknown> = {
77
79
  changes: SyncChange<PK, PropertySchema<PK>>[];
78
80
  /**
79
81
  * Indicates whether there is more data to fetch.
80
- * - `true`: More data available, will trigger another execution with nextContext
82
+ * - `true`: More data available, will trigger another execution with nextState
81
83
  * - `false`: No more data to fetch, sync is complete
82
84
  */
83
85
  hasMore: boolean;
84
86
  /**
85
- * Optional context data to pass to the next execution.
87
+ * Optional state data to pass to the next execution.
86
88
  * Required if `hasMore` is `true`, ignored if `hasMore` is `false`.
87
89
  * This can be any type of data (cursor, page number, timestamp, etc.).
88
- * The same data will be provided in the context parameter of the next execution.
90
+ * The same data will be provided as `state` in the next execution.
89
91
  */
90
- nextContext?: Context;
92
+ nextState?: State;
91
93
  };
92
94
  /**
93
95
  * A configuration object that enables synchronization between a data
94
96
  * source and a third-party source.
95
97
  */
96
- export type SyncConfiguration<PK extends string, S extends Schema<PK>, Context = unknown> = {
98
+ export type SyncConfiguration<PK extends string, S extends Schema<PK>, State = unknown> = {
97
99
  /**
98
100
  * The property of the data source that maps to a "primary key" in the
99
101
  * third-party data. This is used to match existing pages to
@@ -117,45 +119,43 @@ export type SyncConfiguration<PK extends string, S extends Schema<PK>, Context =
117
119
  mode?: SyncMode;
118
120
  /**
119
121
  * How often the sync should run.
120
- * - "continuous": Run as frequently as the system allows (default)
122
+ * - "continuous": Run as frequently as the system allows
121
123
  * - Interval string: Run at specified intervals, e.g. "1h", "30m", "1d"
122
124
  *
123
125
  * Minimum interval: 1 minute ("1m")
124
126
  * Maximum interval: 7 days ("7d")
125
127
  *
126
- * @default "continuous"
128
+ * @default "30m"
127
129
  */
128
130
  schedule?: Schedule;
129
131
  /**
130
132
  * A function that fetches the data to sync from the third-party service.
131
133
  *
132
134
  * This function can return all data at once, or implement pagination by:
133
- * 1. Returning a batch of changes with `hasMore: true` and a `nextContext`
134
- * 2. The runtime will call execute again with that context data
135
+ * 1. Returning a batch of changes with `hasMore: true` and a `nextState`
136
+ * 2. The runtime will call execute again with that state
135
137
  * 3. Continue until `hasMore: false` is returned
136
138
  *
137
139
  * The runtime will handle diffing against the data source and creating,
138
140
  * updating, and deleting pages as necessary.
139
141
  *
140
- * @param context - Optional context data from previous execution (undefined on first call)
141
- * @returns A result containing changes, hasMore status, and optional nextContext
142
+ * @param state - User-defined state from the previous execution (undefined on first call)
143
+ * @param context - Runtime context, including Notion client
144
+ * @returns A result containing changes, hasMore status, and optional nextState
142
145
  */
143
- execute: (context?: Context) => Promise<SyncExecutionResult<PK, Context>>;
144
- };
145
- export type SyncHandlerResult<PK extends string, Context = unknown> = {
146
- primaryKeyProperty: PK;
147
- changes: SyncChange<PK, PropertySchema<PK>>[];
148
- hasMore: boolean;
149
- nextContext?: Context;
150
- mode?: SyncMode;
146
+ execute: (state: State | undefined, context: CapabilityContext) => Promise<SyncExecutionResult<PK, State>>;
151
147
  };
152
148
  export type SyncCapability = ReturnType<typeof createSyncCapability>;
153
149
  /**
154
- * Context object passed from the runtime to sync capability handlers.
150
+ * Runtime context object passed from the runtime to sync capability handlers.
155
151
  */
156
152
  type RuntimeContext<UserContext = unknown> = {
157
- /** The user-defined/-controlled context (cursor, pagination state, etc.) */
153
+ /** The user-defined/-controlled state (cursor, pagination state, etc.) */
154
+ state?: UserContext;
155
+ /** Legacy field for user-defined/-controlled state. */
158
156
  userContext?: UserContext;
157
+ /** Pacer state from the server for rate limiting. */
158
+ pacers?: Record<string, PacerEntry>;
159
159
  };
160
160
  /**
161
161
  * Creates a special handler for syncing third-party data to a collection.
@@ -177,6 +177,7 @@ export declare function createSyncCapability<PK extends string, S extends Schema
177
177
  changes: SyncChange<PK, PropertySchema<PK>>[];
178
178
  hasMore: boolean;
179
179
  nextUserContext: Context | undefined;
180
+ nextPacerState: Record<string, PacerEntry>;
180
181
  }>;
181
182
  };
182
183
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/capabilities/sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/capabilities/sync.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,9 @@
1
1
  import { ExecutionError, unreachable } from "../error.js";
2
+ import {
3
+ getPacerState,
4
+ setPacerState
5
+ } from "../pacer_internal.js";
6
+ import { createCapabilityContext } from "./context.js";
2
7
  function createSyncCapability(key, syncConfiguration) {
3
8
  return {
4
9
  _tag: "sync",
@@ -10,14 +15,21 @@ function createSyncCapability(key, syncConfiguration) {
10
15
  schedule: parseSchedule(syncConfiguration.schedule)
11
16
  },
12
17
  async handler(runtimeContext) {
13
- const userContext = runtimeContext?.userContext;
14
- const executionResult = await syncConfiguration.execute(userContext).catch((err) => {
18
+ const capabilityContext = createCapabilityContext();
19
+ const state = runtimeContext?.state ?? runtimeContext?.userContext;
20
+ const pacerState = {
21
+ pacers: runtimeContext?.pacers ?? {}
22
+ };
23
+ setPacerState(pacerState);
24
+ const executionResult = await syncConfiguration.execute(state, capabilityContext).catch((err) => {
15
25
  throw new ExecutionError(err);
16
26
  });
27
+ const updatedPacerState = getPacerState();
17
28
  const result = {
18
29
  changes: executionResult.changes,
19
30
  hasMore: executionResult.hasMore,
20
- nextUserContext: executionResult.nextContext
31
+ nextUserContext: executionResult.nextState,
32
+ nextPacerState: updatedPacerState.pacers
21
33
  };
22
34
  process.stdout.write(`
23
35
  <output>${JSON.stringify(result)}</output>
@@ -31,10 +43,14 @@ const MS_PER_HOUR = 60 * MS_PER_MINUTE;
31
43
  const MS_PER_DAY = 24 * MS_PER_HOUR;
32
44
  const MIN_INTERVAL_MS = MS_PER_MINUTE;
33
45
  const MAX_INTERVAL_MS = 7 * MS_PER_DAY;
46
+ const DEFAULT_INTERVAL_MS = 30 * MS_PER_MINUTE;
34
47
  function parseSchedule(schedule) {
35
- if (!schedule || schedule === "continuous") {
48
+ if (schedule === "continuous") {
36
49
  return { type: "continuous" };
37
50
  }
51
+ if (!schedule) {
52
+ return { type: "interval", intervalMs: DEFAULT_INTERVAL_MS };
53
+ }
38
54
  const match = schedule.match(/^(\d+)(m|h|d)$/);
39
55
  if (!match || !match[1] || !match[2]) {
40
56
  throw new Error(
@@ -1,11 +1,12 @@
1
1
  import { type JSONSchemaType } from "ajv";
2
2
  import type { JSONValue } from "../types.js";
3
+ import type { CapabilityContext } from "./context.js";
3
4
  export interface ToolConfiguration<I extends JSONValue, O extends JSONValue = JSONValue> {
4
5
  title: string;
5
6
  description: string;
6
7
  schema: JSONSchemaType<I>;
7
8
  outputSchema?: JSONSchemaType<O>;
8
- execute: (input: I) => O | Promise<O>;
9
+ execute: (input: I, context: CapabilityContext) => O | Promise<O>;
9
10
  }
10
11
  /**
11
12
  * An error returned when the input to a tool doesn't match the input schema.
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/capabilities/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,iBAAiB,CACjC,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,SAAS,GAAG,SAAS;IAE/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;IAK3B,MAAM;;;;CAMN;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;IAK3B,MAAM;;;;CAMN;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;IAK3B,MAAM;;;;CAMN;AAED,MAAM,MAAM,cAAc,CACzB,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,SAAS,GAAG,SAAS,IAC5B,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,SAAS,GAAG,SAAS,EAC9B,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC;;;;;;;;;mBAgBvB,SAAS,GAAG,OAAO,CACrC;QACA,IAAI,EAAE,SAAS,CAAC;QAChB,KAAK,EAAE,CAAC,CAAC;KACR,GACD;QACA,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EACF,qBAAqB,GACrB,sBAAsB,GACtB,kBAAkB,CAAC;KACrB,CACH;EA0DF"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/capabilities/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGtD,MAAM,WAAW,iBAAiB,CACjC,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,SAAS,GAAG,SAAS;IAE/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,iBAAiB,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAClE;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;IAK3B,MAAM;;;;CAMN;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;IAK3B,MAAM;;;;CAMN;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;IAK3B,MAAM;;;;CAMN;AAED,MAAM,MAAM,cAAc,CACzB,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,SAAS,GAAG,SAAS,IAC5B,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,SAAS,GAAG,SAAS,EAC9B,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC;;;;;;;;;mBAgBvB,SAAS,GAAG,OAAO,CACrC;QACA,IAAI,EAAE,SAAS,CAAC;QAChB,KAAK,EAAE,CAAC,CAAC;KACR,GACD;QACA,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EACF,qBAAqB,GACrB,sBAAsB,GACtB,kBAAkB,CAAC;KACrB,CACH;EA2DF"}
@@ -1,4 +1,5 @@
1
1
  import { Ajv } from "ajv";
2
+ import { createCapabilityContext } from "./context.js";
2
3
  class InvalidToolInputError extends Error {
3
4
  constructor(message) {
4
5
  super(message);
@@ -67,7 +68,8 @@ function createToolCapability(key, config) {
67
68
  return result;
68
69
  }
69
70
  try {
70
- const result = await config.execute(input);
71
+ const capabilityContext = createCapabilityContext();
72
+ const result = await config.execute(input, capabilityContext);
71
73
  if (validateOutput && !validateOutput(result)) {
72
74
  const result2 = {
73
75
  _tag: "error",
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { emojiIcon, imageIcon, notionIcon, place } from "./builder.js";
2
- export type { AutomationCapability, AutomationConfiguration, AutomationContext, PageObjectResponse, } from "./capabilities/automation.js";
2
+ export type { AutomationCapability, AutomationConfiguration, AutomationEvent, PageObjectResponse, } from "./capabilities/automation.js";
3
+ export type { CapabilityContext } from "./capabilities/context.js";
3
4
  export type { NotionManagedOAuthConfiguration, OAuthCapability, OAuthConfiguration, UserManagedOAuthConfiguration, } from "./capabilities/oauth.js";
4
5
  export type { SyncCapability, SyncChange, SyncChangeDelete, SyncChangeUpsert, SyncConfiguration, SyncExecutionResult, SyncMode, } from "./capabilities/sync.js";
5
6
  export type { ToolCapability, ToolConfiguration } from "./capabilities/tool.js";
7
+ export { Pacer } from "./pacer.js";
6
8
  export type { Icon, ImageIcon, NoticonColor, NoticonName, PlaceValue, Schedule, } from "./types.js";
7
9
  export { Worker } from "./worker.js";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACvE,YAAY,EACX,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,GAClB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACX,+BAA+B,EAC/B,eAAe,EACf,kBAAkB,EAClB,6BAA6B,GAC7B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,QAAQ,GACR,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,YAAY,EACX,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACvE,YAAY,EACX,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,kBAAkB,GAClB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,YAAY,EACX,+BAA+B,EAC/B,eAAe,EACf,kBAAkB,EAClB,6BAA6B,GAC7B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,QAAQ,GACR,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EACX,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { emojiIcon, imageIcon, notionIcon, place } from "./builder.js";
2
+ import { Pacer } from "./pacer.js";
2
3
  import { Worker } from "./worker.js";
3
4
  export {
5
+ Pacer,
4
6
  Worker,
5
7
  emojiIcon,
6
8
  imageIcon,
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Pacer module for rate limiting API requests.
3
+ *
4
+ * The Pacer ensures requests are evenly spaced over time to respect third-party
5
+ * API rate limits. Instead of making all requests immediately until hitting a 429,
6
+ * Pacer paces requests throughout the rate limit window.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Pacer } from "@project-ajax/sdk/pacer"
11
+ *
12
+ * // Rate limit: 10 requests per minute
13
+ * await Pacer.wait("salesforce-api", { requests: 10, intervalMs: 60 * 1000 })
14
+ *
15
+ * // Now make your API call
16
+ * const data = await fetchFromSalesforce()
17
+ * ```
18
+ *
19
+ * @module
20
+ */
21
+ export type PacerLimit = {
22
+ requests: number;
23
+ intervalMs: number;
24
+ };
25
+ export declare const Pacer: {
26
+ /**
27
+ * Wait until a request can proceed under the specified rate limit.
28
+ *
29
+ * This function paces requests evenly across the rate limit interval. For example,
30
+ * with 60 requests allowed per hour, requests are spaced approximately 1 minute apart.
31
+ *
32
+ * @param key - Unique identifier for this rate limit scope (e.g., "salesforce-api", "jira:token123"). If you're not sure, use any string.
33
+ * @param limit - Rate limit configuration: `requests` per `intervalMs`.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // Salesforce: 30,000 requests per 24 hours
38
+ * await Pacer.wait("salesforce", { requests: 30_000, intervalMs: 24 * 60 * 60 * 1000 })
39
+ *
40
+ * // Jira: 100 requests per minute
41
+ * await Pacer.wait("jira", { requests: 100, intervalMs: 60 * 1000 })
42
+ *
43
+ * // Multiple rate limits for different endpoints
44
+ * await Pacer.wait("api-read", { requests: 1000, intervalMs: 60 * 1000 })
45
+ * await Pacer.wait("api-write", { requests: 100, intervalMs: 60 * 1000 })
46
+ * ```
47
+ */
48
+ wait(key: string, limit: PacerLimit): Promise<void>;
49
+ };
50
+ //# sourceMappingURL=pacer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pacer.d.ts","sourceRoot":"","sources":["../src/pacer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,KAAK;IACjB;;;;;;;;;;;;;;;;;;;;;OAqBG;cACa,MAAM,SAAS,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CA4BzD,CAAC"}
package/dist/pacer.js ADDED
@@ -0,0 +1,48 @@
1
+ import { getPacerState, setPacerState } from "./pacer_internal.js";
2
+ const Pacer = {
3
+ /**
4
+ * Wait until a request can proceed under the specified rate limit.
5
+ *
6
+ * This function paces requests evenly across the rate limit interval. For example,
7
+ * with 60 requests allowed per hour, requests are spaced approximately 1 minute apart.
8
+ *
9
+ * @param key - Unique identifier for this rate limit scope (e.g., "salesforce-api", "jira:token123"). If you're not sure, use any string.
10
+ * @param limit - Rate limit configuration: `requests` per `intervalMs`.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // Salesforce: 30,000 requests per 24 hours
15
+ * await Pacer.wait("salesforce", { requests: 30_000, intervalMs: 24 * 60 * 60 * 1000 })
16
+ *
17
+ * // Jira: 100 requests per minute
18
+ * await Pacer.wait("jira", { requests: 100, intervalMs: 60 * 1000 })
19
+ *
20
+ * // Multiple rate limits for different endpoints
21
+ * await Pacer.wait("api-read", { requests: 1000, intervalMs: 60 * 1000 })
22
+ * await Pacer.wait("api-write", { requests: 100, intervalMs: 60 * 1000 })
23
+ * ```
24
+ */
25
+ async wait(key, limit) {
26
+ if (limit.requests <= 0) {
27
+ throw new Error("requests must be greater than 0");
28
+ }
29
+ if (limit.intervalMs <= 0) {
30
+ throw new Error("intervalMs must be greater than 0");
31
+ }
32
+ const now = Date.now();
33
+ const paceMs = Math.ceil(limit.intervalMs / limit.requests);
34
+ const state = getPacerState();
35
+ const existing = state.pacers[key];
36
+ const lastScheduledAtMs = existing?.lastScheduledAtMs ?? 0;
37
+ const scheduledAtMs = Math.max(lastScheduledAtMs + paceMs, now);
38
+ const delayMs = scheduledAtMs - now;
39
+ state.pacers[key] = { lastScheduledAtMs: scheduledAtMs };
40
+ setPacerState(state);
41
+ if (delayMs > 0) {
42
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
43
+ }
44
+ }
45
+ };
46
+ export {
47
+ Pacer
48
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pacer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pacer.test.d.ts","sourceRoot":"","sources":["../src/pacer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export type PacerState = {
2
+ pacers: Record<string, PacerEntry>;
3
+ };
4
+ export declare function setPacerState(state: PacerState): void;
5
+ export declare function getPacerState(): PacerState;
6
+ //# sourceMappingURL=pacer_internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pacer_internal.d.ts","sourceRoot":"","sources":["../src/pacer_internal.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,UAAU,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACnC,CAAC;AAIF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAErD;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C"}
@@ -0,0 +1,11 @@
1
+ let pacerState = { pacers: {} };
2
+ function setPacerState(state) {
3
+ pacerState = state;
4
+ }
5
+ function getPacerState() {
6
+ return pacerState;
7
+ }
8
+ export {
9
+ getPacerState,
10
+ setPacerState
11
+ };
package/dist/worker.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { AutomationCapability, AutomationConfiguration, AutomationContext } from "./capabilities/automation.js";
1
+ import type { AutomationCapability, AutomationConfiguration, AutomationEvent } from "./capabilities/automation.js";
2
+ import type { CapabilityContext } from "./capabilities/context.js";
2
3
  import type { NotionManagedOAuthConfiguration, OAuthCapability, OAuthConfiguration, UserManagedOAuthConfiguration } from "./capabilities/oauth.js";
3
4
  import type { SyncCapability, SyncConfiguration } from "./capabilities/sync.js";
4
5
  import type { ToolCapability, ToolConfiguration } from "./capabilities/tool.js";
5
6
  import type { Schema } from "./schema.js";
6
7
  import type { JSONValue } from "./types.js";
7
- export type { AutomationConfiguration, AutomationContext, OAuthConfiguration, NotionManagedOAuthConfiguration, UserManagedOAuthConfiguration, SyncConfiguration, ToolConfiguration, };
8
+ export type { AutomationConfiguration, AutomationEvent, CapabilityContext, OAuthConfiguration, NotionManagedOAuthConfiguration, UserManagedOAuthConfiguration, SyncConfiguration, ToolConfiguration, };
8
9
  type Capability = SyncCapability | ToolCapability<any, any> | AutomationCapability | OAuthCapability;
9
10
  export declare class Worker {
10
11
  #private;
@@ -72,7 +73,7 @@ export declare class Worker {
72
73
  * },
73
74
  * required: ["name"],
74
75
  * },
75
- * execute: ({ name }) => {
76
+ * execute: ({ name }, { notion }) => {
76
77
  * return `Hello, ${name}!`;
77
78
  * },
78
79
  * })
@@ -96,8 +97,8 @@ export declare class Worker {
96
97
  * worker.automation("sendWelcomeEmail", {
97
98
  * title: "Send Welcome Email",
98
99
  * description: "Sends a welcome email when a new user is added",
99
- * execute: async (context) => {
100
- * const { pageId, pageData } = context;
100
+ * execute: async (event, { notion }) => {
101
+ * const { pageId, pageData } = event;
101
102
  *
102
103
  * // Access page properties from the Public API format
103
104
  * if (pageData) {
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EACX,+BAA+B,EAC/B,eAAe,EACf,kBAAkB,EAClB,6BAA6B,EAC7B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,YAAY,EACX,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,+BAA+B,EAC/B,6BAA6B,EAC7B,iBAAiB,EACjB,iBAAiB,GACjB,CAAC;AAMF,KAAK,UAAU,GACZ,cAAc,GAEd,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,GACxB,oBAAoB,GACpB,eAAe,CAAC;AAMnB,qBAAa,MAAM;;IAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,EAC9D,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,GACvC,cAAc;IAOjB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,IAAI,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,SAAS,GAAG,SAAS,EACxD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAQvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,UAAU,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,uBAAuB,GAC7B,oBAAoB;IAOvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,eAAe;IAO/D;;OAEG;IACH,IAAI,YAAY,IAAI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC,EAAE,CAMhE;IAED;;;;;;OAMG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAyB1D"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EACX,+BAA+B,EAC/B,eAAe,EACf,kBAAkB,EAClB,6BAA6B,EAC7B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,YAAY,EACX,uBAAuB,EACvB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,+BAA+B,EAC/B,6BAA6B,EAC7B,iBAAiB,EACjB,iBAAiB,GACjB,CAAC;AAMF,KAAK,UAAU,GACZ,cAAc,GAEd,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,GACxB,oBAAoB,GACpB,eAAe,CAAC;AAMnB,qBAAa,MAAM;;IAGlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,EAC9D,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,GACvC,cAAc;IAOjB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,IAAI,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,SAAS,GAAG,SAAS,EACxD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAQvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,UAAU,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,uBAAuB,GAC7B,oBAAoB;IAOvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,eAAe;IAO/D;;OAEG;IACH,IAAI,YAAY,IAAI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC,EAAE,CAMhE;IAED;;;;;;OAMG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAyB1D"}
package/dist/worker.js CHANGED
@@ -73,7 +73,7 @@ class Worker {
73
73
  * },
74
74
  * required: ["name"],
75
75
  * },
76
- * execute: ({ name }) => {
76
+ * execute: ({ name }, { notion }) => {
77
77
  * return `Hello, ${name}!`;
78
78
  * },
79
79
  * })
@@ -102,8 +102,8 @@ class Worker {
102
102
  * worker.automation("sendWelcomeEmail", {
103
103
  * title: "Send Welcome Email",
104
104
  * description: "Sends a welcome email when a new user is added",
105
- * execute: async (context) => {
106
- * const { pageId, pageData } = context;
105
+ * execute: async (event, { notion }) => {
106
+ * const { pageId, pageData } = event;
107
107
  *
108
108
  * // Access page properties from the Public API format
109
109
  * if (pageData) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@project-ajax/sdk",
3
- "version": "0.0.74",
3
+ "version": "0.0.76",
4
4
  "description": "An SDK for building workers for the Project Ajax platform",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -28,6 +28,10 @@
28
28
  "./types": {
29
29
  "types": "./dist/types.d.ts",
30
30
  "default": "./dist/types.js"
31
+ },
32
+ "./pacer": {
33
+ "types": "./dist/pacer.d.ts",
34
+ "default": "./dist/pacer.js"
31
35
  }
32
36
  },
33
37
  "engines": {
@@ -58,6 +62,9 @@
58
62
  "typescript": "^5.9.3",
59
63
  "vitest": "^4.0.8"
60
64
  },
65
+ "peerDependencies": {
66
+ "@notionhq/client": "^2.2.15"
67
+ },
61
68
  "dependencies": {
62
69
  "ajv": "^8.17.1"
63
70
  }
@@ -8,7 +8,7 @@ import {
8
8
  vi,
9
9
  } from "vitest";
10
10
  import { ExecutionError } from "../error.js";
11
- import type { AutomationContext, PageObjectResponse } from "./automation.js";
11
+ import type { PageObjectResponse } from "./automation.js";
12
12
  import { createAutomationCapability } from "./automation.js";
13
13
 
14
14
  describe("createAutomationCapability", () => {
@@ -45,14 +45,19 @@ describe("createAutomationCapability", () => {
45
45
  execute: executeFn,
46
46
  });
47
47
 
48
- const context: AutomationContext = {
48
+ const event = {
49
49
  pageId: "page-123",
50
50
  actionType: "test_action",
51
51
  };
52
52
 
53
- await capability.handler(context);
53
+ await capability.handler(event);
54
54
 
55
- expect(executeFn).toHaveBeenCalledWith(context);
55
+ expect(executeFn).toHaveBeenCalledWith(
56
+ event,
57
+ expect.objectContaining({
58
+ notion: expect.any(Object),
59
+ }),
60
+ );
56
61
  expect(stdoutSpy).toHaveBeenCalledWith(
57
62
  `\n<output>{"status":"success"}</output>\n`,
58
63
  );
@@ -85,15 +90,20 @@ describe("createAutomationCapability", () => {
85
90
  public_url: null,
86
91
  };
87
92
 
88
- const context: AutomationContext = {
93
+ const event = {
89
94
  pageId: "page-789",
90
95
  actionType: "process_page",
91
96
  pageData,
92
97
  };
93
98
 
94
- await capability.handler(context);
99
+ await capability.handler(event);
95
100
 
96
- expect(executeFn).toHaveBeenCalledWith(context);
101
+ expect(executeFn).toHaveBeenCalledWith(
102
+ event,
103
+ expect.objectContaining({
104
+ notion: expect.any(Object),
105
+ }),
106
+ );
97
107
  expect(stdoutSpy).toHaveBeenCalledWith(
98
108
  `\n<output>{"status":"success"}</output>\n`,
99
109
  );
@@ -108,12 +118,12 @@ describe("createAutomationCapability", () => {
108
118
  },
109
119
  });
110
120
 
111
- const context: AutomationContext = {
121
+ const event = {
112
122
  pageId: "page-error",
113
123
  actionType: "error_action",
114
124
  };
115
125
 
116
- await expect(capability.handler(context)).rejects.toThrow(ExecutionError);
126
+ await expect(capability.handler(event)).rejects.toThrow(ExecutionError);
117
127
 
118
128
  expect(stdoutSpy).toHaveBeenCalledWith(
119
129
  `\n<output>{"status":"error","error":{"name":"ExecutionError","message":"Error during worker execution: Error: Something went wrong"}}</output>\n`,
@@ -129,11 +139,11 @@ describe("createAutomationCapability", () => {
129
139
  },
130
140
  });
131
141
 
132
- const context: AutomationContext = {
142
+ const event = {
133
143
  actionType: "string_error_action",
134
144
  };
135
145
 
136
- await expect(capability.handler(context)).rejects.toThrow(ExecutionError);
146
+ await expect(capability.handler(event)).rejects.toThrow(ExecutionError);
137
147
 
138
148
  expect(stdoutSpy).toHaveBeenCalledWith(
139
149
  `\n<output>{"status":"error","error":{"name":"ExecutionError","message":"Error during worker execution: String error"}}</output>\n`,
@@ -1,9 +1,11 @@
1
1
  import { ExecutionError } from "../error.js";
2
+ import type { CapabilityContext } from "./context.js";
3
+ import { createCapabilityContext } from "./context.js";
2
4
 
3
5
  /**
4
- * Context provided to automation execute functions
6
+ * Event provided to automation execute functions
5
7
  */
6
- export interface AutomationContext {
8
+ export interface AutomationEvent {
7
9
  /**
8
10
  * The ID of the page that triggered the automation (if applicable)
9
11
  */
@@ -56,10 +58,14 @@ export interface AutomationConfiguration {
56
58
 
57
59
  /**
58
60
  * The function that executes when the automation is triggered
59
- * @param context - Context about the automation trigger, including page data if applicable
61
+ * @param event - Event data about the automation trigger, including page data if applicable
62
+ * @param context - The capability execution context (Notion client, etc.)
60
63
  * @returns A promise that resolves when the automation completes
61
64
  */
62
- execute: (context: AutomationContext) => Promise<void> | void;
65
+ execute: (
66
+ event: AutomationEvent,
67
+ context: CapabilityContext,
68
+ ) => Promise<void> | void;
63
69
  }
64
70
 
65
71
  /**
@@ -92,9 +98,10 @@ export function createAutomationCapability(
92
98
  title: config.title,
93
99
  description: config.description,
94
100
  },
95
- async handler(context: AutomationContext): Promise<void> {
101
+ async handler(event: AutomationEvent): Promise<void> {
96
102
  try {
97
- await config.execute(context);
103
+ const capabilityContext = createCapabilityContext();
104
+ await config.execute(event, capabilityContext);
98
105
  process.stdout.write(
99
106
  `\n<output>${JSON.stringify({ status: "success" })}</output>\n`,
100
107
  );
@@ -0,0 +1,23 @@
1
+ import { Client } from "@notionhq/client";
2
+ import type { ClientOptions } from "@notionhq/client/build/src/Client.js";
3
+
4
+ export type CapabilityContext = {
5
+ /** Notion API SDK client for this execution. */
6
+ notion: Client;
7
+ };
8
+
9
+ export function createCapabilityContext(): CapabilityContext {
10
+ const options: ClientOptions = {};
11
+
12
+ if (process.env.NOTION_API_BASE_URL) {
13
+ options.baseUrl = process.env.NOTION_API_BASE_URL;
14
+ }
15
+
16
+ if (process.env.NOTION_API_TOKEN) {
17
+ options.auth = process.env.NOTION_API_TOKEN;
18
+ }
19
+
20
+ const notion = new Client(options);
21
+
22
+ return { notion };
23
+ }
@@ -1,4 +1,10 @@
1
1
  import { ExecutionError, unreachable } from "../error.js";
2
+ import {
3
+ getPacerState,
4
+ type PacerEntry,
5
+ type PacerState,
6
+ setPacerState,
7
+ } from "../pacer_internal.js";
2
8
  import type {
3
9
  PropertyConfiguration,
4
10
  PropertySchema,
@@ -14,6 +20,8 @@ import type {
14
20
  TextValue,
15
21
  TimeUnit,
16
22
  } from "../types.js";
23
+ import type { CapabilityContext } from "./context.js";
24
+ import { createCapabilityContext } from "./context.js";
17
25
 
18
26
  /**
19
27
  * Maps a property configuration to its corresponding value type.
@@ -96,7 +104,7 @@ export type SyncChange<PK extends string, S extends PropertySchema<PK>> =
96
104
  /**
97
105
  * Result returned from the sync execute function.
98
106
  */
99
- export type SyncExecutionResult<PK extends string, Context = unknown> = {
107
+ export type SyncExecutionResult<PK extends string, State = unknown> = {
100
108
  /**
101
109
  * The batch of changes to apply in this execution.
102
110
  * Can include upserts (create/update) and deletes.
@@ -105,18 +113,18 @@ export type SyncExecutionResult<PK extends string, Context = unknown> = {
105
113
 
106
114
  /**
107
115
  * Indicates whether there is more data to fetch.
108
- * - `true`: More data available, will trigger another execution with nextContext
116
+ * - `true`: More data available, will trigger another execution with nextState
109
117
  * - `false`: No more data to fetch, sync is complete
110
118
  */
111
119
  hasMore: boolean;
112
120
 
113
121
  /**
114
- * Optional context data to pass to the next execution.
122
+ * Optional state data to pass to the next execution.
115
123
  * Required if `hasMore` is `true`, ignored if `hasMore` is `false`.
116
124
  * This can be any type of data (cursor, page number, timestamp, etc.).
117
- * The same data will be provided in the context parameter of the next execution.
125
+ * The same data will be provided as `state` in the next execution.
118
126
  */
119
- nextContext?: Context;
127
+ nextState?: State;
120
128
  };
121
129
 
122
130
  /**
@@ -126,7 +134,7 @@ export type SyncExecutionResult<PK extends string, Context = unknown> = {
126
134
  export type SyncConfiguration<
127
135
  PK extends string,
128
136
  S extends Schema<PK>,
129
- Context = unknown,
137
+ State = unknown,
130
138
  > = {
131
139
  /**
132
140
  * The property of the data source that maps to a "primary key" in the
@@ -154,13 +162,13 @@ export type SyncConfiguration<
154
162
 
155
163
  /**
156
164
  * How often the sync should run.
157
- * - "continuous": Run as frequently as the system allows (default)
165
+ * - "continuous": Run as frequently as the system allows
158
166
  * - Interval string: Run at specified intervals, e.g. "1h", "30m", "1d"
159
167
  *
160
168
  * Minimum interval: 1 minute ("1m")
161
169
  * Maximum interval: 7 days ("7d")
162
170
  *
163
- * @default "continuous"
171
+ * @default "30m"
164
172
  */
165
173
  schedule?: Schedule;
166
174
 
@@ -168,35 +176,35 @@ export type SyncConfiguration<
168
176
  * A function that fetches the data to sync from the third-party service.
169
177
  *
170
178
  * This function can return all data at once, or implement pagination by:
171
- * 1. Returning a batch of changes with `hasMore: true` and a `nextContext`
172
- * 2. The runtime will call execute again with that context data
179
+ * 1. Returning a batch of changes with `hasMore: true` and a `nextState`
180
+ * 2. The runtime will call execute again with that state
173
181
  * 3. Continue until `hasMore: false` is returned
174
182
  *
175
183
  * The runtime will handle diffing against the data source and creating,
176
184
  * updating, and deleting pages as necessary.
177
185
  *
178
- * @param context - Optional context data from previous execution (undefined on first call)
179
- * @returns A result containing changes, hasMore status, and optional nextContext
186
+ * @param state - User-defined state from the previous execution (undefined on first call)
187
+ * @param context - Runtime context, including Notion client
188
+ * @returns A result containing changes, hasMore status, and optional nextState
180
189
  */
181
- execute: (context?: Context) => Promise<SyncExecutionResult<PK, Context>>;
182
- };
183
-
184
- export type SyncHandlerResult<PK extends string, Context = unknown> = {
185
- primaryKeyProperty: PK;
186
- changes: SyncChange<PK, PropertySchema<PK>>[];
187
- hasMore: boolean;
188
- nextContext?: Context;
189
- mode?: SyncMode;
190
+ execute: (
191
+ state: State | undefined,
192
+ context: CapabilityContext,
193
+ ) => Promise<SyncExecutionResult<PK, State>>;
190
194
  };
191
195
 
192
196
  export type SyncCapability = ReturnType<typeof createSyncCapability>;
193
197
 
194
198
  /**
195
- * Context object passed from the runtime to sync capability handlers.
199
+ * Runtime context object passed from the runtime to sync capability handlers.
196
200
  */
197
201
  type RuntimeContext<UserContext = unknown> = {
198
- /** The user-defined/-controlled context (cursor, pagination state, etc.) */
202
+ /** The user-defined/-controlled state (cursor, pagination state, etc.) */
203
+ state?: UserContext;
204
+ /** Legacy field for user-defined/-controlled state. */
199
205
  userContext?: UserContext;
206
+ /** Pacer state from the server for rate limiting. */
207
+ pacers?: Record<string, PacerEntry>;
200
208
  };
201
209
 
202
210
  /**
@@ -221,17 +229,29 @@ export function createSyncCapability<
221
229
  schedule: parseSchedule(syncConfiguration.schedule),
222
230
  },
223
231
  async handler(runtimeContext?: RuntimeContext<Context>) {
224
- const userContext = runtimeContext?.userContext;
232
+ const capabilityContext = createCapabilityContext();
233
+ const state = runtimeContext?.state ?? runtimeContext?.userContext;
234
+
235
+ // Initialize pacer state from runtime context
236
+ const pacerState: PacerState = {
237
+ pacers: runtimeContext?.pacers ?? {},
238
+ };
239
+ setPacerState(pacerState);
240
+
225
241
  const executionResult = await syncConfiguration
226
- .execute(userContext)
242
+ .execute(state, capabilityContext)
227
243
  .catch((err) => {
228
244
  throw new ExecutionError(err);
229
245
  });
230
246
 
247
+ // Get updated pacer state after execution
248
+ const updatedPacerState = getPacerState();
249
+
231
250
  const result = {
232
251
  changes: executionResult.changes,
233
252
  hasMore: executionResult.hasMore,
234
- nextUserContext: executionResult.nextContext,
253
+ nextUserContext: executionResult.nextState,
254
+ nextPacerState: updatedPacerState.pacers,
235
255
  };
236
256
 
237
257
  process.stdout.write(`\n<output>${JSON.stringify(result)}</output>\n`);
@@ -248,14 +268,20 @@ const MS_PER_DAY = 24 * MS_PER_HOUR;
248
268
  const MIN_INTERVAL_MS = MS_PER_MINUTE; // 1m
249
269
  const MAX_INTERVAL_MS = 7 * MS_PER_DAY; // 7d
250
270
 
271
+ const DEFAULT_INTERVAL_MS = 30 * MS_PER_MINUTE; // 30m
272
+
251
273
  /**
252
274
  * Parses a user-friendly schedule string into the normalized backend format.
253
275
  */
254
276
  function parseSchedule(schedule: Schedule | undefined): SyncSchedule {
255
- if (!schedule || schedule === "continuous") {
277
+ if (schedule === "continuous") {
256
278
  return { type: "continuous" };
257
279
  }
258
280
 
281
+ if (!schedule) {
282
+ return { type: "interval", intervalMs: DEFAULT_INTERVAL_MS };
283
+ }
284
+
259
285
  const match = schedule.match(/^(\d+)(m|h|d)$/);
260
286
  if (!match || !match[1] || !match[2]) {
261
287
  throw new Error(
@@ -1,5 +1,7 @@
1
1
  import { Ajv, type JSONSchemaType } from "ajv";
2
2
  import type { JSONValue } from "../types.js";
3
+ import type { CapabilityContext } from "./context.js";
4
+ import { createCapabilityContext } from "./context.js";
3
5
 
4
6
  export interface ToolConfiguration<
5
7
  I extends JSONValue,
@@ -9,7 +11,7 @@ export interface ToolConfiguration<
9
11
  description: string;
10
12
  schema: JSONSchemaType<I>;
11
13
  outputSchema?: JSONSchemaType<O>;
12
- execute: (input: I) => O | Promise<O>;
14
+ execute: (input: I, context: CapabilityContext) => O | Promise<O>;
13
15
  }
14
16
 
15
17
  /**
@@ -127,7 +129,8 @@ export function createToolCapability<
127
129
  }
128
130
 
129
131
  try {
130
- const result = await config.execute(input);
132
+ const capabilityContext = createCapabilityContext();
133
+ const result = await config.execute(input, capabilityContext);
131
134
  if (validateOutput && !validateOutput(result)) {
132
135
  const result = {
133
136
  _tag: "error" as const,
package/src/index.ts CHANGED
@@ -2,9 +2,10 @@ export { emojiIcon, imageIcon, notionIcon, place } from "./builder.js";
2
2
  export type {
3
3
  AutomationCapability,
4
4
  AutomationConfiguration,
5
- AutomationContext,
5
+ AutomationEvent,
6
6
  PageObjectResponse,
7
7
  } from "./capabilities/automation.js";
8
+ export type { CapabilityContext } from "./capabilities/context.js";
8
9
  export type {
9
10
  NotionManagedOAuthConfiguration,
10
11
  OAuthCapability,
@@ -21,6 +22,8 @@ export type {
21
22
  SyncMode,
22
23
  } from "./capabilities/sync.js";
23
24
  export type { ToolCapability, ToolConfiguration } from "./capabilities/tool.js";
25
+ // Pacer module re-exported for convenience (users can also import from "@project-ajax/sdk/pacer")
26
+ export { Pacer } from "./pacer.js";
24
27
  export type {
25
28
  Icon,
26
29
  ImageIcon,
@@ -0,0 +1,41 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { Pacer } from "./pacer.js";
4
+ import { getPacerState, setPacerState } from "./pacer_internal.js";
5
+
6
+ describe("Pacer.wait", () => {
7
+ beforeEach(() => {
8
+ vi.useFakeTimers();
9
+ vi.setSystemTime(0);
10
+ setPacerState({ pacers: {} });
11
+ });
12
+
13
+ afterEach(() => {
14
+ vi.useRealTimers();
15
+ });
16
+
17
+ it("paces requests based on the rate limit", async () => {
18
+ const first = Pacer.wait("api", { requests: 10, intervalMs: 60_000 });
19
+
20
+ expect(getPacerState().pacers.api?.lastScheduledAtMs).toBe(6000);
21
+ await vi.advanceTimersByTimeAsync(6000);
22
+ await first;
23
+
24
+ const second = Pacer.wait("api", { requests: 10, intervalMs: 60_000 });
25
+ expect(getPacerState().pacers.api?.lastScheduledAtMs).toBe(12_000);
26
+ await vi.advanceTimersByTimeAsync(6000);
27
+ await second;
28
+ });
29
+
30
+ it("does not wait when the next slot is already in the past", async () => {
31
+ const first = Pacer.wait("api", { requests: 10, intervalMs: 60_000 });
32
+ await vi.advanceTimersByTimeAsync(6000);
33
+ await first;
34
+
35
+ vi.advanceTimersByTime(20_000);
36
+
37
+ const second = Pacer.wait("api", { requests: 10, intervalMs: 60_000 });
38
+ expect(getPacerState().pacers.api?.lastScheduledAtMs).toBe(26_000);
39
+ await second;
40
+ });
41
+ });
package/src/pacer.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Pacer module for rate limiting API requests.
3
+ *
4
+ * The Pacer ensures requests are evenly spaced over time to respect third-party
5
+ * API rate limits. Instead of making all requests immediately until hitting a 429,
6
+ * Pacer paces requests throughout the rate limit window.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Pacer } from "@project-ajax/sdk/pacer"
11
+ *
12
+ * // Rate limit: 10 requests per minute
13
+ * await Pacer.wait("salesforce-api", { requests: 10, intervalMs: 60 * 1000 })
14
+ *
15
+ * // Now make your API call
16
+ * const data = await fetchFromSalesforce()
17
+ * ```
18
+ *
19
+ * @module
20
+ */
21
+
22
+ import { getPacerState, setPacerState } from "./pacer_internal.js";
23
+
24
+ export type PacerLimit = {
25
+ requests: number;
26
+ intervalMs: number;
27
+ };
28
+
29
+ export const Pacer = {
30
+ /**
31
+ * Wait until a request can proceed under the specified rate limit.
32
+ *
33
+ * This function paces requests evenly across the rate limit interval. For example,
34
+ * with 60 requests allowed per hour, requests are spaced approximately 1 minute apart.
35
+ *
36
+ * @param key - Unique identifier for this rate limit scope (e.g., "salesforce-api", "jira:token123"). If you're not sure, use any string.
37
+ * @param limit - Rate limit configuration: `requests` per `intervalMs`.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * // Salesforce: 30,000 requests per 24 hours
42
+ * await Pacer.wait("salesforce", { requests: 30_000, intervalMs: 24 * 60 * 60 * 1000 })
43
+ *
44
+ * // Jira: 100 requests per minute
45
+ * await Pacer.wait("jira", { requests: 100, intervalMs: 60 * 1000 })
46
+ *
47
+ * // Multiple rate limits for different endpoints
48
+ * await Pacer.wait("api-read", { requests: 1000, intervalMs: 60 * 1000 })
49
+ * await Pacer.wait("api-write", { requests: 100, intervalMs: 60 * 1000 })
50
+ * ```
51
+ */
52
+ async wait(key: string, limit: PacerLimit): Promise<void> {
53
+ if (limit.requests <= 0) {
54
+ throw new Error("requests must be greater than 0");
55
+ }
56
+ if (limit.intervalMs <= 0) {
57
+ throw new Error("intervalMs must be greater than 0");
58
+ }
59
+
60
+ const now = Date.now();
61
+ const paceMs = Math.ceil(limit.intervalMs / limit.requests);
62
+
63
+ const state = getPacerState();
64
+ const existing = state.pacers[key];
65
+ const lastScheduledAtMs = existing?.lastScheduledAtMs ?? 0;
66
+
67
+ // Schedule at the later of: (last scheduled + pace) or now
68
+ const scheduledAtMs = Math.max(lastScheduledAtMs + paceMs, now);
69
+ const delayMs = scheduledAtMs - now;
70
+
71
+ // Update state with new scheduled timestamp
72
+ state.pacers[key] = { lastScheduledAtMs: scheduledAtMs };
73
+ setPacerState(state);
74
+
75
+ // Sleep if needed
76
+ if (delayMs > 0) {
77
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
78
+ }
79
+ },
80
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Internal pacer state helpers used by the runtime.
3
+ * @internal
4
+ */
5
+ export type PacerEntry = {
6
+ lastScheduledAtMs: number;
7
+ };
8
+
9
+ export type PacerState = {
10
+ pacers: Record<string, PacerEntry>;
11
+ };
12
+
13
+ let pacerState: PacerState = { pacers: {} };
14
+
15
+ export function setPacerState(state: PacerState): void {
16
+ pacerState = state;
17
+ }
18
+
19
+ export function getPacerState(): PacerState {
20
+ return pacerState;
21
+ }
package/src/worker.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type {
2
2
  AutomationCapability,
3
3
  AutomationConfiguration,
4
- AutomationContext,
4
+ AutomationEvent,
5
5
  } from "./capabilities/automation.js";
6
6
  import { createAutomationCapability } from "./capabilities/automation.js";
7
+ import type { CapabilityContext } from "./capabilities/context.js";
7
8
  import type {
8
9
  NotionManagedOAuthConfiguration,
9
10
  OAuthCapability,
@@ -21,7 +22,8 @@ import type { JSONValue } from "./types.js";
21
22
  // Re-export types for convenience
22
23
  export type {
23
24
  AutomationConfiguration,
24
- AutomationContext,
25
+ AutomationEvent,
26
+ CapabilityContext,
25
27
  OAuthConfiguration,
26
28
  NotionManagedOAuthConfiguration,
27
29
  UserManagedOAuthConfiguration,
@@ -120,7 +122,7 @@ export class Worker {
120
122
  * },
121
123
  * required: ["name"],
122
124
  * },
123
- * execute: ({ name }) => {
125
+ * execute: ({ name }, { notion }) => {
124
126
  * return `Hello, ${name}!`;
125
127
  * },
126
128
  * })
@@ -154,8 +156,8 @@ export class Worker {
154
156
  * worker.automation("sendWelcomeEmail", {
155
157
  * title: "Send Welcome Email",
156
158
  * description: "Sends a welcome email when a new user is added",
157
- * execute: async (context) => {
158
- * const { pageId, pageData } = context;
159
+ * execute: async (event, { notion }) => {
160
+ * const { pageId, pageData } = event;
159
161
  *
160
162
  * // Access page properties from the Public API format
161
163
  * if (pageData) {