create-ncblock 0.0.39 → 0.0.41
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.
- package/package.json +1 -1
- package/scripts/init.ts +39 -23
- package/scripts/utils/templates.ts +53 -8
- package/sdk-version.json +1 -1
- package/templates/worker/.agents/INSTRUCTIONS.md +593 -0
- package/templates/worker/.agents/skills/auth-guide/SKILL.md +227 -0
- package/templates/worker/.agents/skills/sync/SKILL.md +368 -0
- package/templates/worker/.agents/skills/sync-debug/SKILL.md +101 -0
- package/templates/worker/.agents/skills/sync-guide/SKILL.md +253 -0
- package/templates/worker/.agents/skills/sync-guide/api-pagination-patterns.md +661 -0
- package/templates/worker/.agents/skills/sync-guide/examples/incremental-basic.ts +103 -0
- package/templates/worker/.agents/skills/sync-guide/examples/incremental-bimodal.ts +207 -0
- package/templates/worker/.agents/skills/sync-guide/examples/incremental-events.ts +132 -0
- package/templates/worker/.agents/skills/sync-guide/examples/replace-paginated.ts +79 -0
- package/templates/worker/.agents/skills/sync-guide/examples/replace-simple.ts +57 -0
- package/templates/worker/.agents/skills/sync-validate/SKILL.md +60 -0
- package/templates/worker/.claudeignore +2 -0
- package/templates/worker/.codexignore +2 -0
- package/templates/worker/.examples/automation-example.ts +60 -0
- package/templates/worker/.examples/oauth-example.ts +79 -0
- package/templates/worker/.examples/sync-example.ts +184 -0
- package/templates/worker/.examples/tool-example.ts +37 -0
- package/templates/worker/.examples/webhook-example.ts +66 -0
- package/templates/worker/README.md +765 -0
- package/templates/worker/_gitignore +6 -0
- package/templates/worker/docs/custom-tool.png +0 -0
- package/templates/worker/notionhq-workers-0.4.0.tgz +0 -0
- package/templates/worker/package.json +25 -0
- package/templates/worker/src/index.ts +8 -0
- package/templates/worker/tsconfig.json +16 -0
- package/templates/worker/views/empty/AGENTS.md +71 -0
- package/templates/worker/views/empty/README.md +10 -0
- package/templates/worker/views/empty/_gitignore +2 -0
- package/templates/worker/views/empty/custom_blocks.json +4 -0
- package/templates/worker/views/empty/index.html +15 -0
- package/templates/worker/views/empty/package.json +23 -0
- package/templates/worker/views/empty/src/index.css +33 -0
- package/templates/worker/views/empty/src/index.tsx +20 -0
- package/templates/worker/views/empty/tsconfig.json +17 -0
- package/templates/worker/views/empty/vite.config.ts +7 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync-debug
|
|
3
|
+
description: Diagnose a failing or misbehaving sync — fetch run logs, identify errors, cross-reference with code, and suggest fixes
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
allowed-tools: ["Read", "Bash", "Glob", "Grep", "Edit", "Write"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
Help the user figure out why their sync is failing or producing wrong results. Work through the steps below systematically.
|
|
12
|
+
|
|
13
|
+
### Step 1: Get Current State
|
|
14
|
+
|
|
15
|
+
Run these commands to understand the situation:
|
|
16
|
+
|
|
17
|
+
```shell
|
|
18
|
+
ntn workers sync status
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Note which syncs are listed, their status, last run time, and next scheduled run. Look for syncs that are stuck, failing, or haven't run recently.
|
|
22
|
+
|
|
23
|
+
### Step 2: Fetch Recent Runs
|
|
24
|
+
|
|
25
|
+
```shell
|
|
26
|
+
ntn workers runs list
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Look for runs with non-zero exit codes (shown in red in table output). Note the run IDs for failed runs.
|
|
30
|
+
|
|
31
|
+
### Step 3: Get Logs
|
|
32
|
+
|
|
33
|
+
For the most recent run (any capability):
|
|
34
|
+
```shell
|
|
35
|
+
ntn workers runs list --plain | head -n1 | cut -f1 | xargs -I{} ntn workers runs logs {}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For the most recent run of a specific sync:
|
|
39
|
+
```shell
|
|
40
|
+
ntn workers runs list --plain | grep <syncKey> | head -n1 | cut -f1 | xargs -I{} ntn workers runs logs {}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Read the full log output. Look for error messages, stack traces, and any `console.log` output from the sync code.
|
|
44
|
+
|
|
45
|
+
### Step 4: Read the Sync Code
|
|
46
|
+
|
|
47
|
+
Read `src/index.ts` (and any imported modules) to understand the sync's logic. Cross-reference the error from the logs with the code.
|
|
48
|
+
|
|
49
|
+
### Step 5: Diagnose
|
|
50
|
+
|
|
51
|
+
Common failure patterns and their fixes:
|
|
52
|
+
|
|
53
|
+
**API Authentication Errors (401/403)**
|
|
54
|
+
- Check if OAuth is configured: `ntn workers oauth token <oauthKey>`
|
|
55
|
+
- Check environment variables: `ntn workers env list`
|
|
56
|
+
- If env vars are missing remotely: `ntn workers env push`
|
|
57
|
+
- If OAuth token expired: `ntn workers oauth start <key>` to re-authenticate
|
|
58
|
+
|
|
59
|
+
**Rate Limiting (429)**
|
|
60
|
+
- Reduce batch size in the sync code
|
|
61
|
+
- Add delays between API calls if needed
|
|
62
|
+
- Check if the API has documented rate limits
|
|
63
|
+
|
|
64
|
+
**Timeout / Long Execution**
|
|
65
|
+
- The execute function is taking too long per call
|
|
66
|
+
- Reduce batch size (fewer records per page)
|
|
67
|
+
- Simplify per-record processing (defer heavy transforms)
|
|
68
|
+
|
|
69
|
+
**Cursor / State Errors**
|
|
70
|
+
- TypeError on state access: probably a first-run issue (state is undefined)
|
|
71
|
+
- State shape changed after a code update: the persisted state from the previous run has the old shape
|
|
72
|
+
- Fix: `ntn workers sync state reset <key>` to clear state and re-backfill from scratch
|
|
73
|
+
|
|
74
|
+
**Schema Mismatch**
|
|
75
|
+
- Properties in `changes` don't match the `schema.properties` definition
|
|
76
|
+
- Check that every key in the `properties` object of each change matches a key in the schema
|
|
77
|
+
- Check that `Builder.title()` is used for `Schema.title()` properties, `Builder.richText()` for `Schema.richText()`, etc.
|
|
78
|
+
|
|
79
|
+
**Infinite Loop (sync never completes)**
|
|
80
|
+
- `hasMore` is always `true` — the cursor isn't advancing
|
|
81
|
+
- Check that `nextState` changes between iterations
|
|
82
|
+
- Check the termination condition: is it reachable?
|
|
83
|
+
|
|
84
|
+
**Empty Results**
|
|
85
|
+
- API returning no data: test the API call directly (curl or local exec)
|
|
86
|
+
- Wrong endpoint or query parameters
|
|
87
|
+
- Auth working but insufficient permissions/scopes
|
|
88
|
+
|
|
89
|
+
**Network / Transient Errors**
|
|
90
|
+
- Single occurrence: may be transient — check if subsequent runs succeeded
|
|
91
|
+
- Repeated: check the API endpoint URL, DNS, connectivity
|
|
92
|
+
- Force a retry: `ntn workers sync trigger <key>`
|
|
93
|
+
|
|
94
|
+
### Step 6: Fix and Verify
|
|
95
|
+
|
|
96
|
+
After identifying the issue:
|
|
97
|
+
1. Apply the fix to the code
|
|
98
|
+
2. Run `npm run check` to verify types
|
|
99
|
+
3. If the state shape changed, warn the user they may need `ntn workers sync state reset <key>` (this triggers a full re-backfill)
|
|
100
|
+
4. Deploy and preview to verify: suggest `/sync-preview` or run `ntn workers deploy && ntn workers sync trigger <key> --preview`
|
|
101
|
+
5. When the fix is verified, `ntn workers sync trigger <key>` to resume
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync-guide
|
|
3
|
+
description: Comprehensive guide to building Notion Workers syncs — covers the two-sync architecture (backfill+delta), replace mode, pagination, consistency buffers, pacers, deletion strategies, and common pitfalls. Auto-loads when sync-related work is detected.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What is a Sync?
|
|
8
|
+
|
|
9
|
+
A sync is a recurring `execute` function that returns data changes to populate a Notion database. The runtime calls `execute` in a loop:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const db = worker.database("myDb", {
|
|
13
|
+
type: "managed",
|
|
14
|
+
initialTitle: "My Data",
|
|
15
|
+
primaryKeyProperty: "ID",
|
|
16
|
+
schema: {
|
|
17
|
+
properties: {
|
|
18
|
+
Name: Schema.title(),
|
|
19
|
+
ID: Schema.richText(),
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
worker.sync("mySync", {
|
|
25
|
+
database: db,
|
|
26
|
+
execute: async (state, { notion }) => ({
|
|
27
|
+
changes: [
|
|
28
|
+
{ type: "upsert", key: "1", properties: { Name: Builder.title("Item 1"), ID: Builder.richText("1") } },
|
|
29
|
+
],
|
|
30
|
+
hasMore: false,
|
|
31
|
+
nextState: undefined,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Each call returns `{ changes, hasMore, nextState }`. If `hasMore` is `true`, the runtime calls `execute` again with `nextState`. This continues until `hasMore` is `false`, completing a **cycle**. The next cycle begins at the scheduled interval with the state from the end of the previous cycle.
|
|
37
|
+
|
|
38
|
+
**Imports:**
|
|
39
|
+
```ts
|
|
40
|
+
import { Worker } from "@notionhq/workers";
|
|
41
|
+
import * as Builder from "@notionhq/workers/builder";
|
|
42
|
+
import * as Schema from "@notionhq/workers/schema";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Decision Framework
|
|
46
|
+
|
|
47
|
+
### Step 1: Choose an Architecture
|
|
48
|
+
|
|
49
|
+
The deciding factor is **API capability and dataset size**. Two tiers:
|
|
50
|
+
|
|
51
|
+
| Condition | Architecture |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Small source (<1k records) or API with no change tracking | **Simple replace sync** — one sync, `mode: "replace"` |
|
|
54
|
+
| Everything else (API supports `updated_at`, change feeds, events) | **Backfill + delta pair** — two syncs writing to the same database |
|
|
55
|
+
|
|
56
|
+
**Simple replace sync**: One sync returns the full dataset each cycle. After the final `hasMore: false`, any records not seen are deleted automatically. Use when the dataset is small enough to re-fetch entirely.
|
|
57
|
+
|
|
58
|
+
**Backfill + delta pair**: Two syncs share a single database. The **backfill sync** (`mode: "replace"`, `schedule: "manual"`) re-fetches everything when triggered. The **delta sync** (`mode: "incremental"`, frequent schedule) fetches only changes since the last run. This separates concerns cleanly — no bi-modal state machine, no backfill-to-delta transition bugs.
|
|
59
|
+
|
|
60
|
+
### Step 2: Understand Your API's Pagination
|
|
61
|
+
|
|
62
|
+
Most APIs require paginating through results. Return batches of ~100 changes. Returning too many changes in one `execute` call will fail.
|
|
63
|
+
|
|
64
|
+
**Backfill pagination** (full dataset load):
|
|
65
|
+
1. **Opaque cursor token** — GraphQL `endCursor`, Stripe `starting_after`
|
|
66
|
+
2. **Page number / offset** — `?page=N&limit=100`
|
|
67
|
+
3. **Keyset (timestamp + id)** — `WHERE created_at > X OR (created_at = X AND id > Y)` — the gold standard for timestamp-sorted mutable data
|
|
68
|
+
|
|
69
|
+
**Delta pagination** (change-only loads, incremental mode):
|
|
70
|
+
1. **Timestamp cursor** — `?updated_since=<cursor>` with consistency buffer
|
|
71
|
+
2. **Keyset on updated_at + id** — same keyset pattern on the modification timestamp
|
|
72
|
+
3. **Event/changelog feed** — `GET /events?after=<eventId>`
|
|
73
|
+
4. **Same opaque cursor** — when the API sorts by `updated_at`, the backfill cursor works for delta too
|
|
74
|
+
|
|
75
|
+
### Step 3: Consistency Buffer (Delta Syncs)
|
|
76
|
+
|
|
77
|
+
APIs tend to be eventually consistent. A record that was just written or updated may not appear in query results immediately. Since the cursor never resets in incremental mode, if it advances past a record that hasn't been indexed yet, that record is skipped permanently. Lag the cursor 10-60 seconds behind "now":
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const bufferMs = 15_000;
|
|
81
|
+
const maxCursor = new Date(Date.now() - bufferMs).toISOString();
|
|
82
|
+
const nextCursor = records.length > 0
|
|
83
|
+
? min(lastRecord.updatedAt, maxCursor)
|
|
84
|
+
: maxCursor;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Step 4: Deletion Strategies
|
|
88
|
+
|
|
89
|
+
1. **Backfill sync (replace mode)**: free — unseen records are auto-deleted each cycle. This is the primary mechanism for handling deletes when the API has no delete signal.
|
|
90
|
+
2. **Delta sync with delete API**: emit `{ type: "delete", key }` markers. If the delete signal comes from a separate endpoint (audit log, archived filter), use the **flip-flop pattern**: run the main delta stream until caught up (`hasMore: false`), then switch to the delete stream for a cycle, then back. Both cursors persist in state independently.
|
|
91
|
+
3. **No delete API, large dataset**: rely on the backfill sync's replace-mode mark-and-sweep. Trigger the backfill manually or on a slow schedule to clean up stale records.
|
|
92
|
+
|
|
93
|
+
## Replace Mode
|
|
94
|
+
|
|
95
|
+
Simple: fetch everything, return it all, let the runtime handle deletes. Use as a standalone sync for small sources, or as the backfill half of a backfill+delta pair.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const db = worker.database("records", {
|
|
99
|
+
type: "managed",
|
|
100
|
+
initialTitle: "Records",
|
|
101
|
+
primaryKeyProperty: "ID",
|
|
102
|
+
schema: {
|
|
103
|
+
properties: { Name: Schema.title(), ID: Schema.richText() },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const apiPacer = worker.pacer("myApi", {
|
|
108
|
+
allowedRequests: 10,
|
|
109
|
+
intervalMs: 1000,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
worker.sync("recordsBackfill", {
|
|
113
|
+
database: db,
|
|
114
|
+
mode: "replace",
|
|
115
|
+
schedule: "manual", // trigger manually or on a slow schedule
|
|
116
|
+
execute: async (state) => {
|
|
117
|
+
const page = state?.page ?? 1;
|
|
118
|
+
await apiPacer.wait();
|
|
119
|
+
const { items, totalPages } = await fetchPage(page, 100);
|
|
120
|
+
const hasMore = page < totalPages;
|
|
121
|
+
return {
|
|
122
|
+
changes: items.map((item) => ({
|
|
123
|
+
type: "upsert" as const,
|
|
124
|
+
key: item.id,
|
|
125
|
+
properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) },
|
|
126
|
+
})),
|
|
127
|
+
hasMore,
|
|
128
|
+
nextState: hasMore ? { page: page + 1 } : undefined,
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
See `examples/replace-simple.ts` and `examples/replace-paginated.ts` for complete working examples.
|
|
135
|
+
|
|
136
|
+
## Incremental Mode (Delta Sync)
|
|
137
|
+
|
|
138
|
+
The delta sync fetches only changes since the last run. When paired with a replace-mode backfill sync on the same database, this replaces the old bi-modal single-sync pattern.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// Reuses the same `db` and `apiPacer` from above
|
|
142
|
+
|
|
143
|
+
worker.sync("recordsDelta", {
|
|
144
|
+
database: db,
|
|
145
|
+
mode: "incremental",
|
|
146
|
+
schedule: "5m",
|
|
147
|
+
execute: async (state: { cursor: string } | undefined) => {
|
|
148
|
+
const cursor = state?.cursor ?? new Date(0).toISOString();
|
|
149
|
+
const bufferTs = new Date(Date.now() - 15_000).toISOString();
|
|
150
|
+
|
|
151
|
+
await apiPacer.wait();
|
|
152
|
+
const { items, nextCursor } = await fetchChanges(cursor);
|
|
153
|
+
const done = !nextCursor;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
changes: items.map(toUpsert),
|
|
157
|
+
hasMore: !done,
|
|
158
|
+
nextState: {
|
|
159
|
+
cursor: done ? min(nextCursor ?? cursor, bufferTs) : nextCursor,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Key points:**
|
|
167
|
+
- The delta sync's state is simple — just a cursor. No phase discrimination needed.
|
|
168
|
+
- The backfill sync (replace mode) handles the initial full load and periodic cleanup of deleted records.
|
|
169
|
+
- Both syncs write to the same database via the shared `db` handle.
|
|
170
|
+
- The pacer is shared between syncs — the server apportions the budget evenly.
|
|
171
|
+
|
|
172
|
+
See `examples/incremental-basic.ts`, `examples/incremental-bimodal.ts`, and `examples/incremental-events.ts` for complete patterns.
|
|
173
|
+
|
|
174
|
+
## Schema Reference
|
|
175
|
+
|
|
176
|
+
Define the Notion database shape with `Schema` types and build values with `Builder`:
|
|
177
|
+
|
|
178
|
+
| Schema type | Builder value | Notes |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `Schema.title()` | `Builder.title("text")` | Primary display field. Every schema needs exactly one. |
|
|
181
|
+
| `Schema.richText()` | `Builder.richText("text")` | Text content, IDs |
|
|
182
|
+
| `Schema.url()` | `Builder.url("https://...")` | URL field |
|
|
183
|
+
| `Schema.email()` | `Builder.email("a@b.com")` | Email field |
|
|
184
|
+
| `Schema.phoneNumber()` | `Builder.phoneNumber("+1...")` | Phone field |
|
|
185
|
+
| `Schema.checkbox()` | `Builder.checkbox(true)` | Boolean |
|
|
186
|
+
| `Schema.file()` | `Builder.file("https://...", "name")` | File URL + optional display name |
|
|
187
|
+
| `Schema.number()` | `Builder.number(42)` | Number. Optional format: `Schema.number("percent")` |
|
|
188
|
+
| `Schema.date()` | `Builder.date("2024-01-15")` | Date (YYYY-MM-DD). Also: `Builder.dateTime("2024-01-15T10:30:00Z")`, `Builder.dateRange(start, end)` |
|
|
189
|
+
| `Schema.select([...])` | `Builder.select("Option A")` | Single select. Define options: `Schema.select([{ name: "A" }, { name: "B" }])`. **Options must have non-empty `name` values** — `Schema.select([])` and `{ name: "" }` are not supported. |
|
|
190
|
+
| `Schema.multiSelect([...])` | `Builder.multiSelect("A", "B")` | Multi select |
|
|
191
|
+
| `Schema.status(...)` | `Builder.status("Done")` | Status with groups |
|
|
192
|
+
| `Schema.people()` | `Builder.people("email@co.com")` | People by email |
|
|
193
|
+
| `Schema.place()` | `Builder.place({ latitude, longitude })` | Geographic location |
|
|
194
|
+
| `Schema.relation("databaseKey")` | `[Builder.relation("pk")]` | Relation to another managed database. Value is an **array**. |
|
|
195
|
+
|
|
196
|
+
Relations use the related database key. Two-way relations are configured the same way:
|
|
197
|
+
```ts
|
|
198
|
+
Schema.relation("otherDatabase", { twoWay: true, relatedPropertyName: "Back Link" })
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Row-level icons and page content:
|
|
202
|
+
```ts
|
|
203
|
+
changes: [{
|
|
204
|
+
type: "upsert", key: "1",
|
|
205
|
+
properties: { ... },
|
|
206
|
+
icon: Builder.emojiIcon("🎯"), // or Builder.notionIcon("rocket", "blue")
|
|
207
|
+
pageContentMarkdown: "## Details\nSome text", // Markdown body for the page
|
|
208
|
+
}]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Common Mistakes
|
|
212
|
+
|
|
213
|
+
1. **Not using a pacer** — every API call inside `execute` should be preceded by `await apiPacer.wait()`. Without it, syncs will hit rate limits and fail.
|
|
214
|
+
2. **Missing consistency buffer on delta syncs** — the cursor will permanently skip records not yet indexed in eventually consistent APIs.
|
|
215
|
+
3. **Not paginating** — returning too many changes at once. Start with batches of ~100.
|
|
216
|
+
4. **Using replace mode for large datasets** — if the API supports change tracking, pair a replace-mode backfill sync with an incremental delta sync instead of re-fetching everything each cycle.
|
|
217
|
+
5. **Cursor that doesn't advance** — infinite loop. Ensure `nextState` changes between iterations.
|
|
218
|
+
6. **Forgetting first-run handling** — `state` is `undefined` on first call. Use `state?.cursor ?? null`.
|
|
219
|
+
7. **Forgetting that backfill + delta share a database** — both syncs must use the same `worker.database()` handle and the same key/properties shape.
|
|
220
|
+
8. **Not triggering the backfill sync** — the backfill sync with `schedule: "manual"` won't run automatically. Trigger it on deploy or periodically to clean up deleted records.
|
|
221
|
+
9. **Empty select values** — `Schema.select()` requires at least one option with a non-empty `name`. `Schema.select([])` and `{ name: "" }` are not supported.
|
|
222
|
+
|
|
223
|
+
## CLI Commands for Sync Development
|
|
224
|
+
|
|
225
|
+
```shell
|
|
226
|
+
# Deploy
|
|
227
|
+
ntn workers deploy
|
|
228
|
+
|
|
229
|
+
# Preview (test without writing)
|
|
230
|
+
ntn workers sync trigger <key> --preview
|
|
231
|
+
ntn workers sync trigger <key> --preview --context '<json>' # continue pagination
|
|
232
|
+
|
|
233
|
+
# Trigger a sync run
|
|
234
|
+
ntn workers sync trigger <key>
|
|
235
|
+
|
|
236
|
+
# Check sync status
|
|
237
|
+
ntn workers sync status
|
|
238
|
+
|
|
239
|
+
# View run logs
|
|
240
|
+
ntn workers runs list
|
|
241
|
+
ntn workers runs list --plain | head -n1 | cut -f1 | xargs -I{} ntn workers runs logs {}
|
|
242
|
+
|
|
243
|
+
# Reset state (full re-backfill)
|
|
244
|
+
ntn workers sync state reset <key>
|
|
245
|
+
|
|
246
|
+
# Manage secrets
|
|
247
|
+
ntn workers env set KEY=value
|
|
248
|
+
ntn workers env push
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## API Patterns Reference
|
|
252
|
+
|
|
253
|
+
See [api-pagination-patterns.md](./api-pagination-patterns.md) for detailed strategies drawn from production syncs with Salesforce, Stripe, HubSpot, GitHub, and ServiceNow.
|