@voltro/plugin-flags 0.86.0 → 0.87.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/README.md CHANGED
@@ -24,3 +24,109 @@ deliberately short so there is nothing here to drift out of date.
24
24
 
25
25
  Proprietary. © 2026 Voltro UG. Your use is governed by the
26
26
  [Terms of Service](https://voltro.cloud/legal/terms). See the bundled `LICENSE`.
27
+
28
+ ## Typed Remote Config
29
+
30
+ `defineConfig` is an optional `flagsPlugin` extension. Its declared bounds are
31
+ budgets. Config editing, automatic Room delivery and a full LiveOps lifecycle
32
+ remain open.
33
+
34
+ ```ts
35
+ // lib/public-game-config.ts — browser-safe public declarations only
36
+ import { Schema } from 'effect'
37
+ import { defineConfig } from '@voltro/plugin-flags/config'
38
+
39
+ export const publicConfigSpec = {
40
+ 'xp.multiplier': { schema: Schema.Number, default: 1, min: 0.5, max: 5, public: true },
41
+ 'shop.featured': { schema: Schema.Array(Schema.String), default: [], public: true, restartRequired: 'live' },
42
+ } as const
43
+ export const publicGameConfig = defineConfig(publicConfigSpec)
44
+ ```
45
+
46
+ ```ts
47
+ // app.config.ts — server only
48
+ import { Schema } from 'effect'
49
+ import { flagsPlugin } from '@voltro/plugin-flags'
50
+ import { defineConfig } from '@voltro/plugin-flags/config'
51
+ import { publicConfigSpec } from './lib/public-game-config'
52
+
53
+ export const gameConfig = defineConfig({
54
+ ...publicConfigSpec,
55
+ 'arena.maxPlayers': { schema: Schema.Int, default: 32, min: 1, max: 256, restartRequired: 'room' },
56
+ })
57
+ export const plugins = [flagsPlugin({ config: gameConfig, store: 'postgres' })]
58
+ ```
59
+
60
+ `store:'postgres'` names the existing durable Flag tier; it borrows the app
61
+ DataStore and its SQL dialect. No separate pool or Config table is created.
62
+ Validated rows in `_voltro_feature_flags` replace the code definition whole.
63
+ Invalid values, targeting, schedules or bounds refuse the entire override and
64
+ retain the code definition. Error and inspect responses omit rejected values.
65
+ SQL is the override truth and rebuild source; no direct backend or tick read is
66
+ introduced.
67
+
68
+ Each key declares an Effect Schema and a typed default. Numeric `min`/`max`
69
+ refine that schema. `variants`, `targeting`, `rollout`, `rolloutBy` and `schedule`
70
+ reuse Flag targeting and deterministic bucketing. A disabled, excluded or
71
+ out-of-window Config resolves to its declared default. Keys are private by
72
+ default; only `public:true` keys reach the caller-bound `config.mine` query.
73
+ Existing Flag evaluation, variant queries, lifecycle rows and boolean toggles
74
+ omit Config keys. A Config key cannot be edited through the boolean toggle.
75
+
76
+ ```ts
77
+ // server handler — read before Room creation, never from its tick
78
+ import { Effect } from 'effect'
79
+ import { ConfigPort, configValue } from '@voltro/plugin-flags/config-port'
80
+ import { gameConfig } from './app.config'
81
+
82
+ export const readRoomConfig = (subject: { id: string; tenantId?: string; type?: string }) =>
83
+ Effect.gen(function* () {
84
+ const config = yield* ConfigPort
85
+ const multiplier = yield* configValue(gameConfig, config, 'xp.multiplier', subject)
86
+ const snapshot = yield* config.snapshot(subject, 'room')
87
+ return { multiplier, snapshot }
88
+ })
89
+ ```
90
+
91
+ The complete read port is `get(key,subject)`, `mine(subject)` and
92
+ `snapshot(subject,'room'|'live')`. `room` is the default application scope;
93
+ `live` is explicit. A snapshot uses one time and one merged definition set.
94
+ The app passes it during creation or through an explicit message outside the
95
+ tick. Declaring a scope does not install Room delivery. Escaped effects from a
96
+ released activation refuse reads; another Flag instance does not replace this
97
+ instance's Config snapshot.
98
+
99
+ ```tsx
100
+ import { useConfig } from '@voltro/plugin-flags/web'
101
+ import { publicGameConfig } from '../lib/public-game-config'
102
+
103
+ export function FeaturedItems() {
104
+ const items = useConfig(publicGameConfig, 'shop.featured')
105
+ return <ul>{items.map(item => <li key={item}>{item}</li>)}</ul>
106
+ }
107
+ ```
108
+
109
+ The web hook accepts only explicitly public keys and uses the validated default
110
+ while no valid response exists. Import a browser-safe catalog that contains no
111
+ private values; server-only declarations must stay outside web imports.
112
+
113
+ `configResolver` overrides the pure resolution slot and requires a named
114
+ `configVersion` other than `default.v1`. `configService` replaces all three port
115
+ methods, preserves class receivers and requires the same version plus explicit
116
+ `configQuerySources` (an empty list means an app-owned static view). Resolver and
117
+ complete port replacement cannot be combined. Replacement owns its storage,
118
+ privacy and invalidation sources; it borrows no default persistence behavior.
119
+ Returned keys and values still obey the declared public/snapshot schema.
120
+
121
+ The value and override capture budget is 16,384 encoded JSON bytes, 4,096 nodes,
122
+ 16 nesting levels, 1,024 array entries and 256 object properties; keys are bounded
123
+ to 120 characters and catalogs to 128 keys. Override metadata counts toward the
124
+ byte budget too. Non-finite numbers, cycles, accessors, non-JSON objects and
125
+ unbounded input refuse. These are configured limits, not measured capacities.
126
+ Config contributes no new subject table. App-owned replacement privacy remains
127
+ its owner's responsibility; existing Flag audit/usage retention is independent.
128
+
129
+ Local and Cloud Flags panels show key/type/public/scope/bounds/version/ownership
130
+ through the same canonical inspect schema. They never show resolved values,
131
+ subjects or rejected payloads, and distinguish the remaining editor/delivery
132
+ work.