Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 0 additions & 72 deletions .github/templates/README.md

This file was deleted.

67 changes: 0 additions & 67 deletions .github/workflows/setup.yaml

This file was deleted.

96 changes: 78 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,92 @@
# AirSync snap-in template
# Tasks Demo AirSync connector

This repository provides a template to implement an AirSync snap-in that imports data from an external system into DevRev.
A one-way connector from a small Tasks API to DevRev issues, built from `devrev/airsync-template` and the supplied data extraction guide. See [TEMPLATE_SOURCE.md](TEMPLATE_SOURCE.md) for the exact template revision and source link.

## Usage
## Scope

1. Click "Use this template" > "Create a new repository"
2. Name your repository `airdrop-<external-system-name>-snap-in` (e.g., `airdrop-github-snap-in`)
3. Clone and follow the setup instructions in the generated README
- One project, `demo-project`, discovered as an external sync unit.
- Ten seeded tasks, imported as issues with their source title and timestamps.
- Three tasks per worker invocation, with persisted continuation state.
- Incremental extraction using the source update time and the platform time window.
- Selection, source validation, HTTP timeout, and rate-limit delay handling.
- Static metadata and initial issue mapping, plus a bearer-token connection.

## Documentation
The connector exposes extraction only. Attachment extraction completes without records because this source has no attachments. Reverse sync, users, comments, and deletions are outside this example.

See the [AirSync snap-in documentation](https://developer.devrev.ai/airsync) for development guidance.
## Run locally

The template uses `@devrev/airsync-sdk` and requires Node.js 20 or newer.
Requires Node.js 22 or newer and npm. From the repository root:

## Local Fixture Testing
```sh
cd code
npm ci
cp .env.example .env
npm run source
```

From the `code/` directory, you can run the built-in fixture runner to test the
template locally:
Keep that terminal running. In a second terminal, from `code/`:

```bash
```sh
npm start -- --fixturePath start_extracting_external_sync_units
npm start -- --fixturePath start_extracting_metadata
npm start -- --fixturePath start_extracting_data
npm start -- --fixturePath start_extracting_data_selective
npm start -- --fixturePath start_loading_data
npm start -- --fixturePath start_extracting_attachments
```

These use the template fixture runner and a local mock of DevRev platform endpoints. SDK output files are copied to `code/extracted_files/`. Each fixture invokes one worker; the data fixture extracts the first three tasks. The selective fixture excludes tasks and produces no task artifact. The integration test below drives all continuations automatically.

The source API listens on `127.0.0.1:3100`. It stores records in memory and resets when restarted. `.env.example` contains a local demonstration token; use the same token on the source and its connection.

## Change source data

```sh
curl -X PATCH -H 'Authorization: Bearer local-demo-token' \
-H 'Content-Type: application/json' \
-d '{"title":"Edited example task"}' \
http://127.0.0.1:3100/tasks/1
```

After editing, the `start_extracting_data_incremental` fixture selects updates since February 1, 2026; the seeded task timestamps precede that date. The API's `GET /tasks` accepts `project_id`, `limit`, `cursor`, `updated_from`, and `updated_to`. Time boundaries are inclusive. Its offset pagination assumes the source is not edited during a paginated run.

## Verify

From `code/`:

```sh
npm run build
npm run lint
npm test
npm run test:source
npm run test:integration
npm run package
```

The runner reads `code/.env` and resolves `${TODO_API_KEY}` in fixture files,
so copy `code/.env.example` before running the fixtures.
The fixture runner and local test server set `AIRSYNC_LOCAL_DEVELOPMENT=1`
to enable local logging and artifact copies in `code/extracted_files/`.
The integration test starts both HTTP servers itself. It executes the actual SDK worker threads, installs the mapping against a simulated platform endpoint, saves and reloads state, receives callbacks, uploads and decompresses JSONL artifacts, and checks discovery, metadata, all ten tasks across four invocations, one incremental update, selection exclusion, and attachment completion.

Unit checks cover rate-limit delay, upload failure without cursor advancement, malformed data, repeated cursors, and numeric/HTTP-date retry hints. These checks do not run DevRev's transformer or create real issues.

## Configure for a live DevRev import

1. Host `source-api/server.mjs` at an HTTPS origin reachable from DevRev. Set `EXAMPLE_API_TOKEN` to a new token and set `HOST=0.0.0.0` if your hosting platform needs it. `PORT` defaults to 3100. The included source is an in-memory demonstration service.
2. From `code/`, set the source origin before building:

```sh
npm run configure:source -- https://your-tasks-host.example
npm run package
```

This writes the origin to the bundled `source-config.json` and the manifest verification and organization URLs. `EXAMPLE_API_ORIGIN` overrides it for local runs. Deployed credentials come from the connection key, not local environment variables.
3. Validate the manifest against the intended authenticated DevRev profile:

```sh
devrev snap_in_version validate-manifest ../manifest.yaml
```

4. The template deployment script is retained as `npm run deploy`. It prompts for your environment, organization, email, deployment mode, and package slug. Deployment has not been run here.
5. In DevRev, create the **Tasks API Connection** using the source bearer token, start **Tasks Demo**, and select **Demo Project**. Credentials are verified against the authenticated `/projects` endpoint when creating the connection. Organization discovery uses that same endpoint and identifies this single-account demo as `tasks-demo` / **Tasks Demo**.
6. Review the Tasks-to-issue mapping and select a DevRev product and default issue owner, creator, and modifier. Issues default to Backlog and priority P2 because this source only provides titles and timestamps. No organization-specific record IDs are hardcoded.

## Validation status

Build, lint, five unit tests, one source API test, one complete SDK lifecycle test, four fixture runs, manifest YAML/wiring checks, and package creation passed locally. The DevRev CLI manifest check could not run because its selected profile is not authenticated. Live manifest acceptance, mapping acceptance, and real DevRev issue creation remain unverified.
12 changes: 12 additions & 0 deletions TEMPLATE_SOURCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Template: https://github.com/devrev/airsync-template
Commit: 050349c2e3308507cfcb63cf974883565c72a63c

Extraction guide: https://devrev-marketing-developer-git-docs-iss-383917-a-68bdfc-dev-rev.vercel.app/airsync/data-extraction-example

The connector uses the template runtime, manifest structure, metadata/mapping format, fixture runner, and SDK dependency. The extraction loop follows the supplied guide. No other connector documentation was consulted.

Live deployment exposed a missing required secret token-verification configuration. The follow-up fix uses the connector deployment plugin's documented bearer-token manifest pattern and verifies against the source `/projects` endpoint.

Live discovery also requires the connection's external organization ID and name. The manifest uses the plugin's documented static organization pattern for this single-account demo, verified against `/projects`. The requirement was confirmed in the platform's keyring validation source.

Live mapping installation requires issue stage, priority, and owner mappings. The domain-mapping skill and AirSync mapping tools supplied Backlog, P2, and a configurable default owner; both metadata and initial mapping pass chef-cli validation.
18 changes: 18 additions & 0 deletions VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Local validation

Date: October 15, 2026

| Check | Result |
| --- | --- |
| TypeScript build | Passed |
| ESLint | Passed |
| Worker/client unit tests | 5 passed |
| Source API integration test | 1 passed |
| Actual SDK worker lifecycle test | 1 passed |
| Template fixture runner | Discovery, metadata, initial data page, and selection passed |
| Manifest YAML and local function wiring | Passed |
| Code package creation | Passed |
| Live manifest validation | Not run: selected CLI profile is not authenticated |
| Deployment and DevRev issue creation | Not run |

The SDK lifecycle test uses the real `@devrev/airsync-sdk` 2.0.0 worker threads and a local HTTP platform simulator. It verifies project discovery, metadata artifacts, ten normalized tasks across four invocations, persisted cursor state, a new incremental run importing one edited task, selection exclusion, and empty attachment completion. It decompresses uploaded artifact bytes to check their records. It does not validate DevRev transformer behavior or live mapping semantics.
12 changes: 3 additions & 9 deletions code/.env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
# Your DevRev organization slug (the part after app.devrev.ai/ in your DevRev URL)
EXAMPLE_API_ORIGIN=http://127.0.0.1:3100
EXAMPLE_API_TOKEN=local-demo-token
DEV_ORG=my-org

# Your email address
USER_EMAIL=my@email.com

# Environment
ENV=dev|qa|prod

# TODO: add External system specific variables that will be replaced in the Fixture tests that you want to run.
TODO_API_KEY=test-api-key
ENV=dev
1 change: 0 additions & 1 deletion code/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// eslint-disable-next-line no-undef
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
Expand Down
16 changes: 9 additions & 7 deletions code/eslint.config.mts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";
import prettierConfig from "eslint-config-prettier";
import js from '@eslint/js';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import { defineConfig } from 'eslint/config';
import prettierConfig from 'eslint-config-prettier';

export default defineConfig([
{ ignores: ["dist/**", "test/**"] },
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
{ ignores: ['dist/**', 'test/**', 'extracted_files/**'] },
{ files: ['**/*.{js,mjs,cjs,ts,mts,cts}'], languageOptions: { globals: globals.node } },
js.configs.recommended,
...tseslint.configs.recommended,
{ files: ['integration/**/*.cjs'], rules: { '@typescript-eslint/no-require-imports': 'off' } },
prettierConfig,
]);
16 changes: 16 additions & 0 deletions code/fixtures/start_extracting_attachments/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"payload": {
"connection_data": {
"org_id": "tasks-demo",
"org_name": "Tasks Demo",
"key": "${EXAMPLE_API_TOKEN}",
"key_type": "pat"
},
"event_type": "START_EXTRACTING_ATTACHMENTS",
"event_context": {
"external_sync_unit_id": "demo-project",
"external_sync_unit_name": "Demo Project",
"run_id": "demo-initial-run"
}
}
}
5 changes: 5 additions & 0 deletions code/fixtures/start_extracting_attachments/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"runId": "",
"cursor": null,
"completed": false
}
10 changes: 6 additions & 4 deletions code/fixtures/start_extracting_data/event.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"payload": {
"connection_data": {
"org_id": "test_org",
"org_name": "Test Organization",
"key": "${TODO_API_KEY}",
"org_id": "tasks-demo",
"org_name": "Tasks Demo",
"key": "${EXAMPLE_API_TOKEN}",
"key_type": "pat"
},
"event_type": "START_EXTRACTING_DATA",
"event_context": {
"external_sync_unit_id": "test_external_sync_unit_id"
"external_sync_unit_id": "demo-project",
"external_sync_unit_name": "Demo Project",
"run_id": "demo-initial-run"
}
}
}
6 changes: 3 additions & 3 deletions code/fixtures/start_extracting_data/state.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"todos": { "completed": false },
"users": { "completed": false },
"attachments": { "completed": false }
"runId": "",
"cursor": null,
"completed": false
}
17 changes: 17 additions & 0 deletions code/fixtures/start_extracting_data_incremental/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"payload": {
"connection_data": {
"org_id": "tasks-demo",
"org_name": "Tasks Demo",
"key": "${EXAMPLE_API_TOKEN}",
"key_type": "pat"
},
"event_type": "START_EXTRACTING_DATA",
"event_context": {
"external_sync_unit_id": "demo-project",
"external_sync_unit_name": "Demo Project",
"run_id": "demo-incremental-run",
"extract_from": "2026-02-01T00:00:00.000Z"
}
}
}
Loading
Loading