@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,105 @@
|
|
|
1
|
+
import { channel } from 'node:diagnostics_channel';
|
|
2
|
+
/** Channel names, following the ecosystem's `agora:<lib>:<event>` convention. */
|
|
3
|
+
export const DIAGNOSTICS_CHANNELS = {
|
|
4
|
+
evaluation: 'agora:flags:evaluation',
|
|
5
|
+
exposure: 'agora:flags:exposure',
|
|
6
|
+
track: 'agora:flags:track',
|
|
7
|
+
change: 'agora:flags:change',
|
|
8
|
+
};
|
|
9
|
+
export const enrichers = {
|
|
10
|
+
/**
|
|
11
|
+
* Publishes evaluations, exposures and tracks on `node:diagnostics_channel` (`agora:flags:*`):
|
|
12
|
+
* `@adonis-agora/diagnostics` and telescope pick them up. Free when nobody subscribes.
|
|
13
|
+
*/
|
|
14
|
+
diagnostics() {
|
|
15
|
+
const evaluation = channel(DIAGNOSTICS_CHANNELS.evaluation);
|
|
16
|
+
const exposure = channel(DIAGNOSTICS_CHANNELS.exposure);
|
|
17
|
+
const track = channel(DIAGNOSTICS_CHANNELS.track);
|
|
18
|
+
return {
|
|
19
|
+
name: 'diagnostics',
|
|
20
|
+
onEvaluation(details, context) {
|
|
21
|
+
if (evaluation.hasSubscribers) {
|
|
22
|
+
evaluation.publish({
|
|
23
|
+
flag: details.flagKey,
|
|
24
|
+
variant: details.variant,
|
|
25
|
+
reason: details.reason,
|
|
26
|
+
errorCode: details.errorCode,
|
|
27
|
+
subject: context.targetingKey,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
onEvent(event) {
|
|
32
|
+
const target = event.type === 'exposure' ? exposure : track;
|
|
33
|
+
if (target.hasSubscribers)
|
|
34
|
+
target.publish(event);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* OpenTelemetry: a `feature_flag.evaluation` event on the active span, with the semantic
|
|
40
|
+
* convention attributes (`feature_flag.key`, `feature_flag.result.variant`, …). Needs
|
|
41
|
+
* `@opentelemetry/api` (optional peer); without it this enricher does nothing.
|
|
42
|
+
*/
|
|
43
|
+
otel() {
|
|
44
|
+
let api;
|
|
45
|
+
return {
|
|
46
|
+
name: 'otel',
|
|
47
|
+
async setup() {
|
|
48
|
+
try {
|
|
49
|
+
api = (await import('@opentelemetry/api'));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
api = undefined;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
onEvaluation(details, context) {
|
|
56
|
+
const span = api?.trace.getActiveSpan();
|
|
57
|
+
if (!span)
|
|
58
|
+
return;
|
|
59
|
+
span.addEvent('feature_flag.evaluation', {
|
|
60
|
+
'feature_flag.key': details.flagKey,
|
|
61
|
+
'feature_flag.provider.name': '@adonis-agora/flags',
|
|
62
|
+
'feature_flag.result.variant': details.variant,
|
|
63
|
+
'feature_flag.result.reason': details.reason.toLowerCase(),
|
|
64
|
+
...(details.errorCode ? { 'error.type': details.errorCode.toLowerCase() } : {}),
|
|
65
|
+
...(context.targetingKey ? { 'feature_flag.context.id': context.targetingKey } : {}),
|
|
66
|
+
...(typeof details.flagMetadata.rulesetVersion === 'string'
|
|
67
|
+
? { 'feature_flag.version': details.flagMetadata.rulesetVersion }
|
|
68
|
+
: {}),
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Sentry: records each evaluated flag on the current scope, so an error report lists the flags
|
|
75
|
+
* the request saw. Pass your Sentry module: `enrichers.sentry(Sentry)`. Uses Sentry's
|
|
76
|
+
* feature-flags integration when it is installed, and a `flag.<key>` tag otherwise.
|
|
77
|
+
*/
|
|
78
|
+
sentry(sentry) {
|
|
79
|
+
return {
|
|
80
|
+
name: 'sentry',
|
|
81
|
+
onEvaluation(details) {
|
|
82
|
+
const integration = sentry.getClient?.()?.getIntegrationByName?.('FeatureFlags');
|
|
83
|
+
if (integration?.addFeatureFlag) {
|
|
84
|
+
integration.addFeatureFlag(details.flagKey, details.value);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
sentry
|
|
88
|
+
.getCurrentScope?.()
|
|
89
|
+
?.setTag?.(`flag.${details.flagKey}`, String(details.variant ?? details.value));
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
/** Runs enrichers without ever letting one break evaluation. */
|
|
95
|
+
export function runEnrichers(list, fn) {
|
|
96
|
+
for (const enricher of list) {
|
|
97
|
+
try {
|
|
98
|
+
fn(enricher);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Enrichers are best-effort by contract.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=enrichers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrichers.js","sourceRoot":"","sources":["../../src/enrichers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Transport } from '../transports/transport.js';
|
|
2
|
+
import type { FlagEvent } from './event.js';
|
|
3
|
+
export interface BufferOptions {
|
|
4
|
+
/** Flush when this many events are waiting. Default 500. */
|
|
5
|
+
maxSize?: number;
|
|
6
|
+
/** Flush at least this often (ms). Default 1000. */
|
|
7
|
+
flushMs?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Hard cap while the transport is failing. Beyond it the OLDEST events are dropped (and counted)
|
|
10
|
+
* so a long outage cannot exhaust memory. Default 50,000.
|
|
11
|
+
*/
|
|
12
|
+
maxQueue?: number;
|
|
13
|
+
/** Exposure ids remembered to skip repeats in this process. Default 100,000. */
|
|
14
|
+
dedupeSize?: number;
|
|
15
|
+
onError?: (error: unknown) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Batches events in process before they reach the transport, so a busy page does not turn into
|
|
19
|
+
* one INSERT per exposure. Exposures are also deduped here: the same subject seeing the same
|
|
20
|
+
* experiment on every request produces one event per process, not one per request (the sinks
|
|
21
|
+
* dedupe the rest).
|
|
22
|
+
*/
|
|
23
|
+
export declare class EventBuffer {
|
|
24
|
+
#private;
|
|
25
|
+
private readonly transport;
|
|
26
|
+
private readonly options;
|
|
27
|
+
dropped: number;
|
|
28
|
+
published: number;
|
|
29
|
+
constructor(transport: Transport, options?: BufferOptions);
|
|
30
|
+
start(): void;
|
|
31
|
+
/** Queues an event. Returns `false` for an exposure this process already sent. */
|
|
32
|
+
push(event: FlagEvent): boolean;
|
|
33
|
+
get pending(): number;
|
|
34
|
+
flush(): Promise<void>;
|
|
35
|
+
/** Stops the timer and sends what is left. */
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../../src/events/buffer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED;;;;;GAKG;AACH,qBAAa,WAAW;;IASpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,SAAK;IACZ,SAAS,SAAK;IAEd,YACmB,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,aAAkB,EAC1C;IAEJ,KAAK,SAGJ;IAED,kFAAkF;IAClF,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAuB9B;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAKrB;IAmBD,8CAA8C;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAI3B;CACF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batches events in process before they reach the transport, so a busy page does not turn into
|
|
3
|
+
* one INSERT per exposure. Exposures are also deduped here: the same subject seeing the same
|
|
4
|
+
* experiment on every request produces one event per process, not one per request (the sinks
|
|
5
|
+
* dedupe the rest).
|
|
6
|
+
*/
|
|
7
|
+
export class EventBuffer {
|
|
8
|
+
transport;
|
|
9
|
+
options;
|
|
10
|
+
#queue = [];
|
|
11
|
+
#timer;
|
|
12
|
+
#flushing;
|
|
13
|
+
#seen = new Map();
|
|
14
|
+
dropped = 0;
|
|
15
|
+
published = 0;
|
|
16
|
+
constructor(transport, options = {}) {
|
|
17
|
+
this.transport = transport;
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
start() {
|
|
21
|
+
this.#timer ??= setInterval(() => void this.flush(), this.options.flushMs ?? 1000);
|
|
22
|
+
this.#timer.unref();
|
|
23
|
+
}
|
|
24
|
+
/** Queues an event. Returns `false` for an exposure this process already sent. */
|
|
25
|
+
push(event) {
|
|
26
|
+
if (event.type === 'exposure') {
|
|
27
|
+
if (this.#seen.has(event.id))
|
|
28
|
+
return false;
|
|
29
|
+
this.#seen.set(event.id, true);
|
|
30
|
+
const cap = this.options.dedupeSize ?? 100_000;
|
|
31
|
+
if (this.#seen.size > cap) {
|
|
32
|
+
// Map iteration order is insertion order: drop the oldest tenth.
|
|
33
|
+
let drop = Math.ceil(cap / 10);
|
|
34
|
+
for (const key of this.#seen.keys()) {
|
|
35
|
+
this.#seen.delete(key);
|
|
36
|
+
if (--drop === 0)
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
this.#queue.push(event);
|
|
42
|
+
const maxQueue = this.options.maxQueue ?? 50_000;
|
|
43
|
+
if (this.#queue.length > maxQueue) {
|
|
44
|
+
const excess = this.#queue.length - maxQueue;
|
|
45
|
+
this.#queue.splice(0, excess);
|
|
46
|
+
this.dropped += excess;
|
|
47
|
+
}
|
|
48
|
+
if (this.#queue.length >= (this.options.maxSize ?? 500))
|
|
49
|
+
void this.flush();
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
get pending() {
|
|
53
|
+
return this.#queue.length;
|
|
54
|
+
}
|
|
55
|
+
flush() {
|
|
56
|
+
this.#flushing ??= this.#flush().finally(() => {
|
|
57
|
+
this.#flushing = undefined;
|
|
58
|
+
});
|
|
59
|
+
return this.#flushing;
|
|
60
|
+
}
|
|
61
|
+
async #flush() {
|
|
62
|
+
while (this.#queue.length > 0) {
|
|
63
|
+
const batch = this.#queue.slice(0, this.options.maxSize ?? 500);
|
|
64
|
+
const droppedBefore = this.dropped;
|
|
65
|
+
try {
|
|
66
|
+
await this.transport.publish(batch);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Keep the batch for the next tick; the transport may be back by then.
|
|
70
|
+
this.options.onError?.(error);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Overflow while publishing drops from the front, i.e. from this batch first.
|
|
74
|
+
this.#queue.splice(0, Math.max(0, batch.length - (this.dropped - droppedBefore)));
|
|
75
|
+
this.published += batch.length;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Stops the timer and sends what is left. */
|
|
79
|
+
async close() {
|
|
80
|
+
if (this.#timer)
|
|
81
|
+
clearInterval(this.#timer);
|
|
82
|
+
this.#timer = undefined;
|
|
83
|
+
await this.flush();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.js","sourceRoot":"","sources":["../../../src/events/buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Version of the event contract below. Bumped only for incompatible changes. */
|
|
2
|
+
export declare const EVENT_SCHEMA_VERSION = 1;
|
|
3
|
+
/**
|
|
4
|
+
* The one event shape every transport and sink speaks (DESIGN §7). Timestamps are epoch
|
|
5
|
+
* milliseconds so every backend (Postgres, MySQL, SQLite, DuckDB, ClickHouse) compares them as
|
|
6
|
+
* plain numbers.
|
|
7
|
+
*/
|
|
8
|
+
export interface FlagEvent {
|
|
9
|
+
/** UUID. Exposures use a deterministic id per (experiment, subject) so re-sends dedupe. */
|
|
10
|
+
id: string;
|
|
11
|
+
schemaVersion: number;
|
|
12
|
+
/** The app that produced it (a tenant's `partition`, or the standalone app name). */
|
|
13
|
+
app: string;
|
|
14
|
+
/** The environment whose ruleset assigned it (`production`, `staging`, …). */
|
|
15
|
+
environment: string;
|
|
16
|
+
type: 'exposure' | 'track';
|
|
17
|
+
/** Exposure: the flag. Track: absent. */
|
|
18
|
+
flag?: string;
|
|
19
|
+
/** Exposure: the experiment key. */
|
|
20
|
+
experiment?: string;
|
|
21
|
+
/** Exposure: the variant served. */
|
|
22
|
+
variant?: string;
|
|
23
|
+
/** Track: the event name (`purchase`, `signup`). */
|
|
24
|
+
name?: string;
|
|
25
|
+
subject: string;
|
|
26
|
+
subjectType: string;
|
|
27
|
+
/** Track: the numeric value (revenue, duration). */
|
|
28
|
+
value?: number;
|
|
29
|
+
props?: Record<string, unknown>;
|
|
30
|
+
/** Epoch milliseconds. */
|
|
31
|
+
ts: number;
|
|
32
|
+
}
|
|
33
|
+
/** A UUIDv7: time-ordered, so ids sort roughly by creation in every store. */
|
|
34
|
+
export declare function uuidv7(now?: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* The id of the exposure of `subject` to `experiment`: a UUIDv5-style hash, identical on every
|
|
37
|
+
* send, so a destination that dedupes by id keeps one row however often the relay retries.
|
|
38
|
+
*/
|
|
39
|
+
export declare function exposureId(app: string, environment: string, experiment: string, subject: string): string;
|
|
40
|
+
/** Light structural validation for events arriving over HTTP from a tenant. */
|
|
41
|
+
export declare function parseEvents(input: unknown, app: string, environment: string): FlagEvent[];
|
|
42
|
+
//# sourceMappingURL=event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/events/event.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,2FAA2F;IAC3F,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8EAA8E;AAC9E,wBAAgB,MAAM,CAAC,GAAG,SAAa,GAAG,MAAM,CAY/C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,MAAM,CAQR;AAOD,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,CAyCzF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
/** Version of the event contract below. Bumped only for incompatible changes. */
|
|
3
|
+
export const EVENT_SCHEMA_VERSION = 1;
|
|
4
|
+
/** A UUIDv7: time-ordered, so ids sort roughly by creation in every store. */
|
|
5
|
+
export function uuidv7(now = Date.now()) {
|
|
6
|
+
const bytes = randomBytes(16);
|
|
7
|
+
const ms = BigInt(now);
|
|
8
|
+
bytes[0] = Number((ms >> 40n) & 0xffn);
|
|
9
|
+
bytes[1] = Number((ms >> 32n) & 0xffn);
|
|
10
|
+
bytes[2] = Number((ms >> 24n) & 0xffn);
|
|
11
|
+
bytes[3] = Number((ms >> 16n) & 0xffn);
|
|
12
|
+
bytes[4] = Number((ms >> 8n) & 0xffn);
|
|
13
|
+
bytes[5] = Number(ms & 0xffn);
|
|
14
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x70;
|
|
15
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
16
|
+
return format(bytes);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The id of the exposure of `subject` to `experiment`: a UUIDv5-style hash, identical on every
|
|
20
|
+
* send, so a destination that dedupes by id keeps one row however often the relay retries.
|
|
21
|
+
*/
|
|
22
|
+
export function exposureId(app, environment, experiment, subject) {
|
|
23
|
+
const bytes = createHash('sha1')
|
|
24
|
+
.update(`flags:exposure:${app}:${environment}:${experiment}:${subject}`)
|
|
25
|
+
.digest();
|
|
26
|
+
const out = Buffer.from(bytes.subarray(0, 16));
|
|
27
|
+
out[6] = (out[6] & 0x0f) | 0x50;
|
|
28
|
+
out[8] = (out[8] & 0x3f) | 0x80;
|
|
29
|
+
return format(out);
|
|
30
|
+
}
|
|
31
|
+
function format(bytes) {
|
|
32
|
+
const hex = Buffer.from(bytes).toString('hex');
|
|
33
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
34
|
+
}
|
|
35
|
+
/** Light structural validation for events arriving over HTTP from a tenant. */
|
|
36
|
+
export function parseEvents(input, app, environment) {
|
|
37
|
+
if (!Array.isArray(input))
|
|
38
|
+
throw new TypeError('events must be an array');
|
|
39
|
+
return input.map((raw, i) => {
|
|
40
|
+
const e = raw;
|
|
41
|
+
const fail = (message) => {
|
|
42
|
+
throw new TypeError(`events[${i}]: ${message}`);
|
|
43
|
+
};
|
|
44
|
+
if (typeof e.id !== 'string' || e.id.length > 64)
|
|
45
|
+
fail('id must be a string');
|
|
46
|
+
if (e.type !== 'exposure' && e.type !== 'track')
|
|
47
|
+
fail('type must be exposure or track');
|
|
48
|
+
if (typeof e.subject !== 'string' || e.subject.length === 0)
|
|
49
|
+
fail('subject is required');
|
|
50
|
+
if (typeof e.ts !== 'number' || !Number.isFinite(e.ts))
|
|
51
|
+
fail('ts must be epoch milliseconds');
|
|
52
|
+
if (e.type === 'exposure' &&
|
|
53
|
+
(typeof e.experiment !== 'string' || typeof e.variant !== 'string')) {
|
|
54
|
+
fail('an exposure needs experiment and variant');
|
|
55
|
+
}
|
|
56
|
+
if (e.type === 'track' && typeof e.name !== 'string')
|
|
57
|
+
fail('a track needs a name');
|
|
58
|
+
if (e.value !== undefined && (typeof e.value !== 'number' || !Number.isFinite(e.value))) {
|
|
59
|
+
fail('value must be a finite number');
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id: e.id,
|
|
63
|
+
schemaVersion: typeof e.schemaVersion === 'number' ? e.schemaVersion : EVENT_SCHEMA_VERSION,
|
|
64
|
+
// The tenant's identity and environment come from its verified token, never the payload.
|
|
65
|
+
app,
|
|
66
|
+
environment,
|
|
67
|
+
type: e.type,
|
|
68
|
+
...(e.flag !== undefined ? { flag: String(e.flag) } : {}),
|
|
69
|
+
...(e.experiment !== undefined ? { experiment: String(e.experiment) } : {}),
|
|
70
|
+
...(e.variant !== undefined ? { variant: String(e.variant) } : {}),
|
|
71
|
+
...(e.name !== undefined ? { name: String(e.name) } : {}),
|
|
72
|
+
subject: e.subject,
|
|
73
|
+
subjectType: typeof e.subjectType === 'string' ? e.subjectType : 'user',
|
|
74
|
+
...(e.value !== undefined ? { value: e.value } : {}),
|
|
75
|
+
...(e.props !== undefined && typeof e.props === 'object' && e.props !== null
|
|
76
|
+
? { props: e.props }
|
|
77
|
+
: {}),
|
|
78
|
+
ts: e.ts,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../../src/events/event.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { FlagsRuntime } from '../runtime.js';
|
|
3
|
+
import { BrowserTokens } from './browser_tokens.js';
|
|
4
|
+
import type { ControlPlane } from './control_plane.js';
|
|
5
|
+
interface RouterLike {
|
|
6
|
+
get(pattern: string, handler: (ctx: HttpContext) => unknown): {
|
|
7
|
+
as(name: string): unknown;
|
|
8
|
+
};
|
|
9
|
+
post(pattern: string, handler: (ctx: HttpContext) => unknown): {
|
|
10
|
+
as(name: string): unknown;
|
|
11
|
+
};
|
|
12
|
+
route?(pattern: string, methods: string[], handler: (ctx: HttpContext) => unknown): unknown;
|
|
13
|
+
}
|
|
14
|
+
/** The secret browser keys derive from: `browser.secret`, else the app key. */
|
|
15
|
+
export declare function browserSecret(runtime: FlagsRuntime, appKey: unknown): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Mounts what `browser` in `config/flags.ts` asks for: the token endpoint on every topology, and the
|
|
18
|
+
* flags API on standalone and control-plane apps.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerBrowser(router: RouterLike, runtime: FlagsRuntime, options: {
|
|
21
|
+
secret?: string | undefined;
|
|
22
|
+
controlPlane?: ControlPlane | undefined;
|
|
23
|
+
logger?: {
|
|
24
|
+
warn(message: string): void;
|
|
25
|
+
} | undefined;
|
|
26
|
+
}): BrowserTokens | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* A browser token for the request's subject, e.g. to hand the first one to the frontend in a page
|
|
29
|
+
* response instead of a round-trip to the token endpoint. Needs `browser` in `config/flags.ts`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function browserToken(ctx: HttpContext): Promise<import("./browser_tokens.js").BrowserTokenResponse>;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/http/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAOlD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,UAAU,UAAU;IAClB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG;QAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC5F,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG;QAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC7F,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC;CAC7F;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASxF;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;CACtD,GACA,aAAa,GAAG,SAAS,CAyC3B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,+DAKlD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { localKeySource, remoteKeySource, } from '../topology/browser_token.js';
|
|
2
|
+
import { BrowserApi } from './browser_api.js';
|
|
3
|
+
import { BrowserTokens } from './browser_tokens.js';
|
|
4
|
+
/** The secret browser keys derive from: `browser.secret`, else the app key. */
|
|
5
|
+
export function browserSecret(runtime, appKey) {
|
|
6
|
+
if (!runtime.config.browser || runtime.config.topology === 'tenant')
|
|
7
|
+
return undefined;
|
|
8
|
+
const secret = runtime.config.browser.secret ?? releaseSecret(appKey);
|
|
9
|
+
if (!secret) {
|
|
10
|
+
throw new Error('[flags] `browser` needs a secret: set `browser.secret`, or APP_KEY in the app config');
|
|
11
|
+
}
|
|
12
|
+
return secret;
|
|
13
|
+
}
|
|
14
|
+
function releaseSecret(value) {
|
|
15
|
+
if (typeof value === 'string')
|
|
16
|
+
return value || undefined;
|
|
17
|
+
if (value && typeof value.release === 'function') {
|
|
18
|
+
return String(value.release()) || undefined;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mounts what `browser` in `config/flags.ts` asks for: the token endpoint on every topology, and the
|
|
24
|
+
* flags API on standalone and control-plane apps.
|
|
25
|
+
*/
|
|
26
|
+
export function registerBrowser(router, runtime, options) {
|
|
27
|
+
const config = runtime.config;
|
|
28
|
+
const browser = config.browser;
|
|
29
|
+
if (!browser)
|
|
30
|
+
return undefined;
|
|
31
|
+
const manager = runtime.manager;
|
|
32
|
+
let keys;
|
|
33
|
+
let url;
|
|
34
|
+
if (config.topology === 'tenant') {
|
|
35
|
+
keys = remoteKeySource({
|
|
36
|
+
url: config.tenant.url,
|
|
37
|
+
token: runtime.token,
|
|
38
|
+
partition: config.tenant.partition,
|
|
39
|
+
});
|
|
40
|
+
url = browser.publicUrl ?? config.tenant.url;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const secret = options.secret;
|
|
44
|
+
const prefix = (browser.prefix ??
|
|
45
|
+
(config.topology === 'control-plane' ? config.controlPlane.prefix : undefined) ??
|
|
46
|
+
'/_flags').replace(/\/$/, '');
|
|
47
|
+
keys = localKeySource(secret, manager.app, manager.environment);
|
|
48
|
+
url = browser.publicUrl ?? prefix;
|
|
49
|
+
const rulesetOf = async (environment) => {
|
|
50
|
+
if (options.controlPlane)
|
|
51
|
+
return options.controlPlane.ruleset(environment);
|
|
52
|
+
if (environment === manager.environment)
|
|
53
|
+
return runtime.source.current();
|
|
54
|
+
return runtime.store.readRuleset(environment);
|
|
55
|
+
};
|
|
56
|
+
new BrowserApi(runtime, rulesetOf, {
|
|
57
|
+
secret,
|
|
58
|
+
verifyUser: browser.verifyUser,
|
|
59
|
+
corsOrigins: browser.corsOrigins,
|
|
60
|
+
}).register(router, prefix);
|
|
61
|
+
}
|
|
62
|
+
const tokens = new BrowserTokens(runtime, keys, url, browser, options.logger);
|
|
63
|
+
if (browser.tokenRoute !== false) {
|
|
64
|
+
router
|
|
65
|
+
.get(browser.tokenRoute ?? '/_flags/token', (ctx) => tokens.handle(ctx))
|
|
66
|
+
.as('flags.browser.token');
|
|
67
|
+
}
|
|
68
|
+
return tokens;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A browser token for the request's subject, e.g. to hand the first one to the frontend in a page
|
|
72
|
+
* response instead of a round-trip to the token endpoint. Needs `browser` in `config/flags.ts`.
|
|
73
|
+
*/
|
|
74
|
+
export async function browserToken(ctx) {
|
|
75
|
+
const { getRuntime } = await import('../services/accessor.js');
|
|
76
|
+
const tokens = getRuntime().browser;
|
|
77
|
+
if (!tokens)
|
|
78
|
+
throw new Error('[flags] browserToken() needs `browser` in config/flags.ts');
|
|
79
|
+
return tokens.issue(ctx);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../../src/http/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Ruleset } from '@adonis-agora/flags-core';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
import type { FlagsRuntime } from '../runtime.js';
|
|
4
|
+
import type { UserVerifier } from '../topology/verify.js';
|
|
5
|
+
export interface BrowserApiOptions {
|
|
6
|
+
/** Secret the apps' signing keys derive from (see `browser_token.ts`). */
|
|
7
|
+
secret: string;
|
|
8
|
+
/** Also accept other tokens (authkit access tokens from mobile apps). */
|
|
9
|
+
verifyUser?: UserVerifier | undefined;
|
|
10
|
+
/** Origins allowed to call from browsers (CORS). */
|
|
11
|
+
corsOrigins?: string[] | undefined;
|
|
12
|
+
}
|
|
13
|
+
interface RouterLike {
|
|
14
|
+
get(pattern: string, handler: (ctx: HttpContext) => unknown): {
|
|
15
|
+
as(name: string): unknown;
|
|
16
|
+
};
|
|
17
|
+
post(pattern: string, handler: (ctx: HttpContext) => unknown): {
|
|
18
|
+
as(name: string): unknown;
|
|
19
|
+
};
|
|
20
|
+
route?(pattern: string, methods: string[], handler: (ctx: HttpContext) => unknown): unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Flags for frontends that talk to the flags server directly (SPAs, mobile, static sites):
|
|
24
|
+
*
|
|
25
|
+
* - `POST <prefix>/v1/client/evaluate` — values of the flags declared `client: true`, for the
|
|
26
|
+
* verified user. ETag over ruleset + context.
|
|
27
|
+
* - `GET <prefix>/v1/client/stream?token=…` — server-sent `ruleset` events.
|
|
28
|
+
* - `POST <prefix>/v1/client/events` — exposures (`{ type: 'exposure', flag }`, re-evaluated here)
|
|
29
|
+
* and tracks (`{ type: 'track', name, value?, props? }`).
|
|
30
|
+
*
|
|
31
|
+
* Tokens are minted by the apps' token endpoints (`GET /_flags/token`) or, with `verifyUser`, by
|
|
32
|
+
* an identity provider. The identity always comes from the verified token. Attributes the browser sends are merged
|
|
33
|
+
* underneath the token's, so they can never change who the user is or what the token says.
|
|
34
|
+
*/
|
|
35
|
+
export declare class BrowserApi {
|
|
36
|
+
#private;
|
|
37
|
+
private readonly runtime;
|
|
38
|
+
private readonly rulesetOf;
|
|
39
|
+
private readonly options;
|
|
40
|
+
constructor(runtime: FlagsRuntime, rulesetOf: (environment: string) => Promise<Ruleset>, options: BrowserApiOptions);
|
|
41
|
+
register(router: RouterLike, prefix: string): void;
|
|
42
|
+
evaluate(ctx: HttpContext): Promise<void>;
|
|
43
|
+
/** `EventSource` cannot send headers, so the token comes in the query string. Keep it short-lived. */
|
|
44
|
+
stream(ctx: HttpContext): Promise<void>;
|
|
45
|
+
events(ctx: HttpContext): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=browser_api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser_api.d.ts","sourceRoot":"","sources":["../../../src/http/browser_api.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,uBAAuB,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,UAAU,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACtC,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACpC;AAED,UAAU,UAAU;IAClB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG;QAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC5F,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG;QAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC7F,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC;CAC7F;AAeD;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAU;;IAInB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,YACmB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EACpD,OAAO,EAAE,iBAAiB,EACzC;IAEJ,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAU1C;IAoEK,QAAQ,CAAC,GAAG,EAAE,WAAW,iBAwB9B;IAED,sGAAsG;IAChG,MAAM,CAAC,GAAG,EAAE,WAAW,iBAgD5B;IAEK,MAAM,CAAC,GAAG,EAAE,WAAW,iBA6E5B;CACF"}
|