showwhat 1.0.1 → 2.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/README.md CHANGED
@@ -1,6 +1,21 @@
1
1
  # showwhat
2
2
 
3
- Feature flag resolution engine — the main entry point for **showwhat**, a lightweight, extensible feature flag library.
3
+ Schema and rule engine for **feature flags** and **config** resolution
4
+ <br />Inspired by OpenAPI and Swagger
5
+
6
+ ## Features
7
+
8
+ - Define flags and config as [definitions](https://showwhat.yeojz.dev/docs/definitions) and declare which [variation](https://showwhat.yeojz.dev/docs/variations) is served based on [conditions](https://showwhat.yeojz.dev/docs/conditions).
9
+ - TypeScript-first with Zod validation.
10
+ - Supports `booleans`, `strings`, `numbers`, `arrays` and `objects` as resolved variation values.
11
+ - Supports both `yaml` or `json`.
12
+ - Runtime evaluation against user defined [context](https://showwhat.yeojz.dev/docs/context).
13
+ - Supports [annotations](https://showwhat.yeojz.dev/docs/annotations) for condition chaining and cross-dependency.
14
+ - Ability to define [presets](https://showwhat.yeojz.dev/docs/presets.html) for condition reuse.
15
+ - Extensible with [custom conditions](https://showwhat.yeojz.dev/docs/custom-conditions).
16
+ - Store definitions in files and manage them in version control or serve them from an API.
17
+
18
+ A browser based schema [configurator](https://showwhat.yeojz.dev/configurator/) is also provided / available.
4
19
 
5
20
  ## Installation
6
21
 
@@ -16,9 +31,43 @@ deno install npm:showwhat
16
31
 
17
32
  ## Quick start
18
33
 
34
+ ### 1. Define your flags
35
+
36
+ Definitions can be YAML files or plain objects. Each definition has **variations** evaluated top-to-bottom — the first match wins.
37
+
38
+ ```yaml
39
+ # flags.yaml
40
+ definitions:
41
+ checkout_v2:
42
+ variations:
43
+ - value: true
44
+ conditions:
45
+ - type: env
46
+ value: prod
47
+ - value: false # default — no conditions means always matches
48
+
49
+ max_upload_size:
50
+ variations:
51
+ - value: 100
52
+ conditions:
53
+ - type: number
54
+ key: tier_level
55
+ op: gte
56
+ value: 2
57
+ - value: 25
58
+ ```
59
+
60
+ ### 2. Load definitions
61
+
62
+ Use `MemoryData` to load definitions from YAML strings or plain objects:
63
+
19
64
  ```ts
20
- import { MemoryData, showwhat } from "showwhat";
65
+ import { MemoryData } from "showwhat";
66
+
67
+ // From a YAML string
68
+ const data = await MemoryData.fromYaml(fs.readFileSync("flags.yaml", "utf8"));
21
69
 
70
+ // Or from a plain object
22
71
  const data = await MemoryData.fromObject({
23
72
  definitions: {
24
73
  checkout_v2: {
@@ -26,25 +75,95 @@ const data = await MemoryData.fromObject({
26
75
  },
27
76
  },
28
77
  });
78
+ ```
79
+
80
+ ### 3. Resolve flags
81
+
82
+ Pass a runtime **context** and the keys you want to resolve:
29
83
 
30
- const result = await showwhat({
31
- key: "checkout_v2",
32
- context: { env: "prod" },
84
+ ```ts
85
+ import { showwhat } from "showwhat";
86
+
87
+ const results = await showwhat({
88
+ keys: ["checkout_v2", "max_upload_size"],
89
+ context: { env: "prod", tier_level: 3 },
33
90
  options: { data },
34
91
  });
35
- console.log(result.value); // true
36
92
  ```
37
93
 
38
- ## Features
94
+ Omit `keys` to resolve all definitions at once.
39
95
 
40
- - `showwhat()` resolution engine with context validation
41
- - Built-in condition evaluators: `string`, `number`, `datetime`, `bool`, `env`, `startAt`, `endAt`
42
- - Custom evaluators via `registerEvaluators()`
43
- - YAML and JSON parsing with schema validation
44
- - Pluggable data sources (`DefinitionReader` / `DefinitionWriter`)
45
- - Typed error hierarchy
96
+ ### 4. Use the results
46
97
 
47
- This package re-exports everything from `@showwhat/core`.
98
+ Every result entry is either `{ success: true, value }` or `{ success: false, error }`:
99
+
100
+ ```ts
101
+ const flag = results["checkout_v2"];
102
+ if (flag.success) {
103
+ console.log(flag.value); // true
104
+ }
105
+
106
+ const upload = results["max_upload_size"];
107
+ if (upload.success) {
108
+ console.log(upload.value); // 100
109
+ }
110
+ ```
111
+
112
+ ### Built-in condition types
113
+
114
+ | Type | Description | Example |
115
+ | ---------- | ------------------------------------ | -------------------------------------------------------------------- |
116
+ | `env` | Shorthand for matching `context.env` | `{ type: env, value: prod }` |
117
+ | `string` | Compare any string key | `{ type: string, key: tier, op: eq, value: pro }` |
118
+ | `number` | Compare any numeric key | `{ type: number, key: level, op: gte, value: 2 }` |
119
+ | `bool` | Compare any boolean key | `{ type: bool, key: mobile, value: true }` |
120
+ | `datetime` | Compare any datetime key | `{ type: datetime, key: at, op: gt, value: "2025-01-01T00:00:00Z" }` |
121
+ | `startAt` | Passes when `context.at >= value` | `{ type: startAt, value: "2025-06-01T00:00:00Z" }` |
122
+ | `endAt` | Passes when `context.at < value` | `{ type: endAt, value: "2025-07-01T00:00:00Z" }` |
123
+ | `and` | All child conditions must pass | `{ type: and, conditions: [...] }` |
124
+ | `or` | Any child condition must pass | `{ type: or, conditions: [...] }` |
125
+
126
+ ### Presets
127
+
128
+ Presets define reusable condition shorthands to keep definitions DRY:
129
+
130
+ ```yaml
131
+ definitions:
132
+ premium_feature:
133
+ variations:
134
+ - value: true
135
+ conditions:
136
+ - type: tier
137
+ op: in
138
+ value: [pro, enterprise]
139
+ - value: false
140
+
141
+ presets:
142
+ tier:
143
+ type: string
144
+ key: tier
145
+ ```
146
+
147
+ ### Custom conditions
148
+
149
+ Register your own evaluators to extend the built-in condition types:
150
+
151
+ ```ts
152
+ import { showwhat, registerEvaluators, MemoryData } from "showwhat";
153
+
154
+ const evaluators = registerEvaluators({
155
+ percentage: async ({ condition, context }) => {
156
+ const hash = someHash(context.userId);
157
+ return hash % 100 < condition.value;
158
+ },
159
+ });
160
+
161
+ const results = await showwhat({
162
+ keys: ["gradual_rollout"],
163
+ context: { userId: "user-123" },
164
+ options: { data, evaluators },
165
+ });
166
+ ```
48
167
 
49
168
  ## Documentation
50
169
 
@@ -52,7 +171,6 @@ This package re-exports everything from `@showwhat/core`.
52
171
  - [Conditions](https://showwhat.yeojz.dev/docs/conditions) — built-in condition types and usage
53
172
  - [Context](https://showwhat.yeojz.dev/docs/context) — runtime context object
54
173
  - [Definitions](https://showwhat.yeojz.dev/docs/definitions) — definition structure and variations
55
- - [Core API](https://showwhat.yeojz.dev/docs/core) — `showwhat()`, `resolve()`, parsing, and more
56
174
  - [Errors](https://showwhat.yeojz.dev/docs/errors) — error types and when they are thrown
57
175
  - [Custom Conditions](https://showwhat.yeojz.dev/docs/custom-conditions) — writing your own evaluators
58
176
  - [Custom Data Sources](https://showwhat.yeojz.dev/docs/custom-data-sources) — implementing `DefinitionReader`
package/dist/index.d.ts CHANGED
@@ -1,14 +1,21 @@
1
- import { ResolverOptions, DefinitionReader, ConditionEvaluators, BuiltinCondition, Context, Resolution } from '@showwhat/core';
1
+ import { Resolution, ResolutionError, ResolverOptions, DefinitionReader, PresetReader, Presets, ConditionEvaluators, Context, Dependencies } from '@showwhat/core';
2
2
  export * from '@showwhat/core';
3
3
 
4
4
  type ShowWhatOptions = ResolverOptions & {
5
5
  data: DefinitionReader;
6
6
  };
7
- declare function showwhat({ key, context, options, }: {
8
- key: string;
7
+ type Resolutions = Record<string, Resolution | ResolutionError>;
8
+ declare function showwhat({ keys, context, deps, options, }: {
9
+ keys?: string[];
9
10
  context: Context;
11
+ deps?: Dependencies;
10
12
  options: ShowWhatOptions;
11
- }): Promise<Resolution>;
12
- declare function registerEvaluators<T extends string>(extra: ConditionEvaluators<T>): ConditionEvaluators<BuiltinCondition["type"] | T>;
13
+ }): Promise<Resolutions>;
14
+ declare function mergePresets({ key, presets, overrides, }: {
15
+ key?: string;
16
+ presets?: PresetReader;
17
+ overrides?: Presets;
18
+ }): Promise<Presets>;
19
+ declare function registerEvaluators(extra: ConditionEvaluators): ConditionEvaluators;
13
20
 
14
- export { type ShowWhatOptions, registerEvaluators, showwhat };
21
+ export { type Resolutions, type ShowWhatOptions, mergePresets, registerEvaluators, showwhat };
package/dist/index.js CHANGED
@@ -2,14 +2,35 @@
2
2
  import {
3
3
  ContextSchema,
4
4
  builtinEvaluators,
5
- DefinitionNotFoundError,
6
5
  ValidationError,
6
+ DataError,
7
7
  resolve
8
8
  } from "@showwhat/core";
9
9
  export * from "@showwhat/core";
10
+ async function fetchDefinitions(data, keys) {
11
+ if (!keys) {
12
+ try {
13
+ return await data.getAll();
14
+ } catch (err) {
15
+ throw new DataError("Failed to fetch definitions", err);
16
+ }
17
+ }
18
+ const definitions = {};
19
+ await Promise.all(
20
+ keys.map(async (key) => {
21
+ try {
22
+ definitions[key] = await data.get(key);
23
+ } catch (err) {
24
+ throw new DataError(`Failed to fetch definition "${key}"`, err);
25
+ }
26
+ })
27
+ );
28
+ return definitions;
29
+ }
10
30
  async function showwhat({
11
- key,
31
+ keys,
12
32
  context,
33
+ deps,
13
34
  options
14
35
  }) {
15
36
  const contextResult = ContextSchema.safeParse(context);
@@ -19,23 +40,33 @@ async function showwhat({
19
40
  "context"
20
41
  );
21
42
  }
22
- const validatedContext = contextResult.data;
23
- const def = await options.data.get(key);
24
- if (!def) {
25
- throw new DefinitionNotFoundError(key);
26
- }
27
- const result = await resolve({
28
- definitions: { [key]: def },
29
- context: validatedContext,
43
+ return resolve({
44
+ definitions: await fetchDefinitions(options.data, keys),
45
+ context: contextResult.data,
46
+ deps,
30
47
  options: {
31
- evaluators: options.evaluators ?? builtinEvaluators,
32
- fallback: options.fallback,
33
- logger: options.logger
48
+ ...options,
49
+ evaluators: options.evaluators ?? builtinEvaluators
34
50
  }
35
51
  });
36
- return result[key];
37
52
  }
38
- var COMPOSITE_TYPES = /* @__PURE__ */ new Set(["and", "or"]);
53
+ async function mergePresets({
54
+ key,
55
+ presets,
56
+ overrides
57
+ }) {
58
+ if (!presets) {
59
+ return {
60
+ ...overrides
61
+ };
62
+ }
63
+ const base = key ? await presets.getPresets(key) : await presets.getPresets();
64
+ return {
65
+ ...base,
66
+ ...overrides
67
+ };
68
+ }
69
+ var COMPOSITE_TYPES = /* @__PURE__ */ new Set(["and", "or", "checkAnnotations"]);
39
70
  function registerEvaluators(extra) {
40
71
  for (const key of Object.keys(extra)) {
41
72
  if (COMPOSITE_TYPES.has(key)) {
@@ -45,6 +76,7 @@ function registerEvaluators(extra) {
45
76
  return { ...builtinEvaluators, ...extra };
46
77
  }
47
78
  export {
79
+ mergePresets,
48
80
  registerEvaluators,
49
81
  showwhat
50
82
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n ContextSchema,\n builtinEvaluators,\n DefinitionNotFoundError,\n ValidationError,\n resolve,\n} from \"@showwhat/core\";\nimport type {\n BuiltinCondition,\n ConditionEvaluators,\n Context,\n DefinitionReader,\n Resolution,\n ResolverOptions,\n} from \"@showwhat/core\";\n\nexport * from \"@showwhat/core\";\n\nexport type ShowWhatOptions = ResolverOptions & {\n data: DefinitionReader;\n};\n\nexport async function showwhat({\n key,\n context,\n options,\n}: {\n key: string;\n context: Context;\n options: ShowWhatOptions;\n}): Promise<Resolution> {\n const contextResult = ContextSchema.safeParse(context);\n if (!contextResult.success) {\n throw new ValidationError(\n contextResult.error.issues.map((i) => `[${i.path.join(\".\")}] ${i.message}`).join(\"; \"),\n \"context\",\n );\n }\n\n const validatedContext = contextResult.data;\n const def = await options.data.get(key);\n\n if (!def) {\n throw new DefinitionNotFoundError(key);\n }\n\n const result = await resolve({\n definitions: { [key]: def },\n context: validatedContext,\n options: {\n evaluators: options.evaluators ?? builtinEvaluators,\n fallback: options.fallback,\n logger: options.logger,\n },\n });\n\n return result[key];\n}\n\nconst COMPOSITE_TYPES = new Set([\"and\", \"or\"]);\n\nexport function registerEvaluators<T extends string>(\n extra: ConditionEvaluators<T>,\n): ConditionEvaluators<BuiltinCondition[\"type\"] | T> {\n for (const key of Object.keys(extra)) {\n if (COMPOSITE_TYPES.has(key)) {\n throw new Error(`Cannot register reserved condition type \"${key}\"`);\n }\n }\n return { ...builtinEvaluators, ...extra };\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,cAAc;AAMd,eAAsB,SAAS;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAIwB;AACtB,QAAM,gBAAgB,cAAc,UAAU,OAAO;AACrD,MAAI,CAAC,cAAc,SAAS;AAC1B,UAAM,IAAI;AAAA,MACR,cAAc,MAAM,OAAO,IAAI,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,mBAAmB,cAAc;AACvC,QAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,GAAG;AAEtC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,wBAAwB,GAAG;AAAA,EACvC;AAEA,QAAM,SAAS,MAAM,QAAQ;AAAA,IAC3B,aAAa,EAAE,CAAC,GAAG,GAAG,IAAI;AAAA,IAC1B,SAAS;AAAA,IACT,SAAS;AAAA,MACP,YAAY,QAAQ,cAAc;AAAA,MAClC,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ;AAAA,IAClB;AAAA,EACF,CAAC;AAED,SAAO,OAAO,GAAG;AACnB;AAEA,IAAM,kBAAkB,oBAAI,IAAI,CAAC,OAAO,IAAI,CAAC;AAEtC,SAAS,mBACd,OACmD;AACnD,aAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,QAAI,gBAAgB,IAAI,GAAG,GAAG;AAC5B,YAAM,IAAI,MAAM,4CAA4C,GAAG,GAAG;AAAA,IACpE;AAAA,EACF;AACA,SAAO,EAAE,GAAG,mBAAmB,GAAG,MAAM;AAC1C;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n ContextSchema,\n builtinEvaluators,\n ValidationError,\n DataError,\n resolve,\n} from \"@showwhat/core\";\nimport type {\n ConditionEvaluators,\n Context,\n Definitions,\n DefinitionReader,\n Dependencies,\n Presets,\n PresetReader,\n Resolution,\n ResolutionError,\n ResolverOptions,\n} from \"@showwhat/core\";\n\nexport * from \"@showwhat/core\";\n\nexport type ShowWhatOptions = ResolverOptions & {\n data: DefinitionReader;\n};\n\nexport type Resolutions = Record<string, Resolution | ResolutionError>;\n\nasync function fetchDefinitions(data: DefinitionReader, keys?: string[]): Promise<Definitions> {\n if (!keys) {\n try {\n return await data.getAll();\n } catch (err) {\n throw new DataError(\"Failed to fetch definitions\", err);\n }\n }\n\n const definitions = {} as Record<string, Definitions[string] | null>;\n await Promise.all(\n keys.map(async (key) => {\n try {\n definitions[key] = await data.get(key);\n } catch (err) {\n throw new DataError(`Failed to fetch definition \"${key}\"`, err);\n }\n }),\n );\n return definitions as Definitions;\n}\n\nexport async function showwhat({\n keys,\n context,\n deps,\n options,\n}: {\n keys?: string[];\n context: Context;\n deps?: Dependencies;\n options: ShowWhatOptions;\n}): Promise<Resolutions> {\n const contextResult = ContextSchema.safeParse(context);\n if (!contextResult.success) {\n throw new ValidationError(\n contextResult.error.issues.map((i) => `[${i.path.join(\".\")}] ${i.message}`).join(\"; \"),\n \"context\",\n );\n }\n\n return resolve({\n definitions: await fetchDefinitions(options.data, keys),\n context: contextResult.data as Context,\n deps,\n options: {\n ...options,\n evaluators: options.evaluators ?? builtinEvaluators,\n },\n });\n}\n\nexport async function mergePresets({\n key,\n presets,\n overrides,\n}: {\n key?: string;\n presets?: PresetReader;\n overrides?: Presets;\n}): Promise<Presets> {\n if (!presets) {\n return {\n ...overrides,\n };\n }\n\n const base = key ? await presets.getPresets(key) : await presets.getPresets();\n\n return {\n ...base,\n ...overrides,\n };\n}\n\nconst COMPOSITE_TYPES = new Set([\"and\", \"or\", \"checkAnnotations\"]);\n\nexport function registerEvaluators(extra: ConditionEvaluators): ConditionEvaluators {\n for (const key of Object.keys(extra)) {\n if (COMPOSITE_TYPES.has(key)) {\n throw new Error(`Cannot register reserved condition type \"${key}\"`);\n }\n }\n return { ...builtinEvaluators, ...extra };\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcP,cAAc;AAQd,eAAe,iBAAiB,MAAwB,MAAuC;AAC7F,MAAI,CAAC,MAAM;AACT,QAAI;AACF,aAAO,MAAM,KAAK,OAAO;AAAA,IAC3B,SAAS,KAAK;AACZ,YAAM,IAAI,UAAU,+BAA+B,GAAG;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,cAAc,CAAC;AACrB,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,OAAO,QAAQ;AACtB,UAAI;AACF,oBAAY,GAAG,IAAI,MAAM,KAAK,IAAI,GAAG;AAAA,MACvC,SAAS,KAAK;AACZ,cAAM,IAAI,UAAU,+BAA+B,GAAG,KAAK,GAAG;AAAA,MAChE;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAsB,SAAS;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKyB;AACvB,QAAM,gBAAgB,cAAc,UAAU,OAAO;AACrD,MAAI,CAAC,cAAc,SAAS;AAC1B,UAAM,IAAI;AAAA,MACR,cAAc,MAAM,OAAO,IAAI,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,QAAQ;AAAA,IACb,aAAa,MAAM,iBAAiB,QAAQ,MAAM,IAAI;AAAA,IACtD,SAAS,cAAc;AAAA,IACvB;AAAA,IACA,SAAS;AAAA,MACP,GAAG;AAAA,MACH,YAAY,QAAQ,cAAc;AAAA,IACpC;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,aAAa;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,GAIqB;AACnB,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,GAAG;AAAA,IACL;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,MAAM,QAAQ,WAAW,GAAG,IAAI,MAAM,QAAQ,WAAW;AAE5E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACF;AAEA,IAAM,kBAAkB,oBAAI,IAAI,CAAC,OAAO,MAAM,kBAAkB,CAAC;AAE1D,SAAS,mBAAmB,OAAiD;AAClF,aAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,QAAI,gBAAgB,IAAI,GAAG,GAAG;AAC5B,YAAM,IAAI,MAAM,4CAA4C,GAAG,GAAG;AAAA,IACpE;AAAA,EACF;AACA,SAAO,EAAE,GAAG,mBAAmB,GAAG,MAAM;AAC1C;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showwhat",
3
- "version": "1.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "A schema-based resolution engine for configuration and feature flags",
5
5
  "keywords": [
6
6
  "feature-flags",
@@ -41,14 +41,14 @@
41
41
  "dist"
42
42
  ],
43
43
  "dependencies": {
44
- "@showwhat/core": "1.0.1"
44
+ "@showwhat/core": "2.1.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@types/node": "^25.4.0",
48
- "@vitest/coverage-v8": "^4.0.0",
49
- "tsup": "^8.0.0",
50
- "typescript": "^5.4.0",
51
- "vitest": "^4.0.0"
47
+ "@types/node": "^25.5.0",
48
+ "@vitest/coverage-v8": "^4.1.4",
49
+ "tsup": "^8.5.1",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^4.1.4"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "tsup",