@secondlayer/cli 3.1.2-alpha.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +479 -336
- package/dist/cli.js.map +5 -4
- package/package.json +6 -6
- package/templates/subscriptions/cloudflare/README.md +39 -0
- package/templates/subscriptions/cloudflare/package.json +15 -0
- package/templates/subscriptions/cloudflare/src/index.ts +34 -0
- package/templates/subscriptions/cloudflare/tsconfig.json +10 -0
- package/templates/subscriptions/cloudflare/wrangler.toml +8 -0
- package/templates/subscriptions/inngest/README.md +29 -0
- package/templates/subscriptions/inngest/package.json +18 -0
- package/templates/subscriptions/inngest/src/inngest.ts +19 -0
- package/templates/subscriptions/inngest/src/server.ts +14 -0
- package/templates/subscriptions/inngest/tsconfig.json +11 -0
- package/templates/subscriptions/node/.env.example +2 -0
- package/templates/subscriptions/node/README.md +34 -0
- package/templates/subscriptions/node/package.json +18 -0
- package/templates/subscriptions/node/src/index.ts +50 -0
- package/templates/subscriptions/node/tsconfig.json +11 -0
- package/templates/subscriptions/trigger/README.md +35 -0
- package/templates/subscriptions/trigger/package.json +17 -0
- package/templates/subscriptions/trigger/src/tasks/subgraph-event.ts +15 -0
- package/templates/subscriptions/trigger/trigger.config.ts +6 -0
- package/templates/subscriptions/trigger/tsconfig.json +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secondlayer/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "CLI for subgraphs and blockchain indexing on Stacks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@inquirer/prompts": "^8.2.0",
|
|
45
|
-
"@secondlayer/bundler": "^0.3.1
|
|
46
|
-
"@secondlayer/sdk": "^3.0.0
|
|
47
|
-
"@secondlayer/shared": "^3.0.0
|
|
48
|
-
"@secondlayer/stacks": "^1.0.0
|
|
49
|
-
"@secondlayer/subgraphs": "^1.0.0
|
|
45
|
+
"@secondlayer/bundler": "^0.3.1",
|
|
46
|
+
"@secondlayer/sdk": "^3.0.0",
|
|
47
|
+
"@secondlayer/shared": "^3.0.0",
|
|
48
|
+
"@secondlayer/stacks": "^1.0.0",
|
|
49
|
+
"@secondlayer/subgraphs": "^1.0.0",
|
|
50
50
|
"@biomejs/js-api": "^0.7.0",
|
|
51
51
|
"@biomejs/wasm-nodejs": "^1.9.0",
|
|
52
52
|
"esbuild": "^0.19.0",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# {{NAME}} — Cloudflare Workflows subscription receiver
|
|
2
|
+
|
|
3
|
+
A Cloudflare Workflow triggered by a Secondlayer subscription via the
|
|
4
|
+
`workflows/instances` REST API.
|
|
5
|
+
|
|
6
|
+
## Run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun install
|
|
10
|
+
npx wrangler login
|
|
11
|
+
npx wrangler dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`wrangler dev` spins up a local runtime. Your subscription URL should
|
|
15
|
+
point at Cloudflare's API:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/workflows/{{NAME}}/instances
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
With `auth_config`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{ "authType": "bearer", "token": "<CF_API_TOKEN>" }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The token must have `Workflows: Edit` scope.
|
|
28
|
+
|
|
29
|
+
## Signature verification
|
|
30
|
+
|
|
31
|
+
Cloudflare authenticates the API call with the bearer token. Inside your
|
|
32
|
+
workflow the `event.payload.params._outboxId` is a stable dedup key if
|
|
33
|
+
you want idempotent replay handling.
|
|
34
|
+
|
|
35
|
+
## Deploy
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx wrangler deploy
|
|
39
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "wrangler dev",
|
|
7
|
+
"deploy": "wrangler deploy",
|
|
8
|
+
"types": "wrangler types"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@cloudflare/workers-types": "^4.20250101.0",
|
|
12
|
+
"typescript": "^5",
|
|
13
|
+
"wrangler": "^4.0.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkflowEntrypoint,
|
|
3
|
+
type WorkflowEvent,
|
|
4
|
+
type WorkflowStep,
|
|
5
|
+
} from "cloudflare:workers";
|
|
6
|
+
|
|
7
|
+
interface Params {
|
|
8
|
+
_type: string;
|
|
9
|
+
_outboxId: string;
|
|
10
|
+
[k: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class OnSubgraphEvent extends WorkflowEntrypoint<Env, Params> {
|
|
14
|
+
async run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<void> {
|
|
15
|
+
const params = event.payload;
|
|
16
|
+
await step.do("handle", async () => {
|
|
17
|
+
console.log("[subgraph event]", params._type, params);
|
|
18
|
+
// TODO: plug in your business logic.
|
|
19
|
+
return { ok: true };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Env {
|
|
25
|
+
MY_WORKFLOW: Workflow<Params>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
async fetch(_req: Request): Promise<Response> {
|
|
30
|
+
return new Response(
|
|
31
|
+
"This worker is a Workflow receiver — trigger it via the Cloudflare API.",
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# {{NAME}} — Inngest subscription receiver
|
|
2
|
+
|
|
3
|
+
Inngest Dev Server with a function wired to `{{EVENT_NAME}}`. Events come
|
|
4
|
+
from your Secondlayer subscription — Inngest handles retries, concurrency,
|
|
5
|
+
and durable execution.
|
|
6
|
+
|
|
7
|
+
## Run
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun install
|
|
11
|
+
bun run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This starts the Inngest dev UI at `http://localhost:8288`. Point your
|
|
15
|
+
subscription URL at the Inngest event endpoint shown in the dev UI
|
|
16
|
+
(typically `http://localhost:8288/e/{eventKey}` for local, or the hosted
|
|
17
|
+
`https://inn.gs/e/{eventKey}` endpoint for cloud).
|
|
18
|
+
|
|
19
|
+
## Signature verification
|
|
20
|
+
|
|
21
|
+
Inngest uses its own event key auth — there's no HMAC to verify. The event
|
|
22
|
+
key in the URL IS the auth token. For self-hosted Inngest add an ingress
|
|
23
|
+
allowlist so only the Secondlayer emitter can post.
|
|
24
|
+
|
|
25
|
+
## Deploy
|
|
26
|
+
|
|
27
|
+
- **Inngest Cloud**: create an app at https://inngest.com, copy the event
|
|
28
|
+
key, and set it in the subscription URL: `https://inn.gs/e/{EVENT_KEY}`.
|
|
29
|
+
- **Self-host**: see https://www.inngest.com/docs/self-hosting
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "npx inngest-cli@latest dev",
|
|
8
|
+
"start": "bun --hot src/server.ts"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"hono": "^4.6.0",
|
|
12
|
+
"inngest": "^3.27.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "latest",
|
|
16
|
+
"typescript": "^5"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Inngest } from "inngest";
|
|
2
|
+
|
|
3
|
+
export const inngest = new Inngest({
|
|
4
|
+
id: "{{NAME}}",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const onSubgraphEvent = inngest.createFunction(
|
|
8
|
+
{ id: "on-subgraph-event", name: "On Subgraph Event" },
|
|
9
|
+
{ event: "{{EVENT_NAME}}" },
|
|
10
|
+
async ({ event, step }) => {
|
|
11
|
+
// `event.data` is the row payload delivered by Secondlayer.
|
|
12
|
+
const row = event.data as Record<string, unknown>;
|
|
13
|
+
await step.run("handle", async () => {
|
|
14
|
+
console.log("[subgraph event]", event.name, row);
|
|
15
|
+
// TODO: plug in your business logic — AI SDK, HTTP, DB writes, etc.
|
|
16
|
+
return { ok: true };
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { serve } from "inngest/hono";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { inngest, onSubgraphEvent } from "./inngest.ts";
|
|
4
|
+
|
|
5
|
+
const app = new Hono();
|
|
6
|
+
app.on(
|
|
7
|
+
["GET", "POST", "PUT"],
|
|
8
|
+
"/api/inngest",
|
|
9
|
+
serve({ client: inngest, functions: [onSubgraphEvent] }),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const PORT = Number.parseInt(process.env.PORT ?? "3000", 10);
|
|
13
|
+
Bun.serve({ port: PORT, fetch: app.fetch });
|
|
14
|
+
console.log(`{{NAME}} (Inngest host) listening on :${PORT}`);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# {{NAME}} — Node.js subscription receiver
|
|
2
|
+
|
|
3
|
+
Hono HTTP server that verifies Standard Webhooks signatures and routes events
|
|
4
|
+
to your business logic. No framework lock-in.
|
|
5
|
+
|
|
6
|
+
## Run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun install
|
|
10
|
+
cp .env.example .env # paste the signingSecret you copied on create
|
|
11
|
+
bun run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The server listens on `:3000`. Your subscription's URL should point at
|
|
15
|
+
`http://<host>:3000/webhook`.
|
|
16
|
+
|
|
17
|
+
## Signature verification
|
|
18
|
+
|
|
19
|
+
Every request is verified before your handler runs. Forged requests return
|
|
20
|
+
`401`. The logic lives in `src/index.ts`; swap `@secondlayer/shared` for the
|
|
21
|
+
[Svix verify library](https://docs.standardwebhooks.com/libraries) if you
|
|
22
|
+
want to decouple from `@secondlayer/shared`:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Webhook } from "standardwebhooks";
|
|
26
|
+
const wh = new Webhook(process.env.SIGNING_SECRET!);
|
|
27
|
+
const event = wh.verify(body, headers);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Deploy
|
|
31
|
+
|
|
32
|
+
Fly.io, Render, Railway, your own VM — anywhere that can run `bun` works.
|
|
33
|
+
Expose port 3000, set `SIGNING_SECRET`, point the subscription URL at the
|
|
34
|
+
public hostname.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun --hot src/index.ts",
|
|
8
|
+
"start": "bun src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@secondlayer/shared": "^3.0.0-beta.0",
|
|
12
|
+
"hono": "^4.6.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "latest",
|
|
16
|
+
"typescript": "^5"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { verify } from "@secondlayer/shared/crypto/standard-webhooks";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimal Standard Webhooks receiver.
|
|
6
|
+
*
|
|
7
|
+
* The signing secret was shown ONCE when you ran `sl create subscription`.
|
|
8
|
+
* If you lost it, rotate it from the dashboard: `/subscriptions/<id>` → rotate.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const SIGNING_SECRET = process.env.SIGNING_SECRET;
|
|
12
|
+
if (!SIGNING_SECRET) {
|
|
13
|
+
console.error("SIGNING_SECRET not set. Copy .env.example → .env and fill it in.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const app = new Hono();
|
|
18
|
+
|
|
19
|
+
interface SubgraphEvent {
|
|
20
|
+
type: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
data: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
app.post("/webhook", async (c) => {
|
|
26
|
+
const body = await c.req.text();
|
|
27
|
+
const headers: Record<string, string> = {};
|
|
28
|
+
c.req.raw.headers.forEach((v, k) => {
|
|
29
|
+
headers[k.toLowerCase()] = v;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!verify(body, headers, SIGNING_SECRET)) {
|
|
33
|
+
console.warn("signature verification failed");
|
|
34
|
+
return c.text("unauthorized", 401);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const event = JSON.parse(body) as SubgraphEvent;
|
|
38
|
+
await onEvent(event);
|
|
39
|
+
return c.text("ok", 200);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
async function onEvent(event: SubgraphEvent): Promise<void> {
|
|
43
|
+
// TODO: plug in your business logic. `event.type` is
|
|
44
|
+
// `<subgraph>.<table>.created`; `event.data` is the row payload.
|
|
45
|
+
console.log("[event]", event.type, event.data);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PORT = Number.parseInt(process.env.PORT ?? "3000", 10);
|
|
49
|
+
Bun.serve({ port: PORT, fetch: app.fetch });
|
|
50
|
+
console.log(`{{NAME}} listening on :${PORT}`);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# {{NAME}} — Trigger.dev subscription receiver
|
|
2
|
+
|
|
3
|
+
A Trigger.dev v3 task that receives subgraph events via the
|
|
4
|
+
`{task}/trigger` HTTP API.
|
|
5
|
+
|
|
6
|
+
## Run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun install
|
|
10
|
+
npx trigger.dev@latest init # follow prompts
|
|
11
|
+
bun run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The Trigger CLI provisions a project and shows the task endpoint URL. Your
|
|
15
|
+
Secondlayer subscription URL should point at:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
https://api.trigger.dev/api/v1/tasks/{{TASK_ID}}/trigger
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Set the `TRIGGER_SECRET_KEY` on the subscription via `auth_config`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{ "authType": "bearer", "token": "tr_secret_abc..." }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Signature verification
|
|
28
|
+
|
|
29
|
+
Trigger authenticates requests with the bearer token — there's no separate
|
|
30
|
+
HMAC. Your secret IS the auth. Rotate via the Trigger dashboard when
|
|
31
|
+
needed.
|
|
32
|
+
|
|
33
|
+
## Deploy
|
|
34
|
+
|
|
35
|
+
Run `npx trigger.dev deploy` to push to Trigger Cloud.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "npx trigger.dev@latest dev",
|
|
8
|
+
"deploy": "npx trigger.dev@latest deploy"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@trigger.dev/sdk": "^3.3.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/bun": "latest",
|
|
15
|
+
"typescript": "^5"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { task } from "@trigger.dev/sdk/v3";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Task triggered by a Secondlayer subscription. The Secondlayer emitter
|
|
5
|
+
* POSTs to this task's HTTP endpoint; Trigger.dev handles durable
|
|
6
|
+
* execution, retries, and concurrency.
|
|
7
|
+
*/
|
|
8
|
+
export const onSubgraphEvent = task({
|
|
9
|
+
id: "{{TASK_ID}}",
|
|
10
|
+
run: async (payload: Record<string, unknown>, { ctx: _ctx }) => {
|
|
11
|
+
console.log("[subgraph event]", payload);
|
|
12
|
+
// TODO: your business logic — AI SDK, HTTP, DB, chain broadcasts.
|
|
13
|
+
return { ok: true };
|
|
14
|
+
},
|
|
15
|
+
});
|