Skip to content

Commit caab08b

Browse files
feat(adapters): write-side capability fanout — wave 3 (117 adapters, N–Z) (#72)
* feat(n8n): workflows.create/workflows.update/workflows.delete/executions.delete/executio... * feat(nocodb): tables.list/tables.create/fields.create/records.bulk-create * feat(notion): users.list/blocks.update/blocks.delete * feat(notion-database): pages.archive/databases.create/databases.update/blocks.append * feat(odoo): records.copy/records.unlink_batch/fields.set/workflow.action * feat(omni-co): documents.update/queries.delete/schedules.run-now/documents.share * feat(onedrive): files.upload/files.delete/files.move/files.share/folders.create * feat(open-phone): messages.list/calls.create/contacts.delete/calls.transfer * feat(open-router): credits.get/keys.list/keys.create/keys.revoke * feat(openmic-ai): bots.create/bots.update/bots.delete/calls.cancel * feat(opnform): forms.create/forms.update/forms.delete/submissions.delete * feat(orimon): leads.update/leads.delete/conversations.assign/conversations.tag * feat(paddle): customers.create/customers.update/prices.create/transactions.refund/subscript... * feat(parseur): documents.delete/templates.list/templates.train/mailboxes.list * feat(pastefy): pastes.update/folders.list/folders.create/pastes.share * feat(paywhirl): customers.update/customers.delete/invoices.create/subscriptions.pause * feat(pdfmonkey): templates.list/documents.share/documents.regenerate * feat(personal-ai): memory.delete/document.delete/training.delete/persona.update * feat(pinch-payments): payments.refund/subscriptions.cancel/payers.delete/sources.remove * feat(pinecone): assistants.update/assistants.files.delete/backups.create * feat(pipedream): workflows.deploy/workflows.disable/subscriptions.delete/sources.create * feat(placid): images.delete/pdfs.delete/videos.delete/templates.get * feat(pocketbase): collections.list/collections.create/collections.delete * feat(pollybot-ai): leads.tag/leads.bulk-import/campaigns.list/campaigns.start * feat(posthog): feature-flags.update/feature-flags.delete/cohorts.create/annotations.create/i... * feat(postiz): posts.update/posts.schedule/integrations.disconnect/media.delete * feat(postmark): templates.update/templates.delete/bounces.delete/servers.update * feat(productboard): features.delete/notes.update/notes.delete/components.create * feat(qawafel): products.delete/orders.refund/invoices.update/invoices.send * feat(qdrant): snapshots.restore/shards.create/shards.delete/cluster.peer.remove * feat(quaderno): contacts.update/contacts.delete/invoices.update/invoices.deliver/credits.create * feat(quickbooks): customers.delete/invoices.delete/invoices.send/bills.create/vendors.create * feat(quickzu): orders.cancel/orders.refund/discounts.delete/promo-codes.delete/categories.re... * feat(reachinbox): campaigns.create/campaigns.delete/templates.list/inbox.replies.fetch * feat(reply-io): campaigns.list/campaigns.start/campaigns.pause/templates.create/{name} * feat(retable): records.delete/projects.delete/workspaces.delete/retables.delete * feat(saastic): customers.update/customers.delete/charges.refund/subscriptions.cancel * feat(saleor): orders.cancel/orders.fulfill/orders.refund/orders.update * feat(salesforce): records.delete/records.upsert/records.composite/files.upload * feat(sanity): documents.createOrReplace/documents.publish/documents.delete-batch * feat(sardis): transactions.get/payment.refund/policy.delete/balance.update * feat(savvycal): links.create/links.update/events.update/workflows.create * feat(segment): audiences.update/audiences.delete/tracking-plans.connect/warehouses.create * feat(sendgrid): contacts.delete/lists.delete/lists.addContacts/lists.removeContacts/suppressi... * feat(sendinblue): lists.create/lists.delete/lists.addContacts/campaigns.send/transactional.send * feat(sendpulse): addressbooks.create/addressbooks.delete/campaigns.create/campaigns.cancel * feat(senja): testimonials.update/testimonials.delete/tags.create/collections.list * feat(sentry): issues.resolve/issues.ignore/issues.assign/alerts.list/alerts.create * feat(service-now): incidents.close/incidents.assign/changes.create/tasks.create * feat(sharepoint): files.delete/files.move/permissions.grant/permissions.revoke/lists.items.create * feat(short-io): domains.list/domains.create/links.import/targeting.delete * feat(sign-now): documents.delete/invites.resend/documents.download/templates.create * feat(signrequest): requests.remind/requests.delete/documents.upload/templates.list * feat(simplybookme): clients.update/services.list/providers.list/bookings.confirm * feat(skyprep): users.unenroll/users.create/users.delete/groups.list * feat(smartlead): campaigns.start/campaigns.pause/campaigns.delete/leads.update/leads.remove * feat(smartsheet): sheets.create/rows.delete/attachments.delete/shares.create/comments.create * feat(smartsuite): tables.list/fields.list/records.bulk-create/comments.create * feat(smoove): subscribers.update/subscribers.delete/lists.delete/campaigns.send * feat(snowflake): tables.create/tables.drop/stages.create/stages.unloadData/warehouses.list * feat(square): customers.delete/invoices.delete/invoices.publish/payments.refund/catalog.ups... * feat(stripe): customers.delete/invoices.send/invoices.void/products.update/charges.capture/... * feat(supabase): projects.delete/branches.create/branches.delete/secrets.delete/storage.upload * feat(systeme-io): contacts.delete/tags.create/tags.delete/campaigns.list/campaigns.subscribe * feat(tableau): workbooks.delete/subscriptions.create/datasources.delete * feat(talkable): advocates.update/advocates.delete/referrals.update/rewards.issue * feat(tangle-id): workspaces.create/workspaces.delete/members.invite/members.remove * feat(teable): tables.list/tables.create/{name}/fields.create/views.create * feat(telegram): editMessageMedia/pinChatMessage/unpinChatMessage/banChatMember/restrictChatMe... * feat(telnyx): calls.hangup/calls.transfer/numbers.list/numbers.update * feat(textcortex-ai): templates.list/personas.list/history.list/history.delete * feat(ticktick): projects.create/projects.update/projects.delete/tasks.move * feat(time-ops): customers.update/projects.update/registrations.update/registrations.delete * feat(timelines-ai): chats.assign/chats.tag/messages.markRead/notes.create * feat(tl-dv): meetings.delete/highlights.create/share-links.create/summaries.regenerate * feat(todoist): projects.create/projects.delete/comments.create/labels.create * feat(toggl-track): clients.update/clients.delete/projects.update/projects.delete/time-entries.de... * feat(totalcms): posts.delete/content.delete/media.delete/media.list * feat(trello): lists.archive/boards.update/checklists.checkItem/labels.create/{name}/webhook... * feat(twenty): opportunities.update/{name}/opportunities.delete/contacts.delete/companies.de... * feat(twilio): messages.delete/calls.cancel/calls.update/numbers.list/numbers.update * feat(twilio-sms): send_mms/send_whatsapp/redact_message/list_numbers * feat(typeform): forms.create/forms.delete/themes.list/images.create * feat(typefully): drafts.update/drafts.unschedule/accounts.list/media.delete * feat(umami): websites.create/websites.update/websites.delete/teams.list * feat(upgradechat): subscriptions.cancel/products.update/products.delete/invoices.refund * feat(uscreen): users.update/users.delete/access.revoke/users.list * feat(vapi): calls.hangup/assistants.create/{name}/assistants.delete/phone-numbers.list * feat(vbout): contacts.delete/lists.delete/campaigns.send/campaigns.delete * feat(videoask): forms.create/forms.update/forms.delete/responses.delete * feat(vlm-run): files.upload/files.delete/jobs.list/jobs.cancel * feat(vtex): products.delete/categories.create/skus.update/orders.cancel * feat(vtiger): records.assign/records.convert/files.upload/comments.create * feat(wafeq): contacts.delete/invoices.delete/bills.delete/items.delete * feat(weaviate): classes.update/schema.shards/backups.create/backups.restore * feat(webflow): items.unpublish/collections.create/collections.delete/sites.publish * feat(what-converts): leads.delete/leads.qualify/accounts.list/users.list * feat(whatsapp): messages.reply/messages.react/messages.delete/contacts.list * feat(whatsapp-business): media.upload/templates.create/templates.delete/messages.mark-read * feat(wonderchat): page.remove/bot.train/conversations.list/conversations.delete * feat(woocommerce): customers.update/customers.delete/products.update/products.delete/orders.upda... * feat(woodpecker): prospect.update/prospect.remove-from-campaign/prospect.stop/campaigns.list * feat(wordpress): pages.update/pages.delete/comments.create/comments.delete/media.upload/catego... * feat(workable): candidates.create/candidates.disqualify/candidates.revert/jobs.publish/offers... * feat(wrike): tasks.delete/tasks.complete/folders.delete/comments.update * feat(wufoo): entries.update/entries.delete/webhooks.create/webhooks.delete * feat(xero): contacts.archive/invoices.delete/invoices.email/payments.create/credit-notes.... * feat(youcanbookme): bookings.cancel/bookings.reschedule/profiles.update/profiles.delete * feat(zagomail): subscribers.delete/subscribers.unsubscribe/tags.delete/campaigns.send * feat(zendesk): tickets.delete/tickets.merge/tickets.add-comment/users.update/users.delete * feat(zendesk-sell): contacts.delete/leads.update/leads.delete/deals.delete/tasks.create * feat(zeplin): notes.update/notes.delete/projects.list/components.list * feat(zoho-bookings): services.list/staff.list/appointment.complete/appointment.no-show * feat(zoho-crm): records.convert/records.assign/notes.create/files.upload * feat(zoho-desk): tickets.close/tickets.assign/tickets.add-comment/tickets.merge * feat(zoom): meetings.end/webinars.update/webinars.delete/recordings.delete/users.create * feat(zuora): subscriptions.create/subscriptions.cancel/subscriptions.update/payments.create * fix(tangle-middleware test): add createWorkspace/deleteWorkspace/inviteMember/removeMember mock methods Mock factory drifted from TangleIdentityClient interface after 4 new methods landed on the substrate. vitest passes via swc transpilation; tsc strict catches the missing properties. --------- Co-authored-by: Drew Stone <hello@tangle.tools>
1 parent e520875 commit caab08b

241 files changed

Lines changed: 34238 additions & 650 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/connectors/adapters/__tests__/quickbooks.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,16 @@ describe('quickbooks adapter manifest', () => {
4343
'customers.get',
4444
'customers.create',
4545
'customers.update',
46+
'customers.delete',
4647
'invoices.get',
4748
'invoices.create',
4849
'invoices.update',
50+
'invoices.delete',
51+
'invoices.send',
4952
'items.create',
5053
'payments.create',
54+
'bills.create',
55+
'vendors.create',
5156
].sort(),
5257
)
5358
const reads = quickbooksConnector.manifest.capabilities
@@ -60,12 +65,17 @@ describe('quickbooks adapter manifest', () => {
6065
.sort()
6166
expect(reads).toEqual(['customers.get', 'entities.query', 'invoices.get'])
6267
expect(mutations).toEqual([
68+
'bills.create',
6369
'customers.create',
70+
'customers.delete',
6471
'customers.update',
6572
'invoices.create',
73+
'invoices.delete',
74+
'invoices.send',
6675
'invoices.update',
6776
'items.create',
6877
'payments.create',
78+
'vendors.create',
6979
])
7080
})
7181

src/connectors/adapters/__tests__/sendgrid.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,31 @@ describe('sendgrid adapter', () => {
3232
expect(sendgridConnector.manifest.auth.kind).toBe('api-key')
3333
const names = sendgridConnector.manifest.capabilities.map((c) => c.name).sort()
3434
expect(names).toEqual([
35+
'contacts.delete',
3536
'contacts.get',
3637
'contacts.search',
3738
'contacts.upsert',
39+
'lists.addContacts',
3840
'lists.create',
41+
'lists.delete',
42+
'lists.removeContacts',
3943
'lists.search',
4044
'mail.send',
45+
'suppressions.create',
4146
])
4247
const readers = sendgridConnector.manifest.capabilities.filter((c) => c.class === 'read').map((c) => c.name)
4348
const mutators = sendgridConnector.manifest.capabilities.filter((c) => c.class === 'mutation').map((c) => c.name)
4449
expect(readers).toEqual(['contacts.search', 'contacts.get', 'lists.search'])
45-
expect(mutators).toEqual(['mail.send', 'contacts.upsert', 'lists.create'])
50+
expect(mutators).toEqual([
51+
'mail.send',
52+
'contacts.upsert',
53+
'lists.create',
54+
'contacts.delete',
55+
'lists.delete',
56+
'lists.addContacts',
57+
'lists.removeContacts',
58+
'suppressions.create',
59+
])
4660
})
4761

4862
it('exposes both executeRead and executeMutation handlers', () => {

src/connectors/adapters/__tests__/typeform.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,15 @@ describe('typeform adapter', () => {
5353
const names = typeformConnector.manifest.capabilities.map((c) => c.name).sort()
5454
expect(names).toEqual([
5555
'account.get',
56+
'forms.create',
57+
'forms.delete',
5658
'forms.get',
5759
'forms.list',
5860
'forms.update',
61+
'images.create',
5962
'responses.delete',
6063
'responses.list',
64+
'themes.list',
6165
'webhooks.delete',
6266
'webhooks.get',
6367
'webhooks.list',
@@ -71,12 +75,16 @@ describe('typeform adapter', () => {
7175
'forms.get',
7276
'forms.list',
7377
'responses.list',
78+
'themes.list',
7479
'webhooks.get',
7580
'webhooks.list',
7681
'workspaces.list',
7782
])
7883
expect(mutators).toEqual([
84+
'forms.create',
85+
'forms.delete',
7986
'forms.update',
87+
'images.create',
8088
'responses.delete',
8189
'webhooks.delete',
8290
'webhooks.upsert',

src/connectors/adapters/__tests__/whatsapp-business.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,20 @@ describe('whatsappBusiness adapter', () => {
6565
'send_template_message',
6666
'list_message_templates',
6767
'get_business_phone_number',
68+
'media.upload',
69+
'templates.create',
70+
'templates.delete',
71+
'messages.mark-read',
6872
])
6973

7074
for (const cap of adapter.manifest.capabilities) {
7175
if (cap.class === 'mutation') {
72-
// outbound chat is append-only — Meta has no idempotency primitive on /messages,
73-
// so MutationGuard owns dedup above us.
74-
expect(cap.cas).toBe('none')
7576
expect(cap.externalEffect).toBe(true)
77+
if (cap.name === 'send_text_message' || cap.name === 'send_template_message') {
78+
// outbound chat is append-only — Meta has no idempotency primitive on /messages,
79+
// so MutationGuard owns dedup above us.
80+
expect(cap.cas).toBe('none')
81+
}
7682
}
7783
}
7884
})

src/connectors/adapters/__tests__/xero.test.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,34 @@ describe('xero adapter manifest', () => {
5252
const names = xeroConnector.manifest.capabilities.map((c) => c.name).sort()
5353
expect(names).toEqual(
5454
[
55-
'contacts.search',
56-
'contacts.get',
55+
'accounts.search',
56+
'contacts.archive',
5757
'contacts.create',
58+
'contacts.get',
59+
'contacts.search',
5860
'contacts.update',
59-
'invoices.search',
60-
'invoices.get',
61+
'credit-notes.create',
6162
'invoices.create',
62-
'accounts.search',
63-
].sort(),
63+
'invoices.delete',
64+
'invoices.email',
65+
'invoices.get',
66+
'invoices.search',
67+
'payments.create',
68+
],
6469
)
6570
const reads = xeroConnector.manifest.capabilities.filter((c) => c.class === 'read').map((c) => c.name)
6671
const mutations = xeroConnector.manifest.capabilities.filter((c) => c.class === 'mutation').map((c) => c.name)
6772
expect(reads.sort()).toEqual(['accounts.search', 'contacts.get', 'contacts.search', 'invoices.get', 'invoices.search'])
68-
expect(mutations.sort()).toEqual(['contacts.create', 'contacts.update', 'invoices.create'])
73+
expect(mutations.sort()).toEqual([
74+
'contacts.archive',
75+
'contacts.create',
76+
'contacts.update',
77+
'credit-notes.create',
78+
'invoices.create',
79+
'invoices.delete',
80+
'invoices.email',
81+
'payments.create',
82+
])
6983

7084
const contactRead = xeroConnector.manifest.capabilities.find((c) => c.name === 'contacts.search')!
7185
expect(contactRead.requiredScopes).toEqual(['accounting.contacts'])

src/connectors/adapters/__tests__/zendesk.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,17 @@ describe('zendeskConnector', () => {
6767
const byName = Object.fromEntries(caps.map((c) => [c.name, c]))
6868

6969
expect(Object.keys(byName).sort()).toEqual([
70+
'tickets.add-comment',
7071
'tickets.create',
72+
'tickets.delete',
7173
'tickets.get',
74+
'tickets.merge',
7275
'tickets.search',
7376
'tickets.update',
7477
'users.create',
78+
'users.delete',
7579
'users.search',
80+
'users.update',
7681
])
7782

7883
expect(byName['tickets.search'].class).toBe('read')

src/connectors/adapters/n8n.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,101 @@ export const n8nConnector = declarativeRestConnector({
137137
body: '{payload}',
138138
},
139139
},
140+
{
141+
name: 'workflows.create',
142+
class: 'mutation',
143+
description:
144+
'Create a new n8n workflow. `definition` must include the full workflow JSON shape n8n expects (name, nodes, connections, settings). Newly-created workflows start inactive.',
145+
parameters: {
146+
type: 'object',
147+
properties: {
148+
definition: {
149+
type: 'object',
150+
description: 'Full workflow definition (name, nodes, connections, settings, etc.).',
151+
},
152+
},
153+
required: ['definition'],
154+
},
155+
request: {
156+
method: 'POST',
157+
path: '/api/v1/workflows',
158+
body: '{definition}',
159+
},
160+
cas: 'native-idempotency',
161+
externalEffect: true,
162+
},
163+
{
164+
name: 'workflows.update',
165+
class: 'mutation',
166+
description:
167+
'Replace an n8n workflow definition. `definition` is the full workflow JSON (nodes, connections, settings) that overwrites the existing record.',
168+
parameters: {
169+
type: 'object',
170+
properties: {
171+
workflowId: { type: 'string' },
172+
definition: {
173+
type: 'object',
174+
description: 'Full workflow definition replacing the current one.',
175+
},
176+
},
177+
required: ['workflowId', 'definition'],
178+
},
179+
request: {
180+
method: 'PUT',
181+
path: '/api/v1/workflows/{workflowId}',
182+
body: '{definition}',
183+
},
184+
cas: 'native-idempotency',
185+
externalEffect: true,
186+
},
187+
{
188+
name: 'workflows.delete',
189+
class: 'mutation',
190+
description: 'Permanently delete an n8n workflow by id.',
191+
parameters: {
192+
type: 'object',
193+
properties: { workflowId: { type: 'string' } },
194+
required: ['workflowId'],
195+
},
196+
request: {
197+
method: 'DELETE',
198+
path: '/api/v1/workflows/{workflowId}',
199+
},
200+
cas: 'native-idempotency',
201+
externalEffect: true,
202+
},
203+
{
204+
name: 'executions.delete',
205+
class: 'mutation',
206+
description: 'Delete a past n8n execution record by id.',
207+
parameters: {
208+
type: 'object',
209+
properties: { executionId: { type: 'string' } },
210+
required: ['executionId'],
211+
},
212+
request: {
213+
method: 'DELETE',
214+
path: '/api/v1/executions/{executionId}',
215+
},
216+
cas: 'native-idempotency',
217+
externalEffect: true,
218+
},
219+
{
220+
name: 'executions.stop',
221+
class: 'mutation',
222+
description:
223+
'Stop a running n8n execution. Returns the final execution record once the runtime acknowledges the stop signal.',
224+
parameters: {
225+
type: 'object',
226+
properties: { executionId: { type: 'string' } },
227+
required: ['executionId'],
228+
},
229+
request: {
230+
method: 'POST',
231+
path: '/api/v1/executions/{executionId}/stop',
232+
},
233+
cas: 'native-idempotency',
234+
externalEffect: true,
235+
},
140236
],
141237
})

src/connectors/adapters/nocodb.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,94 @@ export const nocodbConnector = declarativeRestConnector({
108108
},
109109
cas: 'optimistic-read-verify',
110110
},
111+
{
112+
name: 'tables.list',
113+
class: 'read',
114+
description: 'List tables in a NocoDB base/project.',
115+
parameters: {
116+
type: 'object',
117+
properties: {
118+
projectId: { type: 'string', description: 'The base/project ID to list tables for.' },
119+
},
120+
required: ['projectId'],
121+
},
122+
request: {
123+
method: 'GET',
124+
path: '/api/v1/db/meta/projects/{projectId}/tables',
125+
},
126+
},
127+
{
128+
name: 'tables.create',
129+
class: 'mutation',
130+
description:
131+
'Create a new table inside a NocoDB base/project. `definition` carries the table schema (table_name, title, columns).',
132+
parameters: {
133+
type: 'object',
134+
properties: {
135+
projectId: { type: 'string', description: 'The base/project ID to create the table in.' },
136+
definition: {
137+
type: 'object',
138+
description: 'Table definition (table_name, title, columns, etc.).',
139+
},
140+
},
141+
required: ['projectId', 'definition'],
142+
},
143+
request: {
144+
method: 'POST',
145+
path: '/api/v1/db/meta/projects/{projectId}/tables',
146+
body: '{definition}',
147+
},
148+
cas: 'native-idempotency',
149+
externalEffect: true,
150+
},
151+
{
152+
name: 'fields.create',
153+
class: 'mutation',
154+
description:
155+
'Add a column/field to an existing NocoDB table. `column` describes the new field (column_name, title, uidt, etc.).',
156+
parameters: {
157+
type: 'object',
158+
properties: {
159+
tableId: { type: 'string', description: 'The table ID to add the column to.' },
160+
column: {
161+
type: 'object',
162+
description: 'Column definition (column_name, title, uidt, etc.).',
163+
},
164+
},
165+
required: ['tableId', 'column'],
166+
},
167+
request: {
168+
method: 'POST',
169+
path: '/api/v1/db/meta/tables/{tableId}/columns',
170+
body: '{column}',
171+
},
172+
cas: 'native-idempotency',
173+
externalEffect: true,
174+
},
175+
{
176+
name: 'records.bulk-create',
177+
class: 'mutation',
178+
description:
179+
'Insert many records into a NocoDB table in one request. `records` is an array of field-value objects.',
180+
parameters: {
181+
type: 'object',
182+
properties: {
183+
tableId: { type: 'string', description: 'The table ID to insert into.' },
184+
records: {
185+
type: 'array',
186+
description: 'Array of record objects to insert.',
187+
items: { type: 'object' },
188+
},
189+
},
190+
required: ['tableId', 'records'],
191+
},
192+
request: {
193+
method: 'POST',
194+
path: '/api/v1/db/data/bulk/noco/{tableId}',
195+
body: '{records}',
196+
},
197+
cas: 'native-idempotency',
198+
externalEffect: true,
199+
},
111200
],
112201
})

0 commit comments

Comments
 (0)