@secondlayer/cli 3.2.1 → 3.3.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/README.md +27 -1
- package/dist/cli.js +777 -62
- package/dist/cli.js.map +9 -6
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -13927,7 +13927,7 @@ __export(exports_node_impl, {
|
|
|
13927
13927
|
runSetupWizard: () => runSetupWizard,
|
|
13928
13928
|
restartNode: () => restartNode
|
|
13929
13929
|
});
|
|
13930
|
-
import { confirm as
|
|
13930
|
+
import { confirm as confirm4, input as input2, select as select3 } from "@inquirer/prompts";
|
|
13931
13931
|
async function runSetupWizard() {
|
|
13932
13932
|
console.log("");
|
|
13933
13933
|
console.log(blue("Stacks Node Setup Wizard"));
|
|
@@ -13964,13 +13964,13 @@ async function runSetupWizard() {
|
|
|
13964
13964
|
],
|
|
13965
13965
|
default: "mainnet"
|
|
13966
13966
|
});
|
|
13967
|
-
const autoStartIndexer = await
|
|
13967
|
+
const autoStartIndexer = await confirm4({
|
|
13968
13968
|
message: "Auto-start indexer when node starts?",
|
|
13969
13969
|
default: true
|
|
13970
13970
|
});
|
|
13971
13971
|
let indexerPort = 3700;
|
|
13972
13972
|
if (autoStartIndexer) {
|
|
13973
|
-
const customPort = await
|
|
13973
|
+
const customPort = await confirm4({
|
|
13974
13974
|
message: "Use default indexer port (3700)?",
|
|
13975
13975
|
default: true
|
|
13976
13976
|
});
|
|
@@ -14076,7 +14076,7 @@ async function stopNode(_pathOverride, force, _wait) {
|
|
|
14076
14076
|
return;
|
|
14077
14077
|
}
|
|
14078
14078
|
if (!force) {
|
|
14079
|
-
const proceed = await
|
|
14079
|
+
const proceed = await confirm4({
|
|
14080
14080
|
message: "Stop the Stacks node?",
|
|
14081
14081
|
default: false
|
|
14082
14082
|
});
|
|
@@ -14106,7 +14106,7 @@ async function restartNode(pathOverride, force, _wait) {
|
|
|
14106
14106
|
const wasRunning = await isNodeRunning();
|
|
14107
14107
|
if (wasRunning) {
|
|
14108
14108
|
if (!force) {
|
|
14109
|
-
const proceed = await
|
|
14109
|
+
const proceed = await confirm4({
|
|
14110
14110
|
message: "Restart the Stacks node?",
|
|
14111
14111
|
default: false
|
|
14112
14112
|
});
|
|
@@ -14555,7 +14555,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
14555
14555
|
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => (key in env))) {
|
|
14556
14556
|
return 3;
|
|
14557
14557
|
}
|
|
14558
|
-
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((
|
|
14558
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign2) => (sign2 in env)) || env.CI_NAME === "codeship") {
|
|
14559
14559
|
return 1;
|
|
14560
14560
|
}
|
|
14561
14561
|
return min;
|
|
@@ -24264,7 +24264,7 @@ Filtered results for: ${this.inputValue ? this.inputValue : color.gray("Enter so
|
|
|
24264
24264
|
}
|
|
24265
24265
|
function requireConfirm() {
|
|
24266
24266
|
if (hasRequiredConfirm)
|
|
24267
|
-
return
|
|
24267
|
+
return confirm6;
|
|
24268
24268
|
hasRequiredConfirm = 1;
|
|
24269
24269
|
const color = requireKleur();
|
|
24270
24270
|
const Prompt = requirePrompt();
|
|
@@ -24337,8 +24337,8 @@ function requireConfirm() {
|
|
|
24337
24337
|
this.out.write(erase.line + cursor.to(0) + this.outputText);
|
|
24338
24338
|
}
|
|
24339
24339
|
}
|
|
24340
|
-
|
|
24341
|
-
return
|
|
24340
|
+
confirm6 = ConfirmPrompt;
|
|
24341
|
+
return confirm6;
|
|
24342
24342
|
}
|
|
24343
24343
|
function requireElements() {
|
|
24344
24344
|
if (hasRequiredElements)
|
|
@@ -24575,7 +24575,7 @@ function requireSupportsColor() {
|
|
|
24575
24575
|
return 1;
|
|
24576
24576
|
}
|
|
24577
24577
|
if ("CI" in env2) {
|
|
24578
|
-
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((
|
|
24578
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign2) => (sign2 in env2)) || env2.CI_NAME === "codeship") {
|
|
24579
24579
|
return 1;
|
|
24580
24580
|
}
|
|
24581
24581
|
return min;
|
|
@@ -25018,7 +25018,7 @@ async function detect2({ autoInstall, programmatic, cwd } = {}) {
|
|
|
25018
25018
|
}
|
|
25019
25019
|
return agent;
|
|
25020
25020
|
}
|
|
25021
|
-
var ini$1, hasRequiredIni, iniExports, prompts$2, kleur, hasRequiredKleur, action, hasRequiredAction, strip, hasRequiredStrip, src, hasRequiredSrc, clear, hasRequiredClear, figures_1, hasRequiredFigures, style, hasRequiredStyle, lines, hasRequiredLines, wrap, hasRequiredWrap, entriesToDisplay, hasRequiredEntriesToDisplay, util, hasRequiredUtil, prompt, hasRequiredPrompt, text, hasRequiredText, select5, hasRequiredSelect, toggle, hasRequiredToggle, datepart, hasRequiredDatepart, meridiem, hasRequiredMeridiem, day, hasRequiredDay, hours, hasRequiredHours, milliseconds, hasRequiredMilliseconds, minutes, hasRequiredMinutes, month, hasRequiredMonth, seconds, hasRequiredSeconds, year, hasRequiredYear, dateparts, hasRequiredDateparts, date, hasRequiredDate, number, hasRequiredNumber, multiselect, hasRequiredMultiselect, autocomplete, hasRequiredAutocomplete, autocompleteMultiselect, hasRequiredAutocompleteMultiselect,
|
|
25021
|
+
var ini$1, hasRequiredIni, iniExports, prompts$2, kleur, hasRequiredKleur, action, hasRequiredAction, strip, hasRequiredStrip, src, hasRequiredSrc, clear, hasRequiredClear, figures_1, hasRequiredFigures, style, hasRequiredStyle, lines, hasRequiredLines, wrap, hasRequiredWrap, entriesToDisplay, hasRequiredEntriesToDisplay, util, hasRequiredUtil, prompt, hasRequiredPrompt, text, hasRequiredText, select5, hasRequiredSelect, toggle, hasRequiredToggle, datepart, hasRequiredDatepart, meridiem, hasRequiredMeridiem, day, hasRequiredDay, hours, hasRequiredHours, milliseconds, hasRequiredMilliseconds, minutes, hasRequiredMinutes, month, hasRequiredMonth, seconds, hasRequiredSeconds, year, hasRequiredYear, dateparts, hasRequiredDateparts, date, hasRequiredDate, number, hasRequiredNumber, multiselect, hasRequiredMultiselect, autocomplete, hasRequiredAutocomplete, autocompleteMultiselect, hasRequiredAutocompleteMultiselect, confirm6, hasRequiredConfirm, elements, hasRequiredElements, hasRequiredPrompts$1, lib$1, hasRequiredLib$1, prompts$1, hasRequiredPrompts, promptsExports, prompts, isBrowser, platform, OSC = "\x1B]", BEL = "\x07", SEP = ";", link = (text2, url) => [
|
|
25022
25022
|
OSC,
|
|
25023
25023
|
"8",
|
|
25024
25024
|
SEP,
|
|
@@ -28352,7 +28352,7 @@ function prettyMilliseconds(milliseconds2, options3) {
|
|
|
28352
28352
|
throw new TypeError("Expected a finite number or bigint");
|
|
28353
28353
|
}
|
|
28354
28354
|
options3 = { ...options3 };
|
|
28355
|
-
const
|
|
28355
|
+
const sign2 = milliseconds2 < 0 ? "-" : "";
|
|
28356
28356
|
milliseconds2 = milliseconds2 < 0 ? -milliseconds2 : milliseconds2;
|
|
28357
28357
|
if (options3.colonNotation) {
|
|
28358
28358
|
options3.compact = false;
|
|
@@ -28426,13 +28426,13 @@ function prettyMilliseconds(milliseconds2, options3) {
|
|
|
28426
28426
|
}
|
|
28427
28427
|
}
|
|
28428
28428
|
if (result.length === 0) {
|
|
28429
|
-
return
|
|
28429
|
+
return sign2 + "0" + (options3.verbose ? " milliseconds" : "ms");
|
|
28430
28430
|
}
|
|
28431
28431
|
const separator = options3.colonNotation ? ":" : " ";
|
|
28432
28432
|
if (typeof options3.unitCount === "number") {
|
|
28433
28433
|
result = result.slice(0, Math.max(options3.unitCount, 1));
|
|
28434
28434
|
}
|
|
28435
|
-
return
|
|
28435
|
+
return sign2 + result.join(separator);
|
|
28436
28436
|
}
|
|
28437
28437
|
var isZero = (value) => value === 0 || value === 0n, pluralize = (word, count2) => count2 === 1 || count2 === 1n ? word : `${word}s`, SECOND_ROUNDING_EPSILON = 0.0000001, ONE_DAY_IN_MILLISECONDS;
|
|
28438
28438
|
var init_pretty_ms = __esm(() => {
|
|
@@ -32410,7 +32410,7 @@ var {
|
|
|
32410
32410
|
// package.json
|
|
32411
32411
|
var package_default = {
|
|
32412
32412
|
name: "@secondlayer/cli",
|
|
32413
|
-
version: "3.
|
|
32413
|
+
version: "3.3.0",
|
|
32414
32414
|
description: "CLI for subgraphs and blockchain indexing on Stacks",
|
|
32415
32415
|
type: "module",
|
|
32416
32416
|
bin: {
|
|
@@ -32453,8 +32453,8 @@ var package_default = {
|
|
|
32453
32453
|
dependencies: {
|
|
32454
32454
|
"@inquirer/prompts": "^8.2.0",
|
|
32455
32455
|
"@secondlayer/bundler": "^0.3.1",
|
|
32456
|
-
"@secondlayer/sdk": "^3.0
|
|
32457
|
-
"@secondlayer/shared": "^4.
|
|
32456
|
+
"@secondlayer/sdk": "^3.1.0",
|
|
32457
|
+
"@secondlayer/shared": "^4.1.0",
|
|
32458
32458
|
"@secondlayer/stacks": "^1.0.1",
|
|
32459
32459
|
"@secondlayer/subgraphs": "^1.1.0",
|
|
32460
32460
|
"@biomejs/js-api": "^0.7.0",
|
|
@@ -32963,7 +32963,6 @@ async function validateDatabaseConnection(url) {
|
|
|
32963
32963
|
}
|
|
32964
32964
|
}
|
|
32965
32965
|
// src/commands/create.ts
|
|
32966
|
-
init_output();
|
|
32967
32966
|
import {
|
|
32968
32967
|
copyFileSync,
|
|
32969
32968
|
existsSync as existsSync2,
|
|
@@ -32977,6 +32976,96 @@ import { dirname as dirname3, join as join5, relative, resolve as resolve2 } fro
|
|
|
32977
32976
|
import { fileURLToPath } from "node:url";
|
|
32978
32977
|
import { input, select as select2 } from "@inquirer/prompts";
|
|
32979
32978
|
import { SecondLayer as SecondLayer2 } from "@secondlayer/sdk";
|
|
32979
|
+
|
|
32980
|
+
// src/lib/filter-params.ts
|
|
32981
|
+
var FILTER_OPERATORS = ["eq", "neq", "gt", "gte", "lt", "lte"];
|
|
32982
|
+
var QUERY_ONLY_OPERATORS = ["like"];
|
|
32983
|
+
var ALL_OPERATORS = new Set([
|
|
32984
|
+
...FILTER_OPERATORS,
|
|
32985
|
+
...QUERY_ONLY_OPERATORS
|
|
32986
|
+
]);
|
|
32987
|
+
var ALL_OPERATOR_LABELS = [...FILTER_OPERATORS, ...QUERY_ONLY_OPERATORS].join(", ");
|
|
32988
|
+
function parseFilterArg(input) {
|
|
32989
|
+
const eqIndex = input.indexOf("=");
|
|
32990
|
+
if (eqIndex <= 0) {
|
|
32991
|
+
throw new Error(`Invalid filter format: "${input}". Use key=value.`);
|
|
32992
|
+
}
|
|
32993
|
+
const key = input.slice(0, eqIndex).trim();
|
|
32994
|
+
const value = input.slice(eqIndex + 1);
|
|
32995
|
+
if (!key)
|
|
32996
|
+
throw new Error(`Invalid filter format: "${input}". Use key=value.`);
|
|
32997
|
+
const dotIndex = key.lastIndexOf(".");
|
|
32998
|
+
if (dotIndex > 0) {
|
|
32999
|
+
const field = key.slice(0, dotIndex);
|
|
33000
|
+
const operator = key.slice(dotIndex + 1);
|
|
33001
|
+
if (!field)
|
|
33002
|
+
throw new Error(`Invalid filter field in "${input}".`);
|
|
33003
|
+
if (!ALL_OPERATORS.has(operator)) {
|
|
33004
|
+
throw new Error(`Invalid filter operator ".${operator}" in "${input}". Supported operators: ${ALL_OPERATOR_LABELS}.`);
|
|
33005
|
+
}
|
|
33006
|
+
return {
|
|
33007
|
+
field,
|
|
33008
|
+
operator,
|
|
33009
|
+
value,
|
|
33010
|
+
explicitOperator: true
|
|
33011
|
+
};
|
|
33012
|
+
}
|
|
33013
|
+
return { field: key, operator: "eq", value, explicitOperator: false };
|
|
33014
|
+
}
|
|
33015
|
+
function parseFilterArgs(args) {
|
|
33016
|
+
return (args ?? []).map(parseFilterArg);
|
|
33017
|
+
}
|
|
33018
|
+
function parseQueryFilters(args) {
|
|
33019
|
+
const filters = {};
|
|
33020
|
+
for (const filter of parseFilterArgs(args)) {
|
|
33021
|
+
const key = filter.operator === "eq" ? filter.field : `${filter.field}.${filter.operator}`;
|
|
33022
|
+
filters[key] = filter.value;
|
|
33023
|
+
}
|
|
33024
|
+
return Object.keys(filters).length > 0 ? filters : undefined;
|
|
33025
|
+
}
|
|
33026
|
+
function parseSubscriptionFilter(args) {
|
|
33027
|
+
const filters = {};
|
|
33028
|
+
const seenFields = new Set;
|
|
33029
|
+
for (const filter of parseFilterArgs(args)) {
|
|
33030
|
+
if (filter.operator === "like") {
|
|
33031
|
+
throw new Error(`Subscription filters do not support ".like". Supported operators: ${FILTER_OPERATORS.join(", ")}.`);
|
|
33032
|
+
}
|
|
33033
|
+
if (seenFields.has(filter.field)) {
|
|
33034
|
+
throw new Error(`Subscription filters support one condition per field; got multiple filters for "${filter.field}".`);
|
|
33035
|
+
}
|
|
33036
|
+
seenFields.add(filter.field);
|
|
33037
|
+
if (!filter.explicitOperator || filter.operator === "eq") {
|
|
33038
|
+
filters[filter.field] = filter.value;
|
|
33039
|
+
} else {
|
|
33040
|
+
filters[filter.field] = { [filter.operator]: filter.value };
|
|
33041
|
+
}
|
|
33042
|
+
}
|
|
33043
|
+
return Object.keys(filters).length > 0 ? filters : undefined;
|
|
33044
|
+
}
|
|
33045
|
+
|
|
33046
|
+
// src/commands/create.ts
|
|
33047
|
+
init_output();
|
|
33048
|
+
init_resolve_tenant();
|
|
33049
|
+
|
|
33050
|
+
// src/lib/subscription-validation.ts
|
|
33051
|
+
import {
|
|
33052
|
+
validateSubscriptionFilterForTable
|
|
33053
|
+
} from "@secondlayer/shared/schemas/subscriptions";
|
|
33054
|
+
async function validateSubscriptionTargetFromApi(client, input) {
|
|
33055
|
+
const subgraph = await client.subgraphs.get(input.subgraphName);
|
|
33056
|
+
const errors = validateSubscriptionFilterForTable({
|
|
33057
|
+
subgraphName: input.subgraphName,
|
|
33058
|
+
tableName: input.tableName,
|
|
33059
|
+
filter: input.filter,
|
|
33060
|
+
tables: subgraph.tables
|
|
33061
|
+
});
|
|
33062
|
+
if (errors.length > 0) {
|
|
33063
|
+
throw new Error(errors.join(`
|
|
33064
|
+
`));
|
|
33065
|
+
}
|
|
33066
|
+
}
|
|
33067
|
+
|
|
33068
|
+
// src/commands/create.ts
|
|
32980
33069
|
var RUNTIMES = ["inngest", "trigger", "cloudflare", "node"];
|
|
32981
33070
|
var FORMAT_BY_RUNTIME = {
|
|
32982
33071
|
inngest: "inngest",
|
|
@@ -33031,6 +33120,27 @@ async function promptFor(_name, opts) {
|
|
|
33031
33120
|
}
|
|
33032
33121
|
async function createSubscription(name, opts) {
|
|
33033
33122
|
const { runtime, subgraph, table, url } = await promptFor(name, opts);
|
|
33123
|
+
let filter;
|
|
33124
|
+
try {
|
|
33125
|
+
filter = parseSubscriptionFilter(opts.filter);
|
|
33126
|
+
} catch (err) {
|
|
33127
|
+
error(err instanceof Error ? err.message : String(err));
|
|
33128
|
+
process.exit(1);
|
|
33129
|
+
}
|
|
33130
|
+
let sl = null;
|
|
33131
|
+
if (!opts.skipApi) {
|
|
33132
|
+
try {
|
|
33133
|
+
sl = await getSubscriptionClient(opts);
|
|
33134
|
+
await validateSubscriptionTargetFromApi(sl, {
|
|
33135
|
+
subgraphName: subgraph,
|
|
33136
|
+
tableName: table,
|
|
33137
|
+
filter
|
|
33138
|
+
});
|
|
33139
|
+
} catch (err) {
|
|
33140
|
+
error(err instanceof Error ? err.message : String(err));
|
|
33141
|
+
process.exit(1);
|
|
33142
|
+
}
|
|
33143
|
+
}
|
|
33034
33144
|
const eventName = `${subgraph}.${table}.created`;
|
|
33035
33145
|
const targetDir = resolve2(process.cwd(), name);
|
|
33036
33146
|
if (existsSync2(targetDir)) {
|
|
@@ -33048,27 +33158,19 @@ async function createSubscription(name, opts) {
|
|
|
33048
33158
|
let signingSecret = null;
|
|
33049
33159
|
if (!opts.skipApi) {
|
|
33050
33160
|
try {
|
|
33051
|
-
|
|
33052
|
-
|
|
33053
|
-
|
|
33054
|
-
|
|
33055
|
-
|
|
33056
|
-
|
|
33057
|
-
|
|
33058
|
-
|
|
33059
|
-
|
|
33060
|
-
}
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
tableName: table,
|
|
33065
|
-
url,
|
|
33066
|
-
format: FORMAT_BY_RUNTIME[runtime],
|
|
33067
|
-
runtime
|
|
33068
|
-
});
|
|
33069
|
-
signingSecret = res.signingSecret;
|
|
33070
|
-
success(`Subscription provisioned: ${blue(res.subscription.id)}`);
|
|
33071
|
-
}
|
|
33161
|
+
if (!sl)
|
|
33162
|
+
sl = await getSubscriptionClient(opts);
|
|
33163
|
+
const res = await sl.subscriptions.create({
|
|
33164
|
+
name,
|
|
33165
|
+
subgraphName: subgraph,
|
|
33166
|
+
tableName: table,
|
|
33167
|
+
url,
|
|
33168
|
+
format: FORMAT_BY_RUNTIME[runtime],
|
|
33169
|
+
runtime,
|
|
33170
|
+
...filter ? { filter } : {}
|
|
33171
|
+
});
|
|
33172
|
+
signingSecret = res.signingSecret;
|
|
33173
|
+
success(`Subscription provisioned: ${blue(res.subscription.id)}`);
|
|
33072
33174
|
} catch (err) {
|
|
33073
33175
|
warn(`Subscription provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
33074
33176
|
info("Template copied — fix auth + run again, or provision via dashboard.");
|
|
@@ -33098,9 +33200,40 @@ ${dim(" ")}${signingSecret}`);
|
|
|
33098
33200
|
bun install
|
|
33099
33201
|
bun run dev`);
|
|
33100
33202
|
}
|
|
33203
|
+
function resolveSubscriptionClientConfig(opts, env = process.env, resolved) {
|
|
33204
|
+
const needsResolvedKey = !opts.serviceKey && !env.SL_SERVICE_KEY;
|
|
33205
|
+
const needsResolvedUrl = !opts.baseUrl && !env.SL_API_URL;
|
|
33206
|
+
if ((needsResolvedKey || needsResolvedUrl) && !resolved) {
|
|
33207
|
+
return { needsTenantResolution: true };
|
|
33208
|
+
}
|
|
33209
|
+
const apiKey = opts.serviceKey ?? env.SL_SERVICE_KEY ?? resolved?.ephemeralKey;
|
|
33210
|
+
const baseUrl = opts.baseUrl ?? env.SL_API_URL ?? resolved?.apiUrl;
|
|
33211
|
+
if (!apiKey) {
|
|
33212
|
+
throw new Error("No service key available. Run `sl login` from an active project or pass --service-key.");
|
|
33213
|
+
}
|
|
33214
|
+
if (!baseUrl) {
|
|
33215
|
+
throw new Error("No tenant API URL available. Run `sl project use <slug>` or pass --base-url.");
|
|
33216
|
+
}
|
|
33217
|
+
return { needsTenantResolution: false, baseUrl, apiKey };
|
|
33218
|
+
}
|
|
33219
|
+
async function getSubscriptionClient(opts) {
|
|
33220
|
+
const config = resolveSubscriptionClientConfig(opts);
|
|
33221
|
+
if (!config.needsTenantResolution) {
|
|
33222
|
+
return new SecondLayer2({ baseUrl: config.baseUrl, apiKey: config.apiKey });
|
|
33223
|
+
}
|
|
33224
|
+
const resolved = await resolveActiveTenant();
|
|
33225
|
+
const resolvedConfig = resolveSubscriptionClientConfig(opts, process.env, resolved);
|
|
33226
|
+
if (resolvedConfig.needsTenantResolution) {
|
|
33227
|
+
throw new Error("Could not resolve active tenant credentials.");
|
|
33228
|
+
}
|
|
33229
|
+
return new SecondLayer2({
|
|
33230
|
+
baseUrl: resolvedConfig.baseUrl,
|
|
33231
|
+
apiKey: resolvedConfig.apiKey
|
|
33232
|
+
});
|
|
33233
|
+
}
|
|
33101
33234
|
function registerCreateCommand(program2) {
|
|
33102
33235
|
const create = program2.command("create").description("Scaffold new resources (subscription receivers, etc.)");
|
|
33103
|
-
create.command("subscription <name>").description("Scaffold a subscription receiver for a runtime").option("-r, --runtime <runtime>", "inngest | trigger | cloudflare | node").option("-s, --subgraph <name>", "Subgraph to subscribe to").option("-t, --table <name>", "Table to subscribe to").option("-u, --url <url>", "Webhook URL").option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override").option("--skip-api", "Copy template only, don't call the API").action(async (name, options) => {
|
|
33236
|
+
create.command("subscription <name>").description("Scaffold a subscription receiver for a runtime").option("-r, --runtime <runtime>", "inngest | trigger | cloudflare | node").option("-s, --subgraph <name>", "Subgraph to subscribe to").option("-t, --table <name>", "Table to subscribe to").option("-u, --url <url>", "Webhook URL").option("--filter <kv...>", "Filter as key=value (supports .eq/.neq/.gt/.gte/.lt/.lte suffixes)").option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override").option("--skip-api", "Copy template only, don't call the API").action(async (name, options) => {
|
|
33104
33237
|
await createSubscription(name, options);
|
|
33105
33238
|
});
|
|
33106
33239
|
}
|
|
@@ -33193,11 +33326,596 @@ function printStatus(status) {
|
|
|
33193
33326
|
}
|
|
33194
33327
|
console.log(dim(`Last updated: ${status.timestamp}`));
|
|
33195
33328
|
}
|
|
33329
|
+
// src/commands/subscriptions.ts
|
|
33330
|
+
import { confirm } from "@inquirer/prompts";
|
|
33331
|
+
import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
|
|
33332
|
+
init_output();
|
|
33333
|
+
function commonOptions(cmd) {
|
|
33334
|
+
return cmd.option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override");
|
|
33335
|
+
}
|
|
33336
|
+
function parseIntegerOption(value, name, min) {
|
|
33337
|
+
if (value === undefined)
|
|
33338
|
+
return;
|
|
33339
|
+
const parsed = Number.parseInt(value, 10);
|
|
33340
|
+
if (!Number.isFinite(parsed) || String(parsed) !== value.trim()) {
|
|
33341
|
+
throw new Error(`${name} must be an integer`);
|
|
33342
|
+
}
|
|
33343
|
+
if (parsed < min)
|
|
33344
|
+
throw new Error(`${name} must be >= ${min}`);
|
|
33345
|
+
return parsed;
|
|
33346
|
+
}
|
|
33347
|
+
function requireIntegerOption(value, name) {
|
|
33348
|
+
const parsed = parseIntegerOption(value, name, 0);
|
|
33349
|
+
if (parsed === undefined)
|
|
33350
|
+
throw new Error(`${name} is required`);
|
|
33351
|
+
return parsed;
|
|
33352
|
+
}
|
|
33353
|
+
function truncate(value, max = 48) {
|
|
33354
|
+
return value.length <= max ? value : `${value.slice(0, max - 3)}...`;
|
|
33355
|
+
}
|
|
33356
|
+
function formatMaybeDate(value) {
|
|
33357
|
+
return value ? value.replace("T", " ").slice(0, 19) : dim("-");
|
|
33358
|
+
}
|
|
33359
|
+
function isSuccessDelivery(row) {
|
|
33360
|
+
return row.statusCode !== null && row.statusCode >= 200 && row.statusCode < 300;
|
|
33361
|
+
}
|
|
33362
|
+
function printJson(value) {
|
|
33363
|
+
console.log(JSON.stringify(value, null, 2));
|
|
33364
|
+
}
|
|
33365
|
+
async function resolveSubscriptionRef(client, ref) {
|
|
33366
|
+
const { data } = await client.subscriptions.list();
|
|
33367
|
+
const idMatch = data.find((sub) => sub.id === ref);
|
|
33368
|
+
if (idMatch) {
|
|
33369
|
+
return {
|
|
33370
|
+
id: idMatch.id,
|
|
33371
|
+
detail: await client.subscriptions.get(idMatch.id)
|
|
33372
|
+
};
|
|
33373
|
+
}
|
|
33374
|
+
const nameMatches = data.filter((sub) => sub.name === ref);
|
|
33375
|
+
if (nameMatches.length > 1) {
|
|
33376
|
+
throw new Error(`Subscription name "${ref}" is ambiguous; use the subscription id.`);
|
|
33377
|
+
}
|
|
33378
|
+
if (nameMatches[0]) {
|
|
33379
|
+
return {
|
|
33380
|
+
id: nameMatches[0].id,
|
|
33381
|
+
detail: await client.subscriptions.get(nameMatches[0].id)
|
|
33382
|
+
};
|
|
33383
|
+
}
|
|
33384
|
+
return {
|
|
33385
|
+
id: ref,
|
|
33386
|
+
detail: await client.subscriptions.get(ref)
|
|
33387
|
+
};
|
|
33388
|
+
}
|
|
33389
|
+
function buildUpdatePatch(options) {
|
|
33390
|
+
const patch = {};
|
|
33391
|
+
if (options.name)
|
|
33392
|
+
patch.name = options.name;
|
|
33393
|
+
if (options.url)
|
|
33394
|
+
patch.url = options.url;
|
|
33395
|
+
if (options.format) {
|
|
33396
|
+
patch.format = options.format;
|
|
33397
|
+
}
|
|
33398
|
+
if (options.runtime !== undefined) {
|
|
33399
|
+
patch.runtime = options.runtime === "none" || options.runtime === "null" ? null : options.runtime;
|
|
33400
|
+
}
|
|
33401
|
+
if (options.clearFilter)
|
|
33402
|
+
patch.filter = {};
|
|
33403
|
+
if (options.filter) {
|
|
33404
|
+
if (options.clearFilter) {
|
|
33405
|
+
throw new Error("Use either --filter or --clear-filter, not both");
|
|
33406
|
+
}
|
|
33407
|
+
patch.filter = parseSubscriptionFilter(options.filter) ?? {};
|
|
33408
|
+
}
|
|
33409
|
+
const maxRetries = parseIntegerOption(options.maxRetries, "--max-retries", 0);
|
|
33410
|
+
if (maxRetries !== undefined)
|
|
33411
|
+
patch.maxRetries = maxRetries;
|
|
33412
|
+
const timeoutMs = parseIntegerOption(options.timeoutMs, "--timeout-ms", 100);
|
|
33413
|
+
if (timeoutMs !== undefined)
|
|
33414
|
+
patch.timeoutMs = timeoutMs;
|
|
33415
|
+
const concurrency = parseIntegerOption(options.concurrency, "--concurrency", 1);
|
|
33416
|
+
if (concurrency !== undefined)
|
|
33417
|
+
patch.concurrency = concurrency;
|
|
33418
|
+
if (Object.keys(patch).length === 0) {
|
|
33419
|
+
throw new Error("No update fields provided");
|
|
33420
|
+
}
|
|
33421
|
+
return patch;
|
|
33422
|
+
}
|
|
33423
|
+
function printSubscriptionDetail(sub) {
|
|
33424
|
+
console.log(formatKeyValue([
|
|
33425
|
+
["ID", sub.id],
|
|
33426
|
+
["Name", sub.name],
|
|
33427
|
+
["Status", sub.status],
|
|
33428
|
+
["Target", `${sub.subgraphName}.${sub.tableName}`],
|
|
33429
|
+
["Format", sub.format],
|
|
33430
|
+
["Runtime", sub.runtime ?? "none"],
|
|
33431
|
+
["URL", sub.url],
|
|
33432
|
+
["Last Delivery", sub.lastDeliveryAt ?? "none"],
|
|
33433
|
+
["Last Success", sub.lastSuccessAt ?? "none"],
|
|
33434
|
+
["Circuit Failures", String(sub.circuitFailures)],
|
|
33435
|
+
["Circuit Opened", sub.circuitOpenedAt ?? "none"],
|
|
33436
|
+
["Last Error", sub.lastError ?? "none"],
|
|
33437
|
+
["Max Retries", String(sub.maxRetries)],
|
|
33438
|
+
["Timeout", `${sub.timeoutMs}ms`],
|
|
33439
|
+
["Concurrency", String(sub.concurrency)],
|
|
33440
|
+
["Created", sub.createdAt],
|
|
33441
|
+
["Updated", sub.updatedAt]
|
|
33442
|
+
]));
|
|
33443
|
+
console.log(dim(`
|
|
33444
|
+
Filter:`));
|
|
33445
|
+
console.log(JSON.stringify(sub.filter, null, 2));
|
|
33446
|
+
if (Object.keys(sub.authConfig).length > 0) {
|
|
33447
|
+
console.log(dim(`
|
|
33448
|
+
Auth config:`));
|
|
33449
|
+
console.log(JSON.stringify(sub.authConfig, null, 2));
|
|
33450
|
+
}
|
|
33451
|
+
}
|
|
33452
|
+
function buildDoctorReport(input2) {
|
|
33453
|
+
const successful = input2.deliveries.filter(isSuccessDelivery).length;
|
|
33454
|
+
const failed = input2.deliveries.length - successful;
|
|
33455
|
+
const subgraph = input2.subgraph ? {
|
|
33456
|
+
name: input2.subgraph.name,
|
|
33457
|
+
status: input2.subgraph.status,
|
|
33458
|
+
syncStatus: input2.subgraph.sync.status,
|
|
33459
|
+
lastProcessedBlock: input2.subgraph.sync.lastProcessedBlock,
|
|
33460
|
+
chainTip: input2.subgraph.sync.chainTip,
|
|
33461
|
+
gapCount: input2.subgraph.sync.gaps.count,
|
|
33462
|
+
integrity: input2.subgraph.sync.integrity
|
|
33463
|
+
} : null;
|
|
33464
|
+
const hints = [];
|
|
33465
|
+
if (input2.subscription.status === "paused") {
|
|
33466
|
+
hints.push(`Resume when the receiver is healthy: sl subscriptions resume ${input2.subscription.id}`);
|
|
33467
|
+
}
|
|
33468
|
+
if (input2.subscription.lastError) {
|
|
33469
|
+
hints.push("Run sl subscriptions test to reproduce the receiver request.");
|
|
33470
|
+
}
|
|
33471
|
+
if (input2.subscription.circuitOpenedAt || input2.subscription.circuitFailures > 0) {
|
|
33472
|
+
hints.push("Circuit breaker has failures; inspect receiver logs and delivery status codes.");
|
|
33473
|
+
}
|
|
33474
|
+
if (input2.dead.length > 0) {
|
|
33475
|
+
hints.push(`Dead-letter rows exist; inspect with sl subscriptions dead ${input2.subscription.id} and requeue selected rows.`);
|
|
33476
|
+
}
|
|
33477
|
+
if (subgraph?.gapCount && subgraph.gapCount > 0) {
|
|
33478
|
+
hints.push(`Linked subgraph has gaps; run sl subgraphs gaps ${input2.subscription.subgraphName}.`);
|
|
33479
|
+
}
|
|
33480
|
+
if (subgraph?.syncStatus === "catching_up") {
|
|
33481
|
+
hints.push("Linked subgraph is still catching up; new matching rows may arrive later.");
|
|
33482
|
+
}
|
|
33483
|
+
if (input2.deliveries.length === 0) {
|
|
33484
|
+
hints.push("No deliveries yet; confirm the table is receiving inserted rows that match the filter.");
|
|
33485
|
+
}
|
|
33486
|
+
if (hints.length === 0) {
|
|
33487
|
+
hints.push("No immediate action needed.");
|
|
33488
|
+
}
|
|
33489
|
+
return {
|
|
33490
|
+
subscription: input2.subscription,
|
|
33491
|
+
deliverySummary: {
|
|
33492
|
+
total: input2.deliveries.length,
|
|
33493
|
+
successful,
|
|
33494
|
+
failed,
|
|
33495
|
+
last: input2.deliveries[0] ?? null
|
|
33496
|
+
},
|
|
33497
|
+
deadCount: input2.dead.length,
|
|
33498
|
+
subgraph,
|
|
33499
|
+
hints
|
|
33500
|
+
};
|
|
33501
|
+
}
|
|
33502
|
+
function printDoctorReport(report) {
|
|
33503
|
+
const sub = report.subscription;
|
|
33504
|
+
console.log(formatKeyValue([
|
|
33505
|
+
["Subscription", `${sub.name} (${sub.id})`],
|
|
33506
|
+
["Status", sub.status],
|
|
33507
|
+
["Target", `${sub.subgraphName}.${sub.tableName}`],
|
|
33508
|
+
["Format", sub.format],
|
|
33509
|
+
["Runtime", sub.runtime ?? "none"],
|
|
33510
|
+
["URL", sub.url],
|
|
33511
|
+
[
|
|
33512
|
+
"Circuit",
|
|
33513
|
+
sub.circuitOpenedAt ? `open at ${sub.circuitOpenedAt}` : `${sub.circuitFailures} failures`
|
|
33514
|
+
],
|
|
33515
|
+
["Last Error", sub.lastError ?? "none"],
|
|
33516
|
+
["Last Delivery", sub.lastDeliveryAt ?? "none"],
|
|
33517
|
+
["Last Success", sub.lastSuccessAt ?? "none"]
|
|
33518
|
+
]));
|
|
33519
|
+
console.log(dim(`
|
|
33520
|
+
Delivery summary:`));
|
|
33521
|
+
console.log(formatKeyValue([
|
|
33522
|
+
["Recent Attempts", String(report.deliverySummary.total)],
|
|
33523
|
+
["Successful", String(report.deliverySummary.successful)],
|
|
33524
|
+
["Failed", String(report.deliverySummary.failed)],
|
|
33525
|
+
[
|
|
33526
|
+
"Last Attempt",
|
|
33527
|
+
report.deliverySummary.last ? `${report.deliverySummary.last.statusCode ?? "error"} at ${report.deliverySummary.last.dispatchedAt}` : "none"
|
|
33528
|
+
],
|
|
33529
|
+
["Dead Letter Rows", String(report.deadCount)]
|
|
33530
|
+
]));
|
|
33531
|
+
if (report.subgraph) {
|
|
33532
|
+
console.log(dim(`
|
|
33533
|
+
Linked subgraph:`));
|
|
33534
|
+
console.log(formatKeyValue([
|
|
33535
|
+
["Name", report.subgraph.name],
|
|
33536
|
+
["Status", report.subgraph.status],
|
|
33537
|
+
["Sync", report.subgraph.syncStatus],
|
|
33538
|
+
[
|
|
33539
|
+
"Blocks",
|
|
33540
|
+
`${report.subgraph.lastProcessedBlock} / ${report.subgraph.chainTip}`
|
|
33541
|
+
],
|
|
33542
|
+
["Integrity", report.subgraph.integrity],
|
|
33543
|
+
["Gaps", String(report.subgraph.gapCount)]
|
|
33544
|
+
]));
|
|
33545
|
+
}
|
|
33546
|
+
console.log(dim(`
|
|
33547
|
+
Next steps:`));
|
|
33548
|
+
for (const hint of report.hints)
|
|
33549
|
+
console.log(` - ${hint}`);
|
|
33550
|
+
}
|
|
33551
|
+
function syntheticValue(type) {
|
|
33552
|
+
switch (type) {
|
|
33553
|
+
case "uint":
|
|
33554
|
+
case "int":
|
|
33555
|
+
return "1000";
|
|
33556
|
+
case "boolean":
|
|
33557
|
+
return true;
|
|
33558
|
+
case "timestamp":
|
|
33559
|
+
return new Date(0).toISOString();
|
|
33560
|
+
case "jsonb":
|
|
33561
|
+
return {};
|
|
33562
|
+
case "principal":
|
|
33563
|
+
return "SP000000000000000000002Q6VF78";
|
|
33564
|
+
default:
|
|
33565
|
+
return "example";
|
|
33566
|
+
}
|
|
33567
|
+
}
|
|
33568
|
+
function buildSyntheticRow(subgraph, tableName) {
|
|
33569
|
+
const table = subgraph?.tables[tableName];
|
|
33570
|
+
if (!table)
|
|
33571
|
+
return { id: "example", value: "example" };
|
|
33572
|
+
const row = {};
|
|
33573
|
+
for (const [column, def] of Object.entries(table.columns)) {
|
|
33574
|
+
if (column.startsWith("_"))
|
|
33575
|
+
continue;
|
|
33576
|
+
row[column] = syntheticValue(def.type);
|
|
33577
|
+
}
|
|
33578
|
+
return Object.keys(row).length > 0 ? row : { id: "example", value: "example" };
|
|
33579
|
+
}
|
|
33580
|
+
function resolveSigningSecret(options, env = process.env) {
|
|
33581
|
+
const secret = options.signingSecret ?? env.SIGNING_SECRET;
|
|
33582
|
+
if (!secret) {
|
|
33583
|
+
throw new Error("Provide --signing-secret or set SIGNING_SECRET.");
|
|
33584
|
+
}
|
|
33585
|
+
return secret;
|
|
33586
|
+
}
|
|
33587
|
+
function shellQuote(value) {
|
|
33588
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
33589
|
+
}
|
|
33590
|
+
function buildSubscriptionTestFixture(input2) {
|
|
33591
|
+
const nowSeconds = input2.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
33592
|
+
const body = JSON.stringify({
|
|
33593
|
+
type: `${input2.subscription.subgraphName}.${input2.subscription.tableName}.created`,
|
|
33594
|
+
timestamp: new Date(nowSeconds * 1000).toISOString(),
|
|
33595
|
+
data: input2.row
|
|
33596
|
+
});
|
|
33597
|
+
const headers = {
|
|
33598
|
+
"content-type": "application/json",
|
|
33599
|
+
...sign(body, input2.signingSecret, {
|
|
33600
|
+
id: input2.id ?? `test-${input2.subscription.id}`,
|
|
33601
|
+
timestampSeconds: nowSeconds
|
|
33602
|
+
})
|
|
33603
|
+
};
|
|
33604
|
+
const headerArgs = Object.entries(headers).map(([key, value]) => ` -H ${shellQuote(`${key}: ${value}`)} \\`).join(`
|
|
33605
|
+
`);
|
|
33606
|
+
const curl = [
|
|
33607
|
+
`curl -X POST ${shellQuote(input2.subscription.url)} \\`,
|
|
33608
|
+
headerArgs,
|
|
33609
|
+
` --data ${shellQuote(body)}`
|
|
33610
|
+
].join(`
|
|
33611
|
+
`);
|
|
33612
|
+
return { body, headers, curl };
|
|
33613
|
+
}
|
|
33614
|
+
async function representativeRow(client, sub, subgraph) {
|
|
33615
|
+
try {
|
|
33616
|
+
const rows = await client.subgraphs.queryTable(sub.subgraphName, sub.tableName, {
|
|
33617
|
+
sort: "_block_height",
|
|
33618
|
+
order: "desc",
|
|
33619
|
+
limit: 1
|
|
33620
|
+
});
|
|
33621
|
+
if (rows[0] && typeof rows[0] === "object")
|
|
33622
|
+
return rows[0];
|
|
33623
|
+
} catch {}
|
|
33624
|
+
return buildSyntheticRow(subgraph, sub.tableName);
|
|
33625
|
+
}
|
|
33626
|
+
function printDeliveries(rows) {
|
|
33627
|
+
if (rows.length === 0) {
|
|
33628
|
+
console.log(dim("No delivery attempts"));
|
|
33629
|
+
return;
|
|
33630
|
+
}
|
|
33631
|
+
console.log(formatTable(["Dispatched", "Attempt", "Status", "Duration", "Error"], rows.map((row) => [
|
|
33632
|
+
formatMaybeDate(row.dispatchedAt),
|
|
33633
|
+
String(row.attempt),
|
|
33634
|
+
row.statusCode === null ? redStatus("error") : isSuccessDelivery(row) ? green(String(row.statusCode)) : yellow(String(row.statusCode)),
|
|
33635
|
+
row.durationMs === null ? dim("-") : `${row.durationMs}ms`,
|
|
33636
|
+
row.errorMessage ? truncate(row.errorMessage, 64) : dim("-")
|
|
33637
|
+
])));
|
|
33638
|
+
}
|
|
33639
|
+
function redStatus(text) {
|
|
33640
|
+
return `\x1B[31m${text}\x1B[0m`;
|
|
33641
|
+
}
|
|
33642
|
+
function printDead(rows) {
|
|
33643
|
+
if (rows.length === 0) {
|
|
33644
|
+
console.log(dim("No dead-letter rows"));
|
|
33645
|
+
return;
|
|
33646
|
+
}
|
|
33647
|
+
console.log(formatTable(["ID", "Event", "Block", "Attempt", "Failed"], rows.map((row) => [
|
|
33648
|
+
row.id,
|
|
33649
|
+
row.eventType,
|
|
33650
|
+
String(row.blockHeight),
|
|
33651
|
+
String(row.attempt),
|
|
33652
|
+
formatMaybeDate(row.failedAt)
|
|
33653
|
+
])));
|
|
33654
|
+
}
|
|
33655
|
+
async function confirmOrExit(message, yes) {
|
|
33656
|
+
if (yes)
|
|
33657
|
+
return true;
|
|
33658
|
+
const ok = await confirm({ message });
|
|
33659
|
+
if (!ok) {
|
|
33660
|
+
info("Cancelled");
|
|
33661
|
+
return false;
|
|
33662
|
+
}
|
|
33663
|
+
return true;
|
|
33664
|
+
}
|
|
33665
|
+
function registerSubscriptionsCommand(program2) {
|
|
33666
|
+
const subscriptions = commonOptions(program2.command("subscriptions").alias("subs").description("Manage subgraph table subscriptions"));
|
|
33667
|
+
commonOptions(subscriptions.command("list").alias("ls").description("List subscriptions").option("--json", "Output as JSON")).action(async (options) => {
|
|
33668
|
+
try {
|
|
33669
|
+
const client = await getSubscriptionClient(options);
|
|
33670
|
+
const { data } = await client.subscriptions.list();
|
|
33671
|
+
if (options.json) {
|
|
33672
|
+
printJson(data);
|
|
33673
|
+
return;
|
|
33674
|
+
}
|
|
33675
|
+
if (data.length === 0) {
|
|
33676
|
+
console.log(dim("No subscriptions"));
|
|
33677
|
+
return;
|
|
33678
|
+
}
|
|
33679
|
+
console.log(formatTable(["Name", "ID", "Status", "Target", "Format", "Last Success"], data.map((sub) => [
|
|
33680
|
+
sub.name,
|
|
33681
|
+
sub.id,
|
|
33682
|
+
sub.status === "active" ? green(sub.status) : sub.status === "paused" ? yellow(sub.status) : redStatus(sub.status),
|
|
33683
|
+
`${sub.subgraphName}.${sub.tableName}`,
|
|
33684
|
+
sub.format,
|
|
33685
|
+
formatMaybeDate(sub.lastSuccessAt)
|
|
33686
|
+
])));
|
|
33687
|
+
console.log(dim(`
|
|
33688
|
+
${data.length} subscription(s) total`));
|
|
33689
|
+
} catch (err) {
|
|
33690
|
+
handleApiError(err, "list subscriptions");
|
|
33691
|
+
}
|
|
33692
|
+
});
|
|
33693
|
+
commonOptions(subscriptions.command("get <idOrName>").description("Show subscription details").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33694
|
+
try {
|
|
33695
|
+
const client = await getSubscriptionClient(options);
|
|
33696
|
+
const { detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33697
|
+
if (options.json)
|
|
33698
|
+
printJson(detail);
|
|
33699
|
+
else
|
|
33700
|
+
printSubscriptionDetail(detail);
|
|
33701
|
+
} catch (err) {
|
|
33702
|
+
handleApiError(err, "get subscription");
|
|
33703
|
+
}
|
|
33704
|
+
});
|
|
33705
|
+
commonOptions(subscriptions.command("update <idOrName>").description("Update subscription config").option("--name <name>", "Rename subscription").option("--url <url>", "Webhook URL").option("--format <format>", "standard-webhooks | inngest | trigger | cloudflare | cloudevents | raw").option("--runtime <runtime>", "inngest | trigger | cloudflare | node | none").option("--filter <kv...>", "Filter as key=value (supports .eq/.neq/.gt/.gte/.lt/.lte suffixes)").option("--clear-filter", "Replace filter with {}").option("--max-retries <n>", "Maximum delivery retries").option("--timeout-ms <n>", "Delivery timeout in milliseconds").option("--concurrency <n>", "Per-subscription delivery concurrency").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33706
|
+
try {
|
|
33707
|
+
const client = await getSubscriptionClient(options);
|
|
33708
|
+
const patch = buildUpdatePatch(options);
|
|
33709
|
+
const { id, detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33710
|
+
if (patch.filter !== undefined) {
|
|
33711
|
+
await validateSubscriptionTargetFromApi(client, {
|
|
33712
|
+
subgraphName: detail.subgraphName,
|
|
33713
|
+
tableName: detail.tableName,
|
|
33714
|
+
filter: patch.filter
|
|
33715
|
+
});
|
|
33716
|
+
}
|
|
33717
|
+
const updated = await client.subscriptions.update(id, patch);
|
|
33718
|
+
if (options.json)
|
|
33719
|
+
printJson(updated);
|
|
33720
|
+
else
|
|
33721
|
+
success(`Updated subscription ${blue(updated.name)}`);
|
|
33722
|
+
} catch (err) {
|
|
33723
|
+
handleApiError(err, "update subscription");
|
|
33724
|
+
}
|
|
33725
|
+
});
|
|
33726
|
+
for (const action of ["pause", "resume"]) {
|
|
33727
|
+
commonOptions(subscriptions.command(`${action} <idOrName>`).description(`${action === "pause" ? "Pause" : "Resume"} a subscription`).option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33728
|
+
try {
|
|
33729
|
+
const client = await getSubscriptionClient(options);
|
|
33730
|
+
const { id } = await resolveSubscriptionRef(client, idOrName);
|
|
33731
|
+
const updated = await client.subscriptions[action](id);
|
|
33732
|
+
if (options.json)
|
|
33733
|
+
printJson(updated);
|
|
33734
|
+
else
|
|
33735
|
+
success(`${action === "pause" ? "Paused" : "Resumed"} ${blue(updated.name)}`);
|
|
33736
|
+
} catch (err) {
|
|
33737
|
+
handleApiError(err, `${action} subscription`);
|
|
33738
|
+
}
|
|
33739
|
+
});
|
|
33740
|
+
}
|
|
33741
|
+
commonOptions(subscriptions.command("delete <idOrName>").description("Delete a subscription").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33742
|
+
try {
|
|
33743
|
+
const client = await getSubscriptionClient(options);
|
|
33744
|
+
const { id, detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33745
|
+
const ok = await confirmOrExit(`Delete subscription "${detail.name}"? Pending outbox rows will be removed.`, options.yes);
|
|
33746
|
+
if (!ok)
|
|
33747
|
+
return;
|
|
33748
|
+
const res = await client.subscriptions.delete(id);
|
|
33749
|
+
if (options.json)
|
|
33750
|
+
printJson(res);
|
|
33751
|
+
else
|
|
33752
|
+
success(`Deleted subscription ${blue(detail.name)}`);
|
|
33753
|
+
} catch (err) {
|
|
33754
|
+
handleApiError(err, "delete subscription");
|
|
33755
|
+
}
|
|
33756
|
+
});
|
|
33757
|
+
commonOptions(subscriptions.command("rotate-secret <idOrName>").description("Rotate the signing secret").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33758
|
+
try {
|
|
33759
|
+
const client = await getSubscriptionClient(options);
|
|
33760
|
+
const { id, detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33761
|
+
const ok = await confirmOrExit(`Rotate signing secret for "${detail.name}"? Existing receivers using the old secret will fail verification.`, options.yes);
|
|
33762
|
+
if (!ok)
|
|
33763
|
+
return;
|
|
33764
|
+
const res = await client.subscriptions.rotateSecret(id);
|
|
33765
|
+
if (options.json)
|
|
33766
|
+
printJson(res);
|
|
33767
|
+
else {
|
|
33768
|
+
success(`Rotated signing secret for ${blue(res.subscription.name)}`);
|
|
33769
|
+
console.log(res.signingSecret);
|
|
33770
|
+
}
|
|
33771
|
+
} catch (err) {
|
|
33772
|
+
handleApiError(err, "rotate subscription secret");
|
|
33773
|
+
}
|
|
33774
|
+
});
|
|
33775
|
+
commonOptions(subscriptions.command("deliveries <idOrName>").description("Show recent delivery attempts").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33776
|
+
try {
|
|
33777
|
+
const client = await getSubscriptionClient(options);
|
|
33778
|
+
const { id } = await resolveSubscriptionRef(client, idOrName);
|
|
33779
|
+
const { data } = await client.subscriptions.recentDeliveries(id);
|
|
33780
|
+
if (options.json)
|
|
33781
|
+
printJson(data);
|
|
33782
|
+
else
|
|
33783
|
+
printDeliveries(data);
|
|
33784
|
+
} catch (err) {
|
|
33785
|
+
handleApiError(err, "list subscription deliveries");
|
|
33786
|
+
}
|
|
33787
|
+
});
|
|
33788
|
+
commonOptions(subscriptions.command("dead <idOrName>").description("Show dead-letter outbox rows").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33789
|
+
try {
|
|
33790
|
+
const client = await getSubscriptionClient(options);
|
|
33791
|
+
const { id } = await resolveSubscriptionRef(client, idOrName);
|
|
33792
|
+
const { data } = await client.subscriptions.dead(id);
|
|
33793
|
+
if (options.json)
|
|
33794
|
+
printJson(data);
|
|
33795
|
+
else
|
|
33796
|
+
printDead(data);
|
|
33797
|
+
} catch (err) {
|
|
33798
|
+
handleApiError(err, "list dead-letter rows");
|
|
33799
|
+
}
|
|
33800
|
+
});
|
|
33801
|
+
commonOptions(subscriptions.command("requeue <idOrName> <outboxId>").description("Requeue one dead-letter row").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, outboxId, options) => {
|
|
33802
|
+
try {
|
|
33803
|
+
const client = await getSubscriptionClient(options);
|
|
33804
|
+
const { id, detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33805
|
+
const ok = await confirmOrExit(`Requeue ${outboxId} for "${detail.name}"?`, options.yes);
|
|
33806
|
+
if (!ok)
|
|
33807
|
+
return;
|
|
33808
|
+
const res = await client.subscriptions.requeueDead(id, outboxId);
|
|
33809
|
+
if (options.json)
|
|
33810
|
+
printJson(res);
|
|
33811
|
+
else
|
|
33812
|
+
success(`Requeued ${blue(outboxId)}`);
|
|
33813
|
+
} catch (err) {
|
|
33814
|
+
handleApiError(err, "requeue dead-letter row");
|
|
33815
|
+
}
|
|
33816
|
+
});
|
|
33817
|
+
commonOptions(subscriptions.command("replay <idOrName>").description("Replay a block range").requiredOption("--from-block <n>", "Start block height").requiredOption("--to-block <n>", "End block height").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33818
|
+
try {
|
|
33819
|
+
const fromBlock = requireIntegerOption(options.fromBlock, "--from-block");
|
|
33820
|
+
const toBlock = requireIntegerOption(options.toBlock, "--to-block");
|
|
33821
|
+
if (fromBlock > toBlock) {
|
|
33822
|
+
throw new Error("--from-block must be <= --to-block");
|
|
33823
|
+
}
|
|
33824
|
+
const client = await getSubscriptionClient(options);
|
|
33825
|
+
const { id, detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33826
|
+
const ok = await confirmOrExit(`Replay ${detail.name} from block ${fromBlock} to ${toBlock}?`, options.yes);
|
|
33827
|
+
if (!ok)
|
|
33828
|
+
return;
|
|
33829
|
+
const res = await client.subscriptions.replay(id, {
|
|
33830
|
+
fromBlock,
|
|
33831
|
+
toBlock
|
|
33832
|
+
});
|
|
33833
|
+
if (options.json)
|
|
33834
|
+
printJson(res);
|
|
33835
|
+
else {
|
|
33836
|
+
success(`Replay enqueued: ${blue(res.replayId)}`);
|
|
33837
|
+
info(`${res.enqueuedCount} row(s) enqueued from ${res.scannedCount} scanned`);
|
|
33838
|
+
}
|
|
33839
|
+
} catch (err) {
|
|
33840
|
+
handleApiError(err, "replay subscription");
|
|
33841
|
+
}
|
|
33842
|
+
});
|
|
33843
|
+
commonOptions(subscriptions.command("doctor <idOrName>").description("Diagnose subscription health and next steps").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33844
|
+
try {
|
|
33845
|
+
const client = await getSubscriptionClient(options);
|
|
33846
|
+
const { id, detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33847
|
+
const [deliveries, dead, subgraph] = await Promise.allSettled([
|
|
33848
|
+
client.subscriptions.recentDeliveries(id),
|
|
33849
|
+
client.subscriptions.dead(id),
|
|
33850
|
+
client.subgraphs.get(detail.subgraphName)
|
|
33851
|
+
]);
|
|
33852
|
+
const report = buildDoctorReport({
|
|
33853
|
+
subscription: detail,
|
|
33854
|
+
deliveries: deliveries.status === "fulfilled" ? deliveries.value.data : [],
|
|
33855
|
+
dead: dead.status === "fulfilled" ? dead.value.data : [],
|
|
33856
|
+
subgraph: subgraph.status === "fulfilled" ? subgraph.value : null
|
|
33857
|
+
});
|
|
33858
|
+
if (options.json)
|
|
33859
|
+
printJson(report);
|
|
33860
|
+
else
|
|
33861
|
+
printDoctorReport(report);
|
|
33862
|
+
} catch (err) {
|
|
33863
|
+
handleApiError(err, "diagnose subscription");
|
|
33864
|
+
}
|
|
33865
|
+
});
|
|
33866
|
+
commonOptions(subscriptions.command("test <idOrName>").description("Build and optionally POST a signed Standard Webhooks fixture").option("--signing-secret <secret>", "Signing secret override").option("--post", "POST the fixture to the subscription URL").option("--json", "Output as JSON")).action(async (idOrName, options) => {
|
|
33867
|
+
try {
|
|
33868
|
+
const signingSecret = resolveSigningSecret(options);
|
|
33869
|
+
const client = await getSubscriptionClient(options);
|
|
33870
|
+
const { detail } = await resolveSubscriptionRef(client, idOrName);
|
|
33871
|
+
const subgraph = await client.subgraphs.get(detail.subgraphName).catch(() => null);
|
|
33872
|
+
const row = await representativeRow(client, detail, subgraph);
|
|
33873
|
+
const fixture = buildSubscriptionTestFixture({
|
|
33874
|
+
subscription: detail,
|
|
33875
|
+
row,
|
|
33876
|
+
signingSecret
|
|
33877
|
+
});
|
|
33878
|
+
let postResult = null;
|
|
33879
|
+
if (options.post) {
|
|
33880
|
+
const res = await fetch(detail.url, {
|
|
33881
|
+
method: "POST",
|
|
33882
|
+
headers: fixture.headers,
|
|
33883
|
+
body: fixture.body
|
|
33884
|
+
});
|
|
33885
|
+
postResult = {
|
|
33886
|
+
status: res.status,
|
|
33887
|
+
body: (await res.text()).slice(0, 2000)
|
|
33888
|
+
};
|
|
33889
|
+
}
|
|
33890
|
+
if (options.json) {
|
|
33891
|
+
printJson({ ...fixture, postResult });
|
|
33892
|
+
return;
|
|
33893
|
+
}
|
|
33894
|
+
console.log(dim("Body:"));
|
|
33895
|
+
console.log(fixture.body);
|
|
33896
|
+
console.log(dim(`
|
|
33897
|
+
Headers:`));
|
|
33898
|
+
console.log(JSON.stringify(fixture.headers, null, 2));
|
|
33899
|
+
console.log(dim(`
|
|
33900
|
+
Curl:`));
|
|
33901
|
+
console.log(fixture.curl);
|
|
33902
|
+
if (postResult) {
|
|
33903
|
+
console.log(dim(`
|
|
33904
|
+
POST result:`));
|
|
33905
|
+
console.log(`Status: ${postResult.status}`);
|
|
33906
|
+
if (postResult.body)
|
|
33907
|
+
console.log(postResult.body);
|
|
33908
|
+
}
|
|
33909
|
+
} catch (err) {
|
|
33910
|
+
handleApiError(err, "test subscription");
|
|
33911
|
+
}
|
|
33912
|
+
});
|
|
33913
|
+
}
|
|
33196
33914
|
// src/commands/db.ts
|
|
33197
33915
|
init_config();
|
|
33198
33916
|
init_dev_state();
|
|
33199
33917
|
init_output();
|
|
33200
|
-
import { confirm } from "@inquirer/prompts";
|
|
33918
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
33201
33919
|
import { getDb, sql } from "@secondlayer/shared/db";
|
|
33202
33920
|
import {
|
|
33203
33921
|
countMissingBlocks,
|
|
@@ -33408,7 +34126,7 @@ async function resetDatabase(skipConfirm) {
|
|
|
33408
34126
|
console.log(dim("Note: Subgraph configurations will be preserved."));
|
|
33409
34127
|
console.log("");
|
|
33410
34128
|
if (!skipConfirm) {
|
|
33411
|
-
const confirmed = await
|
|
34129
|
+
const confirmed = await confirm2({
|
|
33412
34130
|
message: "Are you sure you want to reset the database?",
|
|
33413
34131
|
default: false
|
|
33414
34132
|
});
|
|
@@ -33445,7 +34163,7 @@ async function resyncDatabase(skipConfirm, backfill) {
|
|
|
33445
34163
|
console.log(dim("Note: Subgraph configurations will be preserved."));
|
|
33446
34164
|
console.log("");
|
|
33447
34165
|
if (!skipConfirm) {
|
|
33448
|
-
const confirmed = await
|
|
34166
|
+
const confirmed = await confirm2({
|
|
33449
34167
|
message: "Are you sure you want to resync?",
|
|
33450
34168
|
default: false
|
|
33451
34169
|
});
|
|
@@ -33549,7 +34267,7 @@ async function resyncDatabase(skipConfirm, backfill) {
|
|
|
33549
34267
|
// src/commands/subgraphs.ts
|
|
33550
34268
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, watch } from "node:fs";
|
|
33551
34269
|
import { resolve as resolve3 } from "node:path";
|
|
33552
|
-
import { confirm as
|
|
34270
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
33553
34271
|
|
|
33554
34272
|
// src/generators/subgraph-scaffold.ts
|
|
33555
34273
|
init_format();
|
|
@@ -33930,7 +34648,7 @@ Stopped watching.`);
|
|
|
33930
34648
|
info(` + columns: ${t}.${cols.join(", ")}`);
|
|
33931
34649
|
}
|
|
33932
34650
|
}
|
|
33933
|
-
const confirmed = options2.force || await
|
|
34651
|
+
const confirmed = options2.force || await confirm3({
|
|
33934
34652
|
message: "⚠ This will drop all data and reindex from scratch. Continue?"
|
|
33935
34653
|
});
|
|
33936
34654
|
if (!confirmed) {
|
|
@@ -34113,16 +34831,12 @@ ${result.meta.total} gap(s), ${result.meta.totalMissingBlocks} total missing blo
|
|
|
34113
34831
|
});
|
|
34114
34832
|
subgraphs.command("query <name> <table>").description("Query a subgraph table").option("--sort <column>", "Sort by column").option("--order <dir>", "Sort direction (asc|desc)", "asc").option("--limit <n>", "Max rows to return", "20").option("--offset <n>", "Skip first N rows").option("--fields <cols>", "Comma-separated columns to include").option("--filter <kv...>", "Filter as key=value (supports .gte/.lte/.gt/.lt/.neq suffixes)").option("--count", "Return row count only").option("--json", "Output as JSON").action(async (name, table, options2) => {
|
|
34115
34833
|
try {
|
|
34116
|
-
|
|
34117
|
-
|
|
34118
|
-
|
|
34119
|
-
|
|
34120
|
-
|
|
34121
|
-
|
|
34122
|
-
process.exit(1);
|
|
34123
|
-
}
|
|
34124
|
-
filters[kv.slice(0, eqIndex)] = kv.slice(eqIndex + 1);
|
|
34125
|
-
}
|
|
34834
|
+
let filters;
|
|
34835
|
+
try {
|
|
34836
|
+
filters = parseQueryFilters(options2.filter);
|
|
34837
|
+
} catch (err) {
|
|
34838
|
+
error(err instanceof Error ? err.message : String(err));
|
|
34839
|
+
process.exit(1);
|
|
34126
34840
|
}
|
|
34127
34841
|
const params = {
|
|
34128
34842
|
sort: options2.sort,
|
|
@@ -34130,7 +34844,7 @@ ${result.meta.total} gap(s), ${result.meta.totalMissingBlocks} total missing blo
|
|
|
34130
34844
|
limit: Number.parseInt(options2.limit, 10),
|
|
34131
34845
|
offset: options2.offset ? Number.parseInt(options2.offset, 10) : undefined,
|
|
34132
34846
|
fields: options2.fields,
|
|
34133
|
-
filters
|
|
34847
|
+
filters
|
|
34134
34848
|
};
|
|
34135
34849
|
if (options2.count) {
|
|
34136
34850
|
const result = await querySubgraphTableCount(name, table, params);
|
|
@@ -34172,8 +34886,8 @@ ${rows.length} row(s)`));
|
|
|
34172
34886
|
subgraphs.command("delete <name>").description("Delete a subgraph and its data").option("-y, --yes", "Skip confirmation").action(async (name, options2) => {
|
|
34173
34887
|
try {
|
|
34174
34888
|
if (!options2.yes) {
|
|
34175
|
-
const { confirm:
|
|
34176
|
-
const ok = await
|
|
34889
|
+
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
34890
|
+
const ok = await confirm4({
|
|
34177
34891
|
message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
|
|
34178
34892
|
});
|
|
34179
34893
|
if (!ok) {
|
|
@@ -35167,7 +35881,7 @@ init_http();
|
|
|
35167
35881
|
init_output();
|
|
35168
35882
|
init_project_file();
|
|
35169
35883
|
init_resolve_tenant();
|
|
35170
|
-
import { confirm as
|
|
35884
|
+
import { confirm as confirm5, input as input4, select as select4 } from "@inquirer/prompts";
|
|
35171
35885
|
function registerInstanceCommand(program2) {
|
|
35172
35886
|
const instance = program2.command("instance").description("Manage your dedicated Secondlayer instance");
|
|
35173
35887
|
instance.command("create").description("Provision a new dedicated instance for the active project").option("--plan <plan>", "Plan: hobby (free) | launch | grow | scale", "hobby").action(async (opts) => {
|
|
@@ -35218,7 +35932,7 @@ function registerInstanceCommand(program2) {
|
|
|
35218
35932
|
target = answer;
|
|
35219
35933
|
}
|
|
35220
35934
|
if (!opts.yes) {
|
|
35221
|
-
const ok = await
|
|
35935
|
+
const ok = await confirm5({
|
|
35222
35936
|
message: `Resize to ${target}? ~30s downtime while containers recreate. Data preserved.`,
|
|
35223
35937
|
default: false
|
|
35224
35938
|
});
|
|
@@ -35367,7 +36081,7 @@ function registerInstanceCommand(program2) {
|
|
|
35367
36081
|
db.command("revoke-key").description("Revoke bastion access for this instance").option("-y, --yes", "Skip confirmation").action(async (opts) => {
|
|
35368
36082
|
guardOssMode();
|
|
35369
36083
|
if (!opts.yes) {
|
|
35370
|
-
const ok = await
|
|
36084
|
+
const ok = await confirm5({
|
|
35371
36085
|
message: "Revoke bastion access for this instance?",
|
|
35372
36086
|
default: false
|
|
35373
36087
|
});
|
|
@@ -35570,6 +36284,7 @@ registerProjectCommand(program);
|
|
|
35570
36284
|
registerInstanceCommand(program);
|
|
35571
36285
|
registerSubgraphsCommand(program);
|
|
35572
36286
|
registerCreateCommand(program);
|
|
36287
|
+
registerSubscriptionsCommand(program);
|
|
35573
36288
|
registerStatusCommand(program);
|
|
35574
36289
|
registerStackCommand(program);
|
|
35575
36290
|
registerDbCommand(program);
|
|
@@ -35579,5 +36294,5 @@ registerLocalCommand(program);
|
|
|
35579
36294
|
registerAccountCommand(program);
|
|
35580
36295
|
program.parse();
|
|
35581
36296
|
|
|
35582
|
-
//# debugId=
|
|
36297
|
+
//# debugId=CDED029144E4004564756E2164756E21
|
|
35583
36298
|
//# sourceMappingURL=cli.js.map
|