Skip to content

Commit 83761a7

Browse files
Merge pull request #10 from fusionlabs-tech/dev
feat(sdk): prepare @chronos-synapse/sdk for publish; add CI workflows
2 parents 9e92b08 + 2827232 commit 83761a7

File tree

8 files changed

+1398
-6
lines changed

8 files changed

+1398
-6
lines changed

.github/workflows/sdk-autotag.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ jobs:
1616

1717
steps:
1818
- name: Checkout
19-
uses: actions/checkout@v4
19+
uses: actions/checkout@v4.1.7
2020
with:
2121
fetch-depth: 0
2222

2323
- name: Determine release type from labels
2424
id: rel
25-
uses: actions/github-script@v7
25+
uses: actions/github-script@v7.0.1
2626
with:
2727
script: |
2828
const labels = (context.payload.pull_request.labels || []).map(l => l.name);
@@ -34,7 +34,7 @@ jobs:
3434
3535
- name: Setup Node
3636
if: steps.rel.outputs.type != ''
37-
uses: actions/setup-node@v4
37+
uses: actions/setup-node@v4.0.2
3838
with:
3939
node-version: '20'
4040

@@ -61,7 +61,7 @@ jobs:
6161
git config user.name "github-actions"
6262
git config user.email "github-actions@github.com"
6363
git add sdk/package.json sdk/package-lock.json || true
64-
git commit -m "chore(sdk): release v${{ steps.ver.outputs.version }}"
64+
git commit -m "chore(sdk): release v${{ steps.ver.outputs.version }}" || true
6565
git tag sdk-v${{ steps.ver.outputs.version }}
6666
git push origin HEAD:main
6767
git push origin sdk-v${{ steps.ver.outputs.version }}

.github/workflows/sdk-publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ jobs:
1919

2020
steps:
2121
- name: Checkout
22-
uses: actions/checkout@v4
22+
uses: actions/checkout@v4.1.7
2323
with:
2424
fetch-depth: 0
2525

2626
- name: Use Node.js
27-
uses: actions/setup-node@v4
27+
uses: actions/setup-node@v4.0.2
2828
with:
2929
node-version: '20'
3030
registry-url: 'https://registry.npmjs.org'

sdk/README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# @chronos-synapse/sdk
2+
3+
Chronos SDK Runner for registering jobs and running them on server triggers with rich telemetry (ingestion, metrics, realtime).
4+
5+
- Single API surface: ChronosRunner
6+
- Buffered batching with periodic flush
7+
- Exponential backoff with retries on 5xx/429
8+
- TypeScript types included
9+
10+
## Installation
11+
12+
```bash
13+
npm install @chronos-synapse/sdk
14+
# or
15+
yarn add @chronos-synapse/sdk
16+
```
17+
18+
## Quick Start (Runner)
19+
20+
```ts
21+
import ChronosRunner from '@chronos-synapse/sdk';
22+
23+
const runner = new ChronosRunner({
24+
apiKey: process.env.CHRONOS_API_KEY!,
25+
captureConsole: true,
26+
});
27+
28+
// Register your job(s)
29+
await runner['client'].registerJobs([
30+
// Recurring: requires a non-empty cron schedule
31+
{
32+
id: 'job:daily-report',
33+
name: 'Daily Report',
34+
schedule: '0 * * * *',
35+
runMode: 'recurring',
36+
},
37+
// One-time: can provide a cron (fires on first matching minute only), or leave schedule '' and provide runAt
38+
{
39+
id: 'job:migrate-once',
40+
name: 'One-time Migration',
41+
schedule: '*/2 * * * *',
42+
runMode: 'once',
43+
},
44+
// Alternatively one-time at a fixed time via runAt (ISO or epoch ms) with empty schedule
45+
{
46+
id: 'job:launch-once',
47+
name: 'Launch',
48+
schedule: '',
49+
runMode: 'once',
50+
runAt: '2025-09-01T12:00:00Z',
51+
},
52+
]);
53+
54+
runner.register('job:daily-report', async () => {
55+
// Your work here
56+
await new Promise((r) => setTimeout(r, 150));
57+
if (Math.random() < 0.3) throw new Error('simulated failure');
58+
});
59+
60+
runner.register('job:migrate-once', async () => {
61+
// Your work here
62+
await new Promise((r) => setTimeout(r, 150));
63+
if (Math.random() < 0.3) throw new Error('simulated failure');
64+
});
65+
66+
runner.register('job:launch-once', async () => {
67+
// Your work here
68+
await new Promise((r) => setTimeout(r, 150));
69+
if (Math.random() < 0.3) throw new Error('simulated failure');
70+
});
71+
72+
// Start listening for triggers
73+
runner.start();
74+
```
75+
76+
- `register(jobId, handler)`: registers a function to execute when the server emits a trigger for that job
77+
- `start()`: connects to the server and begins listening for triggers
78+
- `stop()`: disconnects
79+
80+
### Scheduling rules
81+
82+
- Recurring jobs: `runMode: 'recurring'` and a non-empty cron `schedule`.
83+
- One-time jobs:
84+
- Option A: Provide a cron `schedule` and `runMode: 'once'` → the server triggers at the first matching minute only.
85+
- Option B: Provide `schedule: ''`, `runMode: 'once'`, and `runAt` (ISO string or epoch ms) → the server triggers once when `now >= runAt`.
86+
- Changing `schedule` or `runMode` re-arms the one-time trigger if it hasn’t fired yet.
87+
88+
## What gets auto-captured
89+
90+
- status/exitCode, startedAt/finishedAt/duration
91+
- Errors: errorMessage, errorStack (full), stderr (stack + captured stderr if enabled)
92+
- Code context: codeSnippet (user-code frame), codeLanguage (from file extension)
93+
- Versions: jobVersion (from registerJobs), appVersion (from package.json/env)
94+
- stdout: console capture (when `captureConsole` is enabled)
95+
96+
The SDK truncates large fields by default (configurable via `maxLogBytes`).
97+
98+
## Privacy & Limits
99+
100+
- Truncation: `maxLogBytes` (default 10k) limits `stdout`, `stderr`, and `codeSnippet` sizes.
101+
- Sensitive data: avoid logging secrets. You can preprocess/redact before throwing/printing.
102+
- Roadmap: configurable redaction patterns in capture pipeline.
103+
104+
## Examples
105+
106+
- Local test app (Runner): `examples/sdk-local-test/runner.js`
107+
108+
## Local Testing (without publish)
109+
110+
- Build SDK: `npm run build:sdk`
111+
- Install into local app:
112+
- `cd examples/sdk-local-test`
113+
- `npm install`
114+
- Set env: `export CHRONOS_API_URL=http://localhost:3001; export CHRONOS_API_KEY=...`
115+
- Run runner test: `npm run start:runner`
116+
117+
## Advanced (optional) – Low-level API
118+
119+
If you need direct control of telemetry, you can still enqueue events manually. You are responsible for `execId` generation and timing fields.
120+
121+
```ts
122+
// Access the internal client via runner['client'] (advanced only)
123+
runner['client'].enqueueExecution({
124+
execId: `job:daily-report:${Date.now()}`,
125+
jobId: 'job:daily-report',
126+
status: 'success',
127+
startedAt: new Date().toISOString(),
128+
finishedAt: new Date().toISOString(),
129+
durationMs: 742,
130+
exitCode: 0,
131+
});
132+
await runner['client'].flush();
133+
```
134+
135+
## Telemetry Fields
136+
137+
| Field | Source | Default/Example |
138+
| ---------------- | ------------------------------- | ---------------------------------- |
139+
| status | handler outcome | `success` or `failed` |
140+
| exitCode | handler outcome | 0 on success, 1 on failure |
141+
| startedAt | runner | ISO string |
142+
| finishedAt | runner | ISO string |
143+
| durationMs | runner | `finishedAt - startedAt` |
144+
| errorMessage | caught Error | `err.message` |
145+
| errorType | caught Error | `err.name` |
146+
| errorStack | caught Error | full stack |
147+
| stderr | runner | stack + captured stderr if enabled |
148+
| stdout | runner | captured console output (optional) |
149+
| codeSnippet | user-code frame from stack | truncated to `maxLogBytes` |
150+
| codeLanguage | inferred from file extension | `javascript` if unknown |
151+
| jobVersion | registerJobs cache | defaults from package.json |
152+
| appVersion | package.json or npm*package*... | undefined if not resolved |
153+
| labels, metadata | not used in Runner ||

sdk/examples/basic-usage.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// @ts-nocheck
2+
3+
import ChronosRunner from '../src/index';
4+
5+
async function main() {
6+
const runner = new ChronosRunner({
7+
endpoint: process.env.CHRONOS_ENDPOINT || 'http://localhost:3001',
8+
apiKey: process.env.CHRONOS_API_KEY || 'your-api-key',
9+
captureConsole: true,
10+
});
11+
12+
// Register jobs with schedule + runMode
13+
await runner['client'].registerJobs([
14+
{
15+
id: 'demo-hourly',
16+
name: 'Demo Hourly Job',
17+
schedule: '0 * * * *',
18+
runMode: 'recurring',
19+
},
20+
{
21+
id: 'demo-once',
22+
name: 'One-time Demo',
23+
schedule: '',
24+
runMode: 'once',
25+
runAt: new Date(Date.now() + 60_000).toISOString(),
26+
},
27+
]);
28+
29+
// Handlers invoked when the server emits job:trigger
30+
runner.register('demo-hourly', async () => {
31+
// your work here
32+
await new Promise((r) => setTimeout(r, 200));
33+
});
34+
35+
runner.register('demo-once', async () => {
36+
// this will run once based on runAt (or first cron match if schedule provided)
37+
await new Promise((r) => setTimeout(r, 100));
38+
});
39+
40+
runner.start();
41+
console.log('ChronosRunner started. Waiting for server job:trigger events...');
42+
}
43+
44+
main().catch((err) => {
45+
console.error(err);
46+
process.exit(1);
47+
});

0 commit comments

Comments
 (0)