@stackbone/cli 0.5.0 → 0.5.2
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/CHANGELOG.md +40 -0
- package/main.js +1625 -72
- package/package.json +1 -1
- package/stackbone-cli-0.5.2.tgz +0 -0
- package/stackbone-cli-0.5.0.tgz +0 -0
package/main.js
CHANGED
|
@@ -695,7 +695,17 @@ var init_contract$1 = __esmMin((() => {
|
|
|
695
695
|
}),
|
|
696
696
|
runtimeUrl: z.url().nullable().optional()
|
|
697
697
|
});
|
|
698
|
-
}))
|
|
698
|
+
}));
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region ../../libs/shared/validators/src/lib/agents/deployments.ts
|
|
701
|
+
function protocolOf(raw) {
|
|
702
|
+
try {
|
|
703
|
+
return new URL(raw).protocol;
|
|
704
|
+
} catch {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
var deploymentStatusSchema, deploymentUrlSchema, deploymentResponseSchema;
|
|
699
709
|
var init_deployments = __esmMin((() => {
|
|
700
710
|
init_organizations();
|
|
701
711
|
deploymentStatusSchema = z.enum([
|
|
@@ -704,11 +714,15 @@ var init_deployments = __esmMin((() => {
|
|
|
704
714
|
"ready",
|
|
705
715
|
"failed"
|
|
706
716
|
]);
|
|
717
|
+
deploymentUrlSchema = z.url().refine((raw) => {
|
|
718
|
+
const protocol = protocolOf(raw);
|
|
719
|
+
return protocol === null || protocol === "https:";
|
|
720
|
+
}, { error: (issue) => `scheme "${protocolOf(String(issue.input))}" is not supported — register the box by its https:// address` });
|
|
707
721
|
z.object({
|
|
708
722
|
organizationSlug: organizationSlugSchema,
|
|
709
723
|
agentSlug: agentSlugSchema,
|
|
710
724
|
installationId: z.uuid(),
|
|
711
|
-
url:
|
|
725
|
+
url: deploymentUrlSchema,
|
|
712
726
|
hmacSecret: z.string().min(1),
|
|
713
727
|
imageTag: z.string().min(1).max(200).optional()
|
|
714
728
|
});
|
|
@@ -15725,7 +15739,7 @@ var emulatorCors = (allowlist) => cors({
|
|
|
15725
15739
|
var STACKBONE_CLI_BUILD_VERSION, EMULATOR_CAPABILITY_ROUTES, EMULATOR_CAPABILITIES, buildContractResponse;
|
|
15726
15740
|
var init_contract = __esmMin((() => {
|
|
15727
15741
|
init_src$19();
|
|
15728
|
-
STACKBONE_CLI_BUILD_VERSION = "0.5.
|
|
15742
|
+
STACKBONE_CLI_BUILD_VERSION = "0.5.2";
|
|
15729
15743
|
EMULATOR_CAPABILITY_ROUTES = {
|
|
15730
15744
|
"database.postgres_direct": "/api/db",
|
|
15731
15745
|
"rag.basic": "/api/rag",
|
|
@@ -30684,6 +30698,70 @@ var init_spec_cache_key = __esmMin((() => {
|
|
|
30684
30698
|
SPEC_CACHE_KEY_NAMESPACE = "connect:spec";
|
|
30685
30699
|
}));
|
|
30686
30700
|
//#endregion
|
|
30701
|
+
//#region ../../libs/runtime/connect-broker/src/lib/cache/spec-digest.ts
|
|
30702
|
+
/**
|
|
30703
|
+
* The digest a spec document is known by: sha256 of its text, hex. One function
|
|
30704
|
+
* for both sides of PLA-1714 — the cache that vouches for a document by its
|
|
30705
|
+
* digest and the introspection memo that keys a parse on it — because two
|
|
30706
|
+
* spellings of "the hash of the text" is how a vouched digest stops matching.
|
|
30707
|
+
*/
|
|
30708
|
+
function specTextDigest(text) {
|
|
30709
|
+
return createHash("sha256").update(text).digest("hex");
|
|
30710
|
+
}
|
|
30711
|
+
var init_spec_digest = __esmMin((() => {}));
|
|
30712
|
+
//#endregion
|
|
30713
|
+
//#region ../../libs/runtime/connect-broker/src/lib/cache/digest-tracking-spec-cache.ts
|
|
30714
|
+
/**
|
|
30715
|
+
* Wrap `shared` so it remembers the digest of each document it writes or reads.
|
|
30716
|
+
* `now`, `maxAgeMs` and `maxEntries` are injected so the bounds are provable in
|
|
30717
|
+
* tests without a real clock or thirty-two documents.
|
|
30718
|
+
*/
|
|
30719
|
+
function trackSpecDigests(shared, options = {}) {
|
|
30720
|
+
const now = options.now ?? (() => Date.now());
|
|
30721
|
+
const maxAgeMs = options.maxAgeMs ?? 6e5;
|
|
30722
|
+
const maxEntries = options.maxEntries ?? 32;
|
|
30723
|
+
const digests = /* @__PURE__ */ new Map();
|
|
30724
|
+
const remember = (key, text, ttlMs) => {
|
|
30725
|
+
digests.delete(key);
|
|
30726
|
+
digests.set(key, {
|
|
30727
|
+
digest: specTextDigest(text),
|
|
30728
|
+
expiresAt: now() + Math.min(ttlMs, maxAgeMs)
|
|
30729
|
+
});
|
|
30730
|
+
while (digests.size > maxEntries) {
|
|
30731
|
+
const oldest = digests.keys().next();
|
|
30732
|
+
if (oldest.done === true) break;
|
|
30733
|
+
digests.delete(oldest.value);
|
|
30734
|
+
}
|
|
30735
|
+
};
|
|
30736
|
+
return {
|
|
30737
|
+
async digestOf(key) {
|
|
30738
|
+
const held = digests.get(key);
|
|
30739
|
+
if (held === void 0) return null;
|
|
30740
|
+
if (held.expiresAt <= now()) {
|
|
30741
|
+
digests.delete(key);
|
|
30742
|
+
return null;
|
|
30743
|
+
}
|
|
30744
|
+
return held.digest;
|
|
30745
|
+
},
|
|
30746
|
+
async get(key) {
|
|
30747
|
+
const text = await shared.get(key);
|
|
30748
|
+
if (text !== null) remember(key, text, maxAgeMs);
|
|
30749
|
+
return text;
|
|
30750
|
+
},
|
|
30751
|
+
async set(key, value, ttlMs) {
|
|
30752
|
+
await shared.set(key, value, ttlMs);
|
|
30753
|
+
if (ttlMs > 0) remember(key, value, ttlMs);
|
|
30754
|
+
},
|
|
30755
|
+
async evict(key) {
|
|
30756
|
+
digests.delete(key);
|
|
30757
|
+
await shared.evict(key);
|
|
30758
|
+
}
|
|
30759
|
+
};
|
|
30760
|
+
}
|
|
30761
|
+
var init_digest_tracking_spec_cache = __esmMin((() => {
|
|
30762
|
+
init_spec_digest();
|
|
30763
|
+
}));
|
|
30764
|
+
//#endregion
|
|
30687
30765
|
//#region ../../libs/runtime/connect-broker/src/lib/executor/credential-placement.ts
|
|
30688
30766
|
/**
|
|
30689
30767
|
* Apply the broker-minted token to EVERY placement the connector declares, all
|
|
@@ -30750,6 +30828,37 @@ var init_types$1 = __esmMin((() => {
|
|
|
30750
30828
|
requestBodySchema: z.unknown().optional(),
|
|
30751
30829
|
/** Original Swagger body parameter name; allows body values alongside query filters. */
|
|
30752
30830
|
bodyArgName: z.string().optional(),
|
|
30831
|
+
/**
|
|
30832
|
+
* SEND THIS OPERATION'S BODY AS A FORM, not as JSON. The executor then encodes
|
|
30833
|
+
* the same payload as `application/x-www-form-urlencoded` in the bracket
|
|
30834
|
+
* dialect (`metadata[plan]=pro`, `items[0][price]=p_1`). Absent — the case for
|
|
30835
|
+
* every operation of every other connector — the body is JSON, byte for byte
|
|
30836
|
+
* as before.
|
|
30837
|
+
*
|
|
30838
|
+
* Declared on a document as `x-stackbone-body-encoding: form`, the fifth of the
|
|
30839
|
+
* `x-stackbone-*` extensions and read by the same rules: a per-operation
|
|
30840
|
+
* literal, ignored rather than coerced when it is anything else.
|
|
30841
|
+
*
|
|
30842
|
+
* EXPLICIT, NEVER INFERRED FROM THE SPEC'S CONTENT TYPE. A document that
|
|
30843
|
+
* declares `application/x-www-form-urlencoded` under `requestBody.content` is
|
|
30844
|
+
* saying what the provider ACCEPTS, and plenty of specs list several media
|
|
30845
|
+
* types or the wrong one. Switching the serialisation on that reading would
|
|
30846
|
+
* make a spec edit silently change what goes on the wire, which is the
|
|
30847
|
+
* data-corruption class `openapi-introspector.ts` refuses everywhere else.
|
|
30848
|
+
*
|
|
30849
|
+
* ONE declared value rather than a `json` / `form` pair, because a second
|
|
30850
|
+
* spelling of today's default is a second thing that can be got wrong for no
|
|
30851
|
+
* gain: absent already means JSON and always has.
|
|
30852
|
+
*
|
|
30853
|
+
* Per-OPERATION, like the flags above. Stripe is the first provider we want it
|
|
30854
|
+
* for and its API is form-encoded end to end, but a provider that takes a form
|
|
30855
|
+
* on one endpoint and JSON on the next must not have the whole connector
|
|
30856
|
+
* switched over.
|
|
30857
|
+
*
|
|
30858
|
+
* MULTIPART IS NOT HERE. A body of arbitrary bytes with a caller-chosen
|
|
30859
|
+
* content type is what `rawBody` already does.
|
|
30860
|
+
*/
|
|
30861
|
+
bodyEncoding: z.literal("form").optional(),
|
|
30753
30862
|
responseSchema: z.unknown().optional(),
|
|
30754
30863
|
/**
|
|
30755
30864
|
* This operation's body is BYTES, not JSON. The executor then resolves the
|
|
@@ -31006,7 +31115,7 @@ function hasTopLevelAlternation(pattern) {
|
|
|
31006
31115
|
}
|
|
31007
31116
|
return false;
|
|
31008
31117
|
}
|
|
31009
|
-
var argTargetSchema, triggerCursorSchema, triggerScopeParamSchema, unanchoredPatternReason, triggerListStepSchema, triggerItemStepSchema, declarativePollSchema, strategyPollSchema, pollSpecSchema, triggerDescriptorSchema;
|
|
31118
|
+
var argTargetSchema, triggerCursorSchema, compileAnchored, triggerScopeParamSchema, unanchoredPatternReason, triggerListStepSchema, triggerItemStepSchema, declarativePollSchema, strategyPollSchema, pollSpecSchema, triggerDescriptorSchema;
|
|
31010
31119
|
var init_trigger_descriptor = __esmMin((() => {
|
|
31011
31120
|
init_paths();
|
|
31012
31121
|
argTargetSchema = z.union([z.string(), z.array(z.string()).min(1)], { error: "must be a parameter name (one literal key, dots included) or a non-empty array of path segments to nest it under" });
|
|
@@ -31080,7 +31189,25 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31080
31189
|
* that is a definition promising a margin the data cannot honour, and a
|
|
31081
31190
|
* failed tick on the row beats a protection that silently is not there.
|
|
31082
31191
|
*/
|
|
31083
|
-
lookbackMs: z.number().int().positive().optional()
|
|
31192
|
+
lookbackMs: z.number().int().positive().optional(),
|
|
31193
|
+
/**
|
|
31194
|
+
* The unit this cursor's stamps are counted in, when they are not ISO date
|
|
31195
|
+
* strings. Stripe is the case: an event's `created` is seconds from the
|
|
31196
|
+
* epoch and the list filter takes the same integer.
|
|
31197
|
+
*
|
|
31198
|
+
* DECLARED, never inferred from the value, and that is the whole point of
|
|
31199
|
+
* the field. `timestamp` already admits numbers — a Supabase
|
|
31200
|
+
* `{cursorColumn}` may resolve to an integer `id` — so reading a 10-digit
|
|
31201
|
+
* number as seconds because it looks like seconds would turn those ids into
|
|
31202
|
+
* instants: the future check would start barring them and the lookback
|
|
31203
|
+
* would start subtracting from them, both silently.
|
|
31204
|
+
*
|
|
31205
|
+
* Absent is every trigger written before this, unchanged: the stamp reader
|
|
31206
|
+
* takes the two ISO shapes and nothing else. Its arithmetic lives in
|
|
31207
|
+
* `parseStamp` / `formatStamp` (`stamps.ts`) and nowhere else — the refine
|
|
31208
|
+
* below names the value only to refuse a margin finer than one tick of it.
|
|
31209
|
+
*/
|
|
31210
|
+
unit: z.enum(["unix-seconds"]).optional()
|
|
31084
31211
|
}), z.object({
|
|
31085
31212
|
kind: z.literal("opaque"),
|
|
31086
31213
|
/** Dot-path in the list response to the next opaque token. */
|
|
@@ -31097,6 +31224,13 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31097
31224
|
*/
|
|
31098
31225
|
lookbackMs: z.never({ error: "lookbackMs is only meaningful on a timestamp cursor: an opaque cursor is a token the provider minted, and there is no instant to move back from" }).optional()
|
|
31099
31226
|
})]);
|
|
31227
|
+
compileAnchored = (pattern) => {
|
|
31228
|
+
try {
|
|
31229
|
+
return new RegExp(`^(?:${pattern})$`);
|
|
31230
|
+
} catch {
|
|
31231
|
+
return;
|
|
31232
|
+
}
|
|
31233
|
+
};
|
|
31100
31234
|
triggerScopeParamSchema = z.object({
|
|
31101
31235
|
/**
|
|
31102
31236
|
* The list arg the value is written into, and the key the subscription stores
|
|
@@ -31134,6 +31268,23 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31134
31268
|
/** Placeholder for the field — a real example, not a shape. */
|
|
31135
31269
|
example: z.string().optional(),
|
|
31136
31270
|
/**
|
|
31271
|
+
* Values Studio offers while the operator types. SUGGESTIONS, not options:
|
|
31272
|
+
* the field stays free text and anything `pattern` accepts is still valid.
|
|
31273
|
+
*
|
|
31274
|
+
* The distinction is the whole point. Stripe's `type` takes any of the many
|
|
31275
|
+
* event names its API emits, plus a wildcard family — a closed dropdown could
|
|
31276
|
+
* not express `charge.*` and would be a list too long to read either way.
|
|
31277
|
+
* What an operator actually needs is to be shown that `charge.succeeded`
|
|
31278
|
+
* exists before typing `charge.suceeded`, which passes every pattern anyone
|
|
31279
|
+
* would write and then polls green forever, delivering nothing.
|
|
31280
|
+
*
|
|
31281
|
+
* So this is a spelling aid for a value nothing else can check. A connector
|
|
31282
|
+
* whose scope really IS a closed set should say so with a `pattern` that
|
|
31283
|
+
* alternates over it; that refusal happens at register time, where a refusal
|
|
31284
|
+
* belongs, and this field is not a substitute for it.
|
|
31285
|
+
*/
|
|
31286
|
+
suggestions: z.array(z.string()).optional(),
|
|
31287
|
+
/**
|
|
31137
31288
|
* A poll cannot run without a required value, so the register endpoint refuses
|
|
31138
31289
|
* the link rather than arming one that would fail every tick. An OPTIONAL one
|
|
31139
31290
|
* left blank is simply not sent, which is how a caller asks for the provider's
|
|
@@ -31168,6 +31319,15 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31168
31319
|
}
|
|
31169
31320
|
}, { message: "pattern must be a compilable regular expression" }).optional()
|
|
31170
31321
|
}).superRefine((param, ctx) => {
|
|
31322
|
+
if (param.pattern !== void 0 && param.suggestions !== void 0) {
|
|
31323
|
+
const anchored = compileAnchored(param.pattern);
|
|
31324
|
+
const refused = anchored === void 0 ? [] : param.suggestions.filter((value) => !anchored.test(value));
|
|
31325
|
+
if (refused.length > 0) ctx.addIssue({
|
|
31326
|
+
code: "custom",
|
|
31327
|
+
path: ["suggestions"],
|
|
31328
|
+
message: `every suggestion must satisfy this param's own \`pattern\` (${param.pattern}), or the field offers a value its register endpoint refuses: ${refused.join(", ")}`
|
|
31329
|
+
});
|
|
31330
|
+
}
|
|
31171
31331
|
if (param.template === void 0) return;
|
|
31172
31332
|
if (param.pattern === void 0) {
|
|
31173
31333
|
ctx.addIssue({
|
|
@@ -31217,6 +31377,27 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31217
31377
|
* because this is how EVERY GraphQL provider answers, not a Linear quirk.
|
|
31218
31378
|
*/
|
|
31219
31379
|
errorsPath: z.string().optional(),
|
|
31380
|
+
/**
|
|
31381
|
+
* WHICH END OF THE FEED THIS OPERATION RETURNS FIRST (PLA-1510).
|
|
31382
|
+
*
|
|
31383
|
+
* Documentation only: the engine never sorts, and it delivers `items` in
|
|
31384
|
+
* whatever order the provider gave them. What this changes is the sentence
|
|
31385
|
+
* `scripts/generate-trigger-reference.ts` prints on a trigger's public page,
|
|
31386
|
+
* and through it what an operator planning a backlog drain expects.
|
|
31387
|
+
*
|
|
31388
|
+
* DECLARED, NEVER INFERRED, and never defaulted. The generator used to print
|
|
31389
|
+
* `oldest first` as a literal for every timestamp cursor, which was false for
|
|
31390
|
+
* five of the nine declarative triggers in this catalog — Stripe's event
|
|
31391
|
+
* feed, GitHub's commits and all three Linear triggers come back newest
|
|
31392
|
+
* first. A default would put that same false sentence on any trigger whose
|
|
31393
|
+
* author did not think about it, which is the failure this field exists to
|
|
31394
|
+
* end. Absent, the page says nothing about order, which is the only honest
|
|
31395
|
+
* thing to say about an order nobody established.
|
|
31396
|
+
*
|
|
31397
|
+
* The order matters because the per-tick ceiling is real: a walk that gets
|
|
31398
|
+
* cut off keeps the end it read and passes over the other one.
|
|
31399
|
+
*/
|
|
31400
|
+
order: z.enum(["oldest-first", "newest-first"]).optional(),
|
|
31220
31401
|
/** Page-walk wiring: the request arg that carries the page token + where the
|
|
31221
31402
|
* next token is read from. Absent for non-paged list operations. */
|
|
31222
31403
|
pageToken: z.object({
|
|
@@ -31286,7 +31467,73 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31286
31467
|
* stops on `items.length < stride`, and it also ADVANCES by this, so `0`
|
|
31287
31468
|
* would re-request page one until the bound (PLA-1142). */
|
|
31288
31469
|
stride: z.number().int().positive()
|
|
31289
|
-
}).optional()
|
|
31470
|
+
}).optional(),
|
|
31471
|
+
/**
|
|
31472
|
+
* Paging by the LAST ITEM THE CALLER ALREADY HOLDS: the next page is
|
|
31473
|
+
* addressed by the data, not by a token the provider hands back. Stripe is
|
|
31474
|
+
* the case — `starting_after` takes the id of the last item on the page just
|
|
31475
|
+
* read — and it is the common shape for anything that paginates a
|
|
31476
|
+
* time-ordered feed.
|
|
31477
|
+
*
|
|
31478
|
+
* The engine reads `itemPath` off the LAST element of the page and sends it
|
|
31479
|
+
* as `param` on the next request. Page 0 carries neither, exactly as
|
|
31480
|
+
* `pageToken` sends no token on its first call.
|
|
31481
|
+
*
|
|
31482
|
+
* `hasMorePath` IS REQUIRED HERE, unlike inside `pageToken`, and the
|
|
31483
|
+
* difference is not an oversight. A token walk has the token itself as a
|
|
31484
|
+
* second end signal: a provider that nulls it on the last page ends the walk
|
|
31485
|
+
* with no flag at all. This shape has no such signal — the last item's id is
|
|
31486
|
+
* always there, on the final page as much as on the first — so without a
|
|
31487
|
+
* flag the walk would run to `MAX_PAGES` against the provider's rate limit on
|
|
31488
|
+
* every tick, or stop on an empty page one call too late. A required flag is
|
|
31489
|
+
* also reversible into an optional one; the reverse is not.
|
|
31490
|
+
*
|
|
31491
|
+
* A declared flag that comes back neither `true` nor `false` breaks the walk
|
|
31492
|
+
* WITHOUT draining it, the same rule the token walk follows: the provider
|
|
31493
|
+
* failed to answer, and reading that as "no more" advances the cursor past
|
|
31494
|
+
* pages nobody read.
|
|
31495
|
+
*
|
|
31496
|
+
* Mutually exclusive with the other three shapes.
|
|
31497
|
+
*/
|
|
31498
|
+
pageLastId: z.object({
|
|
31499
|
+
/** Request arg carrying the last item's id, e.g. `starting_after`. */
|
|
31500
|
+
param: argTargetSchema,
|
|
31501
|
+
/**
|
|
31502
|
+
* Dot-path, ON AN ITEM, to the value that fills `param` — `id` for
|
|
31503
|
+
* Stripe. Non-empty, because `getPath(item, '')` is the whole item, which
|
|
31504
|
+
* is never a page address.
|
|
31505
|
+
*/
|
|
31506
|
+
itemPath: z.string().min(1),
|
|
31507
|
+
/**
|
|
31508
|
+
* Dot-path to the boolean saying another page exists — Stripe's
|
|
31509
|
+
* `has_more`. Read BEFORE asking for the next page, and only `true`
|
|
31510
|
+
* continues the walk.
|
|
31511
|
+
*/
|
|
31512
|
+
hasMorePath: z.string().min(1)
|
|
31513
|
+
}).optional(),
|
|
31514
|
+
/**
|
|
31515
|
+
* Where the provider says another page exists, for a list step the engine
|
|
31516
|
+
* CANNOT page — no token in the response, no offset in the request, and no
|
|
31517
|
+
* addressable last item. Stripe USED to be the case here, before `pageLastId`
|
|
31518
|
+
* above existed; it is kept because a provider that reports `has_more` and
|
|
31519
|
+
* exposes no way at all to ask for the next page is still expressible.
|
|
31520
|
+
*
|
|
31521
|
+
* Without this the walk reads one page and reports itself DRAINED, because
|
|
31522
|
+
* the null-token rule is what ends an unpaged walk. The items below that
|
|
31523
|
+
* page are then under the marker the cursor just advanced to, never listed
|
|
31524
|
+
* again, with no warning and a green row — the failure `pageToken`'s own
|
|
31525
|
+
* `hasMorePath` doc calls the worst the intake can have. Declared here, a
|
|
31526
|
+
* page that answers `true` breaks the walk WITHOUT draining it, so the tick
|
|
31527
|
+
* reports it cut off and the loss is at least visible.
|
|
31528
|
+
*
|
|
31529
|
+
* Only for a list step that declares no paging shape: `pageToken` and
|
|
31530
|
+
* `pageLastId` carry their own `hasMorePath`, and `pageNumber` / `pageSkip`
|
|
31531
|
+
* end their walk on a short page and never read this. All are refused below.
|
|
31532
|
+
*
|
|
31533
|
+
* A non-empty path, because `getPath(value, '')` is the whole response —
|
|
31534
|
+
* never a boolean, so an empty path would cut off every tick forever.
|
|
31535
|
+
*/
|
|
31536
|
+
hasMorePath: z.string().min(1).optional()
|
|
31290
31537
|
}).superRefine((list, ctx) => {
|
|
31291
31538
|
const params = (list.scopeParams ?? []).map((param) => param.param);
|
|
31292
31539
|
const duplicated = [...new Set(params.filter((name, i) => params.indexOf(name) !== i))];
|
|
@@ -31298,13 +31545,23 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31298
31545
|
const declared = [
|
|
31299
31546
|
"pageToken",
|
|
31300
31547
|
"pageNumber",
|
|
31301
|
-
"pageSkip"
|
|
31548
|
+
"pageSkip",
|
|
31549
|
+
"pageLastId"
|
|
31302
31550
|
].filter((shape) => list[shape] !== void 0);
|
|
31303
31551
|
if (declared.length > 1) ctx.addIssue({
|
|
31304
31552
|
code: "custom",
|
|
31305
31553
|
path: [declared[1] ?? "pageNumber"],
|
|
31306
31554
|
message: `a list step declares ONE paging shape, and this one declares ${declared.length} (${declared.join(", ")}): the engine would silently use ${declared[0]} and ignore the rest`
|
|
31307
31555
|
});
|
|
31556
|
+
if (list.hasMorePath !== void 0 && declared.length > 0) {
|
|
31557
|
+
const shape = declared[0];
|
|
31558
|
+
const ownsAFlag = shape === "pageToken" || shape === "pageLastId";
|
|
31559
|
+
ctx.addIssue({
|
|
31560
|
+
code: "custom",
|
|
31561
|
+
path: ["hasMorePath"],
|
|
31562
|
+
message: ownsAFlag ? `this list step declares ${shape}, which reads its own hasMorePath and never this one — move the flag inside ${shape}, or drop the paging shape` : `this list step declares ${shape}, which ends its walk on a short page and never reads hasMorePath — drop one of the two`
|
|
31563
|
+
});
|
|
31564
|
+
}
|
|
31308
31565
|
});
|
|
31309
31566
|
triggerItemStepSchema = z.object({
|
|
31310
31567
|
/** The OpenAPI `operationId` that fetches one item's full detail. */
|
|
@@ -31338,6 +31595,11 @@ var init_trigger_descriptor = __esmMin((() => {
|
|
|
31338
31595
|
* absent, the event payload is the raw item verbatim. */
|
|
31339
31596
|
map: z.record(z.string(), z.string()).optional()
|
|
31340
31597
|
}).superRefine((spec, ctx) => {
|
|
31598
|
+
if (spec.cursor.kind === "timestamp" && spec.cursor.unit === "unix-seconds" && spec.cursor.lookbackMs !== void 0 && spec.cursor.lookbackMs % 1e3 !== 0) ctx.addIssue({
|
|
31599
|
+
code: "custom",
|
|
31600
|
+
path: ["cursor", "lookbackMs"],
|
|
31601
|
+
message: `a unix-seconds cursor carries whole seconds, so a lookback of ${spec.cursor.lookbackMs}ms floors to ${Math.floor(spec.cursor.lookbackMs / 1e3)}s on the wire — declare a multiple of 1000`
|
|
31602
|
+
});
|
|
31341
31603
|
const cursor = spec.cursor;
|
|
31342
31604
|
const cursorParam = cursor.kind === "timestamp" ? cursor.param : cursor.requestParam;
|
|
31343
31605
|
const clash = spec.list.scopeParams?.find((param) => sameArgTarget([param.param], cursorParam));
|
|
@@ -32362,6 +32624,7 @@ var init_github_connector = __esmMin((() => {
|
|
|
32362
32624
|
kind: "declarative",
|
|
32363
32625
|
list: {
|
|
32364
32626
|
operationId: "listCommits",
|
|
32627
|
+
order: "newest-first",
|
|
32365
32628
|
args: { "X-GitHub-Api-Version": "2022-11-28" },
|
|
32366
32629
|
pageNumber: {
|
|
32367
32630
|
param: "page",
|
|
@@ -32481,6 +32744,7 @@ var init_github_connector = __esmMin((() => {
|
|
|
32481
32744
|
kind: "declarative",
|
|
32482
32745
|
list: {
|
|
32483
32746
|
operationId: "listIssues",
|
|
32747
|
+
order: "oldest-first",
|
|
32484
32748
|
args: {
|
|
32485
32749
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
32486
32750
|
state: "all",
|
|
@@ -33226,6 +33490,7 @@ var init_google_drive_connector = __esmMin((() => {
|
|
|
33226
33490
|
kind: "declarative",
|
|
33227
33491
|
list: {
|
|
33228
33492
|
operationId: "drive.files.list",
|
|
33493
|
+
order: "oldest-first",
|
|
33229
33494
|
scopeParams: [{
|
|
33230
33495
|
param: "q",
|
|
33231
33496
|
displayName: "Folder ID",
|
|
@@ -33462,6 +33727,7 @@ var init_linear_connector = __esmMin((() => {
|
|
|
33462
33727
|
kind: "declarative",
|
|
33463
33728
|
list: {
|
|
33464
33729
|
operationId: "graphql",
|
|
33730
|
+
order: "newest-first",
|
|
33465
33731
|
args: {
|
|
33466
33732
|
query: ISSUE_CREATED_DOCUMENT,
|
|
33467
33733
|
variables: { first: 50 }
|
|
@@ -33559,6 +33825,7 @@ var init_linear_connector = __esmMin((() => {
|
|
|
33559
33825
|
kind: "declarative",
|
|
33560
33826
|
list: {
|
|
33561
33827
|
operationId: "graphql",
|
|
33828
|
+
order: "newest-first",
|
|
33562
33829
|
args: {
|
|
33563
33830
|
query: COMMENT_CREATED_DOCUMENT,
|
|
33564
33831
|
variables: { first: 50 }
|
|
@@ -33669,6 +33936,7 @@ var init_linear_connector = __esmMin((() => {
|
|
|
33669
33936
|
kind: "declarative",
|
|
33670
33937
|
list: {
|
|
33671
33938
|
operationId: "graphql",
|
|
33939
|
+
order: "newest-first",
|
|
33672
33940
|
args: {
|
|
33673
33941
|
query: ISSUE_UPDATED_DOCUMENT,
|
|
33674
33942
|
variables: { first: 50 }
|
|
@@ -33931,6 +34199,1020 @@ var init_outlook_app_only_connector = __esmMin((() => {
|
|
|
33931
34199
|
};
|
|
33932
34200
|
}));
|
|
33933
34201
|
//#endregion
|
|
34202
|
+
//#region ../../libs/shared/connect-registry/src/lib/catalog/stripe/stripe.connector.ts
|
|
34203
|
+
var IDEMPOTENCY_KEY_PARAM, CHARGE_SCHEMA, STRIPE_CONNECTOR;
|
|
34204
|
+
var init_stripe_connector = __esmMin((() => {
|
|
34205
|
+
IDEMPOTENCY_KEY_PARAM = {
|
|
34206
|
+
name: "Idempotency-Key",
|
|
34207
|
+
in: "header",
|
|
34208
|
+
required: false,
|
|
34209
|
+
description: "A caller-chosen key that stays the same on every retry of the same call — a message id, a run id — and different for a genuinely new one. Stripe replays the first response for 24 hours instead of performing the operation again, and refuses a reused key whose arguments changed. Omitted, a retry is a second write.",
|
|
34210
|
+
schema: { type: "string" }
|
|
34211
|
+
};
|
|
34212
|
+
CHARGE_SCHEMA = {
|
|
34213
|
+
type: "object",
|
|
34214
|
+
properties: {
|
|
34215
|
+
id: { type: "string" },
|
|
34216
|
+
object: { type: "string" },
|
|
34217
|
+
amount: {
|
|
34218
|
+
type: "integer",
|
|
34219
|
+
description: "What was charged, in the smallest unit of `currency`."
|
|
34220
|
+
},
|
|
34221
|
+
amount_captured: { type: "integer" },
|
|
34222
|
+
amount_refunded: { type: "integer" },
|
|
34223
|
+
currency: { type: "string" },
|
|
34224
|
+
customer: {
|
|
34225
|
+
type: ["string", "null"],
|
|
34226
|
+
description: "The customer id, or null for a charge made without one."
|
|
34227
|
+
},
|
|
34228
|
+
description: { type: ["string", "null"] },
|
|
34229
|
+
failure_code: { type: ["string", "null"] },
|
|
34230
|
+
failure_message: { type: ["string", "null"] },
|
|
34231
|
+
paid: { type: "boolean" },
|
|
34232
|
+
refunded: { type: "boolean" },
|
|
34233
|
+
receipt_url: { type: ["string", "null"] },
|
|
34234
|
+
payment_intent: { type: ["string", "null"] },
|
|
34235
|
+
status: {
|
|
34236
|
+
type: "string",
|
|
34237
|
+
description: "One of `succeeded`, `pending` or `failed`."
|
|
34238
|
+
},
|
|
34239
|
+
created: { type: "integer" },
|
|
34240
|
+
livemode: { type: "boolean" },
|
|
34241
|
+
metadata: {
|
|
34242
|
+
type: "object",
|
|
34243
|
+
additionalProperties: true
|
|
34244
|
+
}
|
|
34245
|
+
},
|
|
34246
|
+
required: [
|
|
34247
|
+
"id",
|
|
34248
|
+
"object",
|
|
34249
|
+
"amount",
|
|
34250
|
+
"amount_captured",
|
|
34251
|
+
"amount_refunded",
|
|
34252
|
+
"currency",
|
|
34253
|
+
"created",
|
|
34254
|
+
"livemode",
|
|
34255
|
+
"metadata",
|
|
34256
|
+
"paid",
|
|
34257
|
+
"refunded",
|
|
34258
|
+
"status"
|
|
34259
|
+
]
|
|
34260
|
+
};
|
|
34261
|
+
STRIPE_CONNECTOR = {
|
|
34262
|
+
id: "stripe",
|
|
34263
|
+
displayName: "Stripe",
|
|
34264
|
+
description: "Read a Stripe account — its event feed, its charges, one payment intent, one invoice, one subscription, the dispute on a charge, the balance — and write two things: a payment intent, and a refund. Exposes an `event-received` trigger.",
|
|
34265
|
+
credentialNote: "Create a restricted key (`rk_…`) in your Stripe dashboard with read permission on Events, Charges, Customers, Payment Intents, Balance, Invoices, Subscriptions and Disputes. Tick only the resources your workflows read: a key missing one answers that operation with a 403 and leaves the rest working. If a workflow also creates payment intents or issues refunds, that key needs WRITE on Payment Intents and Refunds too — a read-only key answers those two with a 403.\n\nA full secret key (`sk_…`) works for everything and is the account’s master credential, so it can do a great deal more than this connector asks for.\n\nTwo of the reads follow fields Stripe moved in a recent API version: the subscription behind an invoice, and a subscription’s billing period. Nothing here pins a version, so the account’s own pinned version decides which shape arrives. Check it in the dashboard under Developers before you map those two.\n\nStart with a test-mode key while you wire a workflow up. Test mode and live mode are separate accounts of everything — events, charges, balance — and an event carries `livemode` so a workflow can tell which one it came from.\n\nThe key is held by the broker. It never reaches a workspace or a browser.",
|
|
34266
|
+
logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://stripe.com/&size=64",
|
|
34267
|
+
category: "productivity",
|
|
34268
|
+
protocol: "openapi",
|
|
34269
|
+
baseUrl: "https://api.stripe.com",
|
|
34270
|
+
authMode: "api_key",
|
|
34271
|
+
placement: { kind: "bearer" },
|
|
34272
|
+
scopes: [],
|
|
34273
|
+
specSource: {
|
|
34274
|
+
kind: "inline",
|
|
34275
|
+
document: {
|
|
34276
|
+
openapi: "3.1.0",
|
|
34277
|
+
info: {
|
|
34278
|
+
title: "Stripe API (subset)",
|
|
34279
|
+
version: "2026-08-26.dahlia"
|
|
34280
|
+
},
|
|
34281
|
+
servers: [{ url: "https://api.stripe.com" }],
|
|
34282
|
+
paths: {
|
|
34283
|
+
"/v1/events": { get: {
|
|
34284
|
+
operationId: "listEvents",
|
|
34285
|
+
summary: "List the account's events, newest first",
|
|
34286
|
+
description: "The list endpoint behind the `event-received` trigger. Newest first, and Stripe keeps events for 30 days, so this is a recent window and never the account’s whole history. `created[gte]` is the bracketed spelling of Stripe’s `created` range filter and takes seconds from the epoch rather than an ISO string, the same unit every `created` field in this API is measured in. `type` takes one event name or a family with a `*` wildcard; Stripe also accepts a `types` array, which this document does not declare because one query parameter here carries one value.",
|
|
34287
|
+
parameters: [
|
|
34288
|
+
{
|
|
34289
|
+
name: "created[gte]",
|
|
34290
|
+
in: "query",
|
|
34291
|
+
required: false,
|
|
34292
|
+
description: "Only events created at or after this instant, in seconds from the epoch. Inclusive on purpose — the trigger below explains why the strict `created[gt]` is the wrong filter for a whole-second stamp.",
|
|
34293
|
+
schema: { type: "integer" }
|
|
34294
|
+
},
|
|
34295
|
+
{
|
|
34296
|
+
name: "type",
|
|
34297
|
+
in: "query",
|
|
34298
|
+
required: false,
|
|
34299
|
+
description: "One event name (`charge.succeeded`), or a family with a wildcard (`charge.*`). Omitted, every type the account emits is listed.",
|
|
34300
|
+
schema: { type: "string" }
|
|
34301
|
+
},
|
|
34302
|
+
{
|
|
34303
|
+
name: "limit",
|
|
34304
|
+
in: "query",
|
|
34305
|
+
required: false,
|
|
34306
|
+
description: "Events per page, 1 to 100. Stripe sends 10 when this is absent, so a caller that wants a full page has to ask.",
|
|
34307
|
+
schema: { type: "integer" }
|
|
34308
|
+
},
|
|
34309
|
+
{
|
|
34310
|
+
name: "starting_after",
|
|
34311
|
+
in: "query",
|
|
34312
|
+
required: false,
|
|
34313
|
+
description: "The id of the last event on the previous page. Stripe pages by the id you already hold rather than by a token it hands back, which is why `has_more` is the only thing the response says about a next page.",
|
|
34314
|
+
schema: { type: "string" }
|
|
34315
|
+
}
|
|
34316
|
+
],
|
|
34317
|
+
responses: { "200": {
|
|
34318
|
+
description: "One page of events, newest first.",
|
|
34319
|
+
content: { "application/json": { schema: {
|
|
34320
|
+
type: "object",
|
|
34321
|
+
properties: {
|
|
34322
|
+
object: { type: "string" },
|
|
34323
|
+
url: { type: "string" },
|
|
34324
|
+
has_more: {
|
|
34325
|
+
type: "boolean",
|
|
34326
|
+
description: "Another page exists after this one. Stripe names no next cursor: the caller sends the last id as `starting_after`."
|
|
34327
|
+
},
|
|
34328
|
+
data: {
|
|
34329
|
+
type: "array",
|
|
34330
|
+
items: {
|
|
34331
|
+
type: "object",
|
|
34332
|
+
description: "One event envelope. `data.object` is the API resource the event is about, and its shape follows `type` — a charge for `charge.succeeded`, an invoice for `invoice.created`.",
|
|
34333
|
+
properties: {
|
|
34334
|
+
id: { type: "string" },
|
|
34335
|
+
object: { type: "string" },
|
|
34336
|
+
api_version: { type: ["string", "null"] },
|
|
34337
|
+
created: {
|
|
34338
|
+
type: "integer",
|
|
34339
|
+
description: "Seconds from the epoch. The trigger cursor reads this."
|
|
34340
|
+
},
|
|
34341
|
+
livemode: { type: "boolean" },
|
|
34342
|
+
pending_webhooks: { type: "integer" },
|
|
34343
|
+
type: { type: "string" },
|
|
34344
|
+
request: {
|
|
34345
|
+
type: ["object", "null"],
|
|
34346
|
+
properties: {
|
|
34347
|
+
id: { type: ["string", "null"] },
|
|
34348
|
+
idempotency_key: { type: ["string", "null"] }
|
|
34349
|
+
}
|
|
34350
|
+
},
|
|
34351
|
+
data: {
|
|
34352
|
+
type: "object",
|
|
34353
|
+
properties: {
|
|
34354
|
+
object: {
|
|
34355
|
+
type: "object",
|
|
34356
|
+
additionalProperties: true
|
|
34357
|
+
},
|
|
34358
|
+
previous_attributes: {
|
|
34359
|
+
type: "object",
|
|
34360
|
+
additionalProperties: true,
|
|
34361
|
+
description: "The attributes as they were before the change, on a `*.updated` event only."
|
|
34362
|
+
}
|
|
34363
|
+
},
|
|
34364
|
+
required: ["object"]
|
|
34365
|
+
}
|
|
34366
|
+
},
|
|
34367
|
+
required: [
|
|
34368
|
+
"id",
|
|
34369
|
+
"object",
|
|
34370
|
+
"created",
|
|
34371
|
+
"livemode",
|
|
34372
|
+
"pending_webhooks",
|
|
34373
|
+
"type",
|
|
34374
|
+
"data"
|
|
34375
|
+
]
|
|
34376
|
+
}
|
|
34377
|
+
}
|
|
34378
|
+
},
|
|
34379
|
+
required: [
|
|
34380
|
+
"object",
|
|
34381
|
+
"data",
|
|
34382
|
+
"has_more",
|
|
34383
|
+
"url"
|
|
34384
|
+
]
|
|
34385
|
+
} } }
|
|
34386
|
+
} }
|
|
34387
|
+
} },
|
|
34388
|
+
"/v1/customers": { get: {
|
|
34389
|
+
operationId: "listCustomers",
|
|
34390
|
+
summary: "List customers, filtered by email address",
|
|
34391
|
+
description: "Customers, newest first, and the one hop from a person to their payments: every other read here is keyed by a Stripe id, so a workflow that starts from a mail, a form or a chat message has nothing to look up. Feed an id from here to `listCharges`’ `customer` filter.\n\nA LIST and not a lookup, because an email is not unique on a Stripe account: the same address can sit on several customers, created by different integrations or by a checkout that made a new one each time. A caller that reads `data[0]` and acts on it is refunding whichever row Stripe happened to return first. Zero rows is a normal answer too — the sender may simply not be a customer.\n\nOnly exact addresses match. Stripe does not do partial or case-insensitive matching on this filter; its Search API is a different endpoint with its own query language and is not declared here.",
|
|
34392
|
+
parameters: [
|
|
34393
|
+
{
|
|
34394
|
+
name: "email",
|
|
34395
|
+
in: "query",
|
|
34396
|
+
required: false,
|
|
34397
|
+
description: "Only customers whose address is exactly this one. Omitted, the answer is every customer, newest first.",
|
|
34398
|
+
schema: { type: "string" }
|
|
34399
|
+
},
|
|
34400
|
+
{
|
|
34401
|
+
name: "limit",
|
|
34402
|
+
in: "query",
|
|
34403
|
+
required: false,
|
|
34404
|
+
description: "Customers per page, 1 to 100. Stripe defaults it to 10.",
|
|
34405
|
+
schema: { type: "integer" }
|
|
34406
|
+
},
|
|
34407
|
+
{
|
|
34408
|
+
name: "starting_after",
|
|
34409
|
+
in: "query",
|
|
34410
|
+
required: false,
|
|
34411
|
+
description: "The id of the last customer on the previous page.",
|
|
34412
|
+
schema: { type: "string" }
|
|
34413
|
+
}
|
|
34414
|
+
],
|
|
34415
|
+
responses: { "200": {
|
|
34416
|
+
description: "One page of customers, newest first.",
|
|
34417
|
+
content: { "application/json": { schema: {
|
|
34418
|
+
type: "object",
|
|
34419
|
+
properties: {
|
|
34420
|
+
object: { type: "string" },
|
|
34421
|
+
url: { type: "string" },
|
|
34422
|
+
has_more: { type: "boolean" },
|
|
34423
|
+
data: {
|
|
34424
|
+
type: "array",
|
|
34425
|
+
items: {
|
|
34426
|
+
type: "object",
|
|
34427
|
+
properties: {
|
|
34428
|
+
id: { type: "string" },
|
|
34429
|
+
object: { type: "string" },
|
|
34430
|
+
email: { type: ["string", "null"] },
|
|
34431
|
+
name: { type: ["string", "null"] },
|
|
34432
|
+
created: { type: "integer" },
|
|
34433
|
+
livemode: { type: "boolean" }
|
|
34434
|
+
},
|
|
34435
|
+
required: [
|
|
34436
|
+
"id",
|
|
34437
|
+
"object",
|
|
34438
|
+
"created",
|
|
34439
|
+
"livemode"
|
|
34440
|
+
]
|
|
34441
|
+
}
|
|
34442
|
+
}
|
|
34443
|
+
},
|
|
34444
|
+
required: [
|
|
34445
|
+
"object",
|
|
34446
|
+
"data",
|
|
34447
|
+
"has_more",
|
|
34448
|
+
"url"
|
|
34449
|
+
]
|
|
34450
|
+
} } }
|
|
34451
|
+
} }
|
|
34452
|
+
} },
|
|
34453
|
+
"/v1/charges": { get: {
|
|
34454
|
+
operationId: "listCharges",
|
|
34455
|
+
summary: "List the account's charges, newest first",
|
|
34456
|
+
description: "Charges, newest first. Amounts are in the currency’s smallest unit — 4900 in `eur` is 49.00 euros, and a zero-decimal currency like `jpy` has no such factor at all, so nothing may divide by 100 unconditionally. `created[gte]` takes seconds from the epoch, as everywhere else in this API.",
|
|
34457
|
+
parameters: [
|
|
34458
|
+
{
|
|
34459
|
+
name: "created[gte]",
|
|
34460
|
+
in: "query",
|
|
34461
|
+
required: false,
|
|
34462
|
+
description: "Only charges created at or after this instant, in seconds from the epoch.",
|
|
34463
|
+
schema: { type: "integer" }
|
|
34464
|
+
},
|
|
34465
|
+
{
|
|
34466
|
+
name: "customer",
|
|
34467
|
+
in: "query",
|
|
34468
|
+
required: false,
|
|
34469
|
+
description: "Only charges for this customer id (`cus_…`).",
|
|
34470
|
+
schema: { type: "string" }
|
|
34471
|
+
},
|
|
34472
|
+
{
|
|
34473
|
+
name: "payment_intent",
|
|
34474
|
+
in: "query",
|
|
34475
|
+
required: false,
|
|
34476
|
+
description: "Only charges belonging to this payment intent (`pi_…`).",
|
|
34477
|
+
schema: { type: "string" }
|
|
34478
|
+
},
|
|
34479
|
+
{
|
|
34480
|
+
name: "limit",
|
|
34481
|
+
in: "query",
|
|
34482
|
+
required: false,
|
|
34483
|
+
description: "Charges per page, 1 to 100. Stripe defaults it to 10.",
|
|
34484
|
+
schema: { type: "integer" }
|
|
34485
|
+
},
|
|
34486
|
+
{
|
|
34487
|
+
name: "starting_after",
|
|
34488
|
+
in: "query",
|
|
34489
|
+
required: false,
|
|
34490
|
+
description: "The id of the last charge on the previous page.",
|
|
34491
|
+
schema: { type: "string" }
|
|
34492
|
+
}
|
|
34493
|
+
],
|
|
34494
|
+
responses: { "200": {
|
|
34495
|
+
description: "One page of charges, newest first.",
|
|
34496
|
+
content: { "application/json": { schema: {
|
|
34497
|
+
type: "object",
|
|
34498
|
+
properties: {
|
|
34499
|
+
object: { type: "string" },
|
|
34500
|
+
url: { type: "string" },
|
|
34501
|
+
has_more: { type: "boolean" },
|
|
34502
|
+
data: {
|
|
34503
|
+
type: "array",
|
|
34504
|
+
items: CHARGE_SCHEMA
|
|
34505
|
+
}
|
|
34506
|
+
},
|
|
34507
|
+
required: [
|
|
34508
|
+
"object",
|
|
34509
|
+
"data",
|
|
34510
|
+
"has_more",
|
|
34511
|
+
"url"
|
|
34512
|
+
]
|
|
34513
|
+
} } }
|
|
34514
|
+
} }
|
|
34515
|
+
} },
|
|
34516
|
+
"/v1/charges/{charge}": { get: {
|
|
34517
|
+
operationId: "getCharge",
|
|
34518
|
+
summary: "Retrieve one charge by id",
|
|
34519
|
+
description: "One charge, by the `ch_…` a workflow already holds. The alternative is walking `listCharges` and matching client-side, which works on a quiet account and stops working on a busy one: the charge falls off the page and the caller reports no amount for a charge that exists. The path parameter is named `charge` because that is what Stripe’s own document calls it.\n\nThe body is the same shape `listCharges` returns as a page item, down to the nullable fields — it is one declaration, so a field readable from the list is readable here.",
|
|
34520
|
+
parameters: [{
|
|
34521
|
+
name: "charge",
|
|
34522
|
+
in: "path",
|
|
34523
|
+
required: true,
|
|
34524
|
+
description: "The charge id, e.g. `ch_3PqR2sC0mNqLxYzA0kL1mN2o`.",
|
|
34525
|
+
schema: { type: "string" }
|
|
34526
|
+
}],
|
|
34527
|
+
responses: { "200": {
|
|
34528
|
+
description: "The charge.",
|
|
34529
|
+
content: { "application/json": { schema: CHARGE_SCHEMA } }
|
|
34530
|
+
} }
|
|
34531
|
+
} },
|
|
34532
|
+
"/v1/payment_intents/{intent}": { get: {
|
|
34533
|
+
operationId: "getPaymentIntent",
|
|
34534
|
+
summary: "Retrieve one payment intent by id",
|
|
34535
|
+
description: "One payment intent. The path parameter is named `intent` because that is what Stripe’s own document calls it; the value is the `pi_…` id.\n\n`client_secret` is left out of the schema below, and that is a note rather than a filter: Stripe returns it on every read of an intent and the broker hands the body back as it arrived. It is the value a browser uses to confirm the payment, so a workflow that keeps it has put a bearer credential for one payment into its own output. Leaving it undeclared says do not reach for it; nothing stops you.",
|
|
34536
|
+
parameters: [{
|
|
34537
|
+
name: "intent",
|
|
34538
|
+
in: "path",
|
|
34539
|
+
required: true,
|
|
34540
|
+
description: "The payment intent id, e.g. `pi_3PqR2sC0mNqLxYzA1cD2eF3g`.",
|
|
34541
|
+
schema: { type: "string" }
|
|
34542
|
+
}],
|
|
34543
|
+
responses: { "200": {
|
|
34544
|
+
description: "The payment intent.",
|
|
34545
|
+
content: { "application/json": { schema: {
|
|
34546
|
+
type: "object",
|
|
34547
|
+
properties: {
|
|
34548
|
+
id: { type: "string" },
|
|
34549
|
+
object: { type: "string" },
|
|
34550
|
+
amount: {
|
|
34551
|
+
type: "integer",
|
|
34552
|
+
description: "What was asked for, in the smallest unit of `currency`."
|
|
34553
|
+
},
|
|
34554
|
+
amount_received: { type: "integer" },
|
|
34555
|
+
currency: { type: "string" },
|
|
34556
|
+
customer: { type: ["string", "null"] },
|
|
34557
|
+
description: { type: ["string", "null"] },
|
|
34558
|
+
capture_method: { type: "string" },
|
|
34559
|
+
confirmation_method: { type: "string" },
|
|
34560
|
+
status: {
|
|
34561
|
+
type: "string",
|
|
34562
|
+
description: "One of `requires_payment_method`, `requires_confirmation`, `requires_action`, `processing`, `requires_capture`, `canceled` or `succeeded`."
|
|
34563
|
+
},
|
|
34564
|
+
created: { type: "integer" },
|
|
34565
|
+
livemode: { type: "boolean" },
|
|
34566
|
+
metadata: {
|
|
34567
|
+
type: "object",
|
|
34568
|
+
additionalProperties: true
|
|
34569
|
+
}
|
|
34570
|
+
},
|
|
34571
|
+
required: [
|
|
34572
|
+
"id",
|
|
34573
|
+
"object",
|
|
34574
|
+
"created",
|
|
34575
|
+
"livemode",
|
|
34576
|
+
"status"
|
|
34577
|
+
]
|
|
34578
|
+
} } }
|
|
34579
|
+
} }
|
|
34580
|
+
} },
|
|
34581
|
+
"/v1/balance": { get: {
|
|
34582
|
+
operationId: "getBalance",
|
|
34583
|
+
summary: "Retrieve the account balance",
|
|
34584
|
+
description: "The balance, as arrays and not as one number: `available` and `pending` carry an entry per currency, because a Stripe account can hold several at once. An account that takes only euros still answers a one-element array, so a caller reads the entry whose `currency` it wants rather than the first one.",
|
|
34585
|
+
parameters: [],
|
|
34586
|
+
responses: { "200": {
|
|
34587
|
+
description: "The account balance, one entry per currency.",
|
|
34588
|
+
content: { "application/json": { schema: {
|
|
34589
|
+
type: "object",
|
|
34590
|
+
properties: {
|
|
34591
|
+
object: { type: "string" },
|
|
34592
|
+
livemode: { type: "boolean" },
|
|
34593
|
+
available: {
|
|
34594
|
+
type: "array",
|
|
34595
|
+
description: "Funds that can be paid out now, one entry per currency.",
|
|
34596
|
+
items: {
|
|
34597
|
+
type: "object",
|
|
34598
|
+
properties: {
|
|
34599
|
+
amount: { type: "integer" },
|
|
34600
|
+
currency: { type: "string" }
|
|
34601
|
+
},
|
|
34602
|
+
required: ["amount", "currency"]
|
|
34603
|
+
}
|
|
34604
|
+
},
|
|
34605
|
+
pending: {
|
|
34606
|
+
type: "array",
|
|
34607
|
+
description: "Funds not settled yet, one entry per currency.",
|
|
34608
|
+
items: {
|
|
34609
|
+
type: "object",
|
|
34610
|
+
properties: {
|
|
34611
|
+
amount: { type: "integer" },
|
|
34612
|
+
currency: { type: "string" }
|
|
34613
|
+
},
|
|
34614
|
+
required: ["amount", "currency"]
|
|
34615
|
+
}
|
|
34616
|
+
}
|
|
34617
|
+
},
|
|
34618
|
+
required: [
|
|
34619
|
+
"object",
|
|
34620
|
+
"available",
|
|
34621
|
+
"pending"
|
|
34622
|
+
]
|
|
34623
|
+
} } }
|
|
34624
|
+
} }
|
|
34625
|
+
} },
|
|
34626
|
+
"/v1/invoices/{invoice}": { get: {
|
|
34627
|
+
operationId: "getInvoice",
|
|
34628
|
+
summary: "Retrieve one invoice by id",
|
|
34629
|
+
description: "One invoice, by the `in_…` an event or a workflow already holds. Read it when the snapshot on the event is not enough: an invoice moves through `draft`, `open`, `paid`, `uncollectible` and `void`, and a run that started on `invoice.payment_failed` may be looking at one that has since been paid.\n\nTHE SUBSCRIPTION BEHIND AN INVOICE IS NOT A TOP-LEVEL FIELD. It is `parent.subscription_details.subscription`, and `parent` is null on an invoice nobody subscribed for. THIS DEPENDS ON THE ACCOUNT’S API VERSION: nothing here sends a `Stripe-Version` header, so Stripe serves whatever version the account is pinned to, and an account pinned before `parent` existed answers a top-level `subscription` instead. Read the version in the Stripe dashboard under Developers, and expect the older shape on an account nobody has upgraded. A workflow that wants the subscription reads that path and then calls `getSubscription`.\n\n`lines` is one page of up to ten entries, which is what a read of the invoice returns; an invoice with more has them behind its own endpoint, which this connector does not expose. A workflow that adds up `lines.data` on a longer invoice under-counts, and nothing errors, so check `lines.has_more` before you total anything.",
|
|
34630
|
+
parameters: [{
|
|
34631
|
+
name: "invoice",
|
|
34632
|
+
in: "path",
|
|
34633
|
+
required: true,
|
|
34634
|
+
description: "The invoice id, e.g. `in_1PqR2sC0mNqLxYzA3bC4dE5f`.",
|
|
34635
|
+
schema: { type: "string" }
|
|
34636
|
+
}],
|
|
34637
|
+
responses: { "200": {
|
|
34638
|
+
description: "The invoice.",
|
|
34639
|
+
content: { "application/json": { schema: {
|
|
34640
|
+
type: "object",
|
|
34641
|
+
properties: {
|
|
34642
|
+
id: { type: "string" },
|
|
34643
|
+
object: { type: "string" },
|
|
34644
|
+
number: {
|
|
34645
|
+
type: ["string", "null"],
|
|
34646
|
+
description: "The reference a customer sees. Null until the invoice is finalized."
|
|
34647
|
+
},
|
|
34648
|
+
status: {
|
|
34649
|
+
type: ["string", "null"],
|
|
34650
|
+
description: "One of `draft`, `open`, `paid`, `uncollectible` or `void`."
|
|
34651
|
+
},
|
|
34652
|
+
amount_due: {
|
|
34653
|
+
type: "integer",
|
|
34654
|
+
description: "In the smallest unit of `currency`, like every amount here."
|
|
34655
|
+
},
|
|
34656
|
+
amount_paid: { type: "integer" },
|
|
34657
|
+
amount_remaining: { type: "integer" },
|
|
34658
|
+
total: { type: "integer" },
|
|
34659
|
+
subtotal: { type: "integer" },
|
|
34660
|
+
currency: { type: "string" },
|
|
34661
|
+
customer: { type: ["string", "null"] },
|
|
34662
|
+
customer_email: { type: ["string", "null"] },
|
|
34663
|
+
customer_name: { type: ["string", "null"] },
|
|
34664
|
+
description: { type: ["string", "null"] },
|
|
34665
|
+
collection_method: {
|
|
34666
|
+
type: "string",
|
|
34667
|
+
description: "Either `charge_automatically` or `send_invoice`."
|
|
34668
|
+
},
|
|
34669
|
+
hosted_invoice_url: {
|
|
34670
|
+
type: ["string", "null"],
|
|
34671
|
+
description: "The Stripe-hosted page for this invoice. Null while it is a draft."
|
|
34672
|
+
},
|
|
34673
|
+
invoice_pdf: { type: ["string", "null"] },
|
|
34674
|
+
due_date: { type: ["integer", "null"] },
|
|
34675
|
+
period_start: { type: "integer" },
|
|
34676
|
+
period_end: { type: "integer" },
|
|
34677
|
+
created: { type: "integer" },
|
|
34678
|
+
livemode: { type: "boolean" },
|
|
34679
|
+
metadata: {
|
|
34680
|
+
type: "object",
|
|
34681
|
+
additionalProperties: true
|
|
34682
|
+
},
|
|
34683
|
+
parent: {
|
|
34684
|
+
type: ["object", "null"],
|
|
34685
|
+
description: "What generated this invoice. Null when nothing did.",
|
|
34686
|
+
properties: {
|
|
34687
|
+
type: { type: "string" },
|
|
34688
|
+
subscription_details: {
|
|
34689
|
+
type: ["object", "null"],
|
|
34690
|
+
properties: { subscription: {
|
|
34691
|
+
type: "string",
|
|
34692
|
+
description: "The `sub_…` this invoice bills, which `getSubscription` takes."
|
|
34693
|
+
} }
|
|
34694
|
+
}
|
|
34695
|
+
}
|
|
34696
|
+
},
|
|
34697
|
+
lines: {
|
|
34698
|
+
type: "object",
|
|
34699
|
+
description: "One page of line items, up to ten.",
|
|
34700
|
+
properties: {
|
|
34701
|
+
object: { type: "string" },
|
|
34702
|
+
has_more: { type: "boolean" },
|
|
34703
|
+
data: {
|
|
34704
|
+
type: "array",
|
|
34705
|
+
items: {
|
|
34706
|
+
type: "object",
|
|
34707
|
+
properties: {
|
|
34708
|
+
id: { type: "string" },
|
|
34709
|
+
object: { type: "string" },
|
|
34710
|
+
description: { type: ["string", "null"] },
|
|
34711
|
+
amount: { type: "integer" },
|
|
34712
|
+
currency: { type: "string" },
|
|
34713
|
+
quantity: { type: ["integer", "null"] }
|
|
34714
|
+
},
|
|
34715
|
+
required: [
|
|
34716
|
+
"id",
|
|
34717
|
+
"object",
|
|
34718
|
+
"amount",
|
|
34719
|
+
"currency"
|
|
34720
|
+
]
|
|
34721
|
+
}
|
|
34722
|
+
}
|
|
34723
|
+
},
|
|
34724
|
+
required: [
|
|
34725
|
+
"object",
|
|
34726
|
+
"data",
|
|
34727
|
+
"has_more"
|
|
34728
|
+
]
|
|
34729
|
+
}
|
|
34730
|
+
},
|
|
34731
|
+
required: [
|
|
34732
|
+
"id",
|
|
34733
|
+
"object",
|
|
34734
|
+
"amount_due",
|
|
34735
|
+
"amount_paid",
|
|
34736
|
+
"amount_remaining",
|
|
34737
|
+
"total",
|
|
34738
|
+
"subtotal",
|
|
34739
|
+
"currency",
|
|
34740
|
+
"collection_method",
|
|
34741
|
+
"period_start",
|
|
34742
|
+
"period_end",
|
|
34743
|
+
"created",
|
|
34744
|
+
"livemode",
|
|
34745
|
+
"lines"
|
|
34746
|
+
]
|
|
34747
|
+
} } }
|
|
34748
|
+
} }
|
|
34749
|
+
} },
|
|
34750
|
+
"/v1/subscriptions/{subscription_exposed_id}": { get: {
|
|
34751
|
+
operationId: "getSubscription",
|
|
34752
|
+
summary: "Retrieve one subscription by id",
|
|
34753
|
+
description: "One subscription, by the `sub_…` an event, an invoice or a workflow already holds. The path parameter is named `subscription_exposed_id` because that is what Stripe’s own document calls it; the value is still the `sub_…` id. Read it to answer whether a customer is still paying: `status` is one of `active`, `trialing`, `past_due`, `unpaid`, `canceled`, `incomplete`, `incomplete_expired` or `paused`.\n\nTHE BILLING PERIOD IS ON EACH ITEM, NOT ON THE SUBSCRIPTION. Stripe moved `current_period_start` and `current_period_end` onto the subscription items, because one subscription can bill several prices on different clocks. THIS TOO DEPENDS ON THE ACCOUNT’S API VERSION, for the same reason the invoice’s `parent` does: an account pinned to a version from before the move answers the period on the subscription itself and nothing on the items. A workflow that wants the period reads `items.data[0].current_period_end`, and it should say out loud that it is taking the first item.\n\n`items` IS ONE PAGE. Stripe answers a page of subscription items with `has_more` beside them, and the rest sit behind their own endpoint, which this connector does not expose. A workflow that adds up `items.data` on a subscription carrying more than one page under-counts, and nothing errors, so check `items.has_more` before you total anything.\n\n`cancel_at_period_end` is the field that says a cancellation is scheduled. A subscription with it set is still `active` and still collecting, which is why reading `status` alone answers the wrong question.",
|
|
34754
|
+
parameters: [{
|
|
34755
|
+
name: "subscription_exposed_id",
|
|
34756
|
+
in: "path",
|
|
34757
|
+
required: true,
|
|
34758
|
+
description: "The subscription id, e.g. `sub_1PqR2sC0mNqLxYzA6gH7iJ8k`.",
|
|
34759
|
+
schema: { type: "string" }
|
|
34760
|
+
}],
|
|
34761
|
+
responses: { "200": {
|
|
34762
|
+
description: "The subscription.",
|
|
34763
|
+
content: { "application/json": { schema: {
|
|
34764
|
+
type: "object",
|
|
34765
|
+
properties: {
|
|
34766
|
+
id: { type: "string" },
|
|
34767
|
+
object: { type: "string" },
|
|
34768
|
+
customer: { type: "string" },
|
|
34769
|
+
status: { type: "string" },
|
|
34770
|
+
currency: { type: "string" },
|
|
34771
|
+
collection_method: { type: "string" },
|
|
34772
|
+
description: { type: ["string", "null"] },
|
|
34773
|
+
cancel_at_period_end: { type: "boolean" },
|
|
34774
|
+
cancel_at: { type: ["integer", "null"] },
|
|
34775
|
+
canceled_at: { type: ["integer", "null"] },
|
|
34776
|
+
ended_at: { type: ["integer", "null"] },
|
|
34777
|
+
trial_start: { type: ["integer", "null"] },
|
|
34778
|
+
trial_end: { type: ["integer", "null"] },
|
|
34779
|
+
start_date: { type: "integer" },
|
|
34780
|
+
created: { type: "integer" },
|
|
34781
|
+
latest_invoice: {
|
|
34782
|
+
type: ["string", "null"],
|
|
34783
|
+
description: "The `in_…` of the most recent invoice, which `getInvoice` takes."
|
|
34784
|
+
},
|
|
34785
|
+
livemode: { type: "boolean" },
|
|
34786
|
+
metadata: {
|
|
34787
|
+
type: "object",
|
|
34788
|
+
additionalProperties: true
|
|
34789
|
+
},
|
|
34790
|
+
items: {
|
|
34791
|
+
type: "object",
|
|
34792
|
+
description: "The priced items this subscription bills, and where the billing period lives.",
|
|
34793
|
+
properties: {
|
|
34794
|
+
object: { type: "string" },
|
|
34795
|
+
has_more: { type: "boolean" },
|
|
34796
|
+
data: {
|
|
34797
|
+
type: "array",
|
|
34798
|
+
items: {
|
|
34799
|
+
type: "object",
|
|
34800
|
+
properties: {
|
|
34801
|
+
id: { type: "string" },
|
|
34802
|
+
object: { type: "string" },
|
|
34803
|
+
quantity: { type: "integer" },
|
|
34804
|
+
current_period_start: { type: "integer" },
|
|
34805
|
+
current_period_end: { type: "integer" },
|
|
34806
|
+
price: {
|
|
34807
|
+
type: "object",
|
|
34808
|
+
properties: {
|
|
34809
|
+
id: { type: "string" },
|
|
34810
|
+
currency: { type: "string" },
|
|
34811
|
+
unit_amount: {
|
|
34812
|
+
type: ["integer", "null"],
|
|
34813
|
+
description: "Null on a tiered or metered price, where no single amount exists."
|
|
34814
|
+
},
|
|
34815
|
+
recurring: {
|
|
34816
|
+
type: ["object", "null"],
|
|
34817
|
+
properties: {
|
|
34818
|
+
interval: {
|
|
34819
|
+
type: "string",
|
|
34820
|
+
description: "One of `day`, `week`, `month` or `year`."
|
|
34821
|
+
},
|
|
34822
|
+
interval_count: { type: "integer" }
|
|
34823
|
+
}
|
|
34824
|
+
}
|
|
34825
|
+
},
|
|
34826
|
+
required: ["id", "currency"]
|
|
34827
|
+
}
|
|
34828
|
+
},
|
|
34829
|
+
required: [
|
|
34830
|
+
"id",
|
|
34831
|
+
"object",
|
|
34832
|
+
"current_period_start",
|
|
34833
|
+
"current_period_end",
|
|
34834
|
+
"price"
|
|
34835
|
+
]
|
|
34836
|
+
}
|
|
34837
|
+
}
|
|
34838
|
+
},
|
|
34839
|
+
required: [
|
|
34840
|
+
"object",
|
|
34841
|
+
"data",
|
|
34842
|
+
"has_more"
|
|
34843
|
+
]
|
|
34844
|
+
}
|
|
34845
|
+
},
|
|
34846
|
+
required: [
|
|
34847
|
+
"id",
|
|
34848
|
+
"object",
|
|
34849
|
+
"customer",
|
|
34850
|
+
"status",
|
|
34851
|
+
"currency",
|
|
34852
|
+
"collection_method",
|
|
34853
|
+
"cancel_at_period_end",
|
|
34854
|
+
"start_date",
|
|
34855
|
+
"created",
|
|
34856
|
+
"livemode",
|
|
34857
|
+
"items"
|
|
34858
|
+
]
|
|
34859
|
+
} } }
|
|
34860
|
+
} }
|
|
34861
|
+
} },
|
|
34862
|
+
"/v1/charges/{charge}/dispute": { get: {
|
|
34863
|
+
operationId: "getChargeDispute",
|
|
34864
|
+
summary: "Retrieve the dispute on a charge",
|
|
34865
|
+
description: "The dispute a cardholder raised against a charge, by the `ch_…` the charge event carried. A dispute hangs off its charge rather than standing alone, which is why this reads through the charge and takes no dispute id.\n\nON A `charge.dispute.*` EVENT THE OBJECT IS THE DISPUTE, so the id this operation wants is `data.object.charge` and never `data.object.id`. The latter is a `du_…`, and the call answers 404 with `No dispute for charge: du_…`, a message that points at the wrong mistake.\n\nA CHARGE WITH NO DISPUTE IS AN ERROR AND NOT AN EMPTY ANSWER. Stripe answers 404 with `No dispute for charge: ch_…`, verified against a test account on 2026-09-17. So this is not the way to ask whether a charge was disputed: reach for it on a run started by a `charge.dispute.*` event, or handle the failure.\n\n`evidence_details.due_by` is the deadline, and it is the field the whole operation exists for: miss it and the dispute is lost by default. `is_charge_refundable` says whether refunding is still an option, which usually stops being one once a dispute opens.",
|
|
34866
|
+
parameters: [{
|
|
34867
|
+
name: "charge",
|
|
34868
|
+
in: "path",
|
|
34869
|
+
required: true,
|
|
34870
|
+
description: "The disputed charge id, e.g. `ch_3PqR2sC0mNqLxYzA0kL1mN2o`.",
|
|
34871
|
+
schema: { type: "string" }
|
|
34872
|
+
}],
|
|
34873
|
+
responses: { "200": {
|
|
34874
|
+
description: "The dispute on that charge.",
|
|
34875
|
+
content: { "application/json": { schema: {
|
|
34876
|
+
type: "object",
|
|
34877
|
+
properties: {
|
|
34878
|
+
id: { type: "string" },
|
|
34879
|
+
object: { type: "string" },
|
|
34880
|
+
charge: { type: "string" },
|
|
34881
|
+
payment_intent: { type: ["string", "null"] },
|
|
34882
|
+
amount: {
|
|
34883
|
+
type: "integer",
|
|
34884
|
+
description: "What is being disputed, in the smallest unit of `currency`. It can be less than the charge."
|
|
34885
|
+
},
|
|
34886
|
+
currency: { type: "string" },
|
|
34887
|
+
reason: {
|
|
34888
|
+
type: "string",
|
|
34889
|
+
description: "What the cardholder claimed, such as `fraudulent`, `product_not_received`, `duplicate` or `credit_not_processed`."
|
|
34890
|
+
},
|
|
34891
|
+
status: {
|
|
34892
|
+
type: "string",
|
|
34893
|
+
description: "One of `warning_needs_response`, `warning_under_review`, `warning_closed`, `needs_response`, `under_review`, `won`, `lost` or `prevented`."
|
|
34894
|
+
},
|
|
34895
|
+
is_charge_refundable: { type: "boolean" },
|
|
34896
|
+
created: { type: "integer" },
|
|
34897
|
+
livemode: { type: "boolean" },
|
|
34898
|
+
metadata: {
|
|
34899
|
+
type: "object",
|
|
34900
|
+
additionalProperties: true
|
|
34901
|
+
},
|
|
34902
|
+
evidence_details: {
|
|
34903
|
+
type: "object",
|
|
34904
|
+
properties: {
|
|
34905
|
+
due_by: {
|
|
34906
|
+
type: ["integer", "null"],
|
|
34907
|
+
description: "When evidence has to be submitted. Null while no response is owed."
|
|
34908
|
+
},
|
|
34909
|
+
has_evidence: { type: "boolean" },
|
|
34910
|
+
past_due: { type: "boolean" },
|
|
34911
|
+
submission_count: { type: "integer" }
|
|
34912
|
+
},
|
|
34913
|
+
required: [
|
|
34914
|
+
"has_evidence",
|
|
34915
|
+
"past_due",
|
|
34916
|
+
"submission_count"
|
|
34917
|
+
]
|
|
34918
|
+
}
|
|
34919
|
+
},
|
|
34920
|
+
required: [
|
|
34921
|
+
"id",
|
|
34922
|
+
"object",
|
|
34923
|
+
"charge",
|
|
34924
|
+
"amount",
|
|
34925
|
+
"currency",
|
|
34926
|
+
"reason",
|
|
34927
|
+
"status",
|
|
34928
|
+
"is_charge_refundable",
|
|
34929
|
+
"created",
|
|
34930
|
+
"livemode",
|
|
34931
|
+
"evidence_details"
|
|
34932
|
+
]
|
|
34933
|
+
} } }
|
|
34934
|
+
} }
|
|
34935
|
+
} },
|
|
34936
|
+
"/v1/payment_intents": { post: {
|
|
34937
|
+
operationId: "createPaymentIntent",
|
|
34938
|
+
summary: "Create a payment intent",
|
|
34939
|
+
description: "Creates a payment intent — Stripe’s record of an intent to collect money, which a browser then confirms. `amount` is in the currency’s smallest unit, so 4900 with `eur` is 49.00 euros and a zero-decimal currency like `jpy` has no such factor at all. The intent comes back `requires_payment_method`: creating one moves no money and charges nobody, which is why this operation needs no confirmation step here.\n\n`client_secret` is left out of the response schema, and it is worth knowing exactly how little that does. The schema documents an operation; it never filters a response. `connect-action-executor.ts` parses the provider body and returns it whole, and nothing in the broker reads `responseSchema` at all — so Stripe sends the secret and the workflow receives it, declared or not. Logs are a different story: `@stackbone/redaction` masks any key ending in `secret`, so a logged body loses it. A run’s output is not scrubbed, so a workflow that returns this response persists a bearer credential for one payment. Take the fields you need.\n\nIf a workflow of yours also watches this account’s event feed, declare `echoPath: 'id'` on the call and open that workflow with `isOwnEcho('stripe', event.data.object.id)`: creating an intent raises `payment_intent.created` on the feed, and the `pi_…` id is the value both sides can compare. Without it a link scoped to a payment-intent family, pointed at a workflow that creates intents, starts itself again on every lap.",
|
|
34940
|
+
"x-stackbone-body-encoding": "form",
|
|
34941
|
+
parameters: [IDEMPOTENCY_KEY_PARAM],
|
|
34942
|
+
requestBody: {
|
|
34943
|
+
required: true,
|
|
34944
|
+
content: { "application/x-www-form-urlencoded": { schema: {
|
|
34945
|
+
type: "object",
|
|
34946
|
+
properties: {
|
|
34947
|
+
amount: {
|
|
34948
|
+
type: "integer",
|
|
34949
|
+
description: "How much to collect, in the smallest unit of `currency`. 4900 is 49.00 in a two-decimal currency."
|
|
34950
|
+
},
|
|
34951
|
+
currency: {
|
|
34952
|
+
type: "string",
|
|
34953
|
+
description: "Three-letter ISO code, lowercase, e.g. `eur`."
|
|
34954
|
+
},
|
|
34955
|
+
customer: {
|
|
34956
|
+
type: "string",
|
|
34957
|
+
description: "Optional customer id (`cus_…`) to attach the intent to."
|
|
34958
|
+
},
|
|
34959
|
+
description: {
|
|
34960
|
+
type: "string",
|
|
34961
|
+
description: "Free text shown on the payment in the Stripe dashboard."
|
|
34962
|
+
},
|
|
34963
|
+
metadata: {
|
|
34964
|
+
type: "object",
|
|
34965
|
+
additionalProperties: { type: "string" },
|
|
34966
|
+
description: "Arbitrary key/value pairs stored on the intent, sent as `metadata[key]=value`."
|
|
34967
|
+
}
|
|
34968
|
+
},
|
|
34969
|
+
required: ["amount", "currency"]
|
|
34970
|
+
} } }
|
|
34971
|
+
},
|
|
34972
|
+
responses: { "200": {
|
|
34973
|
+
description: "The payment intent, newly created.",
|
|
34974
|
+
content: { "application/json": { schema: {
|
|
34975
|
+
type: "object",
|
|
34976
|
+
properties: {
|
|
34977
|
+
id: { type: "string" },
|
|
34978
|
+
object: { type: "string" },
|
|
34979
|
+
amount: { type: "integer" },
|
|
34980
|
+
amount_received: { type: "integer" },
|
|
34981
|
+
currency: { type: "string" },
|
|
34982
|
+
customer: { type: ["string", "null"] },
|
|
34983
|
+
description: { type: ["string", "null"] },
|
|
34984
|
+
status: {
|
|
34985
|
+
type: "string",
|
|
34986
|
+
description: "A freshly created intent is `requires_payment_method` until something confirms it."
|
|
34987
|
+
},
|
|
34988
|
+
created: { type: "integer" },
|
|
34989
|
+
livemode: { type: "boolean" },
|
|
34990
|
+
metadata: {
|
|
34991
|
+
type: "object",
|
|
34992
|
+
additionalProperties: true
|
|
34993
|
+
}
|
|
34994
|
+
},
|
|
34995
|
+
required: [
|
|
34996
|
+
"id",
|
|
34997
|
+
"object",
|
|
34998
|
+
"created",
|
|
34999
|
+
"livemode",
|
|
35000
|
+
"status"
|
|
35001
|
+
]
|
|
35002
|
+
} } }
|
|
35003
|
+
} }
|
|
35004
|
+
} },
|
|
35005
|
+
"/v1/refunds": { post: {
|
|
35006
|
+
operationId: "createRefund",
|
|
35007
|
+
summary: "Refund a charge or a payment intent",
|
|
35008
|
+
description: "Gives money back. Name one of `charge` or `payment_intent`, never both — Stripe refuses the pair rather than picking one. Omit `amount` to refund the whole thing; supply it, in the smallest unit, for a partial refund. Two calls are two refunds: nothing about the arguments makes a repeat a no-op, and the second fails only once the charge has nothing left to give back. Send `Idempotency-Key` to make a retry safe — see the parameter. If a workflow of yours also watches this account's event feed, declare `echoPath: 'charge'` on the call and open that workflow with `isOwnEcho('stripe', event.data.object.id)`: a refund raises `charge.refunded` on the feed, and the charge id is the value both sides can compare.\n\n`reason` takes exactly `duplicate`, `fraudulent` or `requested_by_customer`. Any other string is refused, so it is an enum rather than free text.",
|
|
35009
|
+
"x-stackbone-body-encoding": "form",
|
|
35010
|
+
parameters: [IDEMPOTENCY_KEY_PARAM],
|
|
35011
|
+
requestBody: {
|
|
35012
|
+
required: true,
|
|
35013
|
+
content: { "application/x-www-form-urlencoded": { schema: {
|
|
35014
|
+
type: "object",
|
|
35015
|
+
properties: {
|
|
35016
|
+
charge: {
|
|
35017
|
+
type: "string",
|
|
35018
|
+
description: "The charge to refund (`ch_…`). Mutually exclusive with `payment_intent`."
|
|
35019
|
+
},
|
|
35020
|
+
payment_intent: {
|
|
35021
|
+
type: "string",
|
|
35022
|
+
description: "The payment intent to refund (`pi_…`). Mutually exclusive with `charge`."
|
|
35023
|
+
},
|
|
35024
|
+
amount: {
|
|
35025
|
+
type: "integer",
|
|
35026
|
+
description: "How much to give back, in the smallest unit. Omitted refunds the full amount."
|
|
35027
|
+
},
|
|
35028
|
+
reason: {
|
|
35029
|
+
type: "string",
|
|
35030
|
+
enum: [
|
|
35031
|
+
"duplicate",
|
|
35032
|
+
"fraudulent",
|
|
35033
|
+
"requested_by_customer"
|
|
35034
|
+
],
|
|
35035
|
+
description: "Why. Stripe accepts these three strings and no others."
|
|
35036
|
+
},
|
|
35037
|
+
metadata: {
|
|
35038
|
+
type: "object",
|
|
35039
|
+
additionalProperties: { type: "string" },
|
|
35040
|
+
description: "Key/value pairs stored on the refund, as `metadata[key]=value`."
|
|
35041
|
+
}
|
|
35042
|
+
},
|
|
35043
|
+
required: []
|
|
35044
|
+
} } }
|
|
35045
|
+
},
|
|
35046
|
+
responses: { "200": {
|
|
35047
|
+
description: "The refund.",
|
|
35048
|
+
content: { "application/json": { schema: {
|
|
35049
|
+
type: "object",
|
|
35050
|
+
properties: {
|
|
35051
|
+
id: { type: "string" },
|
|
35052
|
+
object: { type: "string" },
|
|
35053
|
+
amount: { type: "integer" },
|
|
35054
|
+
currency: { type: "string" },
|
|
35055
|
+
charge: { type: ["string", "null"] },
|
|
35056
|
+
payment_intent: { type: ["string", "null"] },
|
|
35057
|
+
reason: { type: ["string", "null"] },
|
|
35058
|
+
status: {
|
|
35059
|
+
type: "string",
|
|
35060
|
+
description: "One of `pending`, `succeeded`, `failed` or `canceled`. A card refund in test mode comes back `succeeded` at once."
|
|
35061
|
+
},
|
|
35062
|
+
created: { type: "integer" },
|
|
35063
|
+
metadata: {
|
|
35064
|
+
type: "object",
|
|
35065
|
+
additionalProperties: true
|
|
35066
|
+
}
|
|
35067
|
+
},
|
|
35068
|
+
required: [
|
|
35069
|
+
"id",
|
|
35070
|
+
"object",
|
|
35071
|
+
"created",
|
|
35072
|
+
"status"
|
|
35073
|
+
]
|
|
35074
|
+
} } }
|
|
35075
|
+
} }
|
|
35076
|
+
} }
|
|
35077
|
+
}
|
|
35078
|
+
}
|
|
35079
|
+
},
|
|
35080
|
+
triggers: [{
|
|
35081
|
+
id: "event-received",
|
|
35082
|
+
displayName: "Stripe event",
|
|
35083
|
+
description: "A Stripe event is the account’s own record of something that happened: a charge succeeded, an invoice was created. This link reads that feed and starts one run per event recorded after you turn it on. The event arrives whole — `type` names what happened, and `data.object` carries the thing it happened to.",
|
|
35084
|
+
docs: "Starts a run for each event Stripe records on the account, from the moment you turn the link on. The event arrives whole: `type` names what happened, and the object it happened to rides along with it. That object is a charge on a charge.succeeded event and an invoice on an invoice.created one, so a mapping that reads a charge field belongs on a link scoped to charge events.\n\nEvent type takes one name, or a family with a wildcard such as `charge.*`. Left blank, every type the account emits starts a run.\n\nTest mode and live mode are separate feeds. A test key sees only test events, and every event carries a `livemode` flag saying which feed it came from.\n\nCAUTION: a poll walks page after page until Stripe says there are no more, and stops at 10,000 events. Reaching that ceiling marks the tick cut off, so the loss is on the record rather than silent, but those events do not arrive on a later poll. Only a very busy account gets near it.\n\nCAUTION: Stripe keeps events for 30 days. A link switched off for longer resumes from a bookmark older than anything the feed still holds, and whatever expired in the meantime never arrives.\n\nCAUTION: this connector writes, and a workflow that creates a payment intent or issues a refund through this connection raises events on the very feed this link reads. A refund raises a charge.refunded event, so a link scoped to `charge.*` that also refunds starts itself again, and deduplication cannot stop it because every lap carries a new event id. There are two guards. The cheap one is the scope: keep the writing workflow off the types its own writes raise. That is why the event type field offers no charge or payment intent wildcard, though it still accepts one you type yourself. The other guard is `isOwnEcho` inside the workflow body, which compares a value the delivery carried against one your own write recorded. Declare an echo path on the write so it records the charge id, or the payment intent id, and compare it against the id the event object carries. Compare the resource id and never the event id, which is new on every lap and would match nothing. The `createRefund` and `createPaymentIntent` pages name the exact paths. A mapping cannot skip an event: every delivery starts a run.",
|
|
35085
|
+
sampleData: {
|
|
35086
|
+
id: "evt_1PqR2sC0mNqLxYzA3bK4dEf5",
|
|
35087
|
+
object: "event",
|
|
35088
|
+
api_version: "2026-08-26.dahlia",
|
|
35089
|
+
created: 1757836921,
|
|
35090
|
+
livemode: false,
|
|
35091
|
+
pending_webhooks: 0,
|
|
35092
|
+
request: {
|
|
35093
|
+
id: "req_9fT2kQwZ1aBcDe",
|
|
35094
|
+
idempotency_key: null
|
|
35095
|
+
},
|
|
35096
|
+
type: "charge.succeeded",
|
|
35097
|
+
data: { object: {
|
|
35098
|
+
id: "ch_3PqR2sC0mNqLxYzA0kL1mN2o",
|
|
35099
|
+
object: "charge",
|
|
35100
|
+
amount: 4900,
|
|
35101
|
+
amount_captured: 4900,
|
|
35102
|
+
amount_refunded: 0,
|
|
35103
|
+
currency: "eur",
|
|
35104
|
+
customer: "cus_QhT8kLmNoPqRs",
|
|
35105
|
+
description: "Stackbone Pro, monthly",
|
|
35106
|
+
paid: true,
|
|
35107
|
+
refunded: false,
|
|
35108
|
+
receipt_url: "https://pay.stripe.com/receipts/payment/example",
|
|
35109
|
+
payment_intent: "pi_3PqR2sC0mNqLxYzA1cD2eF3g",
|
|
35110
|
+
status: "succeeded",
|
|
35111
|
+
created: 1757836920,
|
|
35112
|
+
livemode: false,
|
|
35113
|
+
metadata: { workspace: "stackbone-demo" }
|
|
35114
|
+
} }
|
|
35115
|
+
},
|
|
35116
|
+
outputSchema: {
|
|
35117
|
+
type: "object",
|
|
35118
|
+
properties: {
|
|
35119
|
+
id: { type: "string" },
|
|
35120
|
+
object: { type: "string" },
|
|
35121
|
+
api_version: { type: ["string", "null"] },
|
|
35122
|
+
created: {
|
|
35123
|
+
type: "integer",
|
|
35124
|
+
description: "When Stripe recorded the event, in seconds from the epoch."
|
|
35125
|
+
},
|
|
35126
|
+
livemode: {
|
|
35127
|
+
type: "boolean",
|
|
35128
|
+
description: "False for an event from test mode, true for one from live mode."
|
|
35129
|
+
},
|
|
35130
|
+
pending_webhooks: { type: "integer" },
|
|
35131
|
+
type: { type: "string" },
|
|
35132
|
+
request: {
|
|
35133
|
+
type: ["object", "null"],
|
|
35134
|
+
properties: {
|
|
35135
|
+
id: { type: ["string", "null"] },
|
|
35136
|
+
idempotency_key: { type: ["string", "null"] }
|
|
35137
|
+
}
|
|
35138
|
+
},
|
|
35139
|
+
data: {
|
|
35140
|
+
type: "object",
|
|
35141
|
+
properties: {
|
|
35142
|
+
object: {
|
|
35143
|
+
type: "object",
|
|
35144
|
+
additionalProperties: true
|
|
35145
|
+
},
|
|
35146
|
+
previous_attributes: {
|
|
35147
|
+
type: "object",
|
|
35148
|
+
additionalProperties: true
|
|
35149
|
+
}
|
|
35150
|
+
},
|
|
35151
|
+
required: ["object"]
|
|
35152
|
+
}
|
|
35153
|
+
},
|
|
35154
|
+
required: [
|
|
35155
|
+
"id",
|
|
35156
|
+
"object",
|
|
35157
|
+
"created",
|
|
35158
|
+
"livemode",
|
|
35159
|
+
"pending_webhooks",
|
|
35160
|
+
"type",
|
|
35161
|
+
"data"
|
|
35162
|
+
]
|
|
35163
|
+
},
|
|
35164
|
+
poll: {
|
|
35165
|
+
kind: "declarative",
|
|
35166
|
+
list: {
|
|
35167
|
+
operationId: "listEvents",
|
|
35168
|
+
args: { limit: 100 },
|
|
35169
|
+
itemsPath: "data",
|
|
35170
|
+
order: "newest-first",
|
|
35171
|
+
pageLastId: {
|
|
35172
|
+
param: "starting_after",
|
|
35173
|
+
itemPath: "id",
|
|
35174
|
+
hasMorePath: "has_more"
|
|
35175
|
+
},
|
|
35176
|
+
scopeParams: [{
|
|
35177
|
+
param: "type",
|
|
35178
|
+
displayName: "Event type",
|
|
35179
|
+
description: "One event name, or a family with a wildcard: `charge.*`. Blank delivers every type the account emits.",
|
|
35180
|
+
example: "charge.succeeded",
|
|
35181
|
+
required: false,
|
|
35182
|
+
pattern: "^[a-z0-9_.*]+$",
|
|
35183
|
+
suggestions: [
|
|
35184
|
+
"charge.succeeded",
|
|
35185
|
+
"charge.failed",
|
|
35186
|
+
"charge.refunded",
|
|
35187
|
+
"charge.dispute.created",
|
|
35188
|
+
"payment_intent.succeeded",
|
|
35189
|
+
"payment_intent.payment_failed",
|
|
35190
|
+
"payment_intent.canceled",
|
|
35191
|
+
"invoice.paid",
|
|
35192
|
+
"invoice.payment_failed",
|
|
35193
|
+
"invoice.*",
|
|
35194
|
+
"customer.subscription.created",
|
|
35195
|
+
"customer.subscription.updated",
|
|
35196
|
+
"customer.subscription.deleted",
|
|
35197
|
+
"customer.subscription.*",
|
|
35198
|
+
"checkout.session.completed",
|
|
35199
|
+
"payout.paid",
|
|
35200
|
+
"payout.failed"
|
|
35201
|
+
]
|
|
35202
|
+
}]
|
|
35203
|
+
},
|
|
35204
|
+
cursor: {
|
|
35205
|
+
kind: "timestamp",
|
|
35206
|
+
field: "created",
|
|
35207
|
+
param: "created[gte]",
|
|
35208
|
+
unit: "unix-seconds"
|
|
35209
|
+
},
|
|
35210
|
+
dedup: { idPath: "id" }
|
|
35211
|
+
}
|
|
35212
|
+
}]
|
|
35213
|
+
};
|
|
35214
|
+
}));
|
|
35215
|
+
//#endregion
|
|
33934
35216
|
//#region ../../libs/shared/connect-registry/src/lib/catalog/supabase/supabase.connector.ts
|
|
33935
35217
|
var SUPABASE_CONNECTOR;
|
|
33936
35218
|
var init_supabase_connector = __esmMin((() => {
|
|
@@ -34033,6 +35315,7 @@ var init_supabase_connector = __esmMin((() => {
|
|
|
34033
35315
|
kind: "declarative",
|
|
34034
35316
|
list: {
|
|
34035
35317
|
operationId: "GET /{table}",
|
|
35318
|
+
order: "oldest-first",
|
|
34036
35319
|
args: {
|
|
34037
35320
|
order: "{cursorColumn}.asc",
|
|
34038
35321
|
and: "({cursorColumn}.not.is.null)"
|
|
@@ -34146,6 +35429,7 @@ var init_built_in_catalog = __esmMin((() => {
|
|
|
34146
35429
|
init_linear_connector();
|
|
34147
35430
|
init_outlook_connector();
|
|
34148
35431
|
init_outlook_app_only_connector();
|
|
35432
|
+
init_stripe_connector();
|
|
34149
35433
|
init_supabase_connector();
|
|
34150
35434
|
init_telegram_connector();
|
|
34151
35435
|
init_whatsapp_connector();
|
|
@@ -34156,6 +35440,7 @@ var init_built_in_catalog = __esmMin((() => {
|
|
|
34156
35440
|
LINEAR_CONNECTOR,
|
|
34157
35441
|
OUTLOOK_CONNECTOR,
|
|
34158
35442
|
OUTLOOK_APP_ONLY_CONNECTOR,
|
|
35443
|
+
STRIPE_CONNECTOR,
|
|
34159
35444
|
SUPABASE_CONNECTOR,
|
|
34160
35445
|
TELEGRAM_CONNECTOR,
|
|
34161
35446
|
WHATSAPP_CONNECTOR
|
|
@@ -34562,6 +35847,7 @@ function buildOperation(rawPath, resolvedPath, method, operation, pathLevelParam
|
|
|
34562
35847
|
const rawResponse = declaredRawResponse === true || declaredRawResponse === "when-requested" ? declaredRawResponse : void 0;
|
|
34563
35848
|
const declaredVersionPath = operation["x-stackbone-version-path"];
|
|
34564
35849
|
const versionPath = typeof declaredVersionPath === "string" && declaredVersionPath !== "" ? declaredVersionPath : void 0;
|
|
35850
|
+
const bodyEncoding = operation["x-stackbone-body-encoding"] === "form" ? "form" : void 0;
|
|
34565
35851
|
const continuation = readContinuation(operation);
|
|
34566
35852
|
return {
|
|
34567
35853
|
operationId,
|
|
@@ -34573,6 +35859,7 @@ function buildOperation(rawPath, resolvedPath, method, operation, pathLevelParam
|
|
|
34573
35859
|
...parameters.length > 0 ? { parameters } : {},
|
|
34574
35860
|
...requestBodySchema !== void 0 ? { requestBodySchema } : {},
|
|
34575
35861
|
...bodyArgName !== void 0 ? { bodyArgName } : {},
|
|
35862
|
+
...bodyEncoding !== void 0 ? { bodyEncoding } : {},
|
|
34576
35863
|
...responseSchema !== void 0 ? { responseSchema } : {},
|
|
34577
35864
|
...rawBody ? { rawBody } : {},
|
|
34578
35865
|
...rawResponse !== void 0 ? { rawResponse } : {},
|
|
@@ -34806,8 +36093,15 @@ async function mapWithConcurrency(items, limit, fn) {
|
|
|
34806
36093
|
var init_concurrency = __esmMin((() => {}));
|
|
34807
36094
|
//#endregion
|
|
34808
36095
|
//#region ../../libs/shared/connect-registry/src/lib/triggers/stamps.ts
|
|
34809
|
-
/**
|
|
34810
|
-
|
|
36096
|
+
/**
|
|
36097
|
+
* The instant a stamp names, or `null` for anything that is not a stamp.
|
|
36098
|
+
*
|
|
36099
|
+
* `unit` is the cursor's declared unit, passed by every caller that has one.
|
|
36100
|
+
* Omitted — the case for every trigger written before units existed — the two
|
|
36101
|
+
* ISO shapes above are the only ones read, exactly as before.
|
|
36102
|
+
*/
|
|
36103
|
+
function parseStamp(value, unit) {
|
|
36104
|
+
if (unit === "unix-seconds") return parseUnixSeconds(value);
|
|
34811
36105
|
if (typeof value !== "string") return null;
|
|
34812
36106
|
const zoned = EXPLICIT_OFFSET.test(value);
|
|
34813
36107
|
const zoneless = !zoned && ZONELESS_STAMP.test(value);
|
|
@@ -34823,17 +36117,36 @@ function parseStamp(value) {
|
|
|
34823
36117
|
}
|
|
34824
36118
|
};
|
|
34825
36119
|
}
|
|
36120
|
+
/** A stamp counted in seconds from the epoch, as a number or as its digits. */
|
|
36121
|
+
function parseUnixSeconds(value) {
|
|
36122
|
+
const numeric = typeof value === "number";
|
|
36123
|
+
if (!numeric && !(typeof value === "string" && UNIX_SECONDS.test(value))) return null;
|
|
36124
|
+
const seconds = numeric ? value : Number(value);
|
|
36125
|
+
if (!Number.isSafeInteger(seconds) || Math.abs(seconds) >= 0xe8d4a51000) return null;
|
|
36126
|
+
return {
|
|
36127
|
+
epochMs: seconds * 1e3,
|
|
36128
|
+
shape: {
|
|
36129
|
+
unit: "unix-seconds",
|
|
36130
|
+
numeric
|
|
36131
|
+
}
|
|
36132
|
+
};
|
|
36133
|
+
}
|
|
34826
36134
|
/** Write an instant in the shape {@link parseStamp} read. */
|
|
34827
36135
|
function formatStamp(epochMs, shape) {
|
|
36136
|
+
if (shape.unit === "unix-seconds") {
|
|
36137
|
+
const seconds = Math.floor(epochMs / 1e3);
|
|
36138
|
+
return shape.numeric ? seconds : String(seconds);
|
|
36139
|
+
}
|
|
34828
36140
|
const iso = new Date(epochMs).toISOString();
|
|
34829
36141
|
const withFraction = shape.fractional ? iso : iso.replace(/\.\d{3}Z$/, "Z");
|
|
34830
36142
|
if (shape.zoned) return withFraction;
|
|
34831
36143
|
return withFraction.slice(0, -1).replace("T", shape.separator);
|
|
34832
36144
|
}
|
|
34833
|
-
var EXPLICIT_OFFSET, ZONELESS_STAMP;
|
|
36145
|
+
var EXPLICIT_OFFSET, ZONELESS_STAMP, UNIX_SECONDS;
|
|
34834
36146
|
var init_stamps = __esmMin((() => {
|
|
34835
36147
|
EXPLICIT_OFFSET = /(?:Z|[+-]\d{2}:?\d{2})$/i;
|
|
34836
36148
|
ZONELESS_STAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/;
|
|
36149
|
+
UNIX_SECONDS = /^\d{1,12}$/;
|
|
34837
36150
|
}));
|
|
34838
36151
|
//#endregion
|
|
34839
36152
|
//#region ../../libs/shared/connect-registry/src/lib/triggers/list-args.ts
|
|
@@ -34871,17 +36184,30 @@ function scopeValue(param, scope) {
|
|
|
34871
36184
|
* same filter the stored one did and has to parse the way that one parsed. An
|
|
34872
36185
|
* offset other than `Z` comes back as `Z`; it names the same instant.
|
|
34873
36186
|
*
|
|
34874
|
-
* @throws on a stored cursor
|
|
34875
|
-
*
|
|
34876
|
-
*
|
|
34877
|
-
*
|
|
34878
|
-
*
|
|
34879
|
-
|
|
34880
|
-
|
|
36187
|
+
* @throws on a stored cursor the reader cannot take, whenever the definition
|
|
36188
|
+
* claimed it could be read: a declared `unit`, a declared `lookbackMs`, or both
|
|
36189
|
+
* (PLA-1506). Refusing to PARSE a value is not the same as refusing to SEND it,
|
|
36190
|
+
* and sending it is the worse of the two. A `unix-seconds` cursor holding a
|
|
36191
|
+
* 13-digit millisecond value goes out verbatim, matches nothing, never advances,
|
|
36192
|
+
* and every tick after it reports `polled` on a green row — the definition's
|
|
36193
|
+
* promise unkept with nothing anywhere to say so. The failed tick puts the value
|
|
36194
|
+
* on the row where an operator sees it.
|
|
36195
|
+
*
|
|
36196
|
+
* A cursor that declares NEITHER is passed through unread, and has to be: an
|
|
36197
|
+
* opaque provider token, a Supabase integer id, the `'12345'` that
|
|
36198
|
+
* `declarative-poll.spec.ts` pins as passing through untouched. Reading is owed
|
|
36199
|
+
* only where the definition said the value is readable.
|
|
36200
|
+
*/
|
|
36201
|
+
function cursorForRequest(cursor, stored) {
|
|
34881
36202
|
if (stored === null || stored === void 0) return stored;
|
|
34882
|
-
if (cursor.kind !== "timestamp"
|
|
34883
|
-
|
|
34884
|
-
|
|
36203
|
+
if (cursor.kind !== "timestamp") return stored;
|
|
36204
|
+
if (cursor.unit === void 0 && cursor.lookbackMs === void 0) return stored;
|
|
36205
|
+
const stamp = parseStamp(stored, cursor.unit);
|
|
36206
|
+
if (stamp === null) {
|
|
36207
|
+
const value = JSON.stringify(stored);
|
|
36208
|
+
throw new Error(cursor.lookbackMs !== void 0 ? `trigger declares a cursor lookback of ${cursor.lookbackMs}ms but the stored cursor ${value} cannot be read as an instant` + (cursor.unit === void 0 ? ", and this cursor declares no unit, so only an ISO date stamp can be moved back" : ` in the declared unit ${cursor.unit}`) + " — there is nothing to subtract the margin from" : `trigger declares a cursor unit of ${cursor.unit} but the stored cursor ${value} cannot be read in it — sending it on would filter for a moment that is not the one it names, match nothing, and leave the marker where it is on a green row`);
|
|
36209
|
+
}
|
|
36210
|
+
if (cursor.lookbackMs === void 0) return stored;
|
|
34885
36211
|
return formatStamp(stamp.epochMs - cursor.lookbackMs, stamp.shape);
|
|
34886
36212
|
}
|
|
34887
36213
|
/**
|
|
@@ -34892,11 +36218,13 @@ function withLookback(cursor, stored) {
|
|
|
34892
36218
|
* failed tick.
|
|
34893
36219
|
* @throws when a scope clause and the cursor clause target one key and the
|
|
34894
36220
|
* cursor declares no `composeWith` to join them with.
|
|
36221
|
+
* @throws when the stored cursor cannot be read in the unit or shape the
|
|
36222
|
+
* definition declared — see {@link cursorForRequest}.
|
|
34895
36223
|
*/
|
|
34896
36224
|
function buildListArgs(spec, input) {
|
|
34897
36225
|
const args = { ...spec.list.args ?? {} };
|
|
34898
36226
|
const cursorParam = spec.cursor.kind === "timestamp" ? spec.cursor.param : spec.cursor.requestParam;
|
|
34899
|
-
const cursor =
|
|
36227
|
+
const cursor = cursorForRequest(spec.cursor, input.cursor);
|
|
34900
36228
|
const cursorValue = cursor === null || cursor === void 0 ? void 0 : spec.cursor.kind === "timestamp" && spec.cursor.cursorTemplate ? fill(spec.cursor.cursorTemplate, "{cursor}", String(cursor)) : cursor;
|
|
34901
36229
|
let collidingClause = void 0;
|
|
34902
36230
|
for (const param of spec.list.scopeParams ?? []) {
|
|
@@ -34921,7 +36249,8 @@ function buildListArgs(spec, input) {
|
|
|
34921
36249
|
} else if (spec.list.pageNumber) {
|
|
34922
36250
|
setPath(args, spec.list.pageNumber.pageSizeParam, spec.list.pageNumber.size);
|
|
34923
36251
|
setPath(args, spec.list.pageNumber.param, page.index + 1);
|
|
34924
|
-
} else if (spec.list.
|
|
36252
|
+
} else if (spec.list.pageLastId && page.lastId !== null && page.lastId !== void 0) setPath(args, spec.list.pageLastId.param, page.lastId);
|
|
36253
|
+
else if (spec.list.pageToken && page.token !== null && page.token !== void 0) setPath(args, spec.list.pageToken.requestParam, page.token);
|
|
34925
36254
|
return args;
|
|
34926
36255
|
}
|
|
34927
36256
|
var init_list_args = __esmMin((() => {
|
|
@@ -35027,10 +36356,22 @@ function firstErrorMessage(errors) {
|
|
|
35027
36356
|
return String(first);
|
|
35028
36357
|
}
|
|
35029
36358
|
}
|
|
35030
|
-
|
|
36359
|
+
/**
|
|
36360
|
+
* The later of two markers. With a `unit` declared both sides are read as
|
|
36361
|
+
* INSTANTS, because the lexical fallback below gets a declared unit wrong every
|
|
36362
|
+
* time: `'1757000000'` sorts under `'2026-…'`, so a unix marker would lose to
|
|
36363
|
+
* the ISO value the seed used to mint and the cursor would never move — the
|
|
36364
|
+
* link green, the poll asking for the same window forever.
|
|
36365
|
+
*/
|
|
36366
|
+
function laterOf(a, b, unit) {
|
|
35031
36367
|
const other = typeof b === "string" || typeof b === "number" ? b : void 0;
|
|
35032
36368
|
if (a === void 0) return other;
|
|
35033
36369
|
if (other === void 0) return a;
|
|
36370
|
+
if (unit !== void 0) {
|
|
36371
|
+
const left = parseStamp(a, unit);
|
|
36372
|
+
const right = parseStamp(other, unit);
|
|
36373
|
+
if (left !== null && right !== null) return left.epochMs > right.epochMs ? a : other;
|
|
36374
|
+
}
|
|
35034
36375
|
if (typeof a === "number" && typeof other === "number") return a > other ? a : other;
|
|
35035
36376
|
return String(a) > String(other) ? a : other;
|
|
35036
36377
|
}
|
|
@@ -35057,14 +36398,15 @@ function laterOf(a, b) {
|
|
|
35057
36398
|
* A provider clock running ahead therefore makes the marker trail by the skew
|
|
35058
36399
|
* and re-list once — which dedup absorbs — rather than stall.
|
|
35059
36400
|
*
|
|
35060
|
-
* Only
|
|
35061
|
-
*
|
|
35062
|
-
* sequence) and is left alone, and
|
|
35063
|
-
*
|
|
35064
|
-
*
|
|
36401
|
+
* Only values `parseStamp` reads are judged: the two date shapes, plus a number
|
|
36402
|
+
* when the cursor DECLARES its unit. An undeclared numeric cursor is ambiguous
|
|
36403
|
+
* (seconds or milliseconds, or a provider's own sequence) and is left alone, and
|
|
36404
|
+
* so is anything that is not a date shape — which is what keeps
|
|
36405
|
+
* `Date.parse('12345')`, the year 12345, from barring a numeric-looking id. A
|
|
36406
|
+
* zoneless stamp is read as UTC; `stamps.ts` says why.
|
|
35065
36407
|
*/
|
|
35066
|
-
function isFuture(value) {
|
|
35067
|
-
const stamp = parseStamp(value);
|
|
36408
|
+
function isFuture(value, unit) {
|
|
36409
|
+
const stamp = parseStamp(value, unit);
|
|
35068
36410
|
return stamp !== null && stamp.epochMs > Date.now();
|
|
35069
36411
|
}
|
|
35070
36412
|
/** The greatest value across items AND across every declared field (ISO strings
|
|
@@ -35072,14 +36414,15 @@ function isFuture(value) {
|
|
|
35072
36414
|
* yet. `undefined` when no item carries a usable value — an item barred from
|
|
35073
36415
|
* moving the marker is still delivered; it is re-listed until its date arrives,
|
|
35074
36416
|
* and dedup absorbs that. */
|
|
35075
|
-
function latestTimestamp(items, field) {
|
|
36417
|
+
function latestTimestamp(items, field, unit) {
|
|
35076
36418
|
const fields = typeof field === "string" ? [field] : field;
|
|
35077
36419
|
let latest;
|
|
35078
36420
|
for (const item of items) for (const path of fields) {
|
|
35079
36421
|
const value = getPath(item, path);
|
|
35080
36422
|
if (typeof value !== "string" && typeof value !== "number") continue;
|
|
35081
|
-
if (
|
|
35082
|
-
|
|
36423
|
+
if (unit !== void 0 && parseStamp(value, unit) === null) continue;
|
|
36424
|
+
if (isFuture(value, unit)) continue;
|
|
36425
|
+
latest = laterOf(value, latest, unit);
|
|
35083
36426
|
}
|
|
35084
36427
|
return latest;
|
|
35085
36428
|
}
|
|
@@ -35093,6 +36436,7 @@ function createDeclarativePoll(spec) {
|
|
|
35093
36436
|
const rawItems = [];
|
|
35094
36437
|
let opaqueNext;
|
|
35095
36438
|
let pageToken;
|
|
36439
|
+
let pageLastId;
|
|
35096
36440
|
let drained = false;
|
|
35097
36441
|
let pagesWalked = 0;
|
|
35098
36442
|
for (let page = 0; page < 100; page += 1) {
|
|
@@ -35102,7 +36446,8 @@ function createDeclarativePoll(spec) {
|
|
|
35102
36446
|
cursor,
|
|
35103
36447
|
page: {
|
|
35104
36448
|
index: page,
|
|
35105
|
-
token: pageToken
|
|
36449
|
+
token: pageToken,
|
|
36450
|
+
lastId: pageLastId
|
|
35106
36451
|
}
|
|
35107
36452
|
});
|
|
35108
36453
|
const response = await ctx.runOperation(spec.list.operationId, args);
|
|
@@ -35124,9 +36469,20 @@ function createDeclarativePoll(spec) {
|
|
|
35124
36469
|
drained = true;
|
|
35125
36470
|
break;
|
|
35126
36471
|
}
|
|
36472
|
+
} else if (spec.list.pageLastId) {
|
|
36473
|
+
const hasMore = getPath(response, spec.list.pageLastId.hasMorePath);
|
|
36474
|
+
if (hasMore === false) {
|
|
36475
|
+
drained = true;
|
|
36476
|
+
break;
|
|
36477
|
+
}
|
|
36478
|
+
if (hasMore !== true) break;
|
|
36479
|
+
const last = Array.isArray(items) && items.length > 0 ? items[items.length - 1] : void 0;
|
|
36480
|
+
const next = last === void 0 ? void 0 : getPath(last, spec.list.pageLastId.itemPath);
|
|
36481
|
+
if (next === null || next === void 0) break;
|
|
36482
|
+
pageLastId = next;
|
|
35127
36483
|
} else {
|
|
35128
36484
|
pageToken = spec.list.pageToken ? getPath(response, spec.list.pageToken.responsePath) : void 0;
|
|
35129
|
-
const hasMorePath = spec.list.pageToken?.hasMorePath;
|
|
36485
|
+
const hasMorePath = spec.list.pageToken?.hasMorePath ?? spec.list.hasMorePath;
|
|
35130
36486
|
const hasMore = hasMorePath === void 0 ? void 0 : getPath(response, hasMorePath);
|
|
35131
36487
|
if (hasMore === false) {
|
|
35132
36488
|
drained = true;
|
|
@@ -35134,6 +36490,7 @@ function createDeclarativePoll(spec) {
|
|
|
35134
36490
|
}
|
|
35135
36491
|
if (hasMorePath !== void 0 && hasMore !== true) break;
|
|
35136
36492
|
if (pageToken === null || pageToken === void 0) {
|
|
36493
|
+
if (spec.list.pageToken === void 0 && hasMorePath !== void 0) break;
|
|
35137
36494
|
drained = true;
|
|
35138
36495
|
break;
|
|
35139
36496
|
}
|
|
@@ -35152,7 +36509,7 @@ function createDeclarativePoll(spec) {
|
|
|
35152
36509
|
id: dedupId(item, spec.dedup),
|
|
35153
36510
|
payload: withScopeFields(spec.map ? mapFields(spec.map, item) : item, spec.list.scopeParams, ctx.scope)
|
|
35154
36511
|
}));
|
|
35155
|
-
const nextCursor = spec.cursor.kind === "opaque" ? opaqueNext ?? cursor ?? null : laterOf(latestTimestamp(items, spec.cursor.field), cursor) ?? null;
|
|
36512
|
+
const nextCursor = spec.cursor.kind === "opaque" ? opaqueNext ?? cursor ?? null : laterOf(latestTimestamp(items, spec.cursor.field, spec.cursor.unit), cursor, spec.cursor.unit) ?? null;
|
|
35156
36513
|
if (!drained) return {
|
|
35157
36514
|
items: events,
|
|
35158
36515
|
nextCursor,
|
|
@@ -35320,6 +36677,7 @@ var init_src$4 = __esmMin((() => {
|
|
|
35320
36677
|
init_trigger_descriptor();
|
|
35321
36678
|
init_declarative_poll();
|
|
35322
36679
|
init_list_args();
|
|
36680
|
+
init_stamps();
|
|
35323
36681
|
init_paths();
|
|
35324
36682
|
init_concurrency();
|
|
35325
36683
|
init_built_in_strategies();
|
|
@@ -35365,10 +36723,17 @@ function redactUrl(raw) {
|
|
|
35365
36723
|
async function resolveSpecDocument(specSource, deps = {}) {
|
|
35366
36724
|
if (specSource === void 0) throw new Error("connector definition has no specSource to introspect");
|
|
35367
36725
|
if (specSource.kind === "inline") return specSource.document;
|
|
36726
|
+
return parseSpecText(await (await fetchSpec(specSource, deps)).text(), specSource.url);
|
|
36727
|
+
}
|
|
36728
|
+
/** Fetch a `url` specSource through the injected `fetch`, refusing a non-2xx. */
|
|
36729
|
+
async function fetchSpec(specSource, deps) {
|
|
35368
36730
|
if (deps.fetch === void 0) throw new Error(`connector specSource is a url ("${redactUrl(specSource.url)}") but no fetch was injected to resolve it`);
|
|
35369
36731
|
const response = deps.policy !== void 0 ? await deps.fetch(specSource.url, deps.headers, deps.policy) : deps.headers !== void 0 ? await deps.fetch(specSource.url, deps.headers) : await deps.fetch(specSource.url);
|
|
35370
36732
|
if (!response.ok) throw new Error(`fetching connector spec from "${redactUrl(specSource.url)}" failed with status ${response.status}`);
|
|
35371
|
-
|
|
36733
|
+
return response;
|
|
36734
|
+
}
|
|
36735
|
+
/** Parse fetched spec text as JSON, falling back to YAML. `url` only feeds the error. */
|
|
36736
|
+
function parseSpecText(body, url) {
|
|
35372
36737
|
let doc;
|
|
35373
36738
|
try {
|
|
35374
36739
|
doc = JSON.parse(body);
|
|
@@ -35379,9 +36744,46 @@ async function resolveSpecDocument(specSource, deps = {}) {
|
|
|
35379
36744
|
doc = void 0;
|
|
35380
36745
|
}
|
|
35381
36746
|
}
|
|
35382
|
-
if (doc === null || typeof doc !== "object") throw new Error(`connector spec fetched from "${redactUrl(
|
|
36747
|
+
if (doc === null || typeof doc !== "object") throw new Error(`connector spec fetched from "${redactUrl(url)}" is not valid JSON or YAML`);
|
|
35383
36748
|
return doc;
|
|
35384
36749
|
}
|
|
36750
|
+
/** The protocol introspector's own output for a parsed document, with no extras. */
|
|
36751
|
+
function introspectDocument(protocol, document) {
|
|
36752
|
+
if (protocol === "openapi") return Promise.resolve(introspectOpenApi(document));
|
|
36753
|
+
return introspectMcpTools({ listToolsResult: document });
|
|
36754
|
+
}
|
|
36755
|
+
/** A held parse by memo key, refreshed as most recently used. */
|
|
36756
|
+
function heldIntrospection(key) {
|
|
36757
|
+
const held = baseIntrospections.get(key);
|
|
36758
|
+
if (held !== void 0) {
|
|
36759
|
+
baseIntrospections.delete(key);
|
|
36760
|
+
baseIntrospections.set(key, held);
|
|
36761
|
+
}
|
|
36762
|
+
return held;
|
|
36763
|
+
}
|
|
36764
|
+
async function introspectBase(def, deps) {
|
|
36765
|
+
const source = def.specSource;
|
|
36766
|
+
if (source?.kind !== "url") return introspectDocument(def.protocol, await resolveSpecDocument(source, deps));
|
|
36767
|
+
const response = await fetchSpec(source, deps);
|
|
36768
|
+
if (response.digest !== void 0) {
|
|
36769
|
+
const held = heldIntrospection(`${def.protocol}:${response.digest}`);
|
|
36770
|
+
if (held !== void 0) return held;
|
|
36771
|
+
}
|
|
36772
|
+
const text = await response.text();
|
|
36773
|
+
const key = `${def.protocol}:${specTextDigest(text)}`;
|
|
36774
|
+
const held = heldIntrospection(key);
|
|
36775
|
+
if (held !== void 0) return held;
|
|
36776
|
+
const parsing = Promise.resolve().then(() => introspectDocument(def.protocol, parseSpecText(text, source.url))).catch((err) => {
|
|
36777
|
+
if (baseIntrospections.get(key) === parsing) baseIntrospections.delete(key);
|
|
36778
|
+
throw err;
|
|
36779
|
+
});
|
|
36780
|
+
baseIntrospections.set(key, parsing);
|
|
36781
|
+
if (baseIntrospections.size > 16) {
|
|
36782
|
+
const oldest = baseIntrospections.keys().next().value;
|
|
36783
|
+
if (oldest !== void 0) baseIntrospections.delete(oldest);
|
|
36784
|
+
}
|
|
36785
|
+
return parsing;
|
|
36786
|
+
}
|
|
35385
36787
|
/**
|
|
35386
36788
|
* Resolve a connector's spec document and dispatch it to the right introspector
|
|
35387
36789
|
* for its `protocol`. Returns the normalized `IntrospectionResult` —
|
|
@@ -35399,17 +36801,19 @@ async function resolveSpecDocument(specSource, deps = {}) {
|
|
|
35399
36801
|
* not dropping information silently.
|
|
35400
36802
|
*/
|
|
35401
36803
|
async function introspectConnector(def, deps = {}) {
|
|
35402
|
-
const
|
|
35403
|
-
if (def.protocol === "openapi") return appendExtraQueryParams(appendExtraOperations(
|
|
35404
|
-
const result = await introspectMcpTools({ listToolsResult: document });
|
|
36804
|
+
const result = await introspectBase(def, deps);
|
|
36805
|
+
if (def.protocol === "openapi") return appendExtraQueryParams(appendExtraOperations(result, def.extraOperations), def.extraQueryParams);
|
|
35405
36806
|
if (def.extraOperations === void 0 || def.extraOperations.length === 0) return result;
|
|
35406
36807
|
return {
|
|
35407
36808
|
...result,
|
|
35408
36809
|
warnings: [...result.warnings, `extraOperations: ignored — connector '${def.id}' speaks mcp, whose surface is tools, not operations`]
|
|
35409
36810
|
};
|
|
35410
36811
|
}
|
|
36812
|
+
var baseIntrospections;
|
|
35411
36813
|
var init_spec_introspection = __esmMin((() => {
|
|
35412
36814
|
init_src$4();
|
|
36815
|
+
init_spec_digest();
|
|
36816
|
+
baseIntrospections = /* @__PURE__ */ new Map();
|
|
35413
36817
|
}));
|
|
35414
36818
|
//#endregion
|
|
35415
36819
|
//#region ../../libs/runtime/connect-broker/src/lib/executor/spec-request.ts
|
|
@@ -35534,6 +36938,26 @@ function createCachingSpecFetch(options) {
|
|
|
35534
36938
|
fetch: async (url, headers, policy) => {
|
|
35535
36939
|
fromCache = false;
|
|
35536
36940
|
pending = null;
|
|
36941
|
+
const fromNetwork = () => policy !== void 0 ? fetch(url, headers, policy) : headers !== void 0 ? fetch(url, headers) : fetch(url);
|
|
36942
|
+
const digest = cache.digestOf ? await cache.digestOf(key).catch(() => null) : null;
|
|
36943
|
+
if (digest !== null) {
|
|
36944
|
+
fromCache = true;
|
|
36945
|
+
return {
|
|
36946
|
+
ok: true,
|
|
36947
|
+
status: 200,
|
|
36948
|
+
digest,
|
|
36949
|
+
text: async () => {
|
|
36950
|
+
const held = await cache.get(key).catch(() => null);
|
|
36951
|
+
if (held !== null) return held;
|
|
36952
|
+
const response = await fromNetwork();
|
|
36953
|
+
if (!response.ok) throw new Error(`the cached connector spec is gone and re-fetching it failed with status ${response.status}`);
|
|
36954
|
+
const body = await response.text();
|
|
36955
|
+
fromCache = false;
|
|
36956
|
+
pending = body;
|
|
36957
|
+
return body;
|
|
36958
|
+
}
|
|
36959
|
+
};
|
|
36960
|
+
}
|
|
35537
36961
|
const hit = await cache.get(key).catch(() => null);
|
|
35538
36962
|
if (hit !== null) {
|
|
35539
36963
|
fromCache = true;
|
|
@@ -35543,7 +36967,7 @@ function createCachingSpecFetch(options) {
|
|
|
35543
36967
|
text: async () => hit
|
|
35544
36968
|
};
|
|
35545
36969
|
}
|
|
35546
|
-
const response =
|
|
36970
|
+
const response = await fromNetwork();
|
|
35547
36971
|
if (!response.ok) return response;
|
|
35548
36972
|
const body = await response.text();
|
|
35549
36973
|
pending = body;
|
|
@@ -36312,6 +37736,70 @@ var init_body_error = __esmMin((() => {
|
|
|
36312
37736
|
init_src$20();
|
|
36313
37737
|
}));
|
|
36314
37738
|
//#endregion
|
|
37739
|
+
//#region ../../libs/runtime/connect-broker/src/lib/executor/form-encoding.ts
|
|
37740
|
+
/**
|
|
37741
|
+
* Encode an object as a form body. Returns the `&`-joined pairs, with no leading
|
|
37742
|
+
* `?` and no trailing separator; an object with nothing to send returns the
|
|
37743
|
+
* empty string.
|
|
37744
|
+
*
|
|
37745
|
+
* The value rules, each of which a provider can tell apart:
|
|
37746
|
+
*
|
|
37747
|
+
* - `null` and `undefined` are OMITTED, key and all. A form body has no null:
|
|
37748
|
+
* the wire can only carry text, so `x=null` would set the field to the
|
|
37749
|
+
* four-letter string. Omitting is the one encoding that means "not sent".
|
|
37750
|
+
* - the empty string is NOT omitted — `x=` is how a form body clears a field,
|
|
37751
|
+
* and a caller that wrote `''` asked for exactly that.
|
|
37752
|
+
* - an empty object or an empty array contributes NO pairs, because it has no
|
|
37753
|
+
* leaf to hang a key off. Same reading as `qs` and as Stripe's own client.
|
|
37754
|
+
* - booleans go out as `true` / `false`, numbers as their digits.
|
|
37755
|
+
*
|
|
37756
|
+
* @param body the arguments to send, already stripped of anything the broker
|
|
37757
|
+
* consumes (see `buildRequestBody`).
|
|
37758
|
+
*/
|
|
37759
|
+
function encodeFormBody(body) {
|
|
37760
|
+
const pairs = [];
|
|
37761
|
+
for (const [key, value] of Object.entries(body)) appendPairs(encodeURIComponent(key), value, pairs);
|
|
37762
|
+
return pairs.join("&");
|
|
37763
|
+
}
|
|
37764
|
+
/**
|
|
37765
|
+
* Walk one value into `pairs`, carrying the key prefix built so far. The prefix
|
|
37766
|
+
* arrives ALREADY ENCODED, which is why the brackets this function adds survive
|
|
37767
|
+
* literally: `encodeURIComponent` would turn `[` into `%5B`, and while every
|
|
37768
|
+
* provider decodes that back, a percent-escaped key is unreadable in a log and
|
|
37769
|
+
* in a spec assertion for no gain.
|
|
37770
|
+
*/
|
|
37771
|
+
function appendPairs(key, value, pairs) {
|
|
37772
|
+
if (value === null || value === void 0) return;
|
|
37773
|
+
if (Array.isArray(value)) {
|
|
37774
|
+
value.forEach((item, index) => appendPairs(`${key}[${index}]`, item, pairs));
|
|
37775
|
+
return;
|
|
37776
|
+
}
|
|
37777
|
+
if (isNestable(value)) {
|
|
37778
|
+
for (const [childKey, childValue] of Object.entries(value)) appendPairs(`${key}[${encodeURIComponent(childKey)}]`, childValue, pairs);
|
|
37779
|
+
return;
|
|
37780
|
+
}
|
|
37781
|
+
pairs.push(`${key}=${encodeURIComponent(String(value))}`);
|
|
37782
|
+
}
|
|
37783
|
+
/**
|
|
37784
|
+
* Is this value a structure to walk into, or a leaf to stringify?
|
|
37785
|
+
*
|
|
37786
|
+
* Only a PLAIN object counts. A `Date`, a `Map`, a class instance — anything
|
|
37787
|
+
* with its own prototype — has no enumerable own entries worth speaking of, so
|
|
37788
|
+
* walking it would emit NOTHING and the field would vanish from the request with
|
|
37789
|
+
* no error anywhere. Stringifying it instead sends `[object Map]`, which the
|
|
37790
|
+
* provider rejects with a 400 naming the field. A loud rejection beats a field
|
|
37791
|
+
* that silently never arrived.
|
|
37792
|
+
*
|
|
37793
|
+
* In practice every body reaching here was parsed from JSON and is plain, so
|
|
37794
|
+
* this branch is a guard against the day something else does.
|
|
37795
|
+
*/
|
|
37796
|
+
function isNestable(value) {
|
|
37797
|
+
if (typeof value !== "object" || value === null) return false;
|
|
37798
|
+
const proto = Object.getPrototypeOf(value);
|
|
37799
|
+
return proto === Object.prototype || proto === null;
|
|
37800
|
+
}
|
|
37801
|
+
var init_form_encoding = __esmMin((() => {}));
|
|
37802
|
+
//#endregion
|
|
36315
37803
|
//#region ../../libs/runtime/connect-broker/src/lib/executor/provider-error-classes.ts
|
|
36316
37804
|
/**
|
|
36317
37805
|
* Classify one failed provider response, or answer `null` for anything the
|
|
@@ -36737,25 +38225,50 @@ function brokerCodeForProviderStatus(status) {
|
|
|
36737
38225
|
}
|
|
36738
38226
|
}
|
|
36739
38227
|
/**
|
|
36740
|
-
* Build the
|
|
36741
|
-
*
|
|
38228
|
+
* Build the request body for an OpenAPI operation. When the operation names a
|
|
38229
|
+
* Swagger body argument containing an object, send that object directly.
|
|
36742
38230
|
* Otherwise, every payload key that is NOT a declared path/query/
|
|
36743
38231
|
* header param becomes the body. Sets `content-type` when a body is produced.
|
|
36744
38232
|
* Returns `undefined` for body-less operations (e.g. GET).
|
|
38233
|
+
*
|
|
38234
|
+
* JSON unless the operation declares `bodyEncoding: 'form'` — see
|
|
38235
|
+
* {@link serialiseRequestBody}. Both exits go through it, because a provider
|
|
38236
|
+
* that only speaks form-encoding speaks it on every endpoint and a Swagger
|
|
38237
|
+
* named body is not the exception.
|
|
38238
|
+
*
|
|
38239
|
+
* Exported for the specs: the encoding is a property of THIS function — which
|
|
38240
|
+
* bytes go out with which `content-type` — and asserting it here is a direct
|
|
38241
|
+
* reading rather than an inference from what a `fetch` stub was handed.
|
|
36745
38242
|
*/
|
|
36746
38243
|
function buildRequestBody(operation, args, headers) {
|
|
36747
38244
|
if (operation.requestBodySchema === void 0) return void 0;
|
|
36748
38245
|
const namedBody = operation.bodyArgName === void 0 ? void 0 : args[operation.bodyArgName];
|
|
36749
|
-
if (namedBody !== null && typeof namedBody === "object")
|
|
36750
|
-
headers["content-type"] = "application/json";
|
|
36751
|
-
return JSON.stringify(namedBody);
|
|
36752
|
-
}
|
|
38246
|
+
if (namedBody !== null && typeof namedBody === "object") return serialiseRequestBody(operation, namedBody, headers);
|
|
36753
38247
|
const consumed = /* @__PURE__ */ new Set();
|
|
36754
38248
|
for (const param of operation.parameters ?? []) if (param.in === "path" || param.in === "query" || param.in === "header") consumed.add(param.name);
|
|
36755
38249
|
for (const arg of brokerConsumedArgs(operation)) consumed.add(arg.name);
|
|
36756
38250
|
const body = {};
|
|
36757
38251
|
for (const [key, value] of Object.entries(args)) if (!consumed.has(key)) body[key] = value;
|
|
36758
38252
|
if (Object.keys(body).length === 0) return void 0;
|
|
38253
|
+
return serialiseRequestBody(operation, body, headers);
|
|
38254
|
+
}
|
|
38255
|
+
/**
|
|
38256
|
+
* Serialise one already-assembled body, and set the `content-type` it goes out
|
|
38257
|
+
* with. The two travel together on purpose: a form body sent under
|
|
38258
|
+
* `application/json` is a provider parsing `amount=2000` as malformed JSON, and
|
|
38259
|
+
* a JSON body under the form header is a provider reading `{"amount":2000}` as
|
|
38260
|
+
* ONE field whose name is that whole string — it answers 200 and stores
|
|
38261
|
+
* nonsense. Neither failure is readable from the operator's side, so the header
|
|
38262
|
+
* is never set anywhere but here.
|
|
38263
|
+
*
|
|
38264
|
+
* `bodyEncoding` is the operation's own declaration (`x-stackbone-body-encoding`
|
|
38265
|
+
* in the document); absent, this is the JSON path exactly as it has always been.
|
|
38266
|
+
*/
|
|
38267
|
+
function serialiseRequestBody(operation, body, headers) {
|
|
38268
|
+
if (operation.bodyEncoding === "form") {
|
|
38269
|
+
headers["content-type"] = "application/x-www-form-urlencoded";
|
|
38270
|
+
return encodeFormBody(body);
|
|
38271
|
+
}
|
|
36759
38272
|
headers["content-type"] = "application/json";
|
|
36760
38273
|
return JSON.stringify(body);
|
|
36761
38274
|
}
|
|
@@ -36981,6 +38494,7 @@ var init_connect_action_executor = __esmMin((() => {
|
|
|
36981
38494
|
init_connection_fence();
|
|
36982
38495
|
init_delete_guard();
|
|
36983
38496
|
init_body_error();
|
|
38497
|
+
init_form_encoding();
|
|
36984
38498
|
init_provider_error_classes();
|
|
36985
38499
|
init_spec_request();
|
|
36986
38500
|
init_operation_continuation();
|
|
@@ -37252,6 +38766,8 @@ var init_src$3 = __esmMin((() => {
|
|
|
37252
38766
|
init_redis_spec_cache();
|
|
37253
38767
|
init_in_memory_spec_cache();
|
|
37254
38768
|
init_spec_cache_key();
|
|
38769
|
+
init_digest_tracking_spec_cache();
|
|
38770
|
+
init_spec_digest();
|
|
37255
38771
|
init_cached_spec_fetch();
|
|
37256
38772
|
init_get_token();
|
|
37257
38773
|
init_src$20();
|
|
@@ -51885,6 +53401,10 @@ var DEV_CONNECT_ENCRYPTION_KEY = "dev-only-encryption-key";
|
|
|
51885
53401
|
* branch: a compose with no Redis and a single-process self-host box both land on
|
|
51886
53402
|
* the Map and must keep behaving exactly as they do today.
|
|
51887
53403
|
*
|
|
53404
|
+
* Both are wrapped in `trackSpecDigests` (PLA-1714): the box remembers the digest
|
|
53405
|
+
* of each spec it holds, so a trigger tick reuses the parsed document without
|
|
53406
|
+
* pulling it out of Redis — 42 MB per tick for Microsoft Graph.
|
|
53407
|
+
*
|
|
51888
53408
|
* `lazyConnect`, and it matters: a box whose Redis is unreachable must still boot
|
|
51889
53409
|
* and still serve actions. `createCachingSpecFetch` swallows every cache error, so
|
|
51890
53410
|
* a dead Redis makes introspection slower — the miss path this cache replaced —
|
|
@@ -51892,12 +53412,12 @@ var DEV_CONNECT_ENCRYPTION_KEY = "dev-only-encryption-key";
|
|
|
51892
53412
|
*/
|
|
51893
53413
|
var createBoxSpecCache = (redisUrl, deps = {}) => {
|
|
51894
53414
|
if (redisUrl === void 0 || redisUrl === "") return {
|
|
51895
|
-
cache: createInMemorySpecCache(),
|
|
53415
|
+
cache: trackSpecDigests(createInMemorySpecCache()),
|
|
51896
53416
|
close: async () => void 0
|
|
51897
53417
|
};
|
|
51898
53418
|
const redis = (deps.connect ?? defaultSpecCacheRedis)(redisUrl);
|
|
51899
53419
|
return {
|
|
51900
|
-
cache: new RedisSpecCache(redis),
|
|
53420
|
+
cache: trackSpecDigests(new RedisSpecCache(redis)),
|
|
51901
53421
|
close: async () => {
|
|
51902
53422
|
await redis.quit().catch(() => void 0);
|
|
51903
53423
|
}
|
|
@@ -52195,14 +53715,13 @@ var init_trigger_strategies = __esmMin((() => {
|
|
|
52195
53715
|
}));
|
|
52196
53716
|
//#endregion
|
|
52197
53717
|
//#region ../../libs/runtime/workspace-runtime/src/lib/connect-inbound/timestamp-poll-cursor.ts
|
|
52198
|
-
var
|
|
53718
|
+
var readStamp, createTimestampPoll;
|
|
52199
53719
|
var init_timestamp_poll_cursor = __esmMin((() => {
|
|
52200
53720
|
init_src$4();
|
|
52201
|
-
|
|
52202
|
-
|
|
52203
|
-
|
|
52204
|
-
|
|
52205
|
-
return parsed;
|
|
53721
|
+
readStamp = (value, unit) => {
|
|
53722
|
+
const stamp = parseStamp(value, unit);
|
|
53723
|
+
if (stamp === null) throw new Error(unit === void 0 ? "Invalid timestamp cursor: expected an ISO date stamp" : `Invalid timestamp cursor: expected a stamp in ${unit}`);
|
|
53724
|
+
return stamp;
|
|
52206
53725
|
};
|
|
52207
53726
|
createTimestampPoll = (spec) => {
|
|
52208
53727
|
const rawPoll = createDeclarativePoll(spec);
|
|
@@ -52212,8 +53731,10 @@ var init_timestamp_poll_cursor = __esmMin((() => {
|
|
|
52212
53731
|
if (cursor === null || typeof cursor !== "object" || !("kind" in cursor)) return rawPoll(ctx, cursor);
|
|
52213
53732
|
if (cursor.kind !== "timestamp-v1") throw new Error("Invalid timestamp cursor: unknown version");
|
|
52214
53733
|
const stored = cursor;
|
|
52215
|
-
const
|
|
52216
|
-
const
|
|
53734
|
+
const unit = cursorSpec.unit;
|
|
53735
|
+
const value = readStamp(stored.value, unit);
|
|
53736
|
+
const valueEpoch = value.epochMs;
|
|
53737
|
+
const floorEpoch = readStamp(stored.floor, unit).epochMs;
|
|
52217
53738
|
if (valueEpoch < floorEpoch) throw new Error("Invalid timestamp cursor: value precedes arming floor");
|
|
52218
53739
|
const lookbackMs = Math.min(cursorSpec.lookbackMs ?? 0, valueEpoch - floorEpoch);
|
|
52219
53740
|
const result = await createDeclarativePoll({
|
|
@@ -52222,8 +53743,8 @@ var init_timestamp_poll_cursor = __esmMin((() => {
|
|
|
52222
53743
|
...cursorSpec,
|
|
52223
53744
|
lookbackMs
|
|
52224
53745
|
}
|
|
52225
|
-
})(ctx, new Date(valueEpoch).toISOString());
|
|
52226
|
-
const next =
|
|
53746
|
+
})(ctx, unit === void 0 ? new Date(valueEpoch).toISOString() : formatStamp(valueEpoch, value.shape));
|
|
53747
|
+
const next = readStamp(result.nextCursor, unit).epochMs > valueEpoch ? result.nextCursor : stored.value;
|
|
52227
53748
|
return {
|
|
52228
53749
|
...result,
|
|
52229
53750
|
nextCursor: {
|
|
@@ -52386,7 +53907,11 @@ var init_trigger_capability = __esmMin((() => {
|
|
|
52386
53907
|
if (!descriptor || descriptor.poll.kind !== "declarative") return null;
|
|
52387
53908
|
if (descriptor.poll.cursor.kind !== "timestamp") return null;
|
|
52388
53909
|
if (SCOPE_TEMPLATE.test(descriptor.poll.list.operationId)) return null;
|
|
52389
|
-
const
|
|
53910
|
+
const unit = descriptor.poll.cursor.unit;
|
|
53911
|
+
const value = unit === void 0 ? armedAt.toISOString() : formatStamp(armedAt.getTime(), {
|
|
53912
|
+
unit,
|
|
53913
|
+
numeric: true
|
|
53914
|
+
});
|
|
52390
53915
|
return descriptor.poll.cursor.lookbackMs === void 0 ? value : {
|
|
52391
53916
|
kind: "timestamp-v1",
|
|
52392
53917
|
value,
|
|
@@ -53603,7 +55128,8 @@ var createConnectHandlers = (opts) => {
|
|
|
53603
55128
|
const specCache = {
|
|
53604
55129
|
get: async (key) => (await shared()).specCache.get(key),
|
|
53605
55130
|
set: async (key, value, ttlMs) => (await shared()).specCache.set(key, value, ttlMs),
|
|
53606
|
-
evict: async (key) => (await shared()).specCache.evict(key)
|
|
55131
|
+
evict: async (key) => (await shared()).specCache.evict(key),
|
|
55132
|
+
digestOf: async (key) => await (await shared()).specCache.digestOf?.(key) ?? null
|
|
53607
55133
|
};
|
|
53608
55134
|
const registry = opts.registry ?? createRegistryStore();
|
|
53609
55135
|
const agentWire = {
|
|
@@ -67414,7 +68940,8 @@ var init_workflow_http_schemas = __esmMin((() => {
|
|
|
67414
68940
|
displayName: z.string().optional(),
|
|
67415
68941
|
description: z.string().optional(),
|
|
67416
68942
|
example: z.string().optional(),
|
|
67417
|
-
required: z.boolean()
|
|
68943
|
+
required: z.boolean(),
|
|
68944
|
+
suggestions: z.array(z.string()).optional().openapi({ example: ["charge.succeeded", "charge.*"] })
|
|
67418
68945
|
})).openapi({ description: "The per-subscription values this trigger needs, declared by the connector. The register dialog renders a field each and the register call must carry every required one. Empty when the credential is the trigger's whole scope." }),
|
|
67419
68946
|
cursorColumn: z.object({ default: z.string() }).nullable().openapi({
|
|
67420
68947
|
description: "Present when the poll cursor advances on a column the subscription chooses; `default` pre-fills the field.",
|
|
@@ -71266,7 +72793,7 @@ var toWorkflowExport = (name) => `${toCamelCase(name)}Workflow`;
|
|
|
71266
72793
|
var index_ts_default = "import { tool } from \"@langchain/core/tools\";\nimport { z } from \"zod\";\nimport { defineDeepAgent } from \"@stackbone/sdk/deep\";\n\n// A deep agent is a single file: a name, a model, and optional tools /\n// sub-agents. `stackbone dev` discovers it by its folder\n// (`deep-agents/{{name}}/`), builds the LangGraph graph in-process (no server,\n// no port), and serves it over the standard OpenAI + Anthropic chat wire — point\n// any client at the emulator and select this agent with `\"model\": \"{{name}}\"`.\n\n// ITS INSTRUCTION IS NOT IN THIS FILE. Write it in Studio, under the prompt\n// named `{{name}}` (the agent's name is its default prompt key). Until you do,\n// the agent runs with an empty instruction — it answers, it just has no persona\n// yet. To put values into the text, declare them here and reference them in the\n// prompt with the double-brace syntax:\n//\n// instructions: { key: \"{{name}}\", variables: { tone: \"friendly\" } },\n\n// A tiny example tool. Delete it (and the `tools` field below) if your agent\n// needs none, or add more — each is a plain LangChain `tool(...)`.\nconst addNumbers = tool(\n async ({ a, b }: { a: number; b: number }) => String(a + b),\n {\n name: \"add_numbers\",\n description: \"Add two integers and return the sum.\",\n schema: z.object({ a: z.number().int(), b: z.number().int() }),\n },\n);\n\nexport default defineDeepAgent({\n // Must match the folder name: it is who this agent's prompts belong to.\n name: \"{{name}}\",\n model: \"openai/gpt-4o-mini\",\n tools: [addNumbers],\n});\n";
|
|
71267
72794
|
//#endregion
|
|
71268
72795
|
//#region src/services/scaffold/templates/shell/AGENTS.md.tmpl?raw
|
|
71269
|
-
var AGENTS_md_default = "# Agent instructions\n\nThis project is the **{{name}}** Stackbone workspace — a multi-agent project. It\nholds one or more in-process deep agents plus the durable workflows that\norchestrate them:\n\n- `deep-agents/<name>/` — one deep agent per folder (discovered by its `index.ts`).\n Its code is `deep-agents/<name>/index.ts`, which `export default defineDeepAgent({\n name, model, tools })`. Its INSTRUCTION is not in the code: it is written in\n Studio, under the prompt keyed by the agent's name, so changing the wording is an\n edit rather than a deploy. A deep agent is a plain JS module: no server, no port,\n no HTTP code — the emulator builds its LangGraph graph in-process and serves it\n over the standard OpenAI + Anthropic chat wire.\n- `workflows/<name>.workflow.ts` — one durable Workflow SDK workflow per file:\n a plain async function tagged `\"use workflow\"`, with each `\"use step\"` a\n durable checkpoint, and its contract declared as sibling `inputSchema` /\n `outputSchema` exports.\n\n`stackbone dev` discovers every deep agent and workflow by convention — there is no\nregistry file to hand-edit.\n\n## Always use the Stackbone skills\n\n`stackbone init` / `stackbone link` install
|
|
72796
|
+
var AGENTS_md_default = "# Agent instructions\n\nThis project is the **{{name}}** Stackbone workspace — a multi-agent project. It\nholds one or more in-process deep agents plus the durable workflows that\norchestrate them:\n\n- `deep-agents/<name>/` — one deep agent per folder (discovered by its `index.ts`).\n Its code is `deep-agents/<name>/index.ts`, which `export default defineDeepAgent({\n name, model, tools })`. Its INSTRUCTION is not in the code: it is written in\n Studio, under the prompt keyed by the agent's name, so changing the wording is an\n edit rather than a deploy. A deep agent is a plain JS module: no server, no port,\n no HTTP code — the emulator builds its LangGraph graph in-process and serves it\n over the standard OpenAI + Anthropic chat wire.\n- `workflows/<name>.workflow.ts` — one durable Workflow SDK workflow per file:\n a plain async function tagged `\"use workflow\"`, with each `\"use step\"` a\n durable checkpoint, and its contract declared as sibling `inputSchema` /\n `outputSchema` exports.\n\n`stackbone dev` discovers every deep agent and workflow by convention — there is no\nregistry file to hand-edit.\n\n## Always use the Stackbone skills\n\n`stackbone init` / `stackbone link` install seven skills into this workspace for\nevery coding agent you picked (Claude Code, Cursor, Windsurf, Codex, Cline, …), and\nadd the `stackbone-docs` MCP server to that agent's configuration.\n**Use them — do not improvise from memory.** The skills tell you how to work here:\nthe shapes, the order of operations, the rules. The SDK surface, the CLI flags and\nthe error catalog are read from the docs MCP server (`search_docs`, `get_doc`); each\nskill names the page to read before you touch a surface.\n\n| Skill | Use it when… |\n| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `stackbone` | **Editing agent or workflow code.** Writing a `defineDeepAgent`, a `*.workflow.ts`, DB CRUD, storage uploads, LLM calls, RAG, queues, HITL, secrets, or prompts. |\n| `stackbone-cli` | **Running or shipping the workspace.** Scaffolding (`init` / `add` / `link`), the local emulator (`dev`), compiling the deployable bundle (`build`), DB migrations (`db migrate`), reading metadata. |\n| `stackbone-debug` | **Triaging a failure.** An error, a 4xx/5xx, a stuck or timed-out run, a HITL run waiting on approval, a failed deploy, or a quota/billing response. Locate the cause, then fix it. |\n| `stackbone-coder` | **Starting a new piece, feature…** — interviews, then scaffolds it by description. |\n| `stackbone-evals` | **Measuring whether it works.** Turning a Playground exchange into an eval case, a case into a suite, a suite into a run, and comparing two variants of the same suite. |\n| `stackbone-guardrails` | **Putting limits on a running agent.** Blocking a turn, masking data on its way out, or holding it for a person. The rule is state on the box, so it needs no rebuild. |\n| `stackbone-integrations` | **Wiring the workspace to the outside.** Registering a connection, arming a trigger so a workflow starts on its own, and binding what a run outputs to a destination. |\n\n## Grow the workspace\n\n```sh\nstackbone add agent <name> # add a deep agent (alias of add deep-agent)\nstackbone add workflow <name> # add a durable workflow\nstackbone add workflow-agent <name> # add a deep agent + a workflow wired to call it\n```\n\n## Edit a deep agent\n\n- The agent lives in `deep-agents/<name>/index.ts` as `defineDeepAgent({ name,\n model, tools })` from `@stackbone/sdk/deep`. Its tools are plain LangChain\n `tool(...)` values.\n- Its behaviour and tone are its PROMPT, written in Studio and keyed by the agent's\n name. Write text there, never in the code. Pass values into it with\n `instructions: { variables: { ... } }`, and point it at another key with\n `instructions: { key: \"...\" }`. An agent whose prompt is still empty runs and\n answers — it just has no persona yet.\n- Pick the model with a bare id string (resolved through the deployment's **model\n provider**, i.e. whatever OpenAI-compatible endpoint it is pointed at) — e.g.\n `model: \"openai/gpt-4o-mini\"` — or pass a built LangChain model instance.\n- Follow the `stackbone` skill for every `client.*` / `stackbone.*` surface.\n\n## Edit a workflow\n\nA workflow is a bare async function — no class, no decorators:\n\n```ts\nexport const inputSchema = z.object({ message: z.string() });\nexport const outputSchema = z.object({ reply: z.string() });\n\nexport async function myWorkflow(input) {\n \"use workflow\";\n const reply = await askAgent(input.message); // a durable step\n return { reply };\n}\n```\n\nThe `inputSchema` / `outputSchema` exports next to the function ARE the contract.\nTag each durable checkpoint with `\"use step\"`.\n\n## Reaching a deep agent or a connector\n\n- To call a deep agent from a workflow step, use `callDeepAgent` from\n `@stackbone/sdk/workflow` inside a `\"use step\"` — it runs the agent IN-PROCESS\n (no HTTP, no HMAC):\n\n ```ts\n import { callDeepAgent } from '@stackbone/sdk/workflow';\n\n async function askAgent(message: string) {\n \"use step\";\n const { text } = await callDeepAgent('support', message);\n return text;\n }\n ```\n\n- To call a connector directly (typed when its types are generated), use the\n ambient handle:\n\n ```ts\n import { stackbone } from '@stackbone/sdk';\n await stackbone.connection('stub-mail').sendMail({ to, subject, body });\n ```\n\n## Run it locally\n\n```sh\npnpm install\nstackbone dev # runs the workspace against a local World + Studio\n```\n\n**The first run stops for setup.** With no model provider configured, `stackbone\ndev` brings up the local stack and then holds the workflows and agents back,\nprinting a Studio link to finish setup in the browser. Saving there starts the\nworkspace in place, with no restart. To skip that step entirely, export the\nendpoint before booting:\n\n```sh\nexport MODEL_PROVIDER_BASE_URL=https://openrouter.ai/api/v1\nexport MODEL_PROVIDER_API_KEY=sk-... # omit for a keyless local gateway\n```\n\nUse the `stackbone-cli` skill for the emulator, migrations, and building the\nbundle you deploy, and the `stackbone-debug` skill the moment something fails.\n\nThe full SDK and CLI reference lives at https://docs.stackbone.ai, and is\nserved over MCP at https://docs.stackbone.ai/mcp so you can search it from\nhere. Run `stackbone docs` for the connection details.\n";
|
|
71270
72797
|
//#endregion
|
|
71271
72798
|
//#region src/services/scaffold/templates/shell/CLAUDE.md.tmpl?raw
|
|
71272
72799
|
var CLAUDE_md_default = "# CLAUDE.md\n\nThis is the **{{name}}** Stackbone workspace. The full agent-development guidance\nlives in `AGENTS.md` — read it.\n\n@AGENTS.md\n\n## Claude Code note\n\nThe Stackbone skills (`stackbone`, `stackbone-cli`, `stackbone-debug`,\n`stackbone-coder`) are installed for Claude Code under `.claude/`, and the\n`stackbone-docs` MCP server is registered in `.mcp.json`. Invoke the skills\nthrough the Skill tool:\n\n- `stackbone` — write/edit deep agents (`deep-agents/<name>/index.ts`) and\n workflows (`workflows/<name>.workflow.ts`).\n- `stackbone-cli` — run the emulator (`stackbone dev`), migrate the DB, compile\n the deployable bundle (`stackbone build`).\n- `stackbone-debug` — triage any error or stuck run, then return to the skill\n above to apply the fix.\n- `stackbone-coder` — start a new agent or workflow from a one-line idea.\n\nEach skill says how to find the docs page to read before you touch a surface\n(`list_docs`, then `get_doc`); read it instead of guessing a flag or a signature.\n";
|
|
@@ -71281,7 +72808,7 @@ var gitignore_raw_default = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndi
|
|
|
71281
72808
|
var npmrc_raw_default = "# A hoisted (npm-like) node_modules layout so the emulator resolves ONE copy of\n# each externalized runtime dep (@stackbone/sdk, deepagents, @langchain/*) — a\n# second inlined copy would split the SDK's AsyncLocalStorage and empty per-run logs.\nnode-linker=hoisted\n";
|
|
71282
72809
|
//#endregion
|
|
71283
72810
|
//#region src/services/scaffold/templates/shell/package.json.tmpl?raw
|
|
71284
|
-
var package_json_default$1 = "{\n \"name\": \"{{name}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"description\": \"A Stackbone multi-agent workspace: N in-process deep agents + N Workflow SDK workflows.\",\n \"scripts\": {\n \"dev\": \"stackbone dev\",\n \"build\": \"stackbone build\"\n },\n \"dependencies\": {\n \"@langchain/core\": \"1.2.8\",\n \"@langchain/langgraph\": \"1.4.7\",\n \"@langchain/langgraph-checkpoint-postgres\": \"1.0.4\",\n \"@langchain/openai\": \"1.5.3\",\n \"@stackbone/sdk\": \"{{sdkVersion}}\",\n \"@workflow/world\": \"5.0.0-beta.11\",\n \"deepagents\": \"1.10.5\",\n \"workflow\": \"5.0.0-beta.19\",\n \"zod\": \"4.
|
|
72811
|
+
var package_json_default$1 = "{\n \"name\": \"{{name}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"description\": \"A Stackbone multi-agent workspace: N in-process deep agents + N Workflow SDK workflows.\",\n \"scripts\": {\n \"dev\": \"stackbone dev\",\n \"build\": \"stackbone build\"\n },\n \"dependencies\": {\n \"@langchain/core\": \"1.2.8\",\n \"@langchain/langgraph\": \"1.4.7\",\n \"@langchain/langgraph-checkpoint-postgres\": \"1.0.4\",\n \"@langchain/openai\": \"1.5.3\",\n \"@stackbone/sdk\": \"{{sdkVersion}}\",\n \"@workflow/world\": \"5.0.0-beta.11\",\n \"deepagents\": \"1.10.5\",\n \"workflow\": \"5.0.0-beta.19\",\n \"zod\": \"4.3.6\"\n },\n \"devDependencies\": {\n \"@types/node\": \"24.x\",\n \"@typescript/native-preview\": \"7.0.0-dev.20260523.1\"\n },\n \"packageManager\": \"pnpm@9.0.0\",\n \"workspaces\": [\"workflows\"],\n \"engines\": {\n \"node\": \"24.x\"\n }\n}\n";
|
|
71285
72812
|
//#endregion
|
|
71286
72813
|
//#region src/services/scaffold/templates/shell/pnpm-workspace.yaml?raw
|
|
71287
72814
|
var pnpm_workspace_default = "packages:\n - 'workflows'\n";
|
|
@@ -71290,7 +72817,7 @@ var pnpm_workspace_default = "packages:\n - 'workflows'\n";
|
|
|
71290
72817
|
var tsconfig_default$1 = "{\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"moduleResolution\": \"nodenext\",\n \"target\": \"esnext\",\n \"strict\": true,\n \"skipLibCheck\": true,\n \"noEmit\": true,\n \"types\": [\"node\"]\n }\n}\n";
|
|
71291
72818
|
//#endregion
|
|
71292
72819
|
//#region src/services/scaffold/templates/shell/workflows/package.json.tmpl?raw
|
|
71293
|
-
var package_json_default = "{\n \"name\": \"workflows\",\n \"version\": \"0.0.0\",\n \"type\": \"module\",\n \"private\": true,\n \"scripts\": {\n \"typecheck\": \"tsgo\"\n },\n \"dependencies\": {\n \"@stackbone/sdk\": \"{{sdkVersion}}\",\n \"workflow\": \"5.0.0-beta.19\",\n \"zod\": \"4.
|
|
72820
|
+
var package_json_default = "{\n \"name\": \"workflows\",\n \"version\": \"0.0.0\",\n \"type\": \"module\",\n \"private\": true,\n \"scripts\": {\n \"typecheck\": \"tsgo\"\n },\n \"dependencies\": {\n \"@stackbone/sdk\": \"{{sdkVersion}}\",\n \"workflow\": \"5.0.0-beta.19\",\n \"zod\": \"4.3.6\"\n },\n \"devDependencies\": {\n \"@types/node\": \"24.x\",\n \"@typescript/native-preview\": \"7.0.0-dev.20260523.1\"\n },\n \"engines\": {\n \"node\": \"24.x\"\n }\n}\n";
|
|
71294
72821
|
//#endregion
|
|
71295
72822
|
//#region src/services/scaffold/templates/shell/workflows/tsconfig.json?raw
|
|
71296
72823
|
var tsconfig_default = "{\n \"compilerOptions\": {\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"target\": \"esnext\",\n \"strict\": true,\n \"skipLibCheck\": true,\n \"noEmit\": true,\n \"types\": [\"node\"]\n },\n \"include\": [\"**/*.ts\", \"../.stackbone/**/*.d.ts\"]\n}\n";
|
|
@@ -71302,7 +72829,7 @@ var calls_workflow_ts_default = "import { z } from \"zod\";\nimport { streamDeep
|
|
|
71302
72829
|
var plain_workflow_ts_default = "import { z } from \"zod\";\n\n/**\n * Workflow \"{{name}}\" — a durable pipeline.\n *\n * Each `\"use step\"` is a durable checkpoint: kill the runtime mid-run and it\n * resumes from the last completed step instead of replaying everything.\n */\n\n// THE contract — sibling exports next to the bare workflow function. The build\n// harvests these (by convention) into the manifest + a live validator. The\n// parameter type is DERIVED from `inputSchema` and the RETURN type from\n// `outputSchema` (both `z.infer`), so neither side can drift from the contract\n// the build harvests. Without the return annotation the schema is published to\n// Studio and the generated types and never compared to what the body returns\n// (PLA-1272).\nexport const inputSchema = z.object({\n message: z.string().describe(\"The input to process\"),\n});\n\nexport const outputSchema = z.object({\n message: z.string(),\n processed: z.boolean(),\n});\n\ntype {{Name}}Input = z.infer<typeof inputSchema>;\n\nexport async function {{export}}(input: {{Name}}Input): Promise<z.infer<typeof outputSchema>> {\n \"use workflow\";\n\n const result = await processMessage(input); // step 1 — durable checkpoint\n return result;\n}\n\nasync function processMessage(input: {{Name}}Input) {\n \"use step\";\n // Replace this with the deterministic work of your step (DB write, HTTP call,\n // an LLM call, …). Returning a value records it as the step's checkpoint.\n return { message: input.message, processed: true };\n}\n";
|
|
71303
72830
|
//#endregion
|
|
71304
72831
|
//#region src/services/scaffold/template-loader.ts
|
|
71305
|
-
var SDK_TEMPLATE_VERSION = "0.
|
|
72832
|
+
var SDK_TEMPLATE_VERSION = "0.6.0";
|
|
71306
72833
|
var PREFIX = "./templates/";
|
|
71307
72834
|
var templates = new Map(Object.entries(/* @__PURE__ */ Object.assign({
|
|
71308
72835
|
"./templates/deep-agents/index.ts.tmpl": index_ts_default,
|
|
@@ -71358,7 +72885,10 @@ var DEEP_AGENT_GROUP = "deep-agents";
|
|
|
71358
72885
|
* resolvable from the workspace root so the host resolves ONE copy alongside the
|
|
71359
72886
|
* agent's `@langchain/*`. `zod` is usually already pinned by the workspace shell —
|
|
71360
72887
|
* {@link mergeMissingWorkspaceDeps} never overwrites an existing version, so it is
|
|
71361
|
-
* included only as a safety net for a hand-built workspace.
|
|
72888
|
+
* included only as a safety net for a hand-built workspace. Its value is not free:
|
|
72889
|
+
* the shell's `@workflow/world` peer-requires ONE exact `zod`, and npm refuses to
|
|
72890
|
+
* install a workspace that pins another (PLA-1653), so this entry must stay equal to
|
|
72891
|
+
* the two `package.json.tmpl` pins. `scaffold.spec.ts` fails if the three drift.
|
|
71362
72892
|
*/
|
|
71363
72893
|
var DEEP_AGENT_WORKSPACE_DEPS = Object.freeze({
|
|
71364
72894
|
"@langchain/core": "1.2.8",
|
|
@@ -71366,7 +72896,7 @@ var DEEP_AGENT_WORKSPACE_DEPS = Object.freeze({
|
|
|
71366
72896
|
"@langchain/langgraph-checkpoint-postgres": "1.0.4",
|
|
71367
72897
|
"@langchain/openai": "1.5.3",
|
|
71368
72898
|
deepagents: "1.10.5",
|
|
71369
|
-
zod: "4.
|
|
72899
|
+
zod: "4.3.6"
|
|
71370
72900
|
});
|
|
71371
72901
|
/**
|
|
71372
72902
|
* Generate the FilePlan for a deep agent named `name`: just `deep-agents/<name>/
|
|
@@ -75274,7 +76804,7 @@ var REDIS_IMAGE$1 = "redis:7-alpine";
|
|
|
75274
76804
|
* Same image and pin as `stackbone dev`'s compose, so the emulator and the
|
|
75275
76805
|
* deliverable run the identical backend.
|
|
75276
76806
|
*/
|
|
75277
|
-
var MINIO_IMAGE$1 = "minio/minio:RELEASE.2025-03-12T18-04-18Z";
|
|
76807
|
+
var MINIO_IMAGE$1 = "quay.io/minio/minio:RELEASE.2025-03-12T18-04-18Z";
|
|
75278
76808
|
/** Access key the bundled MinIO is created with. The secret is generated per package. */
|
|
75279
76809
|
var MINIO_ACCESS_KEY = "stackbone";
|
|
75280
76810
|
/** Bucket the init container creates, and the agent's `STACKBONE_S3_BUCKET`. */
|
|
@@ -78643,7 +80173,7 @@ var readStackboneConfig = async (cwd) => readJsonFile(stackboneConfigPath(cwd),
|
|
|
78643
80173
|
//#region src/dev/compose-template.ts
|
|
78644
80174
|
var POSTGRES_IMAGE = "pgvector/pgvector:pg17";
|
|
78645
80175
|
var REDIS_IMAGE = "redis:7-alpine";
|
|
78646
|
-
var MINIO_IMAGE = "minio/minio:RELEASE.2025-03-12T18-04-18Z";
|
|
80176
|
+
var MINIO_IMAGE = "quay.io/minio/minio:RELEASE.2025-03-12T18-04-18Z";
|
|
78647
80177
|
var PGVECTOR_INIT_SQL = `-- Generated by stackbone dev. Runs once on Postgres first boot via
|
|
78648
80178
|
-- /docker-entrypoint-initdb.d. The queue extension is gone (BullMQ over Redis
|
|
78649
80179
|
-- is the only job engine); only pgvector is enabled here.
|
|
@@ -84066,7 +85596,7 @@ var createInitCommand = (ctx) => defineCommand({
|
|
|
84066
85596
|
args: {
|
|
84067
85597
|
dir: {
|
|
84068
85598
|
type: "positional",
|
|
84069
|
-
description: "Target subdirectory.
|
|
85599
|
+
description: "Target subdirectory. Pass `.` to scaffold into the current directory; omitted, a new subdirectory is created, named after --name (or after the current directory).",
|
|
84070
85600
|
required: false
|
|
84071
85601
|
},
|
|
84072
85602
|
with: {
|
|
@@ -84311,6 +85841,7 @@ var assertBoxCanBeRegistered = (contract) => {
|
|
|
84311
85841
|
};
|
|
84312
85842
|
//#endregion
|
|
84313
85843
|
//#region src/commands/link/link.command.ts
|
|
85844
|
+
init_src$19();
|
|
84314
85845
|
var requireLinkField = (value, flag, description) => {
|
|
84315
85846
|
const trimmed = (value ?? "").trim();
|
|
84316
85847
|
if (trimmed.length === 0) throw new StackboneCliError({
|
|
@@ -84321,6 +85852,27 @@ var requireLinkField = (value, flag, description) => {
|
|
|
84321
85852
|
return trimmed;
|
|
84322
85853
|
};
|
|
84323
85854
|
/**
|
|
85855
|
+
* PLA-1583 — `link` refuses a plain `http://` box before it touches anything.
|
|
85856
|
+
*
|
|
85857
|
+
* The rule itself comes from `deploymentUrlSchema`, the very schema the control
|
|
85858
|
+
* plane validates the request body with, imported rather than restated: a rule
|
|
85859
|
+
* written twice is a rule the CLI and the API drift apart on, and the CLI is
|
|
85860
|
+
* the caller that used to pass `--url` straight through while only the browser
|
|
85861
|
+
* wizard refused one. What this adds is WHERE the refusal lands — here, before
|
|
85862
|
+
* the handshake dials the box, instead of as a 400 after the operator has
|
|
85863
|
+
* already watched `link` talk to it.
|
|
85864
|
+
*/
|
|
85865
|
+
var requireEncryptedUrl = (url) => {
|
|
85866
|
+
const checked = deploymentUrlSchema.safeParse(url);
|
|
85867
|
+
if (checked.success) return;
|
|
85868
|
+
const refusedScheme = checked.error.issues.find((issue) => issue.code === "custom");
|
|
85869
|
+
throw new StackboneCliError({
|
|
85870
|
+
code: "generic",
|
|
85871
|
+
message: `\`--url ${url}\` cannot be registered: ${refusedScheme?.message ?? checked.error.issues[0]?.message ?? "it is not an https:// address"}`,
|
|
85872
|
+
suggestion: refusedScheme ? "Terminate TLS at your own proxy and pass its https:// address. Callbacks from the control plane and calls from the browser reach the box over this URL; the HMAC secret signs them, it does not encrypt them." : "Pass the full address, scheme and host, like https://box.example.com — a bare host is not enough to know where to call."
|
|
85873
|
+
});
|
|
85874
|
+
};
|
|
85875
|
+
/**
|
|
84324
85876
|
* STA-440 — what `link` tells the operator about the box's browser-identity gate.
|
|
84325
85877
|
* `null` when the box verifies identity: there is nothing to say. The other two
|
|
84326
85878
|
* read differently on purpose — a box that reports it is not verifying is a
|
|
@@ -84423,6 +85975,7 @@ var runLink = async (ctx, args) => {
|
|
|
84423
85975
|
colors: true
|
|
84424
85976
|
}));
|
|
84425
85977
|
const url = requireLinkField(args.url, "url", "the public URL of the deployed agent");
|
|
85978
|
+
requireEncryptedUrl(url);
|
|
84426
85979
|
const secret = requireLinkField(args.secret, "secret", "the HMAC secret the deployed agent authenticates its callbacks with");
|
|
84427
85980
|
const tag = requireLinkField(args.tag, "tag", "the tag of the deep-agents image you deployed");
|
|
84428
85981
|
const agentsArg = readAgentsArg(args.agents);
|