@blokjs/runner 0.6.20 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Blok.d.ts +2 -0
- package/dist/Blok.js +42 -110
- package/dist/Blok.js.map +1 -1
- package/dist/DefaultLogger.d.ts +13 -0
- package/dist/DefaultLogger.js +25 -0
- package/dist/DefaultLogger.js.map +1 -1
- package/dist/RunnerSteps.d.ts +23 -0
- package/dist/RunnerSteps.js +128 -87
- package/dist/RunnerSteps.js.map +1 -1
- package/dist/SubworkflowNode.js +19 -0
- package/dist/SubworkflowNode.js.map +1 -1
- package/dist/TriggerBase.d.ts +12 -0
- package/dist/TriggerBase.js +216 -181
- package/dist/TriggerBase.js.map +1 -1
- package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +9 -0
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js +76 -6
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
- package/dist/index.d.ts +4 -39
- package/dist/index.js +7 -32
- package/dist/index.js.map +1 -1
- package/dist/monitoring/JanitorMetrics.d.ts +3 -0
- package/dist/monitoring/JanitorMetrics.js +11 -0
- package/dist/monitoring/JanitorMetrics.js.map +1 -1
- package/dist/monitoring/ProcessErrorMetrics.d.ts +32 -0
- package/dist/monitoring/ProcessErrorMetrics.js +43 -0
- package/dist/monitoring/ProcessErrorMetrics.js.map +1 -0
- package/dist/monitoring/PrometheusMetricsBridge.d.ts +7 -0
- package/dist/monitoring/PrometheusMetricsBridge.js +8 -2
- package/dist/monitoring/PrometheusMetricsBridge.js.map +1 -1
- package/dist/monitoring/SubworkflowMetrics.d.ts +25 -0
- package/dist/monitoring/SubworkflowMetrics.js +38 -0
- package/dist/monitoring/SubworkflowMetrics.js.map +1 -0
- package/dist/observability/ErrorSink.d.ts +23 -0
- package/dist/observability/ErrorSink.js +32 -0
- package/dist/observability/ErrorSink.js.map +1 -0
- package/dist/observability/SentryIntegration.d.ts +9 -0
- package/dist/observability/SentryIntegration.js +31 -0
- package/dist/observability/SentryIntegration.js.map +1 -0
- package/dist/scheduling/DebounceCoordinator.d.ts +7 -53
- package/dist/scheduling/DebounceCoordinator.js +8 -207
- package/dist/scheduling/DebounceCoordinator.js.map +1 -1
- package/dist/tracing/InMemoryRunStore.d.ts +5 -1
- package/dist/tracing/InMemoryRunStore.js +14 -0
- package/dist/tracing/InMemoryRunStore.js.map +1 -1
- package/dist/tracing/Janitor.js +3 -0
- package/dist/tracing/Janitor.js.map +1 -1
- package/dist/tracing/PostgresRunStore.d.ts +4 -1
- package/dist/tracing/PostgresRunStore.js +73 -3
- package/dist/tracing/PostgresRunStore.js.map +1 -1
- package/dist/tracing/RunStore.d.ts +17 -1
- package/dist/tracing/RunTracker.d.ts +13 -34
- package/dist/tracing/RunTracker.js +62 -32
- package/dist/tracing/RunTracker.js.map +1 -1
- package/dist/tracing/SqliteRunStore.d.ts +4 -1
- package/dist/tracing/SqliteRunStore.js +60 -0
- package/dist/tracing/SqliteRunStore.js.map +1 -1
- package/dist/tracing/TraceRouter.d.ts +13 -0
- package/dist/tracing/TraceRouter.js +43 -11
- package/dist/tracing/TraceRouter.js.map +1 -1
- package/dist/tracing/TracingLogger.js +22 -0
- package/dist/tracing/TracingLogger.js.map +1 -1
- package/dist/tracing/createStore.js +51 -22
- package/dist/tracing/createStore.js.map +1 -1
- package/dist/tracing/types.d.ts +22 -0
- package/dist/types/GlobalOptions.d.ts +5 -7
- package/dist/workflow/WorkflowNormalizer.js +63 -0
- package/dist/workflow/WorkflowNormalizer.js.map +1 -1
- package/package.json +7 -4
- package/dist/cache/NodeResultCache.d.ts +0 -286
- package/dist/cache/NodeResultCache.js +0 -506
- package/dist/cache/NodeResultCache.js.map +0 -1
- package/dist/cache/index.d.ts +0 -1
- package/dist/cache/index.js +0 -2
- package/dist/cache/index.js.map +0 -1
- package/dist/concurrency/ConcurrencyBackend.d.ts +0 -61
- package/dist/concurrency/ConcurrencyBackend.js +0 -20
- package/dist/concurrency/ConcurrencyBackend.js.map +0 -1
- package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +0 -64
- package/dist/concurrency/NatsKvConcurrencyBackend.js +0 -310
- package/dist/concurrency/NatsKvConcurrencyBackend.js.map +0 -1
- package/dist/concurrency/RedisConcurrencyBackend.d.ts +0 -64
- package/dist/concurrency/RedisConcurrencyBackend.js +0 -374
- package/dist/concurrency/RedisConcurrencyBackend.js.map +0 -1
- package/dist/concurrency/createConcurrencyBackend.d.ts +0 -24
- package/dist/concurrency/createConcurrencyBackend.js +0 -38
- package/dist/concurrency/createConcurrencyBackend.js.map +0 -1
- package/dist/graphql/GraphQLSchemaGenerator.d.ts +0 -129
- package/dist/graphql/GraphQLSchemaGenerator.js +0 -425
- package/dist/graphql/GraphQLSchemaGenerator.js.map +0 -1
- package/dist/integrations/APMIntegration.d.ts +0 -141
- package/dist/integrations/APMIntegration.js +0 -212
- package/dist/integrations/APMIntegration.js.map +0 -1
- package/dist/integrations/AzureMonitorIntegration.d.ts +0 -118
- package/dist/integrations/AzureMonitorIntegration.js +0 -254
- package/dist/integrations/AzureMonitorIntegration.js.map +0 -1
- package/dist/integrations/CloudWatchIntegration.d.ts +0 -135
- package/dist/integrations/CloudWatchIntegration.js +0 -293
- package/dist/integrations/CloudWatchIntegration.js.map +0 -1
- package/dist/integrations/SentryIntegration.d.ts +0 -153
- package/dist/integrations/SentryIntegration.js +0 -200
- package/dist/integrations/SentryIntegration.js.map +0 -1
- package/dist/integrations/index.d.ts +0 -19
- package/dist/integrations/index.js +0 -16
- package/dist/integrations/index.js.map +0 -1
- package/dist/marketplace/RuntimeAutoScaler.d.ts +0 -148
- package/dist/marketplace/RuntimeAutoScaler.js +0 -366
- package/dist/marketplace/RuntimeAutoScaler.js.map +0 -1
- package/dist/marketplace/RuntimeCatalog.d.ts +0 -180
- package/dist/marketplace/RuntimeCatalog.js +0 -339
- package/dist/marketplace/RuntimeCatalog.js.map +0 -1
- package/dist/marketplace/RuntimeDiscovery.d.ts +0 -86
- package/dist/marketplace/RuntimeDiscovery.js +0 -231
- package/dist/marketplace/RuntimeDiscovery.js.map +0 -1
- package/dist/marketplace/RuntimeHealthMonitor.d.ts +0 -100
- package/dist/marketplace/RuntimeHealthMonitor.js +0 -241
- package/dist/marketplace/RuntimeHealthMonitor.js.map +0 -1
- package/dist/marketplace/RuntimeMetricsDashboard.d.ts +0 -113
- package/dist/marketplace/RuntimeMetricsDashboard.js +0 -293
- package/dist/marketplace/RuntimeMetricsDashboard.js.map +0 -1
- package/dist/openapi/OpenAPIGenerator.d.ts +0 -192
- package/dist/openapi/OpenAPIGenerator.js +0 -378
- package/dist/openapi/OpenAPIGenerator.js.map +0 -1
- package/dist/openapi/index.d.ts +0 -20
- package/dist/openapi/index.js +0 -20
- package/dist/openapi/index.js.map +0 -1
- package/dist/scheduling/DebounceBackend.d.ts +0 -108
- package/dist/scheduling/DebounceBackend.js +0 -23
- package/dist/scheduling/DebounceBackend.js.map +0 -1
- package/dist/scheduling/NatsKvDebounceBackend.d.ts +0 -53
- package/dist/scheduling/NatsKvDebounceBackend.js +0 -334
- package/dist/scheduling/NatsKvDebounceBackend.js.map +0 -1
- package/dist/scheduling/RedisDebounceBackend.d.ts +0 -49
- package/dist/scheduling/RedisDebounceBackend.js +0 -356
- package/dist/scheduling/RedisDebounceBackend.js.map +0 -1
- package/dist/scheduling/createDebounceBackend.d.ts +0 -25
- package/dist/scheduling/createDebounceBackend.js +0 -39
- package/dist/scheduling/createDebounceBackend.js.map +0 -1
- package/dist/security/ABAC.d.ts +0 -224
- package/dist/security/ABAC.js +0 -380
- package/dist/security/ABAC.js.map +0 -1
- package/dist/security/AuditLogger.d.ts +0 -242
- package/dist/security/AuditLogger.js +0 -317
- package/dist/security/AuditLogger.js.map +0 -1
- package/dist/security/AuthMiddleware.d.ts +0 -162
- package/dist/security/AuthMiddleware.js +0 -289
- package/dist/security/AuthMiddleware.js.map +0 -1
- package/dist/security/EncryptionAtRest.d.ts +0 -206
- package/dist/security/EncryptionAtRest.js +0 -236
- package/dist/security/EncryptionAtRest.js.map +0 -1
- package/dist/security/OAuthProvider.d.ts +0 -334
- package/dist/security/OAuthProvider.js +0 -719
- package/dist/security/OAuthProvider.js.map +0 -1
- package/dist/security/PIIDetector.d.ts +0 -233
- package/dist/security/PIIDetector.js +0 -354
- package/dist/security/PIIDetector.js.map +0 -1
- package/dist/security/RBAC.d.ts +0 -143
- package/dist/security/RBAC.js +0 -285
- package/dist/security/RBAC.js.map +0 -1
- package/dist/security/SecretManager.d.ts +0 -652
- package/dist/security/SecretManager.js +0 -1147
- package/dist/security/SecretManager.js.map +0 -1
- package/dist/security/TLSConfig.d.ts +0 -305
- package/dist/security/TLSConfig.js +0 -550
- package/dist/security/TLSConfig.js.map +0 -1
- package/dist/security/index.d.ts +0 -81
- package/dist/security/index.js +0 -82
- package/dist/security/index.js.map +0 -1
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tier C #1 · Redis-backed debounce backend.
|
|
3
|
-
*
|
|
4
|
-
* Coordinates per-(workflow, debounceKey) window state across processes
|
|
5
|
-
* via a single Redis string key per bucket. Atomicity comes from
|
|
6
|
-
* server-side **Lua scripts** — `registerPing` / `finalize` /
|
|
7
|
-
* `cancel` / `purgeExpired` each run as a single `EVAL` with no OCC
|
|
8
|
-
* retry loop (Lua runs single-threaded against the keyspace).
|
|
9
|
-
*
|
|
10
|
-
* Storage shape: one JSON document per `(workflowName, debounceKey)`
|
|
11
|
-
* bucket. Owner identity is encoded in the doc itself as
|
|
12
|
-
* `(activeRunId, ownerProcessId, ownerLeaseExpiresAt)`; lease handoff
|
|
13
|
-
* happens atomically when a ping arrives after the lease expired.
|
|
14
|
-
*
|
|
15
|
-
* **Owner-local payload**: this backend tracks `pingCount`,
|
|
16
|
-
* `lastPingAt`, and `scheduledAt` only — payloads do NOT travel across
|
|
17
|
-
* processes. The owning process's local `onFire` closure fires when
|
|
18
|
-
* its timer elapses.
|
|
19
|
-
*/
|
|
20
|
-
const DEFAULT_KEY_PREFIX = "blok-debounce";
|
|
21
|
-
/**
|
|
22
|
-
* Atomic registerPing. Reads the current doc (if any), decides
|
|
23
|
-
* ownership, writes back the next state, and returns
|
|
24
|
-
* `{outcome, activeRunId, scheduledAt, pingCount}`.
|
|
25
|
-
*
|
|
26
|
-
* KEYS[1] = bucket key
|
|
27
|
-
* ARGV[1] = mode ("leading" | "trailing")
|
|
28
|
-
* ARGV[2] = delayMs
|
|
29
|
-
* ARGV[3] = maxDelayMs ("-1" when unset)
|
|
30
|
-
* ARGV[4] = runId
|
|
31
|
-
* ARGV[5] = processId
|
|
32
|
-
* ARGV[6] = ownerLeaseMs
|
|
33
|
-
* ARGV[7] = now
|
|
34
|
-
*
|
|
35
|
-
* Returns: { outcome ("owner-new" | "owner-extend" | "coalesce"),
|
|
36
|
-
* activeRunId, scheduledAt, pingCount }
|
|
37
|
-
*/
|
|
38
|
-
const REGISTER_PING_LUA = `
|
|
39
|
-
local raw = redis.call('GET', KEYS[1])
|
|
40
|
-
local existing = nil
|
|
41
|
-
if raw and raw ~= '' then
|
|
42
|
-
local ok, parsed = pcall(cjson.decode, raw)
|
|
43
|
-
if ok and type(parsed) == 'table' then
|
|
44
|
-
existing = parsed
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
local mode = ARGV[1]
|
|
49
|
-
local delayMs = tonumber(ARGV[2])
|
|
50
|
-
local maxDelayMsRaw = tonumber(ARGV[3])
|
|
51
|
-
local maxDelayMs
|
|
52
|
-
if maxDelayMsRaw and maxDelayMsRaw >= 0 then maxDelayMs = maxDelayMsRaw end
|
|
53
|
-
local runId = ARGV[4]
|
|
54
|
-
local processId = ARGV[5]
|
|
55
|
-
local ownerLeaseMs = tonumber(ARGV[6])
|
|
56
|
-
local now = tonumber(ARGV[7])
|
|
57
|
-
|
|
58
|
-
local function compute_scheduled_at(existing_doc, now_)
|
|
59
|
-
local naive = now_ + delayMs
|
|
60
|
-
local deadline
|
|
61
|
-
if existing_doc and existing_doc.maxDelayDeadline then
|
|
62
|
-
deadline = tonumber(existing_doc.maxDelayDeadline)
|
|
63
|
-
elseif maxDelayMs then
|
|
64
|
-
deadline = now_ + maxDelayMs
|
|
65
|
-
end
|
|
66
|
-
if deadline and deadline < naive then return deadline end
|
|
67
|
-
return naive
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
local ownerActive = existing ~= nil and tonumber(existing.ownerLeaseExpiresAt) and tonumber(existing.ownerLeaseExpiresAt) > now
|
|
71
|
-
|
|
72
|
-
if not existing or not ownerActive then
|
|
73
|
-
local first_ping_at = (existing and existing.firstPingAt) and tonumber(existing.firstPingAt) or now
|
|
74
|
-
local max_delay_deadline
|
|
75
|
-
if existing and existing.maxDelayDeadline then
|
|
76
|
-
max_delay_deadline = tonumber(existing.maxDelayDeadline)
|
|
77
|
-
elseif maxDelayMs then
|
|
78
|
-
max_delay_deadline = now + maxDelayMs
|
|
79
|
-
end
|
|
80
|
-
local prev_count = (existing and existing.pingCount) and tonumber(existing.pingCount) or 0
|
|
81
|
-
local doc = {
|
|
82
|
-
mode = mode,
|
|
83
|
-
delayMs = delayMs,
|
|
84
|
-
maxDelayMs = maxDelayMs,
|
|
85
|
-
maxDelayDeadline = max_delay_deadline,
|
|
86
|
-
firstPingAt = first_ping_at,
|
|
87
|
-
lastPingAt = now,
|
|
88
|
-
pingCount = prev_count + 1,
|
|
89
|
-
activeRunId = runId,
|
|
90
|
-
ownerProcessId = processId,
|
|
91
|
-
ownerLeaseExpiresAt = now + ownerLeaseMs,
|
|
92
|
-
scheduledAt = compute_scheduled_at(existing, now),
|
|
93
|
-
}
|
|
94
|
-
redis.call('SET', KEYS[1], cjson.encode(doc))
|
|
95
|
-
return { "owner-new", doc.activeRunId, tostring(doc.scheduledAt), tostring(doc.pingCount) }
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
if tostring(existing.ownerProcessId) == processId then
|
|
99
|
-
-- We still own — extend window.
|
|
100
|
-
existing.lastPingAt = now
|
|
101
|
-
existing.pingCount = (tonumber(existing.pingCount) or 0) + 1
|
|
102
|
-
existing.ownerLeaseExpiresAt = now + ownerLeaseMs
|
|
103
|
-
existing.scheduledAt = compute_scheduled_at(existing, now)
|
|
104
|
-
redis.call('SET', KEYS[1], cjson.encode(existing))
|
|
105
|
-
return { "owner-extend", tostring(existing.activeRunId), tostring(existing.scheduledAt), tostring(existing.pingCount) }
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
-- Different process owns — coalesce.
|
|
109
|
-
existing.lastPingAt = now
|
|
110
|
-
existing.pingCount = (tonumber(existing.pingCount) or 0) + 1
|
|
111
|
-
existing.scheduledAt = compute_scheduled_at(existing, now)
|
|
112
|
-
redis.call('SET', KEYS[1], cjson.encode(existing))
|
|
113
|
-
return { "coalesce", tostring(existing.activeRunId), tostring(existing.scheduledAt), tostring(existing.pingCount) }
|
|
114
|
-
`;
|
|
115
|
-
/**
|
|
116
|
-
* Atomic finalize. The owning process calls this on local timer fire.
|
|
117
|
-
*
|
|
118
|
-
* KEYS[1] = bucket key
|
|
119
|
-
* ARGV[1] = runId (the OWNING runId from the caller's perspective)
|
|
120
|
-
* ARGV[2] = now
|
|
121
|
-
*
|
|
122
|
-
* Returns:
|
|
123
|
-
* { "fire" } — caller still owns AND silence elapsed; DELETE done.
|
|
124
|
-
* { "reschedule", "<scheduledAt>" } — coalesce pings pushed scheduledAt forward.
|
|
125
|
-
* { "abandoned" } — caller no longer owns OR bucket gone.
|
|
126
|
-
*/
|
|
127
|
-
const FINALIZE_LUA = `
|
|
128
|
-
local raw = redis.call('GET', KEYS[1])
|
|
129
|
-
if not raw or raw == '' then return { "abandoned" } end
|
|
130
|
-
|
|
131
|
-
local ok, doc = pcall(cjson.decode, raw)
|
|
132
|
-
if not ok or type(doc) ~= 'table' then return { "abandoned" } end
|
|
133
|
-
|
|
134
|
-
if tostring(doc.activeRunId) ~= ARGV[1] then return { "abandoned" } end
|
|
135
|
-
|
|
136
|
-
local now = tonumber(ARGV[2])
|
|
137
|
-
local scheduled = tonumber(doc.scheduledAt) or 0
|
|
138
|
-
if now < scheduled then
|
|
139
|
-
return { "reschedule", tostring(scheduled) }
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
redis.call('DEL', KEYS[1])
|
|
143
|
-
return { "fire" }
|
|
144
|
-
`;
|
|
145
|
-
export function readRedisDebounceConfigFromEnv() {
|
|
146
|
-
const url = process.env.BLOK_DEBOUNCE_REDIS_URL?.trim() || undefined;
|
|
147
|
-
const host = process.env.BLOK_DEBOUNCE_REDIS_HOST?.trim() || undefined;
|
|
148
|
-
const portRaw = process.env.BLOK_DEBOUNCE_REDIS_PORT?.trim();
|
|
149
|
-
const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : undefined;
|
|
150
|
-
const dbRaw = process.env.BLOK_DEBOUNCE_REDIS_DB?.trim();
|
|
151
|
-
const db = dbRaw && /^\d+$/.test(dbRaw) ? Number(dbRaw) : undefined;
|
|
152
|
-
const tls = process.env.BLOK_DEBOUNCE_REDIS_TLS === "1" || process.env.BLOK_DEBOUNCE_REDIS_TLS === "true";
|
|
153
|
-
return {
|
|
154
|
-
url,
|
|
155
|
-
host,
|
|
156
|
-
port,
|
|
157
|
-
password: process.env.BLOK_DEBOUNCE_REDIS_PASSWORD,
|
|
158
|
-
username: process.env.BLOK_DEBOUNCE_REDIS_USERNAME,
|
|
159
|
-
db,
|
|
160
|
-
tls,
|
|
161
|
-
keyPrefix: process.env.BLOK_DEBOUNCE_REDIS_KEY_PREFIX?.trim() || DEFAULT_KEY_PREFIX,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
export class RedisDebounceBackend {
|
|
165
|
-
name = "redis";
|
|
166
|
-
client = null;
|
|
167
|
-
config;
|
|
168
|
-
connected = false;
|
|
169
|
-
constructor(config) {
|
|
170
|
-
const env = readRedisDebounceConfigFromEnv();
|
|
171
|
-
this.config = {
|
|
172
|
-
url: config?.url ?? env.url,
|
|
173
|
-
host: config?.host ?? env.host,
|
|
174
|
-
port: config?.port ?? env.port,
|
|
175
|
-
password: config?.password ?? env.password,
|
|
176
|
-
username: config?.username ?? env.username,
|
|
177
|
-
db: config?.db ?? env.db,
|
|
178
|
-
tls: config?.tls ?? env.tls,
|
|
179
|
-
keyPrefix: config?.keyPrefix ?? env.keyPrefix,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
async connect() {
|
|
183
|
-
if (this.connected)
|
|
184
|
-
return;
|
|
185
|
-
const blokEnv = process.env.BLOK_ENV;
|
|
186
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
187
|
-
const isProd = blokEnv === "production" || nodeEnv === "production";
|
|
188
|
-
if (isProd && this.config.keyPrefix === DEFAULT_KEY_PREFIX) {
|
|
189
|
-
throw new Error(`[blok] Redis debounce backend refuses to start in production with the default key prefix ('${DEFAULT_KEY_PREFIX}'). Set BLOK_DEBOUNCE_REDIS_KEY_PREFIX to a deployment-unique value (e.g. 'blok-debounce-acme-prod') to prevent cross-deployment collision on a shared Redis instance.`);
|
|
190
|
-
}
|
|
191
|
-
let ioredisModule;
|
|
192
|
-
try {
|
|
193
|
-
ioredisModule = (await import("ioredis"));
|
|
194
|
-
}
|
|
195
|
-
catch (err) {
|
|
196
|
-
throw new Error(`RedisDebounceBackend requires the 'ioredis' package. Install it: \`bun add ioredis\` or \`npm install ioredis\`. Underlying error: ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
-
}
|
|
198
|
-
const IORedisCtor = ioredisModule.default ?? ioredisModule.Redis;
|
|
199
|
-
if (!IORedisCtor) {
|
|
200
|
-
throw new Error("RedisDebounceBackend could not locate the ioredis constructor on the imported module. Reinstall ioredis or report this issue.");
|
|
201
|
-
}
|
|
202
|
-
// Same fail-fast posture as RedisConcurrencyBackend.
|
|
203
|
-
const failFastDefaults = {
|
|
204
|
-
connectTimeout: 5_000,
|
|
205
|
-
maxRetriesPerRequest: 0,
|
|
206
|
-
enableOfflineQueue: false,
|
|
207
|
-
lazyConnect: true,
|
|
208
|
-
};
|
|
209
|
-
if (this.config.url) {
|
|
210
|
-
this.client = new IORedisCtor(this.config.url);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
const opts = { ...failFastDefaults };
|
|
214
|
-
if (this.config.host)
|
|
215
|
-
opts.host = this.config.host;
|
|
216
|
-
if (this.config.port)
|
|
217
|
-
opts.port = this.config.port;
|
|
218
|
-
if (this.config.username)
|
|
219
|
-
opts.username = this.config.username;
|
|
220
|
-
if (this.config.password)
|
|
221
|
-
opts.password = this.config.password;
|
|
222
|
-
if (typeof this.config.db === "number")
|
|
223
|
-
opts.db = this.config.db;
|
|
224
|
-
if (this.config.tls)
|
|
225
|
-
opts.tls = {};
|
|
226
|
-
this.client = new IORedisCtor(opts);
|
|
227
|
-
}
|
|
228
|
-
this.client.on("error", (err) => {
|
|
229
|
-
console.warn(`[blok][debounce][redis] client error: ${err.message}`);
|
|
230
|
-
});
|
|
231
|
-
await this.client.ping();
|
|
232
|
-
this.connected = true;
|
|
233
|
-
}
|
|
234
|
-
async disconnect() {
|
|
235
|
-
if (!this.connected)
|
|
236
|
-
return;
|
|
237
|
-
try {
|
|
238
|
-
await this.client?.quit();
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// best-effort
|
|
242
|
-
}
|
|
243
|
-
finally {
|
|
244
|
-
this.client = null;
|
|
245
|
-
this.connected = false;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
bucketKey(workflowName, debounceKey) {
|
|
249
|
-
return `${this.config.keyPrefix}:${this.encodeSegment(workflowName)}__${this.encodeSegment(debounceKey)}`;
|
|
250
|
-
}
|
|
251
|
-
encodeSegment(s) {
|
|
252
|
-
return s.replace(/[^-_=.a-zA-Z0-9]/g, (ch) => `_${ch.codePointAt(0)?.toString(16)}_`);
|
|
253
|
-
}
|
|
254
|
-
requireClient() {
|
|
255
|
-
if (!this.client) {
|
|
256
|
-
throw new Error("RedisDebounceBackend not connected — call connect() first.");
|
|
257
|
-
}
|
|
258
|
-
return this.client;
|
|
259
|
-
}
|
|
260
|
-
async registerPing(opts) {
|
|
261
|
-
const client = this.requireClient();
|
|
262
|
-
const key = this.bucketKey(opts.workflowName, opts.debounceKey);
|
|
263
|
-
const raw = await client.eval(REGISTER_PING_LUA, 1, key, opts.mode, String(opts.delayMs), opts.maxDelayMs !== undefined ? String(opts.maxDelayMs) : "-1", opts.runId, opts.processId, String(opts.ownerLeaseMs), String(opts.now));
|
|
264
|
-
return this.parseRegisterResult(raw);
|
|
265
|
-
}
|
|
266
|
-
async finalize(workflowName, debounceKey, runId, now) {
|
|
267
|
-
const client = this.requireClient();
|
|
268
|
-
const key = this.bucketKey(workflowName, debounceKey);
|
|
269
|
-
const raw = await client.eval(FINALIZE_LUA, 1, key, runId, String(now));
|
|
270
|
-
return this.parseFinalizeResult(raw);
|
|
271
|
-
}
|
|
272
|
-
async cancel(workflowName, debounceKey) {
|
|
273
|
-
const client = this.requireClient();
|
|
274
|
-
const key = this.bucketKey(workflowName, debounceKey);
|
|
275
|
-
const deleted = await client.del(key);
|
|
276
|
-
return deleted > 0;
|
|
277
|
-
}
|
|
278
|
-
async purgeExpired(now) {
|
|
279
|
-
const client = this.requireClient();
|
|
280
|
-
const pattern = `${this.config.keyPrefix}:*`;
|
|
281
|
-
let cursor = "0";
|
|
282
|
-
let purged = 0;
|
|
283
|
-
do {
|
|
284
|
-
let res;
|
|
285
|
-
try {
|
|
286
|
-
res = await client.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
return purged;
|
|
290
|
-
}
|
|
291
|
-
const [nextCursor, keys] = res;
|
|
292
|
-
cursor = nextCursor;
|
|
293
|
-
for (const key of keys) {
|
|
294
|
-
try {
|
|
295
|
-
const raw = await client.eval(PURGE_EXPIRED_BUCKET_LUA, 1, key, String(now));
|
|
296
|
-
const count = typeof raw === "number" ? raw : Number(raw);
|
|
297
|
-
if (!Number.isNaN(count))
|
|
298
|
-
purged += count;
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
// best-effort
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
} while (cursor !== "0");
|
|
305
|
-
return purged;
|
|
306
|
-
}
|
|
307
|
-
parseRegisterResult(raw) {
|
|
308
|
-
if (!Array.isArray(raw) || raw.length < 4) {
|
|
309
|
-
throw new Error(`Unexpected Lua result shape for registerPing: ${JSON.stringify(raw)}`);
|
|
310
|
-
}
|
|
311
|
-
const outcome = String(raw[0]);
|
|
312
|
-
if (outcome !== "owner-new" && outcome !== "owner-extend" && outcome !== "coalesce") {
|
|
313
|
-
throw new Error(`Unexpected outcome in Lua result: ${outcome}`);
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
outcome,
|
|
317
|
-
activeRunId: String(raw[1]),
|
|
318
|
-
scheduledAt: Number(raw[2]),
|
|
319
|
-
pingCount: Number(raw[3]),
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
parseFinalizeResult(raw) {
|
|
323
|
-
if (!Array.isArray(raw) || raw.length < 1)
|
|
324
|
-
return { finalize: "abandoned" };
|
|
325
|
-
const tag = String(raw[0]);
|
|
326
|
-
if (tag === "fire")
|
|
327
|
-
return { finalize: "fire" };
|
|
328
|
-
if (tag === "reschedule")
|
|
329
|
-
return { finalize: "reschedule", scheduledAt: Number(raw[1]) };
|
|
330
|
-
return { finalize: "abandoned" };
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Per-bucket purge. Deletes the bucket iff the owner-lease has expired
|
|
335
|
-
* AND scheduledAt has elapsed (no active owner with a pending fire).
|
|
336
|
-
*
|
|
337
|
-
* KEYS[1] = bucket key
|
|
338
|
-
* ARGV[1] = now
|
|
339
|
-
*
|
|
340
|
-
* Returns: 1 if deleted, 0 otherwise.
|
|
341
|
-
*/
|
|
342
|
-
const PURGE_EXPIRED_BUCKET_LUA = `
|
|
343
|
-
local raw = redis.call('GET', KEYS[1])
|
|
344
|
-
if not raw or raw == '' then return 0 end
|
|
345
|
-
local ok, doc = pcall(cjson.decode, raw)
|
|
346
|
-
if not ok or type(doc) ~= 'table' then return 0 end
|
|
347
|
-
local now = tonumber(ARGV[1])
|
|
348
|
-
local lease = tonumber(doc.ownerLeaseExpiresAt) or 0
|
|
349
|
-
local sched = tonumber(doc.scheduledAt) or 0
|
|
350
|
-
if lease <= now and sched <= now then
|
|
351
|
-
redis.call('DEL', KEYS[1])
|
|
352
|
-
return 1
|
|
353
|
-
end
|
|
354
|
-
return 0
|
|
355
|
-
`;
|
|
356
|
-
//# sourceMappingURL=RedisDebounceBackend.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RedisDebounceBackend.js","sourceRoot":"","sources":["../../src/scheduling/RedisDebounceBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tier C #1 · Debounce backend factory.
|
|
3
|
-
*
|
|
4
|
-
* Reads `BLOK_DEBOUNCE_BACKEND` and returns the matching backend
|
|
5
|
-
* instance, or `null` when the operator wants the default in-process
|
|
6
|
-
* coordinator (no cross-process coordination).
|
|
7
|
-
*
|
|
8
|
-
* Trigger packages call this in `listen()` and pass the result to
|
|
9
|
-
* `DebounceCoordinator.getInstance().setBackend(backend)`.
|
|
10
|
-
*/
|
|
11
|
-
import type { DebounceBackend } from "./DebounceBackend";
|
|
12
|
-
/**
|
|
13
|
-
* Returns a configured `DebounceBackend` based on
|
|
14
|
-
* `BLOK_DEBOUNCE_BACKEND`, or `null` for the default in-memory coordinator.
|
|
15
|
-
*
|
|
16
|
-
* Recognized values:
|
|
17
|
-
* - unset / `""` / `"memory"` / `"in-process"` — null (use default in-memory)
|
|
18
|
-
* - `"nats-kv"` — NATS KV backend (requires `nats` package + reachable NATS server)
|
|
19
|
-
* - `"redis"` — Redis backend (requires `ioredis` package + reachable Redis server)
|
|
20
|
-
*
|
|
21
|
-
* Unknown values throw at startup with a clear error message — silently
|
|
22
|
-
* falling back would be dangerous (operator thinks they configured
|
|
23
|
-
* cross-process debounce but they didn't).
|
|
24
|
-
*/
|
|
25
|
-
export declare function createDebounceBackend(): DebounceBackend | null;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tier C #1 · Debounce backend factory.
|
|
3
|
-
*
|
|
4
|
-
* Reads `BLOK_DEBOUNCE_BACKEND` and returns the matching backend
|
|
5
|
-
* instance, or `null` when the operator wants the default in-process
|
|
6
|
-
* coordinator (no cross-process coordination).
|
|
7
|
-
*
|
|
8
|
-
* Trigger packages call this in `listen()` and pass the result to
|
|
9
|
-
* `DebounceCoordinator.getInstance().setBackend(backend)`.
|
|
10
|
-
*/
|
|
11
|
-
import { NatsKvDebounceBackend } from "./NatsKvDebounceBackend";
|
|
12
|
-
import { RedisDebounceBackend } from "./RedisDebounceBackend";
|
|
13
|
-
/**
|
|
14
|
-
* Returns a configured `DebounceBackend` based on
|
|
15
|
-
* `BLOK_DEBOUNCE_BACKEND`, or `null` for the default in-memory coordinator.
|
|
16
|
-
*
|
|
17
|
-
* Recognized values:
|
|
18
|
-
* - unset / `""` / `"memory"` / `"in-process"` — null (use default in-memory)
|
|
19
|
-
* - `"nats-kv"` — NATS KV backend (requires `nats` package + reachable NATS server)
|
|
20
|
-
* - `"redis"` — Redis backend (requires `ioredis` package + reachable Redis server)
|
|
21
|
-
*
|
|
22
|
-
* Unknown values throw at startup with a clear error message — silently
|
|
23
|
-
* falling back would be dangerous (operator thinks they configured
|
|
24
|
-
* cross-process debounce but they didn't).
|
|
25
|
-
*/
|
|
26
|
-
export function createDebounceBackend() {
|
|
27
|
-
const kind = (process.env.BLOK_DEBOUNCE_BACKEND ?? "").trim().toLowerCase();
|
|
28
|
-
if (!kind || kind === "memory" || kind === "in-process")
|
|
29
|
-
return null;
|
|
30
|
-
switch (kind) {
|
|
31
|
-
case "nats-kv":
|
|
32
|
-
return new NatsKvDebounceBackend();
|
|
33
|
-
case "redis":
|
|
34
|
-
return new RedisDebounceBackend();
|
|
35
|
-
default:
|
|
36
|
-
throw new Error(`Unknown BLOK_DEBOUNCE_BACKEND='${kind}'. Expected one of: 'memory' (default), 'nats-kv', 'redis'.`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
//# sourceMappingURL=createDebounceBackend.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createDebounceBackend.js","sourceRoot":"","sources":["../../src/scheduling/createDebounceBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB;IACpC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5E,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IAErE,QAAQ,IAAI,EAAE,CAAC;QACd,KAAK,SAAS;YACb,OAAO,IAAI,qBAAqB,EAAE,CAAC;QACpC,KAAK,OAAO;YACX,OAAO,IAAI,oBAAoB,EAAE,CAAC;QACnC;YACC,MAAM,IAAI,KAAK,CACd,kCAAkC,IAAI,6DAA6D,CACnG,CAAC;IACJ,CAAC;AACF,CAAC"}
|
package/dist/security/ABAC.d.ts
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Attribute-Based Access Control (ABAC) for Blok
|
|
3
|
-
*
|
|
4
|
-
* Provides fine-grained, attribute-driven access control that complements RBAC:
|
|
5
|
-
* - Policies evaluate attributes of subject, resource, action, and environment
|
|
6
|
-
* - Supports logical operators (AND, OR, NOT) for complex conditions
|
|
7
|
-
* - Supports comparison operators (equals, not_equals, in, not_in, contains, matches, gt, lt, gte, lte, between)
|
|
8
|
-
* - Supports attribute-to-attribute comparison via `valueRef` (e.g., resource.owner == subject.sub)
|
|
9
|
-
* - Integrates with AuthIdentity claims and RBAC roles
|
|
10
|
-
* - JSON-serializable policies for persistence and external management
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const engine = new ABACEngine();
|
|
15
|
-
*
|
|
16
|
-
* engine.addPolicy({
|
|
17
|
-
* id: "work-hours-only",
|
|
18
|
-
* description: "Allow workflow execution only during business hours",
|
|
19
|
-
* effect: "allow",
|
|
20
|
-
* target: {
|
|
21
|
-
* resource: "workflow",
|
|
22
|
-
* actions: ["execute"],
|
|
23
|
-
* },
|
|
24
|
-
* conditions: {
|
|
25
|
-
* all: [
|
|
26
|
-
* { attribute: "environment.hour", operator: "gte", value: 9 },
|
|
27
|
-
* { attribute: "environment.hour", operator: "lt", value: 17 },
|
|
28
|
-
* { attribute: "subject.department", operator: "equals", value: "engineering" },
|
|
29
|
-
* ],
|
|
30
|
-
* },
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* const result = engine.evaluate({
|
|
34
|
-
* subject: { sub: "user-1", roles: ["developer"], department: "engineering" },
|
|
35
|
-
* resource: { type: "workflow", id: "/api/users" },
|
|
36
|
-
* action: "execute",
|
|
37
|
-
* environment: { hour: 14, ip: "10.0.0.1" },
|
|
38
|
-
* });
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export type ABACOperator = "equals" | "not_equals" | "in" | "not_in" | "contains" | "not_contains" | "matches" | "gt" | "lt" | "gte" | "lte" | "between" | "exists" | "not_exists";
|
|
42
|
-
export type ABACEffect = "allow" | "deny";
|
|
43
|
-
/**
|
|
44
|
-
* A single attribute condition that compares an attribute path against a value.
|
|
45
|
-
*
|
|
46
|
-
* Attribute paths use dot notation to access nested properties:
|
|
47
|
-
* - `subject.department` — the subject's department attribute
|
|
48
|
-
* - `resource.owner` — the resource's owner attribute
|
|
49
|
-
* - `environment.ip` — the environment's IP address
|
|
50
|
-
* - `environment.hour` — the current hour (0-23)
|
|
51
|
-
*/
|
|
52
|
-
export interface ABACCondition {
|
|
53
|
-
/** Dot-separated path to the attribute (e.g., "subject.department") */
|
|
54
|
-
attribute: string;
|
|
55
|
-
/** Comparison operator */
|
|
56
|
-
operator: ABACOperator;
|
|
57
|
-
/** Static value to compare against (ignored for exists/not_exists operators) */
|
|
58
|
-
value?: unknown;
|
|
59
|
-
/** Attribute path to resolve as the comparison value (attribute-to-attribute comparison).
|
|
60
|
-
* When set, `value` is ignored and the comparison value is resolved from the request. */
|
|
61
|
-
valueRef?: string;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Logical grouping of conditions.
|
|
65
|
-
*
|
|
66
|
-
* - `all`: Every condition must be true (AND)
|
|
67
|
-
* - `any`: At least one condition must be true (OR)
|
|
68
|
-
* - `none`: No condition may be true (NOT / NOR)
|
|
69
|
-
*
|
|
70
|
-
* Groups can be nested for complex logic.
|
|
71
|
-
*/
|
|
72
|
-
export interface ABACConditionGroup {
|
|
73
|
-
/** All conditions must be true (AND) */
|
|
74
|
-
all?: Array<ABACCondition | ABACConditionGroup>;
|
|
75
|
-
/** At least one condition must be true (OR) */
|
|
76
|
-
any?: Array<ABACCondition | ABACConditionGroup>;
|
|
77
|
-
/** No condition may be true (NOR) */
|
|
78
|
-
none?: Array<ABACCondition | ABACConditionGroup>;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Policy target restricts which requests the policy applies to.
|
|
82
|
-
*/
|
|
83
|
-
export interface ABACPolicyTarget {
|
|
84
|
-
/** Resource type (e.g., "workflow", "node", "*") */
|
|
85
|
-
resource?: string;
|
|
86
|
-
/** Resource ID pattern (supports * wildcards) */
|
|
87
|
-
resourcePattern?: string;
|
|
88
|
-
/** Actions this policy applies to */
|
|
89
|
-
actions?: string[];
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* An ABAC policy defines conditions under which access is allowed or denied.
|
|
93
|
-
*/
|
|
94
|
-
export interface ABACPolicy {
|
|
95
|
-
/** Unique policy identifier */
|
|
96
|
-
id: string;
|
|
97
|
-
/** Human-readable description */
|
|
98
|
-
description?: string;
|
|
99
|
-
/** Whether this policy grants or denies access */
|
|
100
|
-
effect: ABACEffect;
|
|
101
|
-
/** Target resource/action scope — if omitted, applies to all requests */
|
|
102
|
-
target?: ABACPolicyTarget;
|
|
103
|
-
/** Conditions that must be satisfied for the policy to apply */
|
|
104
|
-
conditions: ABACConditionGroup;
|
|
105
|
-
/** Priority (higher = evaluated first). Default: 0 */
|
|
106
|
-
priority?: number;
|
|
107
|
-
/** Whether the policy is active. Default: true */
|
|
108
|
-
enabled?: boolean;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Attributes about the requesting subject (user or service).
|
|
112
|
-
*/
|
|
113
|
-
export interface SubjectAttributes {
|
|
114
|
-
/** Unique identifier */
|
|
115
|
-
sub: string;
|
|
116
|
-
/** Assigned roles */
|
|
117
|
-
roles?: string[];
|
|
118
|
-
/** Additional attributes (department, clearance, team, etc.) */
|
|
119
|
-
[key: string]: unknown;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Attributes about the target resource.
|
|
123
|
-
*/
|
|
124
|
-
export interface ResourceAttributes {
|
|
125
|
-
/** Resource type (workflow, node, trigger, etc.) */
|
|
126
|
-
type: string;
|
|
127
|
-
/** Resource identifier */
|
|
128
|
-
id: string;
|
|
129
|
-
/** Additional attributes (owner, classification, sensitivity, etc.) */
|
|
130
|
-
[key: string]: unknown;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Attributes about the environment / context.
|
|
134
|
-
*/
|
|
135
|
-
export interface EnvironmentAttributes {
|
|
136
|
-
/** Additional attributes (ip, hour, dayOfWeek, location, etc.) */
|
|
137
|
-
[key: string]: unknown;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* A complete ABAC evaluation request context.
|
|
141
|
-
*/
|
|
142
|
-
export interface ABACRequest {
|
|
143
|
-
subject: SubjectAttributes;
|
|
144
|
-
resource: ResourceAttributes;
|
|
145
|
-
action: string;
|
|
146
|
-
environment?: EnvironmentAttributes;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Result of an ABAC evaluation.
|
|
150
|
-
*/
|
|
151
|
-
export interface ABACResult {
|
|
152
|
-
/** Whether access is allowed */
|
|
153
|
-
allowed: boolean;
|
|
154
|
-
/** The policy that determined the decision (if any) */
|
|
155
|
-
matchedPolicy?: ABACPolicy;
|
|
156
|
-
/** All policies that were evaluated */
|
|
157
|
-
evaluatedPolicies: Array<{
|
|
158
|
-
policyId: string;
|
|
159
|
-
effect: ABACEffect;
|
|
160
|
-
matched: boolean;
|
|
161
|
-
}>;
|
|
162
|
-
/** Reason for the decision */
|
|
163
|
-
reason: string;
|
|
164
|
-
}
|
|
165
|
-
export declare class ABACEngine {
|
|
166
|
-
private policies;
|
|
167
|
-
private defaultEffect;
|
|
168
|
-
constructor(options?: {
|
|
169
|
-
defaultEffect?: ABACEffect;
|
|
170
|
-
});
|
|
171
|
-
/**
|
|
172
|
-
* Add or update a policy.
|
|
173
|
-
*/
|
|
174
|
-
addPolicy(policy: ABACPolicy): void;
|
|
175
|
-
/**
|
|
176
|
-
* Remove a policy by ID.
|
|
177
|
-
*/
|
|
178
|
-
removePolicy(id: string): void;
|
|
179
|
-
/**
|
|
180
|
-
* Get a policy by ID.
|
|
181
|
-
*/
|
|
182
|
-
getPolicy(id: string): ABACPolicy | undefined;
|
|
183
|
-
/**
|
|
184
|
-
* Get all policies, sorted by priority (highest first).
|
|
185
|
-
*/
|
|
186
|
-
getPolicies(): ABACPolicy[];
|
|
187
|
-
/**
|
|
188
|
-
* Evaluate an access request against all policies.
|
|
189
|
-
*
|
|
190
|
-
* Policy evaluation order:
|
|
191
|
-
* 1. Policies are sorted by priority (highest first)
|
|
192
|
-
* 2. Only enabled policies are considered
|
|
193
|
-
* 3. Only policies whose target matches the request are considered
|
|
194
|
-
* 4. The first matching "deny" policy short-circuits with denial
|
|
195
|
-
* 5. Otherwise, at least one matching "allow" policy is required
|
|
196
|
-
* 6. If no policy matches, the default effect applies
|
|
197
|
-
*/
|
|
198
|
-
evaluate(request: ABACRequest): ABACResult;
|
|
199
|
-
/**
|
|
200
|
-
* Export all policies as JSON.
|
|
201
|
-
*/
|
|
202
|
-
toJSON(): {
|
|
203
|
-
policies: ABACPolicy[];
|
|
204
|
-
defaultEffect: ABACEffect;
|
|
205
|
-
};
|
|
206
|
-
/**
|
|
207
|
-
* Load policies from JSON (replaces all existing policies).
|
|
208
|
-
*/
|
|
209
|
-
fromJSON(config: {
|
|
210
|
-
policies: ABACPolicy[];
|
|
211
|
-
defaultEffect?: ABACEffect;
|
|
212
|
-
}): void;
|
|
213
|
-
private matchesTarget;
|
|
214
|
-
private evaluateConditionGroup;
|
|
215
|
-
private evaluateItem;
|
|
216
|
-
private evaluateCondition;
|
|
217
|
-
private resolveAttribute;
|
|
218
|
-
private compare;
|
|
219
|
-
private matchesPattern;
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Create a preconfigured ABAC engine with common policies.
|
|
223
|
-
*/
|
|
224
|
-
export declare function createDefaultABAC(): ABACEngine;
|