@usearete/automations 0.0.0-stage → 0.44.1
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/LICENSE +21 -0
- package/README.md +299 -2
- package/dist/cli.js +4518 -0
- package/dist/index.cjs +3735 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1379 -0
- package/dist/index.js +3690 -0
- package/dist/index.js.map +1 -0
- package/dist/keypair.cjs +267 -0
- package/dist/keypair.cjs.map +1 -0
- package/dist/keypair.d.ts +70 -0
- package/dist/keypair.js +265 -0
- package/dist/keypair.js.map +1 -0
- package/dist/postgres.cjs +757 -0
- package/dist/postgres.cjs.map +1 -0
- package/dist/postgres.d.ts +449 -0
- package/dist/postgres.js +749 -0
- package/dist/postgres.js.map +1 -0
- package/dist/privy.cjs +618 -0
- package/dist/privy.cjs.map +1 -0
- package/dist/privy.d.ts +236 -0
- package/dist/privy.js +614 -0
- package/dist/privy.js.map +1 -0
- package/dist/testing.cjs +467 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.ts +381 -0
- package/dist/testing.js +465 -0
- package/dist/testing.js.map +1 -0
- package/package.json +108 -4
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { ProtocolTransactionFormat, ExecutionIntentV1, ProtocolPreparedTransaction } from '@usearete/sdk';
|
|
2
|
+
|
|
3
|
+
/** Where a trigger came from. */
|
|
4
|
+
type TriggerSource = 'webhook' | 'stream' | 'dispatch' | 'view';
|
|
5
|
+
/** One accepted trigger: a webhook delivery, a stream event or a dispatch. */
|
|
6
|
+
interface Delivery {
|
|
7
|
+
/** Unique per trigger, namespaced by source (`webhook:…`, `stream:…`, `dispatch:…`, `view:…`). */
|
|
8
|
+
readonly id: string;
|
|
9
|
+
/** The handler name: an event name or a dispatch name. */
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly source: TriggerSource;
|
|
12
|
+
readonly payload: unknown;
|
|
13
|
+
/** Milliseconds since the epoch. */
|
|
14
|
+
readonly receivedAt: number;
|
|
15
|
+
}
|
|
16
|
+
/** A step a handler asks for. */
|
|
17
|
+
interface StepRequest {
|
|
18
|
+
readonly step: string;
|
|
19
|
+
readonly wallet: string;
|
|
20
|
+
/** The instance, for steps that act on one; omitted for create steps. */
|
|
21
|
+
readonly instance?: string;
|
|
22
|
+
readonly args: Readonly<Record<string, unknown>>;
|
|
23
|
+
/** Skip the step when the same (wallet, step) completed this recently, in milliseconds. */
|
|
24
|
+
readonly cooldownMs?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Delivery jobs run a handler; they never sign. */
|
|
27
|
+
type DeliveryJobState = 'queued' | 'running' | 'done' | 'failed';
|
|
28
|
+
/**
|
|
29
|
+
* Step jobs run the signing pipeline. `running`, `signed`, `submitted` and
|
|
30
|
+
* `submitted_unknown` are active: while one is, no other step job of the
|
|
31
|
+
* same wallet may start.
|
|
32
|
+
*/
|
|
33
|
+
type StepJobState = 'queued' | 'running' | 'signed' | 'submitted' | 'submitted_unknown' | 'completed' | 'chain_failed' | 'expired' | 'skipped' | 'rejected' | 'refused' | 'failed';
|
|
34
|
+
type JobState = DeliveryJobState | StepJobState;
|
|
35
|
+
/** Why a job ended where it did. */
|
|
36
|
+
interface JobOutcome {
|
|
37
|
+
/** A refusal rule, a guard or rejection code, `no_consent`, `cooldown`, or an error class. */
|
|
38
|
+
readonly code: string;
|
|
39
|
+
readonly reason?: string;
|
|
40
|
+
readonly detail?: unknown;
|
|
41
|
+
}
|
|
42
|
+
/** What a step job carries once it has prepared and signed. */
|
|
43
|
+
interface StepProgress {
|
|
44
|
+
/** Increments when an expired intent is prepared again. */
|
|
45
|
+
readonly generation: number;
|
|
46
|
+
readonly format?: ProtocolTransactionFormat;
|
|
47
|
+
readonly intent?: ExecutionIntentV1;
|
|
48
|
+
readonly prepared?: readonly ProtocolPreparedTransaction[];
|
|
49
|
+
/** The signed transactions, saved before submit so a crash never re-signs blind. */
|
|
50
|
+
readonly signed?: readonly string[];
|
|
51
|
+
/** Transaction signatures once the intent has them. */
|
|
52
|
+
readonly signatures?: readonly (string | null)[];
|
|
53
|
+
/** When the step was signed: its spending counts against windows from then. */
|
|
54
|
+
readonly signedAt?: number;
|
|
55
|
+
/** What each transaction moves out of the wallet (decimal amounts per asset), saved at signing. */
|
|
56
|
+
readonly outflows?: readonly {
|
|
57
|
+
readonly index: number;
|
|
58
|
+
readonly amounts: Readonly<Record<string, string>>;
|
|
59
|
+
}[];
|
|
60
|
+
}
|
|
61
|
+
interface JobBase {
|
|
62
|
+
/** Unique across both kinds: the delivery id for a delivery job, {@link stepJobId} for a step job. */
|
|
63
|
+
readonly id: string;
|
|
64
|
+
readonly attempts: number;
|
|
65
|
+
/** Not claimable before this time (milliseconds since the epoch). */
|
|
66
|
+
readonly notBefore: number;
|
|
67
|
+
readonly lease: {
|
|
68
|
+
readonly worker: string;
|
|
69
|
+
readonly until: number;
|
|
70
|
+
} | null;
|
|
71
|
+
readonly createdAt: number;
|
|
72
|
+
readonly updatedAt: number;
|
|
73
|
+
readonly outcome: JobOutcome | null;
|
|
74
|
+
}
|
|
75
|
+
interface DeliveryJob extends JobBase {
|
|
76
|
+
readonly kind: 'delivery';
|
|
77
|
+
readonly state: DeliveryJobState;
|
|
78
|
+
readonly delivery: Delivery;
|
|
79
|
+
}
|
|
80
|
+
interface StepJob extends JobBase {
|
|
81
|
+
readonly kind: 'step';
|
|
82
|
+
readonly state: StepJobState;
|
|
83
|
+
readonly deliveryId: string;
|
|
84
|
+
readonly deploymentId: string;
|
|
85
|
+
readonly request: StepRequest;
|
|
86
|
+
readonly progress: StepProgress;
|
|
87
|
+
}
|
|
88
|
+
type Job = DeliveryJob | StepJob;
|
|
89
|
+
|
|
90
|
+
/** Spending limits of a consent grant, per asset: `SOL` (in lamports) or a mint (in base units). */
|
|
91
|
+
interface StoredConsentLimits {
|
|
92
|
+
/** The most one step may move out of the wallet. An asset without one may not leave. */
|
|
93
|
+
readonly perExecution: Readonly<Record<string, string>>;
|
|
94
|
+
/** The most all steps together may move out within a rolling window. */
|
|
95
|
+
readonly perWindow: Readonly<Record<string, {
|
|
96
|
+
readonly amount: string;
|
|
97
|
+
readonly windowMs: number;
|
|
98
|
+
}>>;
|
|
99
|
+
}
|
|
100
|
+
/** The signed proof a grant was made from. */
|
|
101
|
+
interface ConsentProof {
|
|
102
|
+
/** The exact message the wallet signed. */
|
|
103
|
+
readonly message: string;
|
|
104
|
+
/** Base58 Ed25519 signature by the wallet. */
|
|
105
|
+
readonly signature: string;
|
|
106
|
+
readonly nonce: string;
|
|
107
|
+
readonly issuedAt: number;
|
|
108
|
+
}
|
|
109
|
+
/** A user's consent for automations to run steps for their wallet. */
|
|
110
|
+
interface ConsentGrant {
|
|
111
|
+
readonly wallet: string;
|
|
112
|
+
readonly deploymentId: string;
|
|
113
|
+
/** `null` for every instance of the deployment. */
|
|
114
|
+
readonly instanceId: string | null;
|
|
115
|
+
readonly steps: readonly string[];
|
|
116
|
+
readonly limits: StoredConsentLimits;
|
|
117
|
+
/** The user's own settings for the steps (amount, weekday, ...). */
|
|
118
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
119
|
+
/** Milliseconds since the epoch. */
|
|
120
|
+
readonly expiresAt: number;
|
|
121
|
+
readonly grantedAt: number;
|
|
122
|
+
readonly revokedAt: number | null;
|
|
123
|
+
readonly proof: ConsentProof;
|
|
124
|
+
}
|
|
125
|
+
/** What one step moved, counted against a grant's windows. */
|
|
126
|
+
interface LedgerEntry {
|
|
127
|
+
readonly jobId: string;
|
|
128
|
+
readonly wallet: string;
|
|
129
|
+
readonly deploymentId: string;
|
|
130
|
+
/** `SOL` or a mint. */
|
|
131
|
+
readonly asset: string;
|
|
132
|
+
/** Lamports or base units, as a decimal string. */
|
|
133
|
+
readonly amount: string;
|
|
134
|
+
readonly recordedAt: number;
|
|
135
|
+
/** Set when the step's intent never landed, so it no longer counts. */
|
|
136
|
+
readonly voidedAt: number | null;
|
|
137
|
+
}
|
|
138
|
+
interface ConsentQuery {
|
|
139
|
+
readonly wallet: string;
|
|
140
|
+
readonly deploymentId: string;
|
|
141
|
+
readonly step: string;
|
|
142
|
+
readonly instanceId: string | null;
|
|
143
|
+
readonly now: number;
|
|
144
|
+
}
|
|
145
|
+
/** What a view source last saw for one key. */
|
|
146
|
+
interface SourceState {
|
|
147
|
+
readonly source: string;
|
|
148
|
+
readonly key: string;
|
|
149
|
+
/** Canonical-JSON SHA-256 of the stable value. */
|
|
150
|
+
readonly hash: string;
|
|
151
|
+
/** The stable derived value (JSON). */
|
|
152
|
+
readonly value: unknown;
|
|
153
|
+
/** Increments with every stable change; delivery ids carry it. */
|
|
154
|
+
readonly generation: number;
|
|
155
|
+
/** A new value waiting out `stableFor`, if any. */
|
|
156
|
+
readonly pending: {
|
|
157
|
+
readonly hash: string;
|
|
158
|
+
readonly value: unknown;
|
|
159
|
+
readonly since: number;
|
|
160
|
+
} | null;
|
|
161
|
+
/** The newest observation order written (a decimal integer), or `null`. */
|
|
162
|
+
readonly observed: string | null;
|
|
163
|
+
/** Increments with every write; compare-and-set reads it. */
|
|
164
|
+
readonly version: number;
|
|
165
|
+
readonly updatedAt: number;
|
|
166
|
+
}
|
|
167
|
+
interface SourceAdvance {
|
|
168
|
+
readonly source: string;
|
|
169
|
+
readonly key: string;
|
|
170
|
+
/** The version read; `null` when there was no state yet. The write fails if it moved. */
|
|
171
|
+
readonly expectedVersion: number | null;
|
|
172
|
+
readonly next: Pick<SourceState, 'hash' | 'value' | 'pending'> & {
|
|
173
|
+
readonly observed?: string | null;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Accept a delivery in the same atomic step, with id
|
|
177
|
+
* `view:${encodeURIComponent(source)}:${encodeURIComponent(key)}:${generation}`
|
|
178
|
+
* (the generation after the write).
|
|
179
|
+
*/
|
|
180
|
+
readonly deliver?: {
|
|
181
|
+
readonly name: string;
|
|
182
|
+
readonly payload: unknown;
|
|
183
|
+
};
|
|
184
|
+
readonly now: number;
|
|
185
|
+
}
|
|
186
|
+
/** A step job to create, without its bookkeeping. */
|
|
187
|
+
interface NewStepJob {
|
|
188
|
+
readonly id: string;
|
|
189
|
+
readonly deploymentId: string;
|
|
190
|
+
readonly request: StepRequest;
|
|
191
|
+
}
|
|
192
|
+
interface ClaimInput {
|
|
193
|
+
readonly worker: string;
|
|
194
|
+
readonly leaseMs: number;
|
|
195
|
+
readonly now: number;
|
|
196
|
+
}
|
|
197
|
+
interface TransitionInput {
|
|
198
|
+
/** The worker that holds the lease. */
|
|
199
|
+
readonly worker: string;
|
|
200
|
+
/** The state the job must be in; the transition fails otherwise. */
|
|
201
|
+
readonly from: JobState;
|
|
202
|
+
readonly to: JobState;
|
|
203
|
+
readonly now: number;
|
|
204
|
+
/** Merged into a step job's progress. */
|
|
205
|
+
readonly progress?: Partial<StepProgress>;
|
|
206
|
+
readonly outcome?: JobOutcome | null;
|
|
207
|
+
/** Keep the lease (for more work in the same claim) instead of releasing it. */
|
|
208
|
+
readonly keepLease?: {
|
|
209
|
+
readonly until: number;
|
|
210
|
+
};
|
|
211
|
+
/** Not claimable again before this time. */
|
|
212
|
+
readonly notBefore?: number;
|
|
213
|
+
readonly attempts?: number;
|
|
214
|
+
/**
|
|
215
|
+
* Ledger changes made in the same atomic step as the transition:
|
|
216
|
+
* `record` adds entries for this job, `void` voids its entries except
|
|
217
|
+
* those for `keepAssets`.
|
|
218
|
+
*/
|
|
219
|
+
readonly ledger?: {
|
|
220
|
+
/** New entries; `recordedAt` defaults to the transition's `now`. */
|
|
221
|
+
readonly record?: readonly (Omit<LedgerEntry, 'jobId' | 'recordedAt' | 'voidedAt'> & {
|
|
222
|
+
readonly recordedAt?: number;
|
|
223
|
+
})[];
|
|
224
|
+
readonly void?: boolean;
|
|
225
|
+
readonly keepAssets?: readonly string[];
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Where automations keep their state. A store must provide:
|
|
230
|
+
*
|
|
231
|
+
* - a unique insert for deliveries, so a trigger is accepted once;
|
|
232
|
+
* - exclusive job claims with a lease;
|
|
233
|
+
* - at most one active step job per wallet (`running`, `signed`,
|
|
234
|
+
* `submitted`, `submitted_unknown`);
|
|
235
|
+
* - compare-and-set transitions, by state and lease holder;
|
|
236
|
+
* - completing a delivery job and enqueuing its step jobs atomically;
|
|
237
|
+
* - ledger writes atomic with the job transition they belong to;
|
|
238
|
+
* - single-use consent nonces;
|
|
239
|
+
* - compare-and-set on view-source state, with its delivery in the same step.
|
|
240
|
+
*
|
|
241
|
+
* `runStoreConformance` from `@usearete/automations/testing` checks all of
|
|
242
|
+
* these; a custom store must pass it.
|
|
243
|
+
*/
|
|
244
|
+
interface AutomationStore {
|
|
245
|
+
/**
|
|
246
|
+
* Tie the store to one deployment. The first call records it; a call for
|
|
247
|
+
* another deployment throws `StoreDeploymentError`. Use a separate store
|
|
248
|
+
* (for Postgres, a separate schema) per deployment.
|
|
249
|
+
*/
|
|
250
|
+
bindDeployment(deploymentId: string): Promise<void>;
|
|
251
|
+
readonly deliveries: {
|
|
252
|
+
/**
|
|
253
|
+
* Record the delivery and queue its delivery job, atomically. `false`
|
|
254
|
+
* when a delivery with this id was already accepted, or its job still
|
|
255
|
+
* exists after the delivery id was pruned.
|
|
256
|
+
*/
|
|
257
|
+
accept(delivery: Delivery, now: number): Promise<boolean>;
|
|
258
|
+
/** Forget accepted delivery ids older than `before`; returns how many. */
|
|
259
|
+
prune(before: number): Promise<number>;
|
|
260
|
+
};
|
|
261
|
+
readonly jobs: {
|
|
262
|
+
get(id: string): Promise<Job | null>;
|
|
263
|
+
/**
|
|
264
|
+
* Claim the next job: a queued delivery job; a queued step job whose
|
|
265
|
+
* wallet has no active step job (it becomes `running`); or an active job
|
|
266
|
+
* whose lease has lapsed. Jobs before their `notBefore` are skipped.
|
|
267
|
+
*/
|
|
268
|
+
claimNext(input: ClaimInput): Promise<Job | null>;
|
|
269
|
+
/** Move a job from `from` to `to` if it is still there and leased by `worker`. */
|
|
270
|
+
transition(id: string, input: TransitionInput): Promise<boolean>;
|
|
271
|
+
/** Extend `worker`'s lease. `false` when it no longer holds it. */
|
|
272
|
+
renew(id: string, worker: string, until: number): Promise<boolean>;
|
|
273
|
+
/**
|
|
274
|
+
* Mark a running delivery job `done` and create its step jobs, in one
|
|
275
|
+
* step. Step jobs whose id exists already are left alone.
|
|
276
|
+
*/
|
|
277
|
+
completeDelivery(id: string, worker: string, steps: readonly NewStepJob[], now: number): Promise<boolean>;
|
|
278
|
+
/** When this wallet last completed this step in this deployment, or `null`. */
|
|
279
|
+
lastCompleted(input: {
|
|
280
|
+
wallet: string;
|
|
281
|
+
deploymentId: string;
|
|
282
|
+
step: string;
|
|
283
|
+
}): Promise<number | null>;
|
|
284
|
+
list(filter?: {
|
|
285
|
+
kind?: Job['kind'];
|
|
286
|
+
state?: JobState;
|
|
287
|
+
}): Promise<Job[]>;
|
|
288
|
+
/** Delete finished jobs last updated before `before`; returns how many. */
|
|
289
|
+
prune(before: number): Promise<number>;
|
|
290
|
+
};
|
|
291
|
+
readonly consent: {
|
|
292
|
+
/** Store a grant, replacing the wallet's grant for the same deployment and instance. */
|
|
293
|
+
put(grant: ConsentGrant): Promise<void>;
|
|
294
|
+
/** The active grant covering the query, or `null`. */
|
|
295
|
+
find(query: ConsentQuery): Promise<ConsentGrant | null>;
|
|
296
|
+
/** Revoke grants (or only some of their steps). Returns how many grants changed. */
|
|
297
|
+
revoke(input: {
|
|
298
|
+
wallet: string;
|
|
299
|
+
deploymentId: string;
|
|
300
|
+
steps?: readonly string[];
|
|
301
|
+
/** Only the grant scoped to this instance. */
|
|
302
|
+
instanceId?: string;
|
|
303
|
+
now: number;
|
|
304
|
+
}): Promise<number>;
|
|
305
|
+
/** Every grant of a wallet, active or not. */
|
|
306
|
+
list(input: {
|
|
307
|
+
wallet: string;
|
|
308
|
+
}): Promise<ConsentGrant[]>;
|
|
309
|
+
/** Active grants in a deployment that cover `step`, for fan-out. */
|
|
310
|
+
active(input: {
|
|
311
|
+
deploymentId: string;
|
|
312
|
+
step?: string;
|
|
313
|
+
now: number;
|
|
314
|
+
}): Promise<ConsentGrant[]>;
|
|
315
|
+
/** Remember a nonce handed out with a proof message. */
|
|
316
|
+
issueNonce(nonce: string, expiresAt: number): Promise<void>;
|
|
317
|
+
/** Use an issued, unexpired nonce once. `false` when it was not issued, expired or used. */
|
|
318
|
+
useNonce(nonce: string, now: number): Promise<boolean>;
|
|
319
|
+
};
|
|
320
|
+
readonly sources: {
|
|
321
|
+
get(source: string, key: string): Promise<SourceState | null>;
|
|
322
|
+
/**
|
|
323
|
+
* Write a key's state if its version is still `expectedVersion`. The
|
|
324
|
+
* generation increments when the stable hash changes. Returns the new
|
|
325
|
+
* state, or `null` when another writer got there first.
|
|
326
|
+
*/
|
|
327
|
+
advance(input: SourceAdvance): Promise<SourceState | null>;
|
|
328
|
+
/** The furthest cursor an append-view source recorded. */
|
|
329
|
+
cursor(source: string): Promise<string | null>;
|
|
330
|
+
/**
|
|
331
|
+
* Record a cursor if it is after the stored one (see `cursorAfter`), so a
|
|
332
|
+
* slow replica can never move a source back. Atomic.
|
|
333
|
+
*/
|
|
334
|
+
putCursor(source: string, cursor: string, now: number): Promise<void>;
|
|
335
|
+
/** Hold a source exclusively until `until`. `false` while another holder's lease runs. */
|
|
336
|
+
acquireLease(source: string, holder: string, until: number, now: number): Promise<boolean>;
|
|
337
|
+
releaseLease(source: string, holder: string): Promise<void>;
|
|
338
|
+
};
|
|
339
|
+
readonly ledger: {
|
|
340
|
+
/** The sum of unvoided entries for the wallet and asset recorded at or after `since`. */
|
|
341
|
+
spent(input: {
|
|
342
|
+
wallet: string;
|
|
343
|
+
deploymentId: string;
|
|
344
|
+
asset: string;
|
|
345
|
+
since: number;
|
|
346
|
+
}): Promise<bigint>;
|
|
347
|
+
list(input: {
|
|
348
|
+
wallet: string;
|
|
349
|
+
}): Promise<LedgerEntry[]>;
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The store conformance suite. Any `AutomationStore` (memory, Postgres, or
|
|
355
|
+
* your own over Redis, DynamoDB, D1 or Durable Objects) must pass it.
|
|
356
|
+
*
|
|
357
|
+
* ```ts
|
|
358
|
+
* import { describe, expect, it } from 'vitest';
|
|
359
|
+
* import { runStoreConformance } from '@usearete/automations/testing';
|
|
360
|
+
*
|
|
361
|
+
* runStoreConformance('my store', async () => myStore(), { describe, it, expect });
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
/** The subset of a test framework the suite uses (vitest and jest both fit). */
|
|
366
|
+
interface ConformanceTestApi {
|
|
367
|
+
describe(name: string, body: () => void): void;
|
|
368
|
+
it(name: string, body: () => Promise<void>): void;
|
|
369
|
+
expect(value: unknown): {
|
|
370
|
+
toBe(expected: unknown): void;
|
|
371
|
+
toEqual(expected: unknown): void;
|
|
372
|
+
toBeNull(): void;
|
|
373
|
+
not: {
|
|
374
|
+
toBeNull(): void;
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
declare function runStoreConformance(name: string, createStore: () => Promise<AutomationStore>, { describe, it, expect }: ConformanceTestApi): void;
|
|
379
|
+
|
|
380
|
+
export { runStoreConformance };
|
|
381
|
+
export type { ConformanceTestApi };
|