@stacksjs/ts-cloud 0.7.109 → 0.7.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +938 -930
- package/dist/bin/dashboard-server.js +254 -246
- package/dist/{chunk-grx1rp09.js → chunk-19bs955w.js} +56 -16
- package/dist/{chunk-xrtwv3v2.js → chunk-d48vtdj4.js} +14 -1
- package/dist/{chunk-cktwk9hy.js → chunk-wmz8x3cy.js} +1 -1
- package/dist/deploy/index.js +2 -2
- package/dist/index.js +3 -3
- package/dist/protection/controls.d.ts +14 -0
- package/dist/protection/index.js +3 -1
- package/dist/spend/index.js +1 -1
- package/dist/spend/signals.d.ts +21 -0
- package/dist/ui/access-denied.html +2 -2
- package/dist/ui/account/automation.html +3 -3
- package/dist/ui/account/security.html +2 -2
- package/dist/ui/applications/compose.html +4 -4
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +4 -4
- package/dist/ui/data/services.html +4 -4
- package/dist/ui/data/volumes.html +4 -4
- package/dist/ui/index.html +3 -3
- package/dist/ui/infrastructure/topology.html +4 -4
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +4 -4
- package/dist/ui/operations/configuration.html +4 -4
- package/dist/ui/operations/jobs.html +4 -4
- package/dist/ui/operations/maintenance.html +4 -4
- package/dist/ui/operations/observability.html +4 -4
- package/dist/ui/operations/previews.html +4 -4
- package/dist/ui/operations/queue.html +4 -4
- package/dist/ui/operations/regions.html +3 -3
- package/dist/ui/operations/releases.html +4 -4
- package/dist/ui/operations/spend.html +4 -4
- package/dist/ui/operations/workloads.html +4 -4
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +3 -3
- package/dist/ui/server/activity.html +2 -2
- package/dist/ui/server/capacity.html +4 -4
- package/dist/ui/server/database.html +4 -4
- package/dist/ui/server/deployments.html +4 -4
- package/dist/ui/server/diagnostics.html +2 -2
- package/dist/ui/server/firewall.html +4 -4
- package/dist/ui/server/fleet.html +4 -4
- package/dist/ui/server/logs.html +3 -3
- package/dist/ui/server/metrics.html +4 -4
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +4 -4
- package/dist/ui/server/ssh-keys.html +4 -4
- package/dist/ui/server/team.html +3 -3
- package/dist/ui/server/terminal.html +2 -2
- package/dist/ui/serverless/alarms.html +4 -4
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/cost.html +2 -2
- package/dist/ui/serverless/data.html +4 -4
- package/dist/ui/serverless/firewall.html +2 -2
- package/dist/ui/serverless/functions.html +4 -4
- package/dist/ui/serverless/logs.html +3 -3
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +4 -4
- package/dist/ui/serverless/secrets.html +4 -4
- package/dist/ui/serverless/traces.html +4 -4
- package/dist/ui/serverless.html +4 -4
- package/package.json +3 -3
|
@@ -462,7 +462,7 @@ function aggregateDeltas(deltas) {
|
|
|
462
462
|
import { createHash as createHash3 } from "node:crypto";
|
|
463
463
|
|
|
464
464
|
// src/control-plane/migrations.ts
|
|
465
|
-
var CONTROL_PLANE_SCHEMA_VERSION =
|
|
465
|
+
var CONTROL_PLANE_SCHEMA_VERSION = 40;
|
|
466
466
|
var controlPlaneMigrations = [
|
|
467
467
|
{
|
|
468
468
|
version: 1,
|
|
@@ -2126,6 +2126,20 @@ var controlPlaneMigrations = [
|
|
|
2126
2126
|
DROP TABLE notification_channels;
|
|
2127
2127
|
ALTER TABLE notification_channels_new RENAME TO notification_channels;
|
|
2128
2128
|
`
|
|
2129
|
+
},
|
|
2130
|
+
{
|
|
2131
|
+
version: 40,
|
|
2132
|
+
name: "telemetry_kind_scope_index",
|
|
2133
|
+
sql: `
|
|
2134
|
+
-- Anomaly detection filters request records by (project, kind, time).
|
|
2135
|
+
-- The existing scope index leads with project_id and environment_id but
|
|
2136
|
+
-- never mentions kind, so every request series scanned each project's
|
|
2137
|
+
-- logs, metrics, and traces too and discarded them in memory. On a
|
|
2138
|
+
-- project emitting mostly logs that is several times the necessary work,
|
|
2139
|
+
-- once a minute, forever.
|
|
2140
|
+
CREATE INDEX telemetry_kind_scope_time_idx
|
|
2141
|
+
ON telemetry_records(project_id, kind, timestamp);
|
|
2142
|
+
`
|
|
2129
2143
|
}
|
|
2130
2144
|
];
|
|
2131
2145
|
// src/control-plane/store.ts
|
|
@@ -5016,21 +5030,23 @@ class AnomalyConfigStore {
|
|
|
5016
5030
|
}
|
|
5017
5031
|
// src/spend/signals.ts
|
|
5018
5032
|
var DETECTABLE_SIGNALS = [
|
|
5019
|
-
{ key: "cost", label: "Cost", source: "usage", gapPolicy: "zero", minSamples: 0, unit: "cents" },
|
|
5020
|
-
{ key: "edge.requests", label: "Edge requests", source: "usage", gapPolicy: "zero", minSamples: 0, unit: "requests" },
|
|
5021
|
-
{ key: "edge.egress_gb", label: "Edge egress", source: "usage", gapPolicy: "zero", minSamples: 0, unit: "GB" },
|
|
5033
|
+
{ key: "cost", routeAware: false, label: "Cost", source: "usage", gapPolicy: "zero", minSamples: 0, unit: "cents" },
|
|
5034
|
+
{ key: "edge.requests", routeAware: false, label: "Edge requests", source: "usage", gapPolicy: "zero", minSamples: 0, unit: "requests" },
|
|
5035
|
+
{ key: "edge.egress_gb", routeAware: false, label: "Edge egress", source: "usage", gapPolicy: "zero", minSamples: 0, unit: "GB" },
|
|
5022
5036
|
{
|
|
5023
5037
|
key: "function.invocations",
|
|
5038
|
+
routeAware: false,
|
|
5024
5039
|
label: "Function invocations",
|
|
5025
5040
|
source: "usage",
|
|
5026
5041
|
gapPolicy: "zero",
|
|
5027
5042
|
minSamples: 0,
|
|
5028
5043
|
unit: "invocations"
|
|
5029
5044
|
},
|
|
5030
|
-
{ key: "http.requests", label: "HTTP requests", source: "telemetry", gapPolicy: "zero", minSamples: 0, unit: "requests" },
|
|
5031
|
-
{ key: "http.errors", label: "HTTP 5xx", source: "telemetry", gapPolicy: "zero", minSamples: 0, unit: "responses" },
|
|
5045
|
+
{ key: "http.requests", routeAware: true, label: "HTTP requests", source: "telemetry", gapPolicy: "zero", minSamples: 0, unit: "requests" },
|
|
5046
|
+
{ key: "http.errors", routeAware: true, label: "HTTP 5xx", source: "telemetry", gapPolicy: "zero", minSamples: 0, unit: "responses" },
|
|
5032
5047
|
{
|
|
5033
5048
|
key: "http.error_rate",
|
|
5049
|
+
routeAware: true,
|
|
5034
5050
|
label: "HTTP error rate",
|
|
5035
5051
|
source: "telemetry",
|
|
5036
5052
|
gapPolicy: "gap",
|
|
@@ -5040,6 +5056,7 @@ var DETECTABLE_SIGNALS = [
|
|
|
5040
5056
|
},
|
|
5041
5057
|
{
|
|
5042
5058
|
key: "http.client_error_rate",
|
|
5059
|
+
routeAware: true,
|
|
5043
5060
|
label: "HTTP 4xx rate",
|
|
5044
5061
|
source: "telemetry",
|
|
5045
5062
|
gapPolicy: "gap",
|
|
@@ -5049,6 +5066,7 @@ var DETECTABLE_SIGNALS = [
|
|
|
5049
5066
|
},
|
|
5050
5067
|
{
|
|
5051
5068
|
key: "http.latency_p95",
|
|
5069
|
+
routeAware: true,
|
|
5052
5070
|
label: "Latency p95",
|
|
5053
5071
|
source: "telemetry",
|
|
5054
5072
|
gapPolicy: "gap",
|
|
@@ -5093,6 +5111,7 @@ function assemble(definition, window, values) {
|
|
|
5093
5111
|
class SignalSource {
|
|
5094
5112
|
controlPlane;
|
|
5095
5113
|
spend;
|
|
5114
|
+
counterCache = new Map;
|
|
5096
5115
|
constructor(controlPlane, spend) {
|
|
5097
5116
|
this.controlPlane = controlPlane;
|
|
5098
5117
|
this.spend = spend;
|
|
@@ -5117,10 +5136,8 @@ class SignalSource {
|
|
|
5117
5136
|
});
|
|
5118
5137
|
return assemble(definition, window, values);
|
|
5119
5138
|
}
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
return { signal: definition.key, points: [], populated: 0, suppressed: 0, unit: definition.unit };
|
|
5123
|
-
const clauses = ["kind = 'request'", "project_id = ?", "timestamp >= ?", "timestamp < ?"];
|
|
5139
|
+
requestFilter(scope, window) {
|
|
5140
|
+
const clauses = ["project_id = ?", "kind = 'request'", "timestamp >= ?", "timestamp < ?"];
|
|
5124
5141
|
const bindings = [scope.projectId, window.from, window.to];
|
|
5125
5142
|
if (scope.environmentId) {
|
|
5126
5143
|
clauses.push("environment_id = ?");
|
|
@@ -5130,16 +5147,35 @@ class SignalSource {
|
|
|
5130
5147
|
clauses.push("path_template = ?");
|
|
5131
5148
|
bindings.push(scope.route);
|
|
5132
5149
|
}
|
|
5150
|
+
return { clauses, bindings };
|
|
5151
|
+
}
|
|
5152
|
+
counters(scope, window) {
|
|
5153
|
+
const key = [scope.projectId, scope.environmentId ?? "", scope.route ?? "", window.from, window.to].join("\x00");
|
|
5154
|
+
const cached = this.counterCache.get(key);
|
|
5155
|
+
if (cached)
|
|
5156
|
+
return cached;
|
|
5157
|
+
const { clauses, bindings } = this.requestFilter(scope, window);
|
|
5133
5158
|
const rows = this.query(`SELECT
|
|
5134
5159
|
CAST(strftime('%s', timestamp) AS INTEGER) / 3600 AS bucket,
|
|
5135
5160
|
COUNT(*) AS total,
|
|
5136
5161
|
SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END) AS server_errors,
|
|
5137
|
-
SUM(CASE WHEN status_code >= 400 AND status_code < 500 THEN 1 ELSE 0 END) AS client_errors
|
|
5138
|
-
SUM(CASE WHEN duration_ms IS NOT NULL THEN 1 ELSE 0 END) AS timed
|
|
5162
|
+
SUM(CASE WHEN status_code >= 400 AND status_code < 500 THEN 1 ELSE 0 END) AS client_errors
|
|
5139
5163
|
FROM telemetry_records
|
|
5140
5164
|
WHERE ${clauses.join(" AND ")}
|
|
5141
5165
|
GROUP BY bucket
|
|
5142
5166
|
ORDER BY bucket`, bindings);
|
|
5167
|
+
this.counterCache.set(key, rows);
|
|
5168
|
+
return rows;
|
|
5169
|
+
}
|
|
5170
|
+
resetCache() {
|
|
5171
|
+
this.counterCache.clear();
|
|
5172
|
+
}
|
|
5173
|
+
telemetrySeries(definition, scope, window) {
|
|
5174
|
+
if (!scope.projectId)
|
|
5175
|
+
return { signal: definition.key, points: [], populated: 0, suppressed: 0, unit: definition.unit };
|
|
5176
|
+
if (definition.key === "http.latency_p95")
|
|
5177
|
+
return this.latencySeries(definition, scope, window);
|
|
5178
|
+
const rows = this.counters(scope, window);
|
|
5143
5179
|
const values = new Map;
|
|
5144
5180
|
for (const row of rows) {
|
|
5145
5181
|
const bucket = Number(row.bucket) * HOUR;
|
|
@@ -5153,11 +5189,10 @@ class SignalSource {
|
|
|
5153
5189
|
else if (definition.key === "http.client_error_rate")
|
|
5154
5190
|
values.set(bucket, { value: total > 0 ? Number(row.client_errors) / total : 0, samples: total });
|
|
5155
5191
|
}
|
|
5156
|
-
if (definition.key === "http.latency_p95")
|
|
5157
|
-
return this.latencySeries(definition, clauses, bindings, window);
|
|
5158
5192
|
return assemble(definition, window, values);
|
|
5159
5193
|
}
|
|
5160
|
-
latencySeries(definition,
|
|
5194
|
+
latencySeries(definition, scope, window) {
|
|
5195
|
+
const { clauses, bindings } = this.requestFilter(scope, window);
|
|
5161
5196
|
const rows = this.query(`WITH ranked AS (
|
|
5162
5197
|
SELECT
|
|
5163
5198
|
CAST(strftime('%s', timestamp) AS INTEGER) / 3600 AS bucket,
|
|
@@ -5415,18 +5450,23 @@ class SpendService {
|
|
|
5415
5450
|
};
|
|
5416
5451
|
const wanted = options.signals ?? DETECTABLE_SIGNALS.map((signal) => signal.key);
|
|
5417
5452
|
const found = [];
|
|
5453
|
+
let routes;
|
|
5418
5454
|
for (const signal of wanted) {
|
|
5419
5455
|
const hours = overrides.seasonLength ? Math.max(24 * 14, overrides.seasonLength * ((overrides.minHistory ?? 3) + 1)) : lookbackHoursForSignal(signal);
|
|
5420
5456
|
const window = { from: new Date(new Date(to).getTime() - hours * 3600000).toISOString(), to };
|
|
5421
5457
|
found.push(...this.detectSignal(signal, scope, window, overrides));
|
|
5422
5458
|
if (!options.routes)
|
|
5423
5459
|
continue;
|
|
5424
|
-
|
|
5460
|
+
if (!signalDefinition(signal)?.routeAware)
|
|
5461
|
+
continue;
|
|
5462
|
+
routes ??= this.signals.busiestRoutes(scope, window);
|
|
5463
|
+
for (const route of routes) {
|
|
5425
5464
|
if (this.anomalyConfigs.isSilenced(scope, { signal, route }))
|
|
5426
5465
|
continue;
|
|
5427
5466
|
found.push(...this.detectSignal(signal, { ...scope, route }, window, overrides, route));
|
|
5428
5467
|
}
|
|
5429
5468
|
}
|
|
5469
|
+
this.signals.resetCache();
|
|
5430
5470
|
return found;
|
|
5431
5471
|
}
|
|
5432
5472
|
detectSignal(signal, scope, window, overrides, route) {
|
|
@@ -417,4 +417,17 @@ function applyControlsToDdos(config, controls) {
|
|
|
417
417
|
}
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
|
-
|
|
420
|
+
function mergeControlsIntoConfig(config, controls) {
|
|
421
|
+
const compute = config?.infrastructure?.compute;
|
|
422
|
+
if (!compute || compute.ddos === false)
|
|
423
|
+
return config;
|
|
424
|
+
const base = typeof compute.ddos === "object" && compute.ddos !== null ? compute.ddos : {};
|
|
425
|
+
return {
|
|
426
|
+
...config,
|
|
427
|
+
infrastructure: {
|
|
428
|
+
...config.infrastructure,
|
|
429
|
+
compute: { ...compute, ddos: applyControlsToDdos(base, controls) }
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
export { RATE_LIMIT_KEY_SEPARATOR, globMatches, ruleMatches, rateLimitKey, RateLimiter, rateLimitHeaders, defaultRateLimitRules, PROTECTION_SETTING, MAX_CONTROL_HOURS, DEFAULT_ATTACK_MODE_HOURS, ProtectionControlStore, describePosture, applyControlsToRateLimits, applyControlsToDdos, mergeControlsIntoConfig };
|
package/dist/deploy/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
synchronizeDashboardUsers,
|
|
37
37
|
trackDashboardOperation,
|
|
38
38
|
verifyStaticApiOrigin
|
|
39
|
-
} from "../chunk-
|
|
39
|
+
} from "../chunk-wmz8x3cy.js";
|
|
40
40
|
import {
|
|
41
41
|
deleteStaticSite,
|
|
42
42
|
deployStaticSite,
|
|
@@ -83,7 +83,7 @@ import"../chunk-7m60qnc8.js";
|
|
|
83
83
|
import"../chunk-4cjrg98a.js";
|
|
84
84
|
import"../chunk-wj3s95p9.js";
|
|
85
85
|
import"../chunk-zqtpg06c.js";
|
|
86
|
-
import"../chunk-
|
|
86
|
+
import"../chunk-19bs955w.js";
|
|
87
87
|
import"../chunk-va2yd85b.js";
|
|
88
88
|
import"../chunk-aa2dkn7b.js";
|
|
89
89
|
import"../chunk-t2s3gdq6.js";
|
package/dist/index.js
CHANGED
|
@@ -208,7 +208,7 @@ import {
|
|
|
208
208
|
verifyUpdateSignature,
|
|
209
209
|
webhookEndpoint,
|
|
210
210
|
zeroCapacity
|
|
211
|
-
} from "./chunk-
|
|
211
|
+
} from "./chunk-wmz8x3cy.js";
|
|
212
212
|
import {
|
|
213
213
|
deleteStaticSite,
|
|
214
214
|
deployStaticSite,
|
|
@@ -511,7 +511,7 @@ import {
|
|
|
511
511
|
zeroFill,
|
|
512
512
|
zoneOffsetMs,
|
|
513
513
|
zonedParts
|
|
514
|
-
} from "./chunk-
|
|
514
|
+
} from "./chunk-19bs955w.js";
|
|
515
515
|
import {
|
|
516
516
|
DEFAULT_ATTACK_MODE_HOURS,
|
|
517
517
|
MAX_CONTROL_HOURS,
|
|
@@ -526,7 +526,7 @@ import {
|
|
|
526
526
|
rateLimitHeaders,
|
|
527
527
|
rateLimitKey,
|
|
528
528
|
ruleMatches
|
|
529
|
-
} from "./chunk-
|
|
529
|
+
} from "./chunk-d48vtdj4.js";
|
|
530
530
|
import {
|
|
531
531
|
escapeSecLang,
|
|
532
532
|
globToRegex,
|
|
@@ -111,3 +111,17 @@ export declare function describePosture(controls: ReturnType<ProtectionControlSt
|
|
|
111
111
|
export declare function applyControlsToRateLimits(rules: readonly RateLimitRule[], controls: ReturnType<ProtectionControlStore['current']>): RateLimitRule[];
|
|
112
112
|
/** Apply the controls to a firewall config before it is rendered. */
|
|
113
113
|
export declare function applyControlsToDdos(config: DdosConfig, controls: ReturnType<ProtectionControlStore['current']>): DdosConfig;
|
|
114
|
+
/**
|
|
115
|
+
* Fold live operator controls into a cloud config before it reaches a driver.
|
|
116
|
+
*
|
|
117
|
+
* The provisioning path takes a `CloudConfig` and nothing else, deliberately:
|
|
118
|
+
* it also builds golden images, and baking one operator's live blocklist into a
|
|
119
|
+
* reusable image would be wrong. So the merge happens here, at the deploy call
|
|
120
|
+
* site that has a control plane, rather than inside the builder.
|
|
121
|
+
*
|
|
122
|
+
* Without this, `cloud protect:block` writes a rule that the next deploy
|
|
123
|
+
* silently renders without - the control appears to work and does nothing.
|
|
124
|
+
*
|
|
125
|
+
* Returns a shallow copy; the caller's config is not mutated.
|
|
126
|
+
*/
|
|
127
|
+
export declare function mergeControlsIntoConfig<T extends Record<string, any>>(config: T, controls: ReturnType<ProtectionControlStore['current']>): T;
|
package/dist/protection/index.js
CHANGED
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
defaultRateLimitRules,
|
|
11
11
|
describePosture,
|
|
12
12
|
globMatches,
|
|
13
|
+
mergeControlsIntoConfig,
|
|
13
14
|
rateLimitHeaders,
|
|
14
15
|
rateLimitKey,
|
|
15
16
|
ruleMatches
|
|
16
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-d48vtdj4.js";
|
|
17
18
|
import {
|
|
18
19
|
escapeSecLang,
|
|
19
20
|
globToRegex,
|
|
@@ -60,6 +61,7 @@ export {
|
|
|
60
61
|
propagationHeaders,
|
|
61
62
|
planMitigation,
|
|
62
63
|
parseChain,
|
|
64
|
+
mergeControlsIntoConfig,
|
|
63
65
|
isValidCidr,
|
|
64
66
|
isSafePattern,
|
|
65
67
|
inspectInvocation,
|
package/dist/spend/index.js
CHANGED
package/dist/spend/signals.d.ts
CHANGED
|
@@ -18,6 +18,15 @@ export interface SignalDefinition {
|
|
|
18
18
|
unit: string;
|
|
19
19
|
/** True when the signal is a ratio in 0-1. */
|
|
20
20
|
ratio?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* True when the signal can be narrowed to a route.
|
|
23
|
+
*
|
|
24
|
+
* Usage rollups have no route dimension: they are priced per meter, not per
|
|
25
|
+
* path. Running route detection over them produces a series that is actually
|
|
26
|
+
* project-wide and records it under a route's name, which is worse than
|
|
27
|
+
* useless - it is a confidently mislabelled anomaly.
|
|
28
|
+
*/
|
|
29
|
+
routeAware: boolean;
|
|
21
30
|
}
|
|
22
31
|
/**
|
|
23
32
|
* The signals detection can run on.
|
|
@@ -53,10 +62,22 @@ export interface SignalSeries {
|
|
|
53
62
|
export declare class SignalSource {
|
|
54
63
|
private readonly controlPlane;
|
|
55
64
|
private readonly spend;
|
|
65
|
+
private readonly counterCache;
|
|
56
66
|
constructor(controlPlane: ControlPlaneStore, spend: SpendStore);
|
|
57
67
|
private query;
|
|
58
68
|
/** Usage-backed series: cost, or one meter's quantity. */
|
|
59
69
|
private usageSeries;
|
|
70
|
+
private requestFilter;
|
|
71
|
+
/**
|
|
72
|
+
* Per-bucket request counters for a scope and window.
|
|
73
|
+
*
|
|
74
|
+
* Memoized because four signals derive from the same numbers. Computing them
|
|
75
|
+
* separately meant four full passes over the request history every cycle, and
|
|
76
|
+
* with per-route detection that multiplied by the route count.
|
|
77
|
+
*/
|
|
78
|
+
private counters;
|
|
79
|
+
/** Drop the memo so a long-lived source re-reads on the next cycle. */
|
|
80
|
+
resetCache(): void;
|
|
60
81
|
/**
|
|
61
82
|
* Telemetry-backed series, aggregated in SQL.
|
|
62
83
|
*
|