@velajs/cloudflare 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,6 +23,10 @@ export { AIService } from './services/ai.service';
23
23
  export { VectorizeService } from './services/vectorize.service';
24
24
  export { HyperdriveService } from './services/hyperdrive.service';
25
25
  export { EnvService } from './services/env.service';
26
+ export { FlagshipFlagDriver, flagshipFlagDriver } from './services/flagship-flag.driver';
27
+ export type { FlagshipBinding, FlagshipFlagDriverOptions } from './services/flagship-flag.driver';
28
+ export { KvFlagDriver, kvFlagDriver } from './services/kv-flag.driver';
29
+ export type { KvFlagDriverOptions } from './services/kv-flag.driver';
26
30
  export { Env } from './decorators/env';
27
31
  export { Scheduled } from './decorators/scheduled';
28
32
  export { QueueConsumer } from './decorators/queue-consumer';
package/dist/index.js CHANGED
@@ -24,6 +24,9 @@ export { AIService } from "./services/ai.service.js";
24
24
  export { VectorizeService } from "./services/vectorize.service.js";
25
25
  export { HyperdriveService } from "./services/hyperdrive.service.js";
26
26
  export { EnvService } from "./services/env.service.js";
27
+ // Feature-flag drivers (implement @velajs/feature-flags' FeatureFlagDriver contract)
28
+ export { FlagshipFlagDriver, flagshipFlagDriver } from "./services/flagship-flag.driver.js";
29
+ export { KvFlagDriver, kvFlagDriver } from "./services/kv-flag.driver.js";
27
30
  // Decorators
28
31
  export { Env } from "./decorators/env.js";
29
32
  export { Scheduled } from "./decorators/scheduled.js";
@@ -0,0 +1,57 @@
1
+ import type { FeatureFlagDriver, FlagContext } from '@velajs/feature-flags';
2
+ /**
3
+ * The subset of a Cloudflare **Flagship** binding this driver evaluates against
4
+ * — the four typed value methods. The binding itself returns the supplied
5
+ * `defaultValue` on evaluation errors; transport-level failures reject and are
6
+ * left to propagate (never-throw is the service layer's job, not the driver's).
7
+ *
8
+ * @see https://developers.cloudflare.com/flagship/binding/
9
+ */
10
+ export interface FlagshipBinding {
11
+ getBooleanValue(key: string, defaultValue: boolean, context?: FlagContext): Promise<boolean>;
12
+ getStringValue(key: string, defaultValue: string, context?: FlagContext): Promise<string>;
13
+ getNumberValue(key: string, defaultValue: number, context?: FlagContext): Promise<number>;
14
+ getObjectValue<T extends object>(key: string, defaultValue: T, context?: FlagContext): Promise<T>;
15
+ }
16
+ export interface FlagshipFlagDriverOptions {
17
+ /** Driver name used for `use(name)` / default-driver selection. Default `"flagship"`. */
18
+ name?: string;
19
+ }
20
+ /**
21
+ * {@link FeatureFlagDriver} backed by a Cloudflare Flagship binding.
22
+ *
23
+ * A thin, honest wrapper: each contract method maps 1:1 onto the binding's
24
+ * corresponding value method, forwarding the caller's `fallback` (the binding's
25
+ * `defaultValue`) and evaluation context. The binding resolves the fallback on
26
+ * evaluation errors; anything the binding *rejects* with (e.g. a `remote: true`
27
+ * dev-proxy tunnel dropping) propagates — `@velajs/feature-flags`'s service owns
28
+ * the never-throw guarantee.
29
+ *
30
+ * The binding only exists per request in a Worker, so pass a lazy accessor when
31
+ * wiring from a bootstrap factory (mirroring how {@link EnvService} reads are
32
+ * deferred); a resolved binding may be passed directly in tests.
33
+ *
34
+ * ```ts
35
+ * FeatureFlagsModule.forRootAsync({
36
+ * inject: [EnvService],
37
+ * useFactory: (env: EnvService) => ({
38
+ * drivers: [flagshipFlagDriver(() => env.get<FlagshipBinding>('FLAGS')!)],
39
+ * }),
40
+ * });
41
+ * ```
42
+ *
43
+ * Evaluation ergonomics (the 1:1 binding-method mapping) are ported from the
44
+ * Stratal feature-flags service (MIT, © Temitayo Fadojutimi), reshaped as a
45
+ * bare driver.
46
+ */
47
+ export declare class FlagshipFlagDriver implements FeatureFlagDriver {
48
+ readonly name: string;
49
+ private readonly resolve;
50
+ constructor(binding: FlagshipBinding | (() => FlagshipBinding), options?: FlagshipFlagDriverOptions);
51
+ getBoolean(key: string, fallback: boolean, ctx?: FlagContext): Promise<boolean>;
52
+ getString(key: string, fallback: string, ctx?: FlagContext): Promise<string>;
53
+ getNumber(key: string, fallback: number, ctx?: FlagContext): Promise<number>;
54
+ getObject<T extends object>(key: string, fallback: T, ctx?: FlagContext): Promise<T>;
55
+ }
56
+ /** Convenience factory for {@link FlagshipFlagDriver}. */
57
+ export declare function flagshipFlagDriver(binding: FlagshipBinding | (() => FlagshipBinding), options?: FlagshipFlagDriverOptions): FlagshipFlagDriver;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * {@link FeatureFlagDriver} backed by a Cloudflare Flagship binding.
3
+ *
4
+ * A thin, honest wrapper: each contract method maps 1:1 onto the binding's
5
+ * corresponding value method, forwarding the caller's `fallback` (the binding's
6
+ * `defaultValue`) and evaluation context. The binding resolves the fallback on
7
+ * evaluation errors; anything the binding *rejects* with (e.g. a `remote: true`
8
+ * dev-proxy tunnel dropping) propagates — `@velajs/feature-flags`'s service owns
9
+ * the never-throw guarantee.
10
+ *
11
+ * The binding only exists per request in a Worker, so pass a lazy accessor when
12
+ * wiring from a bootstrap factory (mirroring how {@link EnvService} reads are
13
+ * deferred); a resolved binding may be passed directly in tests.
14
+ *
15
+ * ```ts
16
+ * FeatureFlagsModule.forRootAsync({
17
+ * inject: [EnvService],
18
+ * useFactory: (env: EnvService) => ({
19
+ * drivers: [flagshipFlagDriver(() => env.get<FlagshipBinding>('FLAGS')!)],
20
+ * }),
21
+ * });
22
+ * ```
23
+ *
24
+ * Evaluation ergonomics (the 1:1 binding-method mapping) are ported from the
25
+ * Stratal feature-flags service (MIT, © Temitayo Fadojutimi), reshaped as a
26
+ * bare driver.
27
+ */ export class FlagshipFlagDriver {
28
+ name;
29
+ resolve;
30
+ constructor(binding, options = {}){
31
+ this.resolve = typeof binding === 'function' ? binding : ()=>binding;
32
+ this.name = options.name ?? 'flagship';
33
+ }
34
+ getBoolean(key, fallback, ctx) {
35
+ return this.resolve().getBooleanValue(key, fallback, ctx);
36
+ }
37
+ getString(key, fallback, ctx) {
38
+ return this.resolve().getStringValue(key, fallback, ctx);
39
+ }
40
+ getNumber(key, fallback, ctx) {
41
+ return this.resolve().getNumberValue(key, fallback, ctx);
42
+ }
43
+ getObject(key, fallback, ctx) {
44
+ return this.resolve().getObjectValue(key, fallback, ctx);
45
+ }
46
+ }
47
+ /** Convenience factory for {@link FlagshipFlagDriver}. */ export function flagshipFlagDriver(binding, options) {
48
+ return new FlagshipFlagDriver(binding, options);
49
+ }
@@ -0,0 +1,48 @@
1
+ import type { FeatureFlagDriver, FlagContext } from '@velajs/feature-flags';
2
+ import { KVService } from './kv.service';
3
+ export interface KvFlagDriverOptions {
4
+ /** Driver name used for `use(name)` / default-driver selection. Default `"kv"`. */
5
+ name?: string;
6
+ /** Prefix prepended to every flag key before the KV read. Default `""` (none). */
7
+ prefix?: string;
8
+ }
9
+ /**
10
+ * Cloudflare KV-backed {@link FeatureFlagDriver}. Flags are stored as JSON
11
+ * values under an optional key prefix and read with `get(key, 'json')`. Reads
12
+ * are type-checked against the requested type: a missing key or a value of the
13
+ * wrong JSON type returns the caller's `fallback`. KV has no targeting, so the
14
+ * evaluation context is ignored.
15
+ *
16
+ * The driver stays honest — it does **not** swallow errors. A KV failure (or a
17
+ * `SyntaxError` from a malformed stored value) propagates; the never-throw
18
+ * guarantee lives in `@velajs/feature-flags`'s service layer.
19
+ *
20
+ * Placed like {@link KVCacheStore}: construct it in a wiring factory over a
21
+ * resolved {@link KVService}.
22
+ *
23
+ * ```ts
24
+ * FeatureFlagsModule.forRootAsync({
25
+ * inject: [KVService],
26
+ * useFactory: (kv: KVService) => ({ drivers: [new KvFlagDriver(kv, { prefix: 'flag:' })] }),
27
+ * });
28
+ * ```
29
+ */
30
+ export declare class KvFlagDriver implements FeatureFlagDriver {
31
+ private readonly kv;
32
+ readonly name: string;
33
+ private readonly prefix;
34
+ constructor(kv: KVService, options?: KvFlagDriverOptions);
35
+ private get ns();
36
+ getBoolean(key: string, fallback: boolean, _ctx?: FlagContext): Promise<boolean>;
37
+ getString(key: string, fallback: string, _ctx?: FlagContext): Promise<string>;
38
+ getNumber(key: string, fallback: number, _ctx?: FlagContext): Promise<number>;
39
+ getObject<T extends object>(key: string, fallback: T, _ctx?: FlagContext): Promise<T>;
40
+ /**
41
+ * Reads and JSON-parses the (prefixed) key, returning the parsed value only
42
+ * when `matches` accepts its type; otherwise the caller's fallback. A missing
43
+ * key reads as `null` → fallback. Read/parse errors are left to propagate.
44
+ */
45
+ private read;
46
+ }
47
+ /** Convenience factory for {@link KvFlagDriver}. */
48
+ export declare function kvFlagDriver(kv: KVService, options?: KvFlagDriverOptions): KvFlagDriver;
@@ -0,0 +1,62 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ function _ts_metadata(k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ }
10
+ function _ts_param(paramIndex, decorator) {
11
+ return function(target, key) {
12
+ decorator(target, key, paramIndex);
13
+ };
14
+ }
15
+ import { Inject, Injectable } from "@velajs/vela";
16
+ import { KVService } from "./kv.service.js";
17
+ export class KvFlagDriver {
18
+ kv;
19
+ name;
20
+ prefix;
21
+ constructor(kv, options = {}){
22
+ this.kv = kv;
23
+ this.name = options.name ?? 'kv';
24
+ this.prefix = options.prefix ?? '';
25
+ }
26
+ get ns() {
27
+ return this.kv.namespace;
28
+ }
29
+ getBoolean(key, fallback, _ctx) {
30
+ return this.read(key, fallback, (v)=>typeof v === 'boolean');
31
+ }
32
+ getString(key, fallback, _ctx) {
33
+ return this.read(key, fallback, (v)=>typeof v === 'string');
34
+ }
35
+ getNumber(key, fallback, _ctx) {
36
+ return this.read(key, fallback, (v)=>typeof v === 'number');
37
+ }
38
+ getObject(key, fallback, _ctx) {
39
+ return this.read(key, fallback, (v)=>typeof v === 'object' && v !== null);
40
+ }
41
+ /**
42
+ * Reads and JSON-parses the (prefixed) key, returning the parsed value only
43
+ * when `matches` accepts its type; otherwise the caller's fallback. A missing
44
+ * key reads as `null` → fallback. Read/parse errors are left to propagate.
45
+ */ async read(key, fallback, matches) {
46
+ const value = await this.ns.get(this.prefix + key, 'json');
47
+ if (value === null || value === undefined) return fallback;
48
+ return matches(value) ? value : fallback;
49
+ }
50
+ }
51
+ KvFlagDriver = _ts_decorate([
52
+ Injectable(),
53
+ _ts_param(0, Inject(KVService)),
54
+ _ts_metadata("design:type", Function),
55
+ _ts_metadata("design:paramtypes", [
56
+ typeof KVService === "undefined" ? Object : KVService,
57
+ typeof KvFlagDriverOptions === "undefined" ? Object : KvFlagDriverOptions
58
+ ])
59
+ ], KvFlagDriver);
60
+ /** Convenience factory for {@link KvFlagDriver}. */ export function kvFlagDriver(kv, options) {
61
+ return new KvFlagDriver(kv, options);
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/cloudflare",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Cloudflare Workers integration for Vela framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,13 +45,20 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@cloudflare/workers-types": ">=4",
48
+ "@velajs/feature-flags": "^0.1.0",
48
49
  "@velajs/vela": ">=1.11.0",
49
50
  "hono": ">=4"
50
51
  },
52
+ "peerDependenciesMeta": {
53
+ "@velajs/feature-flags": {
54
+ "optional": true
55
+ }
56
+ },
51
57
  "devDependencies": {
52
58
  "@cloudflare/workers-types": "^4.20260624.1",
53
59
  "@swc/cli": "^0.8.1",
54
60
  "@swc/core": "^1.15.43",
61
+ "@velajs/feature-flags": "^0.1.0",
55
62
  "@velajs/vela": "^1.12.0",
56
63
  "hono": "^4.12.27",
57
64
  "typescript": "^6.0.3",