@adonis-agora/flags 0.0.0-beta-20261112141227
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 +20 -0
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/assets/spa/assets/index-DdmFo7xr.js +12 -0
- package/dist/assets/spa/assets/index-DdmFo7xr.js.map +1 -0
- package/dist/assets/spa/assets/index-DhkcP3Cw.css +2 -0
- package/dist/assets/spa/index.html +26 -0
- package/dist/commands/flags_list.d.ts +9 -0
- package/dist/commands/flags_list.d.ts.map +1 -0
- package/dist/commands/flags_list.js +51 -0
- package/dist/commands/flags_list.js.map +1 -0
- package/dist/commands/flags_ops.d.ts +60 -0
- package/dist/commands/flags_ops.d.ts.map +1 -0
- package/dist/commands/flags_ops.js +253 -0
- package/dist/commands/flags_ops.js.map +1 -0
- package/dist/commands/flags_refs.d.ts +15 -0
- package/dist/commands/flags_refs.d.ts.map +1 -0
- package/dist/commands/flags_refs.js +90 -0
- package/dist/commands/flags_refs.js.map +1 -0
- package/dist/commands/flags_set.d.ts +19 -0
- package/dist/commands/flags_set.d.ts.map +1 -0
- package/dist/commands/flags_set.js +97 -0
- package/dist/commands/flags_set.js.map +1 -0
- package/dist/commands/flags_types.d.ts +18 -0
- package/dist/commands/flags_types.d.ts.map +1 -0
- package/dist/commands/flags_types.js +94 -0
- package/dist/commands/flags_types.js.map +1 -0
- package/dist/commands/flags_work.d.ts +16 -0
- package/dist/commands/flags_work.d.ts.map +1 -0
- package/dist/commands/flags_work.js +71 -0
- package/dist/commands/flags_work.js.map +1 -0
- package/dist/commands/main.d.ts +15 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +30 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/shared.d.ts +21 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +70 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/configure.d.ts +11 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +32 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/dashboard_provider.d.ts +65 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +340 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/flags_provider.d.ts +30 -0
- package/dist/providers/flags_provider.d.ts.map +1 -0
- package/dist/providers/flags_provider.js +184 -0
- package/dist/providers/flags_provider.js.map +1 -0
- package/dist/services/main.d.ts +17 -0
- package/dist/services/main.d.ts.map +1 -0
- package/dist/services/main.js +20 -0
- package/dist/services/main.js.map +1 -0
- package/dist/src/admin.d.ts +189 -0
- package/dist/src/admin.d.ts.map +1 -0
- package/dist/src/admin.js +539 -0
- package/dist/src/admin.js.map +1 -0
- package/dist/src/analytics/backend.d.ts +18 -0
- package/dist/src/analytics/backend.d.ts.map +1 -0
- package/dist/src/analytics/backend.js +25 -0
- package/dist/src/analytics/backend.js.map +1 -0
- package/dist/src/analytics/clickhouse_backend.d.ts +16 -0
- package/dist/src/analytics/clickhouse_backend.d.ts.map +1 -0
- package/dist/src/analytics/clickhouse_backend.js +48 -0
- package/dist/src/analytics/clickhouse_backend.js.map +1 -0
- package/dist/src/analytics/duckdb.d.ts +39 -0
- package/dist/src/analytics/duckdb.d.ts.map +1 -0
- package/dist/src/analytics/duckdb.js +117 -0
- package/dist/src/analytics/duckdb.js.map +1 -0
- package/dist/src/analytics/lucid_backend.d.ts +16 -0
- package/dist/src/analytics/lucid_backend.d.ts.map +1 -0
- package/dist/src/analytics/lucid_backend.js +45 -0
- package/dist/src/analytics/lucid_backend.js.map +1 -0
- package/dist/src/analytics/metrics.d.ts +50 -0
- package/dist/src/analytics/metrics.d.ts.map +1 -0
- package/dist/src/analytics/metrics.js +42 -0
- package/dist/src/analytics/metrics.js.map +1 -0
- package/dist/src/analytics/results.d.ts +80 -0
- package/dist/src/analytics/results.d.ts.map +1 -0
- package/dist/src/analytics/results.js +143 -0
- package/dist/src/analytics/results.js.map +1 -0
- package/dist/src/dashboard/access_denied_page.d.ts +100 -0
- package/dist/src/dashboard/access_denied_page.d.ts.map +1 -0
- package/dist/src/dashboard/access_denied_page.js +262 -0
- package/dist/src/dashboard/access_denied_page.js.map +1 -0
- package/dist/src/dashboard/actor.d.ts +12 -0
- package/dist/src/dashboard/actor.d.ts.map +1 -0
- package/dist/src/dashboard/actor.js +33 -0
- package/dist/src/dashboard/actor.js.map +1 -0
- package/dist/src/dashboard/authorize_decision.d.ts +48 -0
- package/dist/src/dashboard/authorize_decision.d.ts.map +1 -0
- package/dist/src/dashboard/authorize_decision.js +44 -0
- package/dist/src/dashboard/authorize_decision.js.map +1 -0
- package/dist/src/dashboard/console_authorize.d.ts +77 -0
- package/dist/src/dashboard/console_authorize.d.ts.map +1 -0
- package/dist/src/dashboard/console_authorize.js +104 -0
- package/dist/src/dashboard/console_authorize.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +71 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +18 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +172 -0
- package/dist/src/dashboard/handlers.d.ts.map +1 -0
- package/dist/src/dashboard/handlers.js +949 -0
- package/dist/src/dashboard/handlers.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +15 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +10 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/dashboard/shape.d.ts +12 -0
- package/dist/src/dashboard/shape.d.ts.map +1 -0
- package/dist/src/dashboard/shape.js +232 -0
- package/dist/src/dashboard/shape.js.map +1 -0
- package/dist/src/dashboard/spa.d.ts +34 -0
- package/dist/src/dashboard/spa.d.ts.map +1 -0
- package/dist/src/dashboard/spa.js +50 -0
- package/dist/src/dashboard/spa.js.map +1 -0
- package/dist/src/declare.d.ts +85 -0
- package/dist/src/declare.d.ts.map +1 -0
- package/dist/src/declare.js +62 -0
- package/dist/src/declare.js.map +1 -0
- package/dist/src/define_config.d.ts +256 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +90 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/durable.d.ts +29 -0
- package/dist/src/durable.d.ts.map +1 -0
- package/dist/src/durable.js +55 -0
- package/dist/src/durable.js.map +1 -0
- package/dist/src/enrichers.d.ts +51 -0
- package/dist/src/enrichers.d.ts.map +1 -0
- package/dist/src/enrichers.js +105 -0
- package/dist/src/enrichers.js.map +1 -0
- package/dist/src/events/buffer.d.ts +38 -0
- package/dist/src/events/buffer.d.ts.map +1 -0
- package/dist/src/events/buffer.js +86 -0
- package/dist/src/events/buffer.js.map +1 -0
- package/dist/src/events/event.d.ts +42 -0
- package/dist/src/events/event.d.ts.map +1 -0
- package/dist/src/events/event.js +82 -0
- package/dist/src/events/event.js.map +1 -0
- package/dist/src/http/browser.d.ts +33 -0
- package/dist/src/http/browser.d.ts.map +1 -0
- package/dist/src/http/browser.js +81 -0
- package/dist/src/http/browser.js.map +1 -0
- package/dist/src/http/browser_api.d.ts +48 -0
- package/dist/src/http/browser_api.d.ts.map +1 -0
- package/dist/src/http/browser_api.js +273 -0
- package/dist/src/http/browser_api.js.map +1 -0
- package/dist/src/http/browser_tokens.d.ts +27 -0
- package/dist/src/http/browser_tokens.d.ts.map +1 -0
- package/dist/src/http/browser_tokens.js +48 -0
- package/dist/src/http/browser_tokens.js.map +1 -0
- package/dist/src/http/control_plane.d.ts +58 -0
- package/dist/src/http/control_plane.d.ts.map +1 -0
- package/dist/src/http/control_plane.js +318 -0
- package/dist/src/http/control_plane.js.map +1 -0
- package/dist/src/http/csrf.d.ts +22 -0
- package/dist/src/http/csrf.d.ts.map +1 -0
- package/dist/src/http/csrf.js +24 -0
- package/dist/src/http/csrf.js.map +1 -0
- package/dist/src/http/edge_tag.d.ts +55 -0
- package/dist/src/http/edge_tag.d.ts.map +1 -0
- package/dist/src/http/edge_tag.js +45 -0
- package/dist/src/http/edge_tag.js.map +1 -0
- package/dist/src/http/flag_middleware.d.ts +27 -0
- package/dist/src/http/flag_middleware.d.ts.map +1 -0
- package/dist/src/http/flag_middleware.js +30 -0
- package/dist/src/http/flag_middleware.js.map +1 -0
- package/dist/src/http/flags_middleware.d.ts +20 -0
- package/dist/src/http/flags_middleware.d.ts.map +1 -0
- package/dist/src/http/flags_middleware.js +20 -0
- package/dist/src/http/flags_middleware.js.map +1 -0
- package/dist/src/http/subject_resolver.d.ts +45 -0
- package/dist/src/http/subject_resolver.d.ts.map +1 -0
- package/dist/src/http/subject_resolver.js +93 -0
- package/dist/src/http/subject_resolver.js.map +1 -0
- package/dist/src/identity.d.ts +82 -0
- package/dist/src/identity.d.ts.map +1 -0
- package/dist/src/identity.js +100 -0
- package/dist/src/identity.js.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +34 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/manager.d.ts +154 -0
- package/dist/src/manager.d.ts.map +1 -0
- package/dist/src/manager.js +333 -0
- package/dist/src/manager.js.map +1 -0
- package/dist/src/openfeature/provider.d.ts +36 -0
- package/dist/src/openfeature/provider.d.ts.map +1 -0
- package/dist/src/openfeature/provider.js +82 -0
- package/dist/src/openfeature/provider.js.map +1 -0
- package/dist/src/relay/relay.d.ts +36 -0
- package/dist/src/relay/relay.d.ts.map +1 -0
- package/dist/src/relay/relay.js +73 -0
- package/dist/src/relay/relay.js.map +1 -0
- package/dist/src/relay/results_cache.d.ts +20 -0
- package/dist/src/relay/results_cache.d.ts.map +1 -0
- package/dist/src/relay/results_cache.js +31 -0
- package/dist/src/relay/results_cache.js.map +1 -0
- package/dist/src/relay/worker.d.ts +66 -0
- package/dist/src/relay/worker.d.ts.map +1 -0
- package/dist/src/relay/worker.js +177 -0
- package/dist/src/relay/worker.js.map +1 -0
- package/dist/src/ruleset_source.d.ts +73 -0
- package/dist/src/ruleset_source.d.ts.map +1 -0
- package/dist/src/ruleset_source.js +128 -0
- package/dist/src/ruleset_source.js.map +1 -0
- package/dist/src/runtime.d.ts +70 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +301 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/schema.d.ts +39 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +311 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/services/accessor.d.ts +5 -0
- package/dist/src/services/accessor.d.ts.map +1 -0
- package/dist/src/services/accessor.js +20 -0
- package/dist/src/services/accessor.js.map +1 -0
- package/dist/src/sinks/clickhouse.d.ts +41 -0
- package/dist/src/sinks/clickhouse.d.ts.map +1 -0
- package/dist/src/sinks/clickhouse.js +117 -0
- package/dist/src/sinks/clickhouse.js.map +1 -0
- package/dist/src/sinks/lucid_sink.d.ts +25 -0
- package/dist/src/sinks/lucid_sink.d.ts.map +1 -0
- package/dist/src/sinks/lucid_sink.js +87 -0
- package/dist/src/sinks/lucid_sink.js.map +1 -0
- package/dist/src/sinks/parquet_sink.d.ts +41 -0
- package/dist/src/sinks/parquet_sink.d.ts.map +1 -0
- package/dist/src/sinks/parquet_sink.js +140 -0
- package/dist/src/sinks/parquet_sink.js.map +1 -0
- package/dist/src/sinks/posthog_sink.d.ts +29 -0
- package/dist/src/sinks/posthog_sink.d.ts.map +1 -0
- package/dist/src/sinks/posthog_sink.js +65 -0
- package/dist/src/sinks/posthog_sink.js.map +1 -0
- package/dist/src/sinks/sink.d.ts +36 -0
- package/dist/src/sinks/sink.d.ts.map +1 -0
- package/dist/src/sinks/sink.js +36 -0
- package/dist/src/sinks/sink.js.map +1 -0
- package/dist/src/sql.d.ts +33 -0
- package/dist/src/sql.d.ts.map +1 -0
- package/dist/src/sql.js +66 -0
- package/dist/src/sql.js.map +1 -0
- package/dist/src/stores/lucid_store.d.ts +55 -0
- package/dist/src/stores/lucid_store.d.ts.map +1 -0
- package/dist/src/stores/lucid_store.js +412 -0
- package/dist/src/stores/lucid_store.js.map +1 -0
- package/dist/src/stores/memory_store.d.ts +38 -0
- package/dist/src/stores/memory_store.d.ts.map +1 -0
- package/dist/src/stores/memory_store.js +166 -0
- package/dist/src/stores/memory_store.js.map +1 -0
- package/dist/src/stores/store.d.ts +166 -0
- package/dist/src/stores/store.d.ts.map +1 -0
- package/dist/src/stores/store.js +67 -0
- package/dist/src/stores/store.js.map +1 -0
- package/dist/src/subject.d.ts +25 -0
- package/dist/src/subject.d.ts.map +1 -0
- package/dist/src/subject.js +30 -0
- package/dist/src/subject.js.map +1 -0
- package/dist/src/testing/index.d.ts +31 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +60 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/topology/browser_token.d.ts +48 -0
- package/dist/src/topology/browser_token.d.ts.map +1 -0
- package/dist/src/topology/browser_token.js +128 -0
- package/dist/src/topology/browser_token.js.map +1 -0
- package/dist/src/topology/remote_source.d.ts +39 -0
- package/dist/src/topology/remote_source.d.ts.map +1 -0
- package/dist/src/topology/remote_source.js +184 -0
- package/dist/src/topology/remote_source.js.map +1 -0
- package/dist/src/topology/token.d.ts +36 -0
- package/dist/src/topology/token.d.ts.map +1 -0
- package/dist/src/topology/token.js +68 -0
- package/dist/src/topology/token.js.map +1 -0
- package/dist/src/topology/verify.d.ts +88 -0
- package/dist/src/topology/verify.d.ts.map +1 -0
- package/dist/src/topology/verify.js +128 -0
- package/dist/src/topology/verify.js.map +1 -0
- package/dist/src/transports/db_transport.d.ts +55 -0
- package/dist/src/transports/db_transport.d.ts.map +1 -0
- package/dist/src/transports/db_transport.js +202 -0
- package/dist/src/transports/db_transport.js.map +1 -0
- package/dist/src/transports/http_transport.d.ts +30 -0
- package/dist/src/transports/http_transport.d.ts.map +1 -0
- package/dist/src/transports/http_transport.js +51 -0
- package/dist/src/transports/http_transport.js.map +1 -0
- package/dist/src/transports/memory_transport.d.ts +29 -0
- package/dist/src/transports/memory_transport.d.ts.map +1 -0
- package/dist/src/transports/memory_transport.js +91 -0
- package/dist/src/transports/memory_transport.js.map +1 -0
- package/dist/src/transports/redis_transport.d.ts +50 -0
- package/dist/src/transports/redis_transport.d.ts.map +1 -0
- package/dist/src/transports/redis_transport.js +107 -0
- package/dist/src/transports/redis_transport.js.map +1 -0
- package/dist/src/transports/transport.d.ts +62 -0
- package/dist/src/transports/transport.d.ts.map +1 -0
- package/dist/src/transports/transport.js +2 -0
- package/dist/src/transports/transport.js.map +1 -0
- package/dist/src/types.d.ts +30 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/usage.d.ts +18 -0
- package/dist/src/usage.d.ts.map +1 -0
- package/dist/src/usage.js +52 -0
- package/dist/src/usage.js.map +1 -0
- package/dist/stubs/config/flags.stub +50 -0
- package/dist/stubs/config/flags_dashboard.stub +35 -0
- package/dist/stubs/database/migrations/create_flags_tables.stub +27 -0
- package/dist/stubs/main.d.ts +3 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +9 -0
- package/dist/stubs/main.js.map +1 -0
- package/package.json +181 -0
- package/skills/flags-basics/SKILL.md +142 -0
- package/skills/flags-deployment/SKILL.md +125 -0
- package/skills/flags-experiments/SKILL.md +99 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { createFlagsTables, ensureEventPartitions } from '../schema.js';
|
|
2
|
+
import { dialectOf, insertIgnore, jsonValue, parseJson, query, toNumber, } from '../sql.js';
|
|
3
|
+
const COLUMNS = [
|
|
4
|
+
'event_id',
|
|
5
|
+
'schema_version',
|
|
6
|
+
'app',
|
|
7
|
+
'environment',
|
|
8
|
+
'type',
|
|
9
|
+
'flag',
|
|
10
|
+
'experiment',
|
|
11
|
+
'variant',
|
|
12
|
+
'name',
|
|
13
|
+
'subject',
|
|
14
|
+
'subject_type',
|
|
15
|
+
'value',
|
|
16
|
+
'props',
|
|
17
|
+
'ts',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* The event log in your database (`flags_events`), with one cursor per sink in
|
|
21
|
+
* `flags_sink_cursors`. The only transactional transport: `publish` accepts the caller's Lucid
|
|
22
|
+
* transaction.
|
|
23
|
+
*
|
|
24
|
+
* Leases are `UPDATE … WHERE lease expired` (no transaction held open while a sink sends), and the
|
|
25
|
+
* cursor only moves while the lease is still ours, so a replica that stalled past its lease cannot
|
|
26
|
+
* move it back. Delivery is at-least-once; sinks dedupe by event id.
|
|
27
|
+
*
|
|
28
|
+
* Gap safety: ids are assigned before commit, so reading strictly by id could skip an event whose
|
|
29
|
+
* transaction committed late. On Postgres the cursor is `(xid, id)` and the relay reads only rows of
|
|
30
|
+
* transactions older than the oldest still running (`pg_snapshot_xmin`), which closes the gap
|
|
31
|
+
* exactly. MySQL uses a time window (`mysqlSafetyMs`); SQLite serializes writers.
|
|
32
|
+
*/
|
|
33
|
+
export class DbTransport {
|
|
34
|
+
db;
|
|
35
|
+
options;
|
|
36
|
+
name = 'db';
|
|
37
|
+
capabilities = { transactional: true, durable: true };
|
|
38
|
+
dialect;
|
|
39
|
+
constructor(db, options = {}) {
|
|
40
|
+
this.db = db;
|
|
41
|
+
this.options = options;
|
|
42
|
+
this.dialect = dialectOf(db);
|
|
43
|
+
}
|
|
44
|
+
async setup() {
|
|
45
|
+
if (this.options.autoCreateSchema !== false)
|
|
46
|
+
await createFlagsTables(this.db, this.options);
|
|
47
|
+
else
|
|
48
|
+
await ensureEventPartitions(this.db, Date.now());
|
|
49
|
+
}
|
|
50
|
+
async publish(events, options = {}) {
|
|
51
|
+
if (events.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
const client = options.client ?? this.db;
|
|
54
|
+
// Chunked: one statement per 500 rows keeps every driver under its bind-parameter limit.
|
|
55
|
+
for (let i = 0; i < events.length; i += 500) {
|
|
56
|
+
const chunk = events.slice(i, i + 500);
|
|
57
|
+
const placeholders = chunk.map(() => `(${COLUMNS.map(() => '?').join(', ')})`).join(', ');
|
|
58
|
+
const bindings = chunk.flatMap((e) => [
|
|
59
|
+
e.id,
|
|
60
|
+
e.schemaVersion,
|
|
61
|
+
e.app,
|
|
62
|
+
e.environment,
|
|
63
|
+
e.type,
|
|
64
|
+
e.flag ?? null,
|
|
65
|
+
e.experiment ?? null,
|
|
66
|
+
e.variant ?? null,
|
|
67
|
+
e.name ?? null,
|
|
68
|
+
e.subject,
|
|
69
|
+
e.subjectType,
|
|
70
|
+
e.value ?? null,
|
|
71
|
+
jsonValue(e.props),
|
|
72
|
+
e.ts,
|
|
73
|
+
]);
|
|
74
|
+
await client.rawQuery(`INSERT INTO flags_events (${COLUMNS.join(', ')}) VALUES ${placeholders}`, bindings);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async consume(sink, options) {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
await this.db.rawQuery(insertIgnore(this.dialect, 'flags_sink_cursors', ['sink', 'last_id', 'last_tx', 'delivered', 'updated_at'], 1), [sink, 0, 0, 0, now]);
|
|
80
|
+
await this.db.rawQuery(`UPDATE flags_sink_cursors SET leased_by = ?, lease_until = ?
|
|
81
|
+
WHERE sink = ? AND (lease_until IS NULL OR lease_until < ? OR leased_by = ?)`, [options.owner, now + options.leaseMs, sink, now, options.owner]);
|
|
82
|
+
const cursorRows = await query(this.db, 'SELECT leased_by, last_id, last_tx FROM flags_sink_cursors WHERE sink = ?', [
|
|
83
|
+
sink,
|
|
84
|
+
]);
|
|
85
|
+
const cursor = cursorRows[0];
|
|
86
|
+
if (!cursor || cursor.leased_by !== options.owner)
|
|
87
|
+
return null;
|
|
88
|
+
const lastId = toNumber(cursor.last_id);
|
|
89
|
+
const lastTx = String(cursor.last_tx ?? 0);
|
|
90
|
+
const rows = await this.#read(lastTx, lastId, options.batchSize);
|
|
91
|
+
const events = rows.map(toEvent);
|
|
92
|
+
const tail = rows.at(-1);
|
|
93
|
+
const owner = options.owner;
|
|
94
|
+
let settled = false;
|
|
95
|
+
const once = async (fn) => {
|
|
96
|
+
if (settled)
|
|
97
|
+
return;
|
|
98
|
+
settled = true;
|
|
99
|
+
await fn();
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
events,
|
|
103
|
+
ack: () => once(async () => {
|
|
104
|
+
if (!tail)
|
|
105
|
+
return this.#release(sink, owner);
|
|
106
|
+
await this.db.rawQuery(`UPDATE flags_sink_cursors
|
|
107
|
+
SET last_id = ?, last_tx = ?, delivered = delivered + ?, last_error = NULL, updated_at = ?, lease_until = NULL
|
|
108
|
+
WHERE sink = ? AND leased_by = ?`, [
|
|
109
|
+
toNumber(tail.id),
|
|
110
|
+
tail.tx_text === undefined ? 0 : String(tail.tx_text),
|
|
111
|
+
events.length,
|
|
112
|
+
Date.now(),
|
|
113
|
+
sink,
|
|
114
|
+
owner,
|
|
115
|
+
]);
|
|
116
|
+
}),
|
|
117
|
+
release: () => once(() => this.#release(sink, owner)),
|
|
118
|
+
fail: (error) => once(async () => {
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
await this.db.rawQuery('UPDATE flags_sink_cursors SET last_error = ?, lease_until = NULL, updated_at = ? WHERE sink = ? AND leased_by = ?', [message.slice(0, 2000), Date.now(), sink, owner]);
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async #release(sink, owner) {
|
|
125
|
+
await this.db.rawQuery('UPDATE flags_sink_cursors SET lease_until = NULL WHERE sink = ? AND leased_by = ?', [sink, owner]);
|
|
126
|
+
}
|
|
127
|
+
async #read(lastTx, lastId, limit) {
|
|
128
|
+
if (this.dialect === 'postgres') {
|
|
129
|
+
return query(this.db, `SELECT *, tx::text AS tx_text FROM flags_events
|
|
130
|
+
WHERE (tx, id) > (?::text::xid8, ?)
|
|
131
|
+
AND tx < pg_snapshot_xmin(pg_current_snapshot())
|
|
132
|
+
ORDER BY tx, id
|
|
133
|
+
LIMIT ?`, [lastTx, lastId, limit]);
|
|
134
|
+
}
|
|
135
|
+
if (this.dialect === 'mysql') {
|
|
136
|
+
const horizon = Date.now() - (this.options.mysqlSafetyMs ?? 10_000);
|
|
137
|
+
return query(this.db, 'SELECT * FROM flags_events WHERE id > ? AND ts < ? ORDER BY id LIMIT ?', [lastId, horizon, limit]);
|
|
138
|
+
}
|
|
139
|
+
return query(this.db, 'SELECT * FROM flags_events WHERE id > ? ORDER BY id LIMIT ?', [
|
|
140
|
+
lastId,
|
|
141
|
+
limit,
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
async rewind(sink) {
|
|
145
|
+
await this.db.rawQuery('UPDATE flags_sink_cursors SET last_id = 0, last_tx = 0, delivered = 0, last_error = NULL, updated_at = ? WHERE sink = ?', [Date.now(), sink]);
|
|
146
|
+
}
|
|
147
|
+
async status(sinks) {
|
|
148
|
+
const cursors = await query(this.db, 'SELECT * FROM flags_sink_cursors');
|
|
149
|
+
const byName = new Map(cursors.map((row) => [String(row.sink), row]));
|
|
150
|
+
const maxRows = await query(this.db, 'SELECT MAX(id) AS max FROM flags_events');
|
|
151
|
+
const maxId = toNumber(maxRows[0]?.max ?? 0);
|
|
152
|
+
const out = [];
|
|
153
|
+
for (const sink of sinks) {
|
|
154
|
+
const row = byName.get(sink);
|
|
155
|
+
const lastId = toNumber(row?.last_id ?? 0);
|
|
156
|
+
// Exact lag would be a COUNT over the log; ids are dense enough for an estimate.
|
|
157
|
+
out.push({
|
|
158
|
+
sink,
|
|
159
|
+
lag: Math.max(0, maxId - lastId),
|
|
160
|
+
delivered: toNumber(row?.delivered ?? 0),
|
|
161
|
+
leasedBy: row?.leased_by && toNumber(row.lease_until) > Date.now() ? String(row.leased_by) : null,
|
|
162
|
+
lastError: row?.last_error ? String(row.last_error) : null,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
async safePruneId(sinks) {
|
|
168
|
+
if (sinks.length === 0) {
|
|
169
|
+
const maxRows = await query(this.db, 'SELECT MAX(id) AS max FROM flags_events');
|
|
170
|
+
return toNumber(maxRows[0]?.max ?? 0);
|
|
171
|
+
}
|
|
172
|
+
const placeholders = sinks.map(() => '?').join(', ');
|
|
173
|
+
const rows = await query(this.db, `SELECT MIN(last_id) AS min, COUNT(*) AS count FROM flags_sink_cursors WHERE sink IN (${placeholders})`, sinks);
|
|
174
|
+
// A sink that never consumed has no cursor row yet: nothing is safe to prune.
|
|
175
|
+
if (toNumber(rows[0]?.count ?? 0) < sinks.length)
|
|
176
|
+
return 0;
|
|
177
|
+
return toNumber(rows[0]?.min ?? 0);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
export function toEvent(row) {
|
|
181
|
+
return {
|
|
182
|
+
id: String(row.event_id),
|
|
183
|
+
schemaVersion: toNumber(row.schema_version),
|
|
184
|
+
app: String(row.app),
|
|
185
|
+
environment: String(row.environment),
|
|
186
|
+
type: row.type,
|
|
187
|
+
...(row.flag !== null && row.flag !== undefined ? { flag: String(row.flag) } : {}),
|
|
188
|
+
...(row.experiment !== null && row.experiment !== undefined
|
|
189
|
+
? { experiment: String(row.experiment) }
|
|
190
|
+
: {}),
|
|
191
|
+
...(row.variant !== null && row.variant !== undefined ? { variant: String(row.variant) } : {}),
|
|
192
|
+
...(row.name !== null && row.name !== undefined ? { name: String(row.name) } : {}),
|
|
193
|
+
subject: String(row.subject),
|
|
194
|
+
subjectType: String(row.subject_type),
|
|
195
|
+
...(row.value !== null && row.value !== undefined ? { value: toNumber(row.value) } : {}),
|
|
196
|
+
...(row.props !== null && row.props !== undefined
|
|
197
|
+
? { props: parseJson(row.props) }
|
|
198
|
+
: {}),
|
|
199
|
+
ts: toNumber(row.ts),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=db_transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db_transport.js","sourceRoot":"","sources":["../../../src/transports/db_transport.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAsB,MAAM,cAAc,CAAC;AAC5F,OAAO,EAEL,SAAS,EACT,YAAY,EACZ,SAAS,EACT,SAAS,EACT,KAAK,EAGL,QAAQ,GACT,MAAM,WAAW,CAAC;AAenB,MAAM,OAAO,GAAG;IACd,UAAU;IACV,gBAAgB;IAChB,KAAK;IACL,aAAa;IACb,MAAM;IACN,MAAM;IACN,YAAY;IACZ,SAAS;IACT,MAAM;IACN,SAAS;IACT,cAAc;IACd,OAAO;IACP,OAAO;IACP,IAAI;CACL,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,WAAW;IAMX,EAAE;IACM,OAAO;IANjB,IAAI,GAAG,IAAI,CAAC;IACZ,YAAY,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACtD,OAAO,CAAU;IAE1B,YACW,EAAe,EACP,OAAO,GAAuB,EAAE;kBADxC,EAAE;uBACM,OAAO;QAExB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,KAAK,KAAK;YAAE,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;;YACvF,MAAM,qBAAqB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAmB,EAAE,OAAO,GAAmB,EAAE;QAC7D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC;QACzC,yFAAyF;QACzF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC;YACvC,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1F,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACpC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,aAAa;gBACf,CAAC,CAAC,GAAG;gBACL,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI,IAAI,IAAI;gBACd,CAAC,CAAC,UAAU,IAAI,IAAI;gBACpB,CAAC,CAAC,OAAO,IAAI,IAAI;gBACjB,CAAC,CAAC,IAAI,IAAI,IAAI;gBACd,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,KAAK,IAAI,IAAI;gBACf,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;gBAClB,CAAC,CAAC,EAAE;aACL,CAAC,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CACnB,6BAA6B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,YAAY,EAAE,EACzE,QAAQ,CACT,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAY,EACZ,OAA8D;QAE9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,CAAC,EAAE,CAAC,QAAQ,CACpB,YAAY,CACV,IAAI,CAAC,OAAO,EACZ,oBAAoB,EACpB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,CAAC,EACzD,CAAC,CACF,EACD,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CACrB,CAAC;QACF,MAAM,IAAI,CAAC,EAAE,CAAC,QAAQ,CACpB;qFAC+E,EAC/E,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,CACjE,CAAC;QACF,MAAM,UAAU,GAAG,MAAM,KAAK,CAI3B,IAAI,CAAC,EAAE,EAAE,2EAA2E,EAAE;YACvF,IAAI;SACL,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,KAAK,OAAO,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC/D,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;QAE3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC5B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,EAAE,EAAuB,EAAE,EAAE;YAC7C,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,MAAM,EAAE,EAAE,CAAC;QACb,CAAC,CAAC;QACF,OAAO;YACL,MAAM;YACN,GAAG,EAAE,GAAG,EAAE,CACR,IAAI,CAAC,KAAK,IAAI,EAAE;gBACd,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAC7C,MAAM,IAAI,CAAC,EAAE,CAAC,QAAQ,CACpB;;+CAEmC,EACnC;oBACE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjB,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;oBACrD,MAAM,CAAC,MAAM;oBACb,IAAI,CAAC,GAAG,EAAE;oBACV,IAAI;oBACJ,KAAK;iBACN,CACF,CAAC;YACJ,CAAC,CAAC;YACJ,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrD,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACd,IAAI,CAAC,KAAK,IAAI,EAAE;gBACd,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvE,MAAM,IAAI,CAAC,EAAE,CAAC,QAAQ,CACpB,mHAAmH,EACnH,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAClD,CAAC;YACJ,CAAC,CAAC;SACL,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,KAAa;QACxC,MAAM,IAAI,CAAC,EAAE,CAAC,QAAQ,CACpB,mFAAmF,EACnF,CAAC,IAAI,EAAE,KAAK,CAAC,CACd,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CACT,MAAc,EACd,MAAc,EACd,KAAa;QAEb,IAAI,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAChC,OAAO,KAAK,CACV,IAAI,CAAC,EAAE,EACP;;;;kBAIU,EACV,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CACxB,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,MAAM,CAAC,CAAC;YACpE,OAAO,KAAK,CACV,IAAI,CAAC,EAAE,EACP,wEAAwE,EACxE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CACzB,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,6DAA6D,EAAE;YACnF,MAAM;YACN,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,IAAI,CAAC,EAAE,CAAC,QAAQ,CACpB,yHAAyH,EACzH,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CACnB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAe;QAC1B,MAAM,OAAO,GAAG,MAAM,KAAK,CACzB,IAAI,CAAC,EAAE,EACP,kCAAkC,CACnC,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,MAAM,KAAK,CACzB,IAAI,CAAC,EAAE,EACP,yCAAyC,CAC1C,CAAC;QACF,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAiB,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC;YAC3C,iFAAiF;YACjF,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;gBAChC,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,SAAS,IAAI,CAAC,CAAC;gBACxC,QAAQ,EACN,GAAG,EAAE,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;gBACzF,SAAS,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;aAC3D,CAAC,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAe;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,MAAM,KAAK,CACzB,IAAI,CAAC,EAAE,EACP,yCAAyC,CAC1C,CAAC;YACF,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QACxC,CAAC;QACD,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,KAAK,CACtB,IAAI,CAAC,EAAE,EACP,wFAAwF,YAAY,GAAG,EACvG,KAAK,CACN,CAAC;QACF,8EAA8E;QAC9E,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM;YAAE,OAAO,CAAC,CAAC;QAC3D,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,GAA4B;IAClD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACxB,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC;QAC3C,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACpC,IAAI,EAAE,GAAG,CAAC,IAAyB;QACnC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS;YACzD,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YACxC,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9F,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;QAC5B,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;QACrC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;YAC/C,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAA0B,GAAG,CAAC,KAAK,CAAC,EAAE;YAC1D,CAAC,CAAC,EAAE,CAAC;QACP,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;KACrB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FlagEvent } from '../events/event.js';
|
|
2
|
+
import type { TokenSource } from '../topology/token.js';
|
|
3
|
+
import type { Transport } from './transport.js';
|
|
4
|
+
export interface HttpTransportOptions {
|
|
5
|
+
/** The control-plane base URL (`https://flags.example.com/_flags`). */
|
|
6
|
+
url: string;
|
|
7
|
+
token: TokenSource;
|
|
8
|
+
/** The tenant's partition, sent as `x-flags-tenant` (the token decides what it really is). */
|
|
9
|
+
partition: string;
|
|
10
|
+
/** Retries per batch on network errors and 5xx. Default 3. */
|
|
11
|
+
retries?: number;
|
|
12
|
+
fetch?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A tenant's transport: batches go to the control-plane over HTTP, which writes them into its own
|
|
16
|
+
* transport and fans them out to the sinks. Nothing is stored locally, so there is no relay on a
|
|
17
|
+
* tenant — the buffer in front of it retries, and events still unsent when the process dies are lost.
|
|
18
|
+
*/
|
|
19
|
+
export declare class HttpTransport implements Transport {
|
|
20
|
+
#private;
|
|
21
|
+
private readonly options;
|
|
22
|
+
readonly name = "http";
|
|
23
|
+
readonly capabilities: {
|
|
24
|
+
transactional: boolean;
|
|
25
|
+
durable: boolean;
|
|
26
|
+
};
|
|
27
|
+
constructor(options: HttpTransportOptions);
|
|
28
|
+
publish(events: FlagEvent[]): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=http_transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http_transport.d.ts","sourceRoot":"","sources":["../../../src/transports/http_transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,aAAc,YAAW,SAAS;;IAKjC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,YAAY;QAAK,aAAa;QAAS,OAAO;MAAU;IAGjE,YAA6B,OAAO,EAAE,oBAAoB,EAEzD;IAEK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgChD;CACF"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tenant's transport: batches go to the control-plane over HTTP, which writes them into its own
|
|
3
|
+
* transport and fans them out to the sinks. Nothing is stored locally, so there is no relay on a
|
|
4
|
+
* tenant — the buffer in front of it retries, and events still unsent when the process dies are lost.
|
|
5
|
+
*/
|
|
6
|
+
export class HttpTransport {
|
|
7
|
+
options;
|
|
8
|
+
name = 'http';
|
|
9
|
+
capabilities = { transactional: false, durable: false };
|
|
10
|
+
#fetch;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.#fetch = options.fetch ?? fetch;
|
|
14
|
+
}
|
|
15
|
+
async publish(events) {
|
|
16
|
+
if (events.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
const retries = this.options.retries ?? 3;
|
|
19
|
+
let lastError;
|
|
20
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
21
|
+
try {
|
|
22
|
+
const response = await this.#fetch(`${this.options.url.replace(/\/$/, '')}/v1/events`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'content-type': 'application/json',
|
|
26
|
+
authorization: `Bearer ${await this.options.token.get()}`,
|
|
27
|
+
'x-flags-tenant': this.options.partition,
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({ events }),
|
|
30
|
+
});
|
|
31
|
+
if (response.ok)
|
|
32
|
+
return;
|
|
33
|
+
if (response.status === 401)
|
|
34
|
+
this.options.token.invalidate?.();
|
|
35
|
+
// 4xx other than 401/429 will not succeed on retry.
|
|
36
|
+
if (response.status < 500 && response.status !== 429 && response.status !== 401) {
|
|
37
|
+
throw new Error(`control-plane rejected events: ${response.status} ${await response.text()}`);
|
|
38
|
+
}
|
|
39
|
+
lastError = new Error(`control-plane answered ${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
lastError = error;
|
|
43
|
+
if (error instanceof Error && error.message.startsWith('control-plane rejected'))
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, 200 * 2 ** attempt));
|
|
47
|
+
}
|
|
48
|
+
throw lastError;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=http_transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http_transport.js","sourceRoot":"","sources":["../../../src/transports/http_transport.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,MAAM,OAAO,aAAa;IAKK,OAAO;IAJ3B,IAAI,GAAG,MAAM,CAAC;IACd,YAAY,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACxD,MAAM,CAAe;IAE9B,YAA6B,OAA6B;uBAA7B,OAAO;QAClC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAmB;QAC/B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;QAC1C,IAAI,SAAkB,CAAC;QACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;YACpD,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,EAAE;oBACrF,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,aAAa,EAAE,UAAU,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE;wBACzD,gBAAgB,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;qBACzC;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;iBACjC,CAAC,CAAC;gBACH,IAAI,QAAQ,CAAC,EAAE;oBAAE,OAAO;gBACxB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;oBAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC;gBAC/D,oDAAoD;gBACpD,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;oBAChF,MAAM,IAAI,KAAK,CACb,kCAAkC,QAAQ,CAAC,MAAM,IAAI,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,CAC7E,CAAC;gBACJ,CAAC;gBACD,SAAS,GAAG,IAAI,KAAK,CAAC,0BAA0B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACrE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAK,CAAC;gBAClB,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,wBAAwB,CAAC;oBAC9E,MAAM,KAAK,CAAC;YAChB,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,SAAS,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FlagEvent } from '../events/event.js';
|
|
2
|
+
import type { Delivery, SinkStatus, Transport } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* An in-process log. For tests and local development: nothing survives a restart, and replicas do
|
|
5
|
+
* not share it. Cursors and leases behave like the `db` transport's so code exercised against it
|
|
6
|
+
* behaves the same in production.
|
|
7
|
+
*/
|
|
8
|
+
export declare class MemoryTransport implements Transport {
|
|
9
|
+
#private;
|
|
10
|
+
readonly name = "memory";
|
|
11
|
+
readonly capabilities: {
|
|
12
|
+
transactional: boolean;
|
|
13
|
+
durable: boolean;
|
|
14
|
+
};
|
|
15
|
+
readonly log: Array<{
|
|
16
|
+
id: number;
|
|
17
|
+
event: FlagEvent;
|
|
18
|
+
}>;
|
|
19
|
+
publish(events: FlagEvent[]): Promise<void>;
|
|
20
|
+
consume(sink: string, options: {
|
|
21
|
+
batchSize: number;
|
|
22
|
+
leaseMs: number;
|
|
23
|
+
owner: string;
|
|
24
|
+
}): Promise<Delivery | null>;
|
|
25
|
+
rewind(sink: string): Promise<void>;
|
|
26
|
+
status(sinks: string[]): Promise<SinkStatus[]>;
|
|
27
|
+
safePruneId(sinks: string[]): Promise<number>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=memory_transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory_transport.d.ts","sourceRoot":"","sources":["../../../src/transports/memory_transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEtE;;;;GAIG;AACH,qBAAa,eAAgB,YAAW,SAAS;;IAC/C,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,YAAY;QAAK,aAAa;QAAS,OAAO;MAAU;IACjE,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC,CAAM;IAarD,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhD;IAWK,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC7D,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqC1B;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxC;IAEK,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAYnD;IAEK,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlD;CACF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-process log. For tests and local development: nothing survives a restart, and replicas do
|
|
3
|
+
* not share it. Cursors and leases behave like the `db` transport's so code exercised against it
|
|
4
|
+
* behaves the same in production.
|
|
5
|
+
*/
|
|
6
|
+
export class MemoryTransport {
|
|
7
|
+
name = 'memory';
|
|
8
|
+
capabilities = { transactional: false, durable: false };
|
|
9
|
+
log = [];
|
|
10
|
+
#nextId = 1;
|
|
11
|
+
#cursors = new Map();
|
|
12
|
+
async publish(events) {
|
|
13
|
+
for (const event of events)
|
|
14
|
+
this.log.push({ id: this.#nextId++, event: structuredClone(event) });
|
|
15
|
+
}
|
|
16
|
+
#cursor(sink) {
|
|
17
|
+
let cursor = this.#cursors.get(sink);
|
|
18
|
+
if (!cursor) {
|
|
19
|
+
cursor = { lastId: 0, delivered: 0, leasedBy: null, leaseUntil: 0, lastError: null };
|
|
20
|
+
this.#cursors.set(sink, cursor);
|
|
21
|
+
}
|
|
22
|
+
return cursor;
|
|
23
|
+
}
|
|
24
|
+
async consume(sink, options) {
|
|
25
|
+
const cursor = this.#cursor(sink);
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
if (cursor.leasedBy && cursor.leasedBy !== options.owner && cursor.leaseUntil > now)
|
|
28
|
+
return null;
|
|
29
|
+
cursor.leasedBy = options.owner;
|
|
30
|
+
cursor.leaseUntil = now + options.leaseMs;
|
|
31
|
+
const batch = this.log.filter((entry) => entry.id > cursor.lastId).slice(0, options.batchSize);
|
|
32
|
+
let settled = false;
|
|
33
|
+
const release = () => {
|
|
34
|
+
if (cursor.leasedBy === options.owner)
|
|
35
|
+
cursor.leaseUntil = 0;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
events: batch.map((entry) => structuredClone(entry.event)),
|
|
39
|
+
ack: async () => {
|
|
40
|
+
if (settled)
|
|
41
|
+
return;
|
|
42
|
+
settled = true;
|
|
43
|
+
if (cursor.leasedBy !== options.owner)
|
|
44
|
+
return;
|
|
45
|
+
if (batch.length > 0) {
|
|
46
|
+
cursor.lastId = batch.at(-1).id;
|
|
47
|
+
cursor.delivered += batch.length;
|
|
48
|
+
}
|
|
49
|
+
cursor.lastError = null;
|
|
50
|
+
release();
|
|
51
|
+
},
|
|
52
|
+
release: async () => {
|
|
53
|
+
if (settled)
|
|
54
|
+
return;
|
|
55
|
+
settled = true;
|
|
56
|
+
release();
|
|
57
|
+
},
|
|
58
|
+
fail: async (error) => {
|
|
59
|
+
if (settled)
|
|
60
|
+
return;
|
|
61
|
+
settled = true;
|
|
62
|
+
cursor.lastError = error instanceof Error ? error.message : String(error);
|
|
63
|
+
release();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async rewind(sink) {
|
|
68
|
+
const cursor = this.#cursor(sink);
|
|
69
|
+
cursor.lastId = 0;
|
|
70
|
+
cursor.delivered = 0;
|
|
71
|
+
}
|
|
72
|
+
async status(sinks) {
|
|
73
|
+
const maxId = this.log.at(-1)?.id ?? 0;
|
|
74
|
+
return sinks.map((sink) => {
|
|
75
|
+
const cursor = this.#cursor(sink);
|
|
76
|
+
return {
|
|
77
|
+
sink,
|
|
78
|
+
lag: Math.max(0, maxId - cursor.lastId),
|
|
79
|
+
delivered: cursor.delivered,
|
|
80
|
+
leasedBy: cursor.leaseUntil > Date.now() ? cursor.leasedBy : null,
|
|
81
|
+
lastError: cursor.lastError,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async safePruneId(sinks) {
|
|
86
|
+
if (sinks.length === 0)
|
|
87
|
+
return this.log.at(-1)?.id ?? 0;
|
|
88
|
+
return Math.min(...sinks.map((sink) => this.#cursor(sink).lastId));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=memory_transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory_transport.js","sourceRoot":"","sources":["../../../src/transports/memory_transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { FlagEvent } from '../events/event.js';
|
|
2
|
+
import type { Delivery, SinkStatus, Transport } from './transport.js';
|
|
3
|
+
/** The ioredis commands this transport uses (what `@adonisjs/redis` connections expose). */
|
|
4
|
+
export interface RedisStreamClient {
|
|
5
|
+
xadd(...args: Array<string | number>): Promise<unknown>;
|
|
6
|
+
xgroup(...args: Array<string | number>): Promise<unknown>;
|
|
7
|
+
xreadgroup(...args: Array<string | number>): Promise<unknown>;
|
|
8
|
+
xautoclaim(...args: Array<string | number>): Promise<unknown>;
|
|
9
|
+
xack(...args: Array<string | number>): Promise<unknown>;
|
|
10
|
+
xinfo(...args: Array<string | number>): Promise<unknown>;
|
|
11
|
+
xpending(...args: Array<string | number>): Promise<unknown>;
|
|
12
|
+
pipeline?(): {
|
|
13
|
+
xadd(...args: Array<string | number>): unknown;
|
|
14
|
+
exec(): Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface RedisTransportOptions {
|
|
18
|
+
/** Stream key. Default `agora:flags:events`. */
|
|
19
|
+
stream?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Approximate cap on the stream length (`XADD MAXLEN ~`). Entries beyond it are trimmed even if a
|
|
22
|
+
* sink has not read them, so size it for the longest outage you want to absorb. Default 1,000,000.
|
|
23
|
+
*/
|
|
24
|
+
maxLength?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Redis Streams: one consumer group per sink, so each sink reads at its own pace and a message is
|
|
28
|
+
* delivered to one consumer of the group. A consumer that dies mid-batch leaves its messages
|
|
29
|
+
* pending; they are reclaimed (`XAUTOCLAIM`) once idle for longer than the lease. Durability is
|
|
30
|
+
* whatever the Redis server's persistence (AOF) gives; it is never transactional with the database.
|
|
31
|
+
*/
|
|
32
|
+
export declare class RedisTransport implements Transport {
|
|
33
|
+
#private;
|
|
34
|
+
private readonly redis;
|
|
35
|
+
readonly name = "redis";
|
|
36
|
+
readonly capabilities: {
|
|
37
|
+
transactional: boolean;
|
|
38
|
+
durable: boolean;
|
|
39
|
+
};
|
|
40
|
+
readonly stream: string;
|
|
41
|
+
constructor(redis: RedisStreamClient, options?: RedisTransportOptions);
|
|
42
|
+
publish(events: FlagEvent[]): Promise<void>;
|
|
43
|
+
consume(sink: string, options: {
|
|
44
|
+
batchSize: number;
|
|
45
|
+
leaseMs: number;
|
|
46
|
+
owner: string;
|
|
47
|
+
}): Promise<Delivery | null>;
|
|
48
|
+
status(sinks: string[]): Promise<SinkStatus[]>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=redis_transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis_transport.d.ts","sourceRoot":"","sources":["../../../src/transports/redis_transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEtE,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,UAAU,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,UAAU,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,KAAK,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,QAAQ,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,QAAQ,CAAC,IAAI;QAAE,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC;QAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;CAC3F;AAED,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,SAAS;;IAQ5C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAPxB,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,YAAY;QAAK,aAAa;QAAS,OAAO;MAAS;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAIxB,YACmB,KAAK,EAAE,iBAAiB,EACzC,OAAO,GAAE,qBAA0B,EAIpC;IAEK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAahD;IAaK,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC7D,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAgD1B;IAEK,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAwBnD;CACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Streams: one consumer group per sink, so each sink reads at its own pace and a message is
|
|
3
|
+
* delivered to one consumer of the group. A consumer that dies mid-batch leaves its messages
|
|
4
|
+
* pending; they are reclaimed (`XAUTOCLAIM`) once idle for longer than the lease. Durability is
|
|
5
|
+
* whatever the Redis server's persistence (AOF) gives; it is never transactional with the database.
|
|
6
|
+
*/
|
|
7
|
+
export class RedisTransport {
|
|
8
|
+
redis;
|
|
9
|
+
name = 'redis';
|
|
10
|
+
capabilities = { transactional: false, durable: true };
|
|
11
|
+
stream;
|
|
12
|
+
#maxLength;
|
|
13
|
+
#groups = new Set();
|
|
14
|
+
constructor(redis, options = {}) {
|
|
15
|
+
this.redis = redis;
|
|
16
|
+
this.stream = options.stream ?? 'agora:flags:events';
|
|
17
|
+
this.#maxLength = options.maxLength ?? 1_000_000;
|
|
18
|
+
}
|
|
19
|
+
async publish(events) {
|
|
20
|
+
if (events.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const pipeline = this.redis.pipeline?.();
|
|
23
|
+
for (const event of events) {
|
|
24
|
+
const args = [this.stream, 'MAXLEN', '~', this.#maxLength, '*', 'e', JSON.stringify(event)];
|
|
25
|
+
if (pipeline)
|
|
26
|
+
pipeline.xadd(...args);
|
|
27
|
+
else
|
|
28
|
+
await this.redis.xadd(...args);
|
|
29
|
+
}
|
|
30
|
+
if (pipeline) {
|
|
31
|
+
const results = (await pipeline.exec());
|
|
32
|
+
const failed = results?.find(([error]) => error);
|
|
33
|
+
if (failed)
|
|
34
|
+
throw failed[0];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async #ensureGroup(sink) {
|
|
38
|
+
if (this.#groups.has(sink))
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
// `0`: a new sink starts from the oldest retained entry (backfill), like a db cursor at 0.
|
|
42
|
+
await this.redis.xgroup('CREATE', this.stream, sink, '0', 'MKSTREAM');
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!/BUSYGROUP/.test(String(error.message)))
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
this.#groups.add(sink);
|
|
49
|
+
}
|
|
50
|
+
async consume(sink, options) {
|
|
51
|
+
await this.#ensureGroup(sink);
|
|
52
|
+
// Reclaim entries another consumer leased and abandoned, then read new ones.
|
|
53
|
+
const claimed = (await this.redis.xautoclaim(this.stream, sink, options.owner, options.leaseMs, '0-0', 'COUNT', options.batchSize));
|
|
54
|
+
let entries = (claimed?.[1] ?? []).filter((entry) => Array.isArray(entry));
|
|
55
|
+
if (entries.length < options.batchSize) {
|
|
56
|
+
const read = (await this.redis.xreadgroup('GROUP', sink, options.owner, 'COUNT', options.batchSize - entries.length, 'STREAMS', this.stream, '>'));
|
|
57
|
+
entries = entries.concat(read?.[0]?.[1] ?? []);
|
|
58
|
+
}
|
|
59
|
+
const ids = entries.map(([id]) => id);
|
|
60
|
+
const events = entries.map(([, fields]) => JSON.parse(fields[fields.indexOf('e') + 1]));
|
|
61
|
+
let settled = false;
|
|
62
|
+
const settle = async (fn) => {
|
|
63
|
+
if (settled)
|
|
64
|
+
return;
|
|
65
|
+
settled = true;
|
|
66
|
+
await fn();
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
events,
|
|
70
|
+
ack: () => settle(async () => {
|
|
71
|
+
if (ids.length > 0)
|
|
72
|
+
await this.redis.xack(this.stream, sink, ...ids);
|
|
73
|
+
}),
|
|
74
|
+
// Unacked entries stay pending and are reclaimed after the lease; nothing to undo.
|
|
75
|
+
release: () => settle(async () => { }),
|
|
76
|
+
fail: () => settle(async () => { }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async status(sinks) {
|
|
80
|
+
const out = [];
|
|
81
|
+
let groups = [];
|
|
82
|
+
try {
|
|
83
|
+
const raw = (await this.redis.xinfo('GROUPS', this.stream));
|
|
84
|
+
groups = raw.map((pairs) => {
|
|
85
|
+
const record = {};
|
|
86
|
+
for (let i = 0; i < pairs.length; i += 2)
|
|
87
|
+
record[String(pairs[i])] = pairs[i + 1];
|
|
88
|
+
return record;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// No stream yet.
|
|
93
|
+
}
|
|
94
|
+
for (const sink of sinks) {
|
|
95
|
+
const group = groups.find((g) => g.name === sink);
|
|
96
|
+
out.push({
|
|
97
|
+
sink,
|
|
98
|
+
lag: Number(group?.lag ?? 0) + Number(group?.pending ?? 0),
|
|
99
|
+
delivered: Number(group?.['entries-read'] ?? 0),
|
|
100
|
+
leasedBy: null,
|
|
101
|
+
lastError: null,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=redis_transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis_transport.js","sourceRoot":"","sources":["../../../src/transports/redis_transport.ts"],"names":[],"mappings":"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"}
|