@adonis-agora/flags 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +15 -2
- 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 +178 -3
- 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,62 @@
|
|
|
1
|
+
import type { FlagEvent } from '../events/event.js';
|
|
2
|
+
import type { SqlClient } from '../sql.js';
|
|
3
|
+
/** What a transport guarantees. The provider warns once at boot about what it does not. */
|
|
4
|
+
export interface TransportCapabilities {
|
|
5
|
+
/**
|
|
6
|
+
* Can write inside the caller's database transaction, so a conversion and the business write it
|
|
7
|
+
* belongs to commit or roll back together. Only `db` can.
|
|
8
|
+
*/
|
|
9
|
+
transactional: boolean;
|
|
10
|
+
/** Survives a process crash once `publish` resolved. */
|
|
11
|
+
durable: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** A leased batch for one sink. Exactly one of `ack`/`release`/`fail` must be called. */
|
|
14
|
+
export interface Delivery {
|
|
15
|
+
events: FlagEvent[];
|
|
16
|
+
/** Marks the batch delivered and moves the sink's cursor past it. */
|
|
17
|
+
ack(): Promise<void>;
|
|
18
|
+
/** Gives the lease back without moving the cursor (nothing to deliver, or shutting down). */
|
|
19
|
+
release(): Promise<void>;
|
|
20
|
+
/** Records the error and gives the lease back; the same batch is retried later. */
|
|
21
|
+
fail(error: unknown): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export interface SinkStatus {
|
|
24
|
+
sink: string;
|
|
25
|
+
/** Events waiting for this sink (approximate on some transports). */
|
|
26
|
+
lag: number;
|
|
27
|
+
delivered: number;
|
|
28
|
+
leasedBy: string | null;
|
|
29
|
+
lastError: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface PublishOptions {
|
|
32
|
+
/** A database transaction to write in (transactional transports only). */
|
|
33
|
+
client?: SqlClient;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Moves events from the process that produced them to the relay that delivers them to sinks.
|
|
37
|
+
* DESIGN §8: the transport is the log, and each sink reads it at its own pace through its own
|
|
38
|
+
* cursor.
|
|
39
|
+
*/
|
|
40
|
+
export interface Transport {
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly capabilities: TransportCapabilities;
|
|
43
|
+
setup?(): Promise<void>;
|
|
44
|
+
publish(events: FlagEvent[], options?: PublishOptions): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Leases the next batch for `sink`. Resolves `null` when another process holds the lease, and a
|
|
47
|
+
* delivery with no events when the sink is caught up. Transports without a consumer side (`http`
|
|
48
|
+
* on a tenant) do not implement it.
|
|
49
|
+
*/
|
|
50
|
+
consume?(sink: string, options: {
|
|
51
|
+
batchSize: number;
|
|
52
|
+
leaseMs: number;
|
|
53
|
+
owner: string;
|
|
54
|
+
}): Promise<Delivery | null>;
|
|
55
|
+
/** Moves a sink's cursor back to the beginning (replay / backfill a new destination). */
|
|
56
|
+
rewind?(sink: string): Promise<void>;
|
|
57
|
+
status?(sinks: string[]): Promise<SinkStatus[]>;
|
|
58
|
+
/** The newest id every listed sink has delivered (what is safe to prune). */
|
|
59
|
+
safePruneId?(sinks: string[]): Promise<number>;
|
|
60
|
+
close?(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/transports/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,yFAAyF;AACzF,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,qEAAqE;IACrE,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,6FAA6F;IAC7F,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,mFAAmF;IACnF,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC;IAC7C,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE;;;;OAIG;IACH,OAAO,CAAC,CACN,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,CAAC;IAC5B,yFAAyF;IACzF,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAChD,6EAA6E;IAC7E,WAAW,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/transports/transport.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { JsonValue } from '@adonis-agora/flags-core';
|
|
2
|
+
/**
|
|
3
|
+
* The typed flag registry. Flags declared in `config/flags.ts` are added with
|
|
4
|
+
* `interface FlagsList extends InferFlags<typeof flagsConfig> {}`, and flags created in the console
|
|
5
|
+
* with `node ace flags:types` (it writes a `.d.ts` that augments this interface).
|
|
6
|
+
*/
|
|
7
|
+
export interface FlagsList {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Type-level settings. `node ace flags:types` sets `strictKeys: true`: once every flag (code and
|
|
11
|
+
* console) is in {@link FlagsList}, an unknown key is a compile error. Without it, any string is
|
|
12
|
+
* accepted (flags created in the console since the last generation still compile) and declared
|
|
13
|
+
* keys are offered by autocomplete.
|
|
14
|
+
*/
|
|
15
|
+
export interface FlagsSettings {
|
|
16
|
+
}
|
|
17
|
+
type Declared = Extract<keyof FlagsList, string>;
|
|
18
|
+
type Strict = FlagsSettings extends {
|
|
19
|
+
strictKeys: true;
|
|
20
|
+
} ? true : false;
|
|
21
|
+
/** Flag keys: the declared ones, plus any string unless `strictKeys` is on. */
|
|
22
|
+
export type FlagKey = keyof FlagsList extends never ? string : Strict extends true ? Declared : Declared | (string & {});
|
|
23
|
+
/** The value type of a flag key (any JSON for keys not in {@link FlagsList}). */
|
|
24
|
+
export type FlagValue<K extends string> = K extends keyof FlagsList ? FlagsList[K] : JsonValue;
|
|
25
|
+
/** Keys whose value is boolean (what `enabled()` accepts). */
|
|
26
|
+
export type BooleanFlagKey = keyof FlagsList extends never ? string : ({
|
|
27
|
+
[K in keyof FlagsList]: FlagsList[K] extends boolean ? K : never;
|
|
28
|
+
}[keyof FlagsList] & string) | (Strict extends true ? never : string & {});
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,SAAS;CAAG;AAE7B;;;;;GAKG;AACH,MAAM,WAAW,aAAa;CAAG;AAEjC,KAAK,QAAQ,GAAG,OAAO,CAAC,MAAM,SAAS,EAAE,MAAM,CAAC,CAAC;AACjD,KAAK,MAAM,GAAG,aAAa,SAAS;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,KAAK,CAAC;AAExE,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GAAG,MAAM,SAAS,SAAS,KAAK,GAC/C,MAAM,GACN,MAAM,SAAS,IAAI,GACjB,QAAQ,GACR,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE/B,iFAAiF;AACjF,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AAE/F,8DAA8D;AAC9D,MAAM,MAAM,cAAc,GAAG,MAAM,SAAS,SAAS,KAAK,GACtD,MAAM,GAEF,CAAC;KAAG,CAAC,IAAI,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC,GAAG,KAAK;CAAE,CAAC,MAAM,SAAS,CAAC,GACpF,MAAM,CAAC,GACT,CAAC,MAAM,SAAS,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UsageRow } from './stores/store.js';
|
|
2
|
+
/**
|
|
3
|
+
* Counts evaluations per flag in memory and hands them off periodically — the input of stale-flag
|
|
4
|
+
* detection ("not evaluated in 30 days"). Counting is a Map increment; nothing touches I/O on the
|
|
5
|
+
* evaluation path.
|
|
6
|
+
*/
|
|
7
|
+
export declare class UsageCounter {
|
|
8
|
+
#private;
|
|
9
|
+
private readonly environment;
|
|
10
|
+
private readonly sink;
|
|
11
|
+
private readonly onError?;
|
|
12
|
+
constructor(environment: string, sink: (rows: UsageRow[]) => Promise<void>, onError?: ((error: unknown) => void) | undefined);
|
|
13
|
+
hit(flag: string): void;
|
|
14
|
+
start(intervalMs?: number): void;
|
|
15
|
+
flush(): Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;GAIG;AACH,qBAAa,YAAY;;IAKrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAH3B,YACmB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACzC,OAAO,CAAC,GAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,aAAA,EACjD;IAEJ,GAAG,CAAC,IAAI,EAAE,MAAM,QAEf;IAED,KAAK,CAAC,UAAU,SAAS,QAGxB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAkB3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAI3B;CACF"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counts evaluations per flag in memory and hands them off periodically — the input of stale-flag
|
|
3
|
+
* detection ("not evaluated in 30 days"). Counting is a Map increment; nothing touches I/O on the
|
|
4
|
+
* evaluation path.
|
|
5
|
+
*/
|
|
6
|
+
export class UsageCounter {
|
|
7
|
+
environment;
|
|
8
|
+
sink;
|
|
9
|
+
onError;
|
|
10
|
+
#counts = new Map();
|
|
11
|
+
#timer;
|
|
12
|
+
constructor(environment, sink, onError) {
|
|
13
|
+
this.environment = environment;
|
|
14
|
+
this.sink = sink;
|
|
15
|
+
this.onError = onError;
|
|
16
|
+
}
|
|
17
|
+
hit(flag) {
|
|
18
|
+
this.#counts.set(flag, (this.#counts.get(flag) ?? 0) + 1);
|
|
19
|
+
}
|
|
20
|
+
start(intervalMs = 60_000) {
|
|
21
|
+
this.#timer ??= setInterval(() => void this.flush(), intervalMs);
|
|
22
|
+
this.#timer.unref();
|
|
23
|
+
}
|
|
24
|
+
async flush() {
|
|
25
|
+
if (this.#counts.size === 0)
|
|
26
|
+
return;
|
|
27
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
28
|
+
const rows = [...this.#counts].map(([flag, evaluations]) => ({
|
|
29
|
+
environment: this.environment,
|
|
30
|
+
flag,
|
|
31
|
+
day,
|
|
32
|
+
evaluations,
|
|
33
|
+
}));
|
|
34
|
+
this.#counts.clear();
|
|
35
|
+
try {
|
|
36
|
+
await this.sink(rows);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// Put the counts back; they go out with the next flush.
|
|
40
|
+
for (const row of rows)
|
|
41
|
+
this.#counts.set(row.flag, (this.#counts.get(row.flag) ?? 0) + row.evaluations);
|
|
42
|
+
this.onError?.(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async close() {
|
|
46
|
+
if (this.#timer)
|
|
47
|
+
clearInterval(this.#timer);
|
|
48
|
+
this.#timer = undefined;
|
|
49
|
+
await this.flush();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/usage.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,YAAY;IAKJ,WAAW;IACX,IAAI;IACJ,OAAO;IAN1B,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,MAAM,CAA6B;IAEnC,YACmB,WAAmB,EACnB,IAAyC,EACzC,OAAkC;2BAFlC,WAAW;oBACX,IAAI;uBACJ,OAAO;IACvB,CAAC;IAEJ,GAAG,CAAC,IAAY;QACd,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,UAAU,GAAG,MAAM;QACvB,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QACpC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3D,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,IAAI;YACJ,GAAG;YACH,WAAW;SACZ,CAAC,CAAC,CAAC;QACJ,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,wDAAwD;YACxD,KAAK,MAAM,GAAG,IAAI,IAAI;gBACpB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;YAClF,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,MAAM;YAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{{{
|
|
2
|
+
exports({ to: app.configPath('flags.ts') })
|
|
3
|
+
}}}
|
|
4
|
+
import env from '#start/env'
|
|
5
|
+
import app from '@adonisjs/core/services/app'
|
|
6
|
+
import { defineConfig, flag, type InferFlags } from '@adonis-agora/flags'
|
|
7
|
+
|
|
8
|
+
const flagsConfig = defineConfig({
|
|
9
|
+
/**
|
|
10
|
+
* Flags declared in code: their type and the value served when the ruleset cannot answer.
|
|
11
|
+
* Targeting (who gets what) is edited in the console or with \`node ace flags:set\`.
|
|
12
|
+
*/
|
|
13
|
+
flags: {
|
|
14
|
+
example_flag: flag.boolean({
|
|
15
|
+
default: false,
|
|
16
|
+
description: 'What this flag controls',
|
|
17
|
+
owner: 'your-team',
|
|
18
|
+
// Shown by \`node ace flags:stale\` once this date passes.
|
|
19
|
+
expiresAt: '2027-06-30',
|
|
20
|
+
// Allowed to reach the browser through ctx.flags.all() / useFlag().
|
|
21
|
+
client: true,
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Which environment's ruleset this process reads (and the console and \`flags:*\` commands edit
|
|
27
|
+
* by default). Each environment has its own targeting.
|
|
28
|
+
*/
|
|
29
|
+
environment: env.get('FLAGS_ENVIRONMENT', app.inProduction ? 'production' : 'development'),
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Metrics experiments are judged by, computed from \`flags.track(subject, 'event', { value })\`.
|
|
33
|
+
* Build them with \`metric\` from '@adonis-agora/flags'.
|
|
34
|
+
*/
|
|
35
|
+
metrics: {
|
|
36
|
+
// conversion: metric.conversion('purchase'),
|
|
37
|
+
// revenue: metric.sum('purchase', { cupedMs: 14 * 24 * 3600 * 1000 }),
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/** Experiment key (the flag key) → the metrics that decide it and the ones that must not regress. */
|
|
41
|
+
experiments: {
|
|
42
|
+
// checkout_v2: { metrics: ['conversion', 'revenue'], guardrails: [] },
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export default flagsConfig
|
|
47
|
+
|
|
48
|
+
declare module '@adonis-agora/flags/types' {
|
|
49
|
+
interface FlagsList extends InferFlags<typeof flagsConfig> {}
|
|
50
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{{{
|
|
2
|
+
exports({ to: app.configPath('flags_dashboard.ts') })
|
|
3
|
+
}}}
|
|
4
|
+
import { defineConfig } from '@adonis-agora/flags/dashboard'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the @adonis-agora/flags console.
|
|
8
|
+
*
|
|
9
|
+
* Without \`authorize\` the console is open OUTSIDE production and answers
|
|
10
|
+
* every request with 403 in production (a warning is logged at boot). Set
|
|
11
|
+
* \`authorize\` to choose who may open it: an inline function,
|
|
12
|
+
* an authorizer class (\`@inject()\` works), or a lazy import of one
|
|
13
|
+
* (\`() => import('#policies/console_access')\`). It answers \`true\`/\`false\`,
|
|
14
|
+
* \`{ allowed, reason?: 'unauthenticated' | 'forbidden' }\` or \`{ redirectTo }\`:
|
|
15
|
+
* \`unauthenticated\` is a 401, any other denial (or a throw) a 403. With authkit,
|
|
16
|
+
* \`authkitDashboardAuthorize(...)\` from @adonis-agora/authkit-client fits as-is.
|
|
17
|
+
* For machine access use \`authorizeByToken({ token: env.get('CONSOLE_TOKEN') })\`.
|
|
18
|
+
*
|
|
19
|
+
* An identity provider in config/flags.ts (\`identity: identities.adonisAuth({ canOpenConsole })\`,
|
|
20
|
+
* \`identities.authkit()\` or your own class) answers both questions for you.
|
|
21
|
+
*
|
|
22
|
+
* \`actor\` names who made a change (audit log, two-person approvals). Default:
|
|
23
|
+
* the signed-in user's id, else 'dashboard'.
|
|
24
|
+
*
|
|
25
|
+
* Set \`enabled: false\` to remove it entirely — no routes are registered at all.
|
|
26
|
+
*/
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
// enabled: true,
|
|
29
|
+
// path: '/flags',
|
|
30
|
+
// authorize: async (ctx) => {
|
|
31
|
+
// if (!(await ctx.auth.check())) return { redirectTo: '/login' }
|
|
32
|
+
// return ctx.auth.user?.isAdmin === true
|
|
33
|
+
// },
|
|
34
|
+
// actor: (ctx) => ctx.auth.user ? { id: String(ctx.auth.user.id), name: ctx.auth.user.email } : undefined,
|
|
35
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{{{
|
|
2
|
+
exports({ to: app.migrationsPath(`${new Date().getTime()}_create_flags_tables.ts`) })
|
|
3
|
+
}}}
|
|
4
|
+
import { BaseSchema } from '@adonisjs/lucid/schema'
|
|
5
|
+
import { createFlagsTables, dropFlagsTables } from '@adonis-agora/flags'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The flags tables, for apps that run DDL explicitly.
|
|
9
|
+
*
|
|
10
|
+
* **You may not need this file.** \`@adonis-agora/flags\` creates its tables at boot
|
|
11
|
+
* (\`autoCreateSchema\`, on by default), like the other Agora libraries. Keep this migration if you
|
|
12
|
+
* want migrations to own the schema, and set \`autoCreateSchema: false\` in config/flags.ts. Both
|
|
13
|
+
* paths call the same \`createFlagsTables\`.
|
|
14
|
+
*/
|
|
15
|
+
export default class extends BaseSchema {
|
|
16
|
+
async up() {
|
|
17
|
+
this.defer(async (db) => {
|
|
18
|
+
await createFlagsTables(db as never, { partitionEvents: false })
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async down() {
|
|
23
|
+
this.defer(async (db) => {
|
|
24
|
+
await dropFlagsTables(db as never)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../stubs/main.ts"],"names":[],"mappings":"AAIA,uFAAuF;AACvF,wBAAgB,SAAS,IAAI,MAAM,CAGlC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
let cached;
|
|
3
|
+
/** Absolute path to the published stubs directory (`dist/stubs/`), computed lazily. */
|
|
4
|
+
export function stubsRoot() {
|
|
5
|
+
if (cached === undefined)
|
|
6
|
+
cached = fileURLToPath(new URL('./', import.meta.url));
|
|
7
|
+
return cached;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../stubs/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,IAAI,MAA0B,CAAC;AAE/B,uFAAuF;AACvF,MAAM,UAAU,SAAS;IACvB,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACjF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,181 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/flags",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Feature flags, A/B experiments and their analysis for AdonisJS — typed flags in code, targeting in your database, exposures relayed to Postgres, Parquet/DuckDB, ClickHouse or PostHog. Speaks OpenFeature. Part of the Agora ecosystem.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DavideCarvalho/adonis-agora-flags.git",
|
|
9
|
+
"directory": "packages/adonis"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://davidecarvalho.github.io/agora/docs/flags",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/DavideCarvalho/adonis-agora-flags/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"adonisjs",
|
|
17
|
+
"adonis",
|
|
18
|
+
"feature-flags",
|
|
19
|
+
"ab-testing",
|
|
20
|
+
"experiments",
|
|
21
|
+
"openfeature",
|
|
22
|
+
"ofrep",
|
|
23
|
+
"agora",
|
|
24
|
+
"tanstack-intent"
|
|
25
|
+
],
|
|
26
|
+
"author": "Davi Carvalho <davi@goflip.ai>",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/src/index.js",
|
|
29
|
+
"types": "./dist/src/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/src/index.d.ts",
|
|
33
|
+
"import": "./dist/src/index.js",
|
|
34
|
+
"default": "./dist/src/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./types": {
|
|
37
|
+
"types": "./dist/src/types.d.ts",
|
|
38
|
+
"import": "./dist/src/types.js",
|
|
39
|
+
"default": "./dist/src/types.js"
|
|
40
|
+
},
|
|
41
|
+
"./services/main": {
|
|
42
|
+
"types": "./dist/services/main.d.ts",
|
|
43
|
+
"import": "./dist/services/main.js",
|
|
44
|
+
"default": "./dist/services/main.js"
|
|
45
|
+
},
|
|
46
|
+
"./flags_provider": {
|
|
47
|
+
"types": "./dist/providers/flags_provider.d.ts",
|
|
48
|
+
"import": "./dist/providers/flags_provider.js",
|
|
49
|
+
"default": "./dist/providers/flags_provider.js"
|
|
50
|
+
},
|
|
51
|
+
"./flags_middleware": {
|
|
52
|
+
"types": "./dist/src/http/flags_middleware.d.ts",
|
|
53
|
+
"import": "./dist/src/http/flags_middleware.js",
|
|
54
|
+
"default": "./dist/src/http/flags_middleware.js"
|
|
55
|
+
},
|
|
56
|
+
"./flag_middleware": {
|
|
57
|
+
"types": "./dist/src/http/flag_middleware.d.ts",
|
|
58
|
+
"import": "./dist/src/http/flag_middleware.js",
|
|
59
|
+
"default": "./dist/src/http/flag_middleware.js"
|
|
60
|
+
},
|
|
61
|
+
"./openfeature": {
|
|
62
|
+
"types": "./dist/src/openfeature/provider.d.ts",
|
|
63
|
+
"import": "./dist/src/openfeature/provider.js",
|
|
64
|
+
"default": "./dist/src/openfeature/provider.js"
|
|
65
|
+
},
|
|
66
|
+
"./testing": {
|
|
67
|
+
"types": "./dist/src/testing/index.d.ts",
|
|
68
|
+
"import": "./dist/src/testing/index.js",
|
|
69
|
+
"default": "./dist/src/testing/index.js"
|
|
70
|
+
},
|
|
71
|
+
"./commands": {
|
|
72
|
+
"types": "./dist/commands/main.d.ts",
|
|
73
|
+
"import": "./dist/commands/main.js",
|
|
74
|
+
"default": "./dist/commands/main.js"
|
|
75
|
+
},
|
|
76
|
+
"./configure": {
|
|
77
|
+
"types": "./dist/configure.d.ts",
|
|
78
|
+
"import": "./dist/configure.js",
|
|
79
|
+
"default": "./dist/configure.js"
|
|
80
|
+
},
|
|
81
|
+
"./dashboard": {
|
|
82
|
+
"types": "./dist/src/dashboard/index.d.ts",
|
|
83
|
+
"import": "./dist/src/dashboard/index.js",
|
|
84
|
+
"default": "./dist/src/dashboard/index.js"
|
|
85
|
+
},
|
|
86
|
+
"./dashboard_provider": {
|
|
87
|
+
"types": "./dist/providers/dashboard_provider.d.ts",
|
|
88
|
+
"import": "./dist/providers/dashboard_provider.js",
|
|
89
|
+
"default": "./dist/providers/dashboard_provider.js"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"files": [
|
|
93
|
+
"dist/",
|
|
94
|
+
"README.md",
|
|
95
|
+
"CHANGELOG.md",
|
|
96
|
+
"skills"
|
|
97
|
+
],
|
|
98
|
+
"dependencies": {
|
|
99
|
+
"@adonis-agora/flags-core": "^0.1.0"
|
|
100
|
+
},
|
|
101
|
+
"peerDependencies": {
|
|
102
|
+
"@adonis-agora/authkit-client": ">=0.18.0 <2.0.0 || >=1.0.0-rc.0 <2.0.0",
|
|
103
|
+
"@adonis-agora/durable": ">=0.40.0 <2.0.0 || >=1.0.0-rc.0 <2.0.0",
|
|
104
|
+
"@adonisjs/core": "^7.3.0",
|
|
105
|
+
"@adonisjs/lucid": "^22.4.0",
|
|
106
|
+
"@adonisjs/redis": "^10.0.0 || ^11.0.0",
|
|
107
|
+
"@adonisjs/transmit": "^3.0.0",
|
|
108
|
+
"@duckdb/node-api": ">=1.4.0-r.0",
|
|
109
|
+
"@openfeature/server-sdk": "^1.17.0",
|
|
110
|
+
"@opentelemetry/api": "^1.9.0",
|
|
111
|
+
"edge.js": "^6.0.0"
|
|
112
|
+
},
|
|
113
|
+
"peerDependenciesMeta": {
|
|
114
|
+
"@adonis-agora/authkit-client": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"@adonis-agora/durable": {
|
|
118
|
+
"optional": true
|
|
119
|
+
},
|
|
120
|
+
"@adonisjs/lucid": {
|
|
121
|
+
"optional": true
|
|
122
|
+
},
|
|
123
|
+
"@adonisjs/redis": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"@adonisjs/transmit": {
|
|
127
|
+
"optional": true
|
|
128
|
+
},
|
|
129
|
+
"@duckdb/node-api": {
|
|
130
|
+
"optional": true
|
|
131
|
+
},
|
|
132
|
+
"@openfeature/server-sdk": {
|
|
133
|
+
"optional": true
|
|
134
|
+
},
|
|
135
|
+
"@opentelemetry/api": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"edge.js": {
|
|
139
|
+
"optional": true
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"devDependencies": {
|
|
143
|
+
"@adonis-agora/authkit-client": "1.0.0-rc.0",
|
|
144
|
+
"@adonis-agora/durable": "1.0.0-rc.11",
|
|
145
|
+
"@adonis-agora/flags-dashboard": "0.1.0",
|
|
146
|
+
"@adonisjs/assembler": "8.6.0",
|
|
147
|
+
"@adonisjs/core": "7.6.0",
|
|
148
|
+
"@adonisjs/lucid": "22.4.2",
|
|
149
|
+
"@duckdb/node-api": "1.5.6-r.2",
|
|
150
|
+
"@openfeature/core": "1.12.0",
|
|
151
|
+
"@openfeature/server-sdk": "1.23.0",
|
|
152
|
+
"@opentelemetry/api": "1.9.0",
|
|
153
|
+
"@tanstack/intent": "0.5.6",
|
|
154
|
+
"@testcontainers/postgresql": "12.2.1",
|
|
155
|
+
"@testcontainers/redis": "12.2.1",
|
|
156
|
+
"@types/luxon": "3.7.6",
|
|
157
|
+
"@types/node": "26.6.6",
|
|
158
|
+
"@types/pg": "8.23.1",
|
|
159
|
+
"better-sqlite3": "13.0.3",
|
|
160
|
+
"edge.js": "6.5.1",
|
|
161
|
+
"ioredis": "5.11.1",
|
|
162
|
+
"jose": "6.2.15",
|
|
163
|
+
"luxon": "3.7.2",
|
|
164
|
+
"mysql2": "3.24.6",
|
|
165
|
+
"pg": "8.23.1",
|
|
166
|
+
"typescript": "7.0.2",
|
|
167
|
+
"vitest": "5.1.0"
|
|
168
|
+
},
|
|
169
|
+
"engines": {
|
|
170
|
+
"node": ">=24"
|
|
171
|
+
},
|
|
172
|
+
"scripts": {
|
|
173
|
+
"build": "tsc -p tsconfig.json && pnpm run copy:stubs && pnpm run copy:spa",
|
|
174
|
+
"copy:stubs": "rm -rf dist/stubs/config dist/stubs/database && mkdir -p dist/stubs && cp -r stubs/config stubs/database dist/stubs/",
|
|
175
|
+
"test": "vitest run --passWithNoTests",
|
|
176
|
+
"test:integration": "vitest run -c vitest.integration.config.ts",
|
|
177
|
+
"test:watch": "vitest",
|
|
178
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
179
|
+
"copy:spa": "mkdir -p dist/assets && rm -rf dist/assets/spa && cp -r ../dashboard/dist/spa dist/assets/spa"
|
|
180
|
+
}
|
|
6
181
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flags-basics
|
|
3
|
+
description: >-
|
|
4
|
+
Set up @adonis-agora/flags in an AdonisJS 7 app and read feature flags: node
|
|
5
|
+
ace configure, declaring typed flags in config/flags.ts with flag.boolean /
|
|
6
|
+
flag.string({ variants }) / flag.number / flag.json, the FlagsList module
|
|
7
|
+
augmentation with InferFlags, ctx.flags.enabled / value / variant / details,
|
|
8
|
+
the flags service (flags.for(user)), the `flag` route-guard middleware, the
|
|
9
|
+
Edge @flag tag, client: true flags for the browser, flags:set (on, off,
|
|
10
|
+
kill, 25%, variant), and fakeFlags / createTestFlags for tests. Use for
|
|
11
|
+
"add a feature flag", "roll out to 10% of users", "hide a route behind a
|
|
12
|
+
flag", "flag is always the default", or "test code that reads a flag".
|
|
13
|
+
metadata:
|
|
14
|
+
type: core
|
|
15
|
+
library: "@adonis-agora/flags"
|
|
16
|
+
library_version: "0.1.0"
|
|
17
|
+
framework: adonisjs
|
|
18
|
+
license: MIT
|
|
19
|
+
sources:
|
|
20
|
+
- 'DavideCarvalho/adonis-agora-flags:docs/getting-started.mdx'
|
|
21
|
+
- 'DavideCarvalho/adonis-agora-flags:docs/declaring-flags.mdx'
|
|
22
|
+
- 'DavideCarvalho/adonis-agora-flags:docs/reading-flags.mdx'
|
|
23
|
+
- 'DavideCarvalho/adonis-agora-flags:docs/targeting.mdx'
|
|
24
|
+
- 'DavideCarvalho/adonis-agora-flags:docs/testing.mdx'
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# @adonis-agora/flags — declaring and reading flags
|
|
28
|
+
|
|
29
|
+
Flags are declared in code (key, type, default); who gets what lives in the
|
|
30
|
+
database and changes without a deploy. Evaluation is in memory: reading a flag
|
|
31
|
+
never waits on I/O and never throws — on any problem the declared default is
|
|
32
|
+
served.
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
node ace add @adonis-agora/flags
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// config/flags.ts
|
|
42
|
+
import { defineConfig, flag, type InferFlags } from '@adonis-agora/flags'
|
|
43
|
+
|
|
44
|
+
const flagsConfig = defineConfig({
|
|
45
|
+
flags: {
|
|
46
|
+
new_player: flag.boolean({ default: false, owner: 'video-team', expiresAt: '2027-03-31' }),
|
|
47
|
+
checkout: flag.string({ default: 'control', variants: ['control', 'one_page'] }),
|
|
48
|
+
max_uploads: flag.number({ default: 10 }),
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
export default flagsConfig
|
|
52
|
+
|
|
53
|
+
declare module '@adonis-agora/flags/types' {
|
|
54
|
+
interface FlagsList extends InferFlags<typeof flagsConfig> {}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Flags are usually created in the console (`/flags` → New flag: key, type, default, variants,
|
|
59
|
+
owner); their declaration travels with the ruleset. `node ace flags:types` writes
|
|
60
|
+
`types/flags.d.ts` so console flags are typed (and turns on strict keys). Declaring in
|
|
61
|
+
`config/flags.ts`, as above, is the alternative; code wins when both exist.
|
|
62
|
+
|
|
63
|
+
## Core patterns
|
|
64
|
+
|
|
65
|
+
### Pattern 1 — read a flag in a controller
|
|
66
|
+
|
|
67
|
+
```ts no-check
|
|
68
|
+
async show({ flags, inertia }: HttpContext) {
|
|
69
|
+
if (await flags.enabled('new_player')) return inertia.render('player/new')
|
|
70
|
+
return inertia.render('player/classic')
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`ctx.flags` evaluates for the authkit identity when `@adonis-agora/authkit-client`
|
|
75
|
+
is installed (targetingKey = `sub`; attributes `email`, `emailDomain`,
|
|
76
|
+
`globalRoles`, `orgId`, `orgSlug`, `orgRole`), else `ctx.auth.user`.
|
|
77
|
+
`identity` in the config replaces all of it with one `FlagsIdentityProvider`
|
|
78
|
+
(`subject`, `actor`, `authorizeConsole`, `can`): `identities.authkit()`,
|
|
79
|
+
`identities.adonisAuth({ guard, canOpenConsole: (user, ctx) =>
|
|
80
|
+
ctx.bouncer.allows(manageFlags) })`, or a class implementing the interface.
|
|
81
|
+
Outside HTTP:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import flags from '@adonis-agora/flags/services/main'
|
|
85
|
+
await flags.for(user).value('max_uploads')
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Pattern 2 — targeting attributes from server state
|
|
89
|
+
|
|
90
|
+
```ts no-check
|
|
91
|
+
// config/flags.ts
|
|
92
|
+
attributes: async (subject) => {
|
|
93
|
+
const user = await User.find(subject.id)
|
|
94
|
+
return { plan: user?.plan, orgId: user?.organizationId }
|
|
95
|
+
},
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Pattern 3 — guard a route
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
router.get('/beta', [BetaController]).use(middleware.flag({ flag: 'new_player' }))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Answers 404 when the flag says no.
|
|
105
|
+
|
|
106
|
+
### Pattern 4 — change a flag
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
node ace flags:set new_player 10% # rollout; raising it only adds people
|
|
110
|
+
node ace flags:set new_player on
|
|
111
|
+
node ace flags:set new_player kill # kill switch: off for everyone
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Pattern 5 — tests
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { fakeFlags } from '@adonis-agora/flags/testing'
|
|
118
|
+
test('new player', async ({ client, cleanup }) => {
|
|
119
|
+
cleanup(fakeFlags({ new_player: true }))
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Common mistakes
|
|
124
|
+
|
|
125
|
+
### HIGH — Expecting `setEnabled(true)` / `unkill` to turn a boolean flag on
|
|
126
|
+
|
|
127
|
+
`enabled` is the kill switch. Turning targeting back on does not serve `on`.
|
|
128
|
+
Use `flags:set <flag> on` (`admin.turnOn`).
|
|
129
|
+
|
|
130
|
+
### HIGH — Sending flags to the browser with `ctx.flags.all()` and getting `{}`
|
|
131
|
+
|
|
132
|
+
Only flags declared with `client: true` are included, so internal names never
|
|
133
|
+
leak. Declare the flag with `client: true`.
|
|
134
|
+
|
|
135
|
+
### MEDIUM — Reading targeting attributes from the request
|
|
136
|
+
|
|
137
|
+
`attributes` must come from server state. A query string or header lets users
|
|
138
|
+
pick their own variant.
|
|
139
|
+
|
|
140
|
+
### MEDIUM — Forgetting the `declare module` block
|
|
141
|
+
|
|
142
|
+
Without it keys are plain strings: typos compile and values are untyped.
|