@ops-ai/astro-feature-flags-toggly 1.2.1 → 1.3.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 +101 -0
- package/dist/{chunk-MKHBDBTR.js → chunk-GW5NHZA2.js} +2 -2
- package/dist/{chunk-Z3WWTK42.js → chunk-ILEHRLEW.js} +153 -2
- package/dist/chunk-ILEHRLEW.js.map +1 -0
- package/dist/client/setup.d.ts +2 -1
- package/dist/client/setup.js +1 -1
- package/dist/client/store.d.ts +12 -2
- package/dist/client/store.js +5 -1
- package/dist/frameworks/react/Feature.js +2 -2
- package/dist/frameworks/react/index.js +2 -2
- package/dist/frameworks/svelte/stores.d.ts +2 -1
- package/dist/frameworks/svelte/stores.js +1 -1
- package/dist/frameworks/vue/composables.js +1 -1
- package/dist/{index-CNGi0JrL.d.ts → index-Cjy13d0b.d.ts} +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/integration/index.d.ts +2 -1
- package/dist/server/toggly-server.d.ts +2 -1
- package/dist/server/utils.d.ts +2 -1
- package/package.json +2 -1
- package/dist/chunk-Z3WWTK42.js.map +0 -1
- /package/dist/{chunk-MKHBDBTR.js.map → chunk-GW5NHZA2.js.map} +0 -0
package/README.md
CHANGED
|
@@ -504,4 +504,105 @@ MIT
|
|
|
504
504
|
- [@ops-ai/react-feature-flags-toggly](https://www.npmjs.com/package/@ops-ai/react-feature-flags-toggly) - React SDK
|
|
505
505
|
- [@ops-ai/feature-flags-toggly](https://www.npmjs.com/package/@ops-ai/feature-flags-toggly) - Vanilla JavaScript SDK
|
|
506
506
|
|
|
507
|
+
## Extensibility with Hooks
|
|
508
|
+
|
|
509
|
+
Toggly provides a powerful hooks system that allows you to extend SDK functionality by hooking into feature flag lifecycle events. This is perfect for integrating with analytics, monitoring tools, or implementing custom behaviors.
|
|
510
|
+
|
|
511
|
+
### What are Hooks?
|
|
512
|
+
|
|
513
|
+
Hooks let you execute custom code at specific points in the feature flag evaluation lifecycle:
|
|
514
|
+
|
|
515
|
+
- **beforeEvaluation**: Called before a feature flag is evaluated
|
|
516
|
+
- **afterEvaluation**: Called after a feature flag is evaluated (with the result)
|
|
517
|
+
- **beforeIdentify**: Called before user identity is set or cleared
|
|
518
|
+
- **afterIdentify**: Called after user identity is set or cleared
|
|
519
|
+
- **afterRefresh**: Called after feature definitions are refreshed from Toggly
|
|
520
|
+
|
|
521
|
+
### Creating a Hook
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import type { Hook } from '@ops-ai/toggly-hooks-types';
|
|
525
|
+
|
|
526
|
+
const myAnalyticsHook: Hook = {
|
|
527
|
+
getMetadata: () => ({
|
|
528
|
+
name: 'MyAnalyticsHook',
|
|
529
|
+
version: '1.0.0'
|
|
530
|
+
}),
|
|
531
|
+
|
|
532
|
+
afterEvaluation: async (data) => {
|
|
533
|
+
// Send to analytics
|
|
534
|
+
analytics.track('Feature Flag Evaluated', {
|
|
535
|
+
feature: data.featureKey,
|
|
536
|
+
enabled: data.result
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### Registering Hooks
|
|
543
|
+
|
|
544
|
+
**During initialization in astro.config:**
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
// astro.config.mjs
|
|
548
|
+
import { defineConfig } from 'astro/config';
|
|
549
|
+
import toggly from '@ops-ai/astro-feature-flags-toggly';
|
|
550
|
+
|
|
551
|
+
export default defineConfig({
|
|
552
|
+
integrations: [
|
|
553
|
+
toggly({
|
|
554
|
+
appKey: 'your-app-key',
|
|
555
|
+
environment: 'your-environment-name',
|
|
556
|
+
hooks: [myAnalyticsHook]
|
|
557
|
+
})
|
|
558
|
+
]
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**At runtime (client-side):**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { togglyStore } from '@ops-ai/astro-feature-flags-toggly/client';
|
|
566
|
+
import { get } from 'svelte/store';
|
|
567
|
+
|
|
568
|
+
const store = get(togglyStore);
|
|
569
|
+
|
|
570
|
+
// Add a hook
|
|
571
|
+
store.hookExecutor.addHook(myAnalyticsHook);
|
|
572
|
+
|
|
573
|
+
// Remove a hook
|
|
574
|
+
store.hookExecutor.removeHook(myAnalyticsHook);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Common Use Cases
|
|
578
|
+
|
|
579
|
+
**Analytics Integration:**
|
|
580
|
+
```typescript
|
|
581
|
+
const clarityHook: Hook = {
|
|
582
|
+
getMetadata: () => ({ name: 'Microsoft Clarity', version: '1.0.0' }),
|
|
583
|
+
afterEvaluation: async (data) => {
|
|
584
|
+
if (typeof clarity !== 'undefined') {
|
|
585
|
+
clarity('event', `FeatureFlag:${data.featureKey}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Debug Logging:**
|
|
592
|
+
```typescript
|
|
593
|
+
const debugHook: Hook = {
|
|
594
|
+
getMetadata: () => ({ name: 'DebugLogger', version: '1.0.0' }),
|
|
595
|
+
afterEvaluation: async (data) => {
|
|
596
|
+
if (import.meta.env.DEV) {
|
|
597
|
+
console.debug('[Toggly]', data.featureKey, '=', data.result);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## Related SDKs
|
|
604
|
+
|
|
605
|
+
- [@ops-ai/react-feature-flags-toggly](https://www.npmjs.com/package/@ops-ai/react-feature-flags-toggly) - React SDK
|
|
606
|
+
- [@ops-ai/feature-flags-toggly](https://www.npmjs.com/package/@ops-ai/feature-flags-toggly) - Vanilla JavaScript SDK
|
|
607
|
+
|
|
507
608
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
$flags,
|
|
3
3
|
$isReady
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ILEHRLEW.js";
|
|
5
5
|
|
|
6
6
|
// src/frameworks/react/Feature.tsx
|
|
7
7
|
import { useStore } from "@nanostores/react";
|
|
@@ -71,4 +71,4 @@ export {
|
|
|
71
71
|
useFeatureGate,
|
|
72
72
|
Feature_default
|
|
73
73
|
};
|
|
74
|
-
//# sourceMappingURL=chunk-
|
|
74
|
+
//# sourceMappingURL=chunk-GW5NHZA2.js.map
|
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
// src/client/store.ts
|
|
2
2
|
import { atom, computed } from "nanostores";
|
|
3
|
+
|
|
4
|
+
// src/client/hooks.ts
|
|
5
|
+
var HookExecutor = class {
|
|
6
|
+
hooks = [];
|
|
7
|
+
/**
|
|
8
|
+
* Register a new hook
|
|
9
|
+
*/
|
|
10
|
+
addHook(hook) {
|
|
11
|
+
const metadata = hook.getMetadata();
|
|
12
|
+
const existingHook = this.hooks.find((h) => h.getMetadata().name === metadata.name);
|
|
13
|
+
if (existingHook) {
|
|
14
|
+
console.warn(`[Toggly] Hook with name "${metadata.name}" already registered. Skipping.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
this.hooks.push(hook);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Remove a hook by name
|
|
21
|
+
* @returns true if hook was found and removed, false otherwise
|
|
22
|
+
*/
|
|
23
|
+
removeHook(name) {
|
|
24
|
+
const index = this.hooks.findIndex((h) => h.getMetadata().name === name);
|
|
25
|
+
if (index > -1) {
|
|
26
|
+
this.hooks.splice(index, 1);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Execute beforeEvaluation hooks in registration order (FIFO)
|
|
33
|
+
* Collects data from each hook to pass to afterEvaluation
|
|
34
|
+
*/
|
|
35
|
+
executeBeforeEvaluation(flagKey, defaultValue) {
|
|
36
|
+
const dataMap = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const hook of this.hooks) {
|
|
38
|
+
if (hook.beforeEvaluation) {
|
|
39
|
+
try {
|
|
40
|
+
const data = hook.beforeEvaluation(flagKey, defaultValue);
|
|
41
|
+
dataMap.set(hook.getMetadata().name, data);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(
|
|
44
|
+
`[Toggly] Error in hook "${hook.getMetadata().name}.beforeEvaluation":`,
|
|
45
|
+
error
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return dataMap;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Execute afterEvaluation hooks in reverse order (LIFO)
|
|
54
|
+
* Passes data from corresponding beforeEvaluation
|
|
55
|
+
*/
|
|
56
|
+
executeAfterEvaluation(flagKey, dataMap, result) {
|
|
57
|
+
for (let i = this.hooks.length - 1; i >= 0; i--) {
|
|
58
|
+
const hook = this.hooks[i];
|
|
59
|
+
if (hook.afterEvaluation) {
|
|
60
|
+
try {
|
|
61
|
+
const data = dataMap.get(hook.getMetadata().name);
|
|
62
|
+
hook.afterEvaluation(flagKey, data, result);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(
|
|
65
|
+
`[Toggly] Error in hook "${hook.getMetadata().name}.afterEvaluation":`,
|
|
66
|
+
error
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Execute beforeIdentify hooks in registration order (FIFO)
|
|
74
|
+
*/
|
|
75
|
+
executeBeforeIdentify(identity) {
|
|
76
|
+
const dataMap = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const hook of this.hooks) {
|
|
78
|
+
if (hook.beforeIdentify) {
|
|
79
|
+
try {
|
|
80
|
+
const data = hook.beforeIdentify(identity);
|
|
81
|
+
dataMap.set(hook.getMetadata().name, data);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(
|
|
84
|
+
`[Toggly] Error in hook "${hook.getMetadata().name}.beforeIdentify":`,
|
|
85
|
+
error
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return dataMap;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Execute afterIdentify hooks in reverse order (LIFO)
|
|
94
|
+
*/
|
|
95
|
+
executeAfterIdentify(identity, dataMap) {
|
|
96
|
+
for (let i = this.hooks.length - 1; i >= 0; i--) {
|
|
97
|
+
const hook = this.hooks[i];
|
|
98
|
+
if (hook.afterIdentify) {
|
|
99
|
+
try {
|
|
100
|
+
const data = dataMap.get(hook.getMetadata().name);
|
|
101
|
+
hook.afterIdentify(identity, data);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(
|
|
104
|
+
`[Toggly] Error in hook "${hook.getMetadata().name}.afterIdentify":`,
|
|
105
|
+
error
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Execute afterRefresh hooks in registration order (FIFO)
|
|
113
|
+
*/
|
|
114
|
+
executeAfterRefresh(flags) {
|
|
115
|
+
for (const hook of this.hooks) {
|
|
116
|
+
if (hook.afterRefresh) {
|
|
117
|
+
try {
|
|
118
|
+
hook.afterRefresh(flags);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(
|
|
121
|
+
`[Toggly] Error in hook "${hook.getMetadata().name}.afterRefresh":`,
|
|
122
|
+
error
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/client/store.ts
|
|
3
131
|
var $flags = atom({});
|
|
4
132
|
var $isReady = atom(false);
|
|
5
133
|
var $error = atom(null);
|
|
@@ -8,6 +136,7 @@ var TogglyClientInstance = class {
|
|
|
8
136
|
config;
|
|
9
137
|
cache = null;
|
|
10
138
|
refreshInterval = null;
|
|
139
|
+
hookExecutor = new HookExecutor();
|
|
11
140
|
constructor(config) {
|
|
12
141
|
this.config = {
|
|
13
142
|
baseURI: "https://client.toggly.io",
|
|
@@ -16,8 +145,12 @@ var TogglyClientInstance = class {
|
|
|
16
145
|
featureFlagsRefreshInterval: 3 * 60 * 1e3,
|
|
17
146
|
isDebug: false,
|
|
18
147
|
connectTimeout: 5 * 1e3,
|
|
148
|
+
hooks: [],
|
|
19
149
|
...config
|
|
20
150
|
};
|
|
151
|
+
if (this.config.hooks) {
|
|
152
|
+
this.config.hooks.forEach((hook) => this.hookExecutor.addHook(hook));
|
|
153
|
+
}
|
|
21
154
|
}
|
|
22
155
|
getApiUrl() {
|
|
23
156
|
const { baseURI, appKey, environment, identity } = this.config;
|
|
@@ -82,6 +215,7 @@ var TogglyClientInstance = class {
|
|
|
82
215
|
$flags.set(flags);
|
|
83
216
|
$isReady.set(true);
|
|
84
217
|
$error.set(null);
|
|
218
|
+
this.hookExecutor.executeAfterRefresh(flags);
|
|
85
219
|
if (this.config.featureFlagsRefreshInterval && this.config.featureFlagsRefreshInterval > 0) {
|
|
86
220
|
this.startRefreshInterval();
|
|
87
221
|
}
|
|
@@ -96,6 +230,7 @@ var TogglyClientInstance = class {
|
|
|
96
230
|
const flags = await this.fetchFlags();
|
|
97
231
|
this.cache = flags;
|
|
98
232
|
$flags.set(flags);
|
|
233
|
+
this.hookExecutor.executeAfterRefresh(flags);
|
|
99
234
|
if (this.config.isDebug) {
|
|
100
235
|
console.log("[Toggly Client] Flags refreshed");
|
|
101
236
|
}
|
|
@@ -185,6 +320,20 @@ function $gate(keys, requirement = "all", negate = false) {
|
|
|
185
320
|
return negate ? !isEnabled : isEnabled;
|
|
186
321
|
});
|
|
187
322
|
}
|
|
323
|
+
function addHook(hook) {
|
|
324
|
+
if (!clientInstance) {
|
|
325
|
+
console.error("[Toggly Client] Client not initialized");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
clientInstance.hookExecutor.addHook(hook);
|
|
329
|
+
}
|
|
330
|
+
function removeHook(name) {
|
|
331
|
+
if (!clientInstance) {
|
|
332
|
+
console.error("[Toggly Client] Client not initialized");
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return clientInstance.hookExecutor.removeHook(name);
|
|
336
|
+
}
|
|
188
337
|
|
|
189
338
|
export {
|
|
190
339
|
$flags,
|
|
@@ -196,6 +345,8 @@ export {
|
|
|
196
345
|
clearIdentity,
|
|
197
346
|
stopRefreshInterval,
|
|
198
347
|
$flag,
|
|
199
|
-
$gate
|
|
348
|
+
$gate,
|
|
349
|
+
addHook,
|
|
350
|
+
removeHook
|
|
200
351
|
};
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
352
|
+
//# sourceMappingURL=chunk-ILEHRLEW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/store.ts","../src/client/hooks.ts"],"sourcesContent":["/**\n * Toggly Client-Side Store using Nanostores\n * \n * Provides reactive state management for feature flags on the client side.\n * This module includes its own embedded Toggly client implementation.\n */\n\nimport { atom, computed, type ReadableAtom } from 'nanostores';\nimport type { TogglyConfig, Flags } from '../types/index.js';\nimport type { Hook } from '@ops-ai/toggly-hooks-types';\nimport { HookExecutor } from './hooks.js';\n\n/**\n * Atom containing all feature flags\n */\nexport const $flags = atom<Flags>({});\n\n/**\n * Atom indicating if flags are loaded and ready\n */\nexport const $isReady = atom<boolean>(false);\n\n/**\n * Atom containing any error that occurred during initialization\n */\nexport const $error = atom<Error | null>(null);\n\n/**\n * Internal client instance storage\n */\nlet clientInstance: TogglyClientInstance | null = null;\n\n/**\n * Internal client implementation\n */\nclass TogglyClientInstance {\n private config: TogglyConfig;\n private cache: Flags | null = null;\n private refreshInterval: NodeJS.Timeout | null = null;\n public hookExecutor = new HookExecutor();\n\n constructor(config: TogglyConfig) {\n this.config = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n hooks: [],\n ...config,\n };\n \n // Register initial hooks\n if (this.config.hooks) {\n this.config.hooks.forEach(hook => this.hookExecutor.addHook(hook));\n }\n }\n\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/${appKey}-${environment}/defs`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n cache: 'no-store',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status} ${response.statusText}`);\n }\n\n const flags = (await response.json()) as Flags;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Client] Error fetching flags:', error);\n }\n\n // Fall back to cached flags or defaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using cached flags');\n }\n return { ...this.cache };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults');\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n async init(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n $isReady.set(true);\n $error.set(null);\n \n // Trigger afterRefresh hooks\n this.hookExecutor.executeAfterRefresh(flags);\n\n // Start refresh interval if configured\n if (\n this.config.featureFlagsRefreshInterval &&\n this.config.featureFlagsRefreshInterval > 0\n ) {\n this.startRefreshInterval();\n }\n } catch (error) {\n $error.set(error as Error);\n $isReady.set(true); // Still mark as ready even on error\n console.error('[Toggly Client] Initialization error:', error);\n }\n }\n\n async refresh(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n \n // Trigger afterRefresh hooks\n this.hookExecutor.executeAfterRefresh(flags);\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Flags refreshed');\n }\n } catch (error) {\n console.error('[Toggly Client] Refresh error:', error);\n }\n }\n\n private startRefreshInterval(): void {\n if (this.refreshInterval) {\n return;\n }\n\n this.refreshInterval = setInterval(() => {\n this.refresh();\n }, this.config.featureFlagsRefreshInterval!);\n\n if (this.config.isDebug) {\n console.log(\n `[Toggly Client] Started refresh interval: ${this.config.featureFlagsRefreshInterval}ms`\n );\n }\n }\n\n stopRefreshInterval(): void {\n if (this.refreshInterval) {\n clearInterval(this.refreshInterval);\n this.refreshInterval = null;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Stopped refresh interval');\n }\n }\n }\n\n setIdentity(identity: string): void {\n this.config.identity = identity;\n this.refresh(); // Refresh with new identity\n }\n\n clearIdentity(): void {\n this.config.identity = undefined;\n this.refresh(); // Refresh without identity\n }\n}\n\n/**\n * Initialize Toggly client with configuration\n * \n * @param config - Toggly configuration\n */\nexport async function initTogglyClient(config: TogglyConfig): Promise<void> {\n if (clientInstance) {\n console.warn('[Toggly Client] Client already initialized');\n return;\n }\n\n clientInstance = new TogglyClientInstance(config);\n await clientInstance.init();\n}\n\n/**\n * Manually refresh feature flags\n */\nexport async function refreshFlags(): Promise<void> {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n await clientInstance.refresh();\n}\n\n/**\n * Set user identity for targeting\n * \n * @param identity - User identifier\n */\nexport function setIdentity(identity: string): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.setIdentity(identity);\n}\n\n/**\n * Clear user identity\n */\nexport function clearIdentity(): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.clearIdentity();\n}\n\n/**\n * Stop automatic refresh interval\n */\nexport function stopRefreshInterval(): void {\n if (clientInstance) {\n clientInstance.stopRefreshInterval();\n }\n}\n\n/**\n * Create a computed atom for a specific feature flag\n * \n * @param key - Feature flag key\n * @param defaultValue - Default value if flag not found\n * @returns Readable atom with the flag value\n */\nexport function $flag(key: string, defaultValue: boolean = false): ReadableAtom<boolean> {\n return computed($flags, (flags) => flags[key] ?? defaultValue);\n}\n\n/**\n * Create a computed atom that evaluates multiple feature flags\n * \n * @param keys - Array of feature flag keys\n * @param requirement - 'all' or 'any'\n * @param negate - Whether to negate the result\n * @returns Readable atom with the evaluation result\n */\nexport function $gate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n): ReadableAtom<boolean> {\n return computed($flags, (flags) => {\n if (keys.length === 0) {\n return !negate;\n }\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n });\n}\n\n/**\n * Add a hook dynamically\n */\nexport function addHook(hook: Hook): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n clientInstance.hookExecutor.addHook(hook);\n}\n\n/**\n * Remove a hook by name\n * @returns true if hook was found and removed, false otherwise\n */\nexport function removeHook(name: string): boolean {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return false;\n }\n return clientInstance.hookExecutor.removeHook(name);\n}\n\n","import type { Hook, EvaluationSeriesData, IdentitySeriesData } from '@ops-ai/toggly-hooks-types';\n\n/**\n * Internal class that manages hook registration and execution\n */\nexport class HookExecutor {\n private hooks: Hook[] = [];\n\n /**\n * Register a new hook\n */\n addHook(hook: Hook): void {\n const metadata = hook.getMetadata();\n \n // Check for duplicate hook names\n const existingHook = this.hooks.find(h => h.getMetadata().name === metadata.name);\n if (existingHook) {\n console.warn(`[Toggly] Hook with name \"${metadata.name}\" already registered. Skipping.`);\n return;\n }\n \n this.hooks.push(hook);\n }\n\n /**\n * Remove a hook by name\n * @returns true if hook was found and removed, false otherwise\n */\n removeHook(name: string): boolean {\n const index = this.hooks.findIndex(h => h.getMetadata().name === name);\n if (index > -1) {\n this.hooks.splice(index, 1);\n return true;\n }\n return false;\n }\n\n /**\n * Execute beforeEvaluation hooks in registration order (FIFO)\n * Collects data from each hook to pass to afterEvaluation\n */\n executeBeforeEvaluation(\n flagKey: string,\n defaultValue?: boolean\n ): Map<string, EvaluationSeriesData | void> {\n const dataMap = new Map<string, EvaluationSeriesData | void>();\n \n for (const hook of this.hooks) {\n if (hook.beforeEvaluation) {\n try {\n const data = hook.beforeEvaluation(flagKey, defaultValue);\n dataMap.set(hook.getMetadata().name, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.beforeEvaluation\":`,\n error\n );\n }\n }\n }\n \n return dataMap;\n }\n\n /**\n * Execute afterEvaluation hooks in reverse order (LIFO)\n * Passes data from corresponding beforeEvaluation\n */\n executeAfterEvaluation(\n flagKey: string,\n dataMap: Map<string, EvaluationSeriesData | void>,\n result: boolean\n ): void {\n // Execute in reverse order\n for (let i = this.hooks.length - 1; i >= 0; i--) {\n const hook = this.hooks[i];\n if (hook.afterEvaluation) {\n try {\n const data = dataMap.get(hook.getMetadata().name);\n hook.afterEvaluation(flagKey, data, result);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterEvaluation\":`,\n error\n );\n }\n }\n }\n }\n\n /**\n * Execute beforeIdentify hooks in registration order (FIFO)\n */\n executeBeforeIdentify(identity: string): Map<string, IdentitySeriesData | void> {\n const dataMap = new Map<string, IdentitySeriesData | void>();\n \n for (const hook of this.hooks) {\n if (hook.beforeIdentify) {\n try {\n const data = hook.beforeIdentify(identity);\n dataMap.set(hook.getMetadata().name, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.beforeIdentify\":`,\n error\n );\n }\n }\n }\n \n return dataMap;\n }\n\n /**\n * Execute afterIdentify hooks in reverse order (LIFO)\n */\n executeAfterIdentify(\n identity: string,\n dataMap: Map<string, IdentitySeriesData | void>\n ): void {\n for (let i = this.hooks.length - 1; i >= 0; i--) {\n const hook = this.hooks[i];\n if (hook.afterIdentify) {\n try {\n const data = dataMap.get(hook.getMetadata().name);\n hook.afterIdentify(identity, data);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterIdentify\":`,\n error\n );\n }\n }\n }\n }\n\n /**\n * Execute afterRefresh hooks in registration order (FIFO)\n */\n executeAfterRefresh(flags: { [key: string]: boolean }): void {\n for (const hook of this.hooks) {\n if (hook.afterRefresh) {\n try {\n hook.afterRefresh(flags);\n } catch (error) {\n console.error(\n `[Toggly] Error in hook \"${hook.getMetadata().name}.afterRefresh\":`,\n error\n );\n }\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/client/setup.d.ts
CHANGED
package/dist/client/setup.js
CHANGED
package/dist/client/store.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as nanostores from 'nanostores';
|
|
2
2
|
import { ReadableAtom } from 'nanostores';
|
|
3
|
-
import { T as TogglyConfig, F as Flags } from '../index-
|
|
3
|
+
import { T as TogglyConfig, F as Flags } from '../index-Cjy13d0b.js';
|
|
4
|
+
import { Hook } from '@ops-ai/toggly-hooks-types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Atom containing all feature flags
|
|
@@ -55,5 +56,14 @@ declare function $flag(key: string, defaultValue?: boolean): ReadableAtom<boolea
|
|
|
55
56
|
* @returns Readable atom with the evaluation result
|
|
56
57
|
*/
|
|
57
58
|
declare function $gate(keys: string[], requirement?: 'all' | 'any', negate?: boolean): ReadableAtom<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Add a hook dynamically
|
|
61
|
+
*/
|
|
62
|
+
declare function addHook(hook: Hook): void;
|
|
63
|
+
/**
|
|
64
|
+
* Remove a hook by name
|
|
65
|
+
* @returns true if hook was found and removed, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
declare function removeHook(name: string): boolean;
|
|
58
68
|
|
|
59
|
-
export { $error, $flag, $flags, $gate, $isReady, clearIdentity, initTogglyClient, refreshFlags, setIdentity, stopRefreshInterval };
|
|
69
|
+
export { $error, $flag, $flags, $gate, $isReady, addHook, clearIdentity, initTogglyClient, refreshFlags, removeHook, setIdentity, stopRefreshInterval };
|
package/dist/client/store.js
CHANGED
|
@@ -4,21 +4,25 @@ import {
|
|
|
4
4
|
$flags,
|
|
5
5
|
$gate,
|
|
6
6
|
$isReady,
|
|
7
|
+
addHook,
|
|
7
8
|
clearIdentity,
|
|
8
9
|
initTogglyClient,
|
|
9
10
|
refreshFlags,
|
|
11
|
+
removeHook,
|
|
10
12
|
setIdentity,
|
|
11
13
|
stopRefreshInterval
|
|
12
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-ILEHRLEW.js";
|
|
13
15
|
export {
|
|
14
16
|
$error,
|
|
15
17
|
$flag,
|
|
16
18
|
$flags,
|
|
17
19
|
$gate,
|
|
18
20
|
$isReady,
|
|
21
|
+
addHook,
|
|
19
22
|
clearIdentity,
|
|
20
23
|
initTogglyClient,
|
|
21
24
|
refreshFlags,
|
|
25
|
+
removeHook,
|
|
22
26
|
setIdentity,
|
|
23
27
|
stopRefreshInterval
|
|
24
28
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as svelte_store from 'svelte/store';
|
|
2
2
|
export { $flags as flags, $isReady as isReady } from '../../client/store.js';
|
|
3
3
|
import 'nanostores';
|
|
4
|
-
import '../../index-
|
|
4
|
+
import '../../index-Cjy13d0b.js';
|
|
5
|
+
import '@ops-ai/toggly-hooks-types';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Create a derived store for a specific feature flag
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Hook } from '@ops-ai/toggly-hooks-types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Toggly Astro SDK - Type Definitions
|
|
3
5
|
*/
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Configuration options for Toggly integration
|
|
6
9
|
*/
|
|
@@ -29,6 +32,8 @@ interface TogglyConfig {
|
|
|
29
32
|
* (default: false)
|
|
30
33
|
*/
|
|
31
34
|
allFeaturesEnabledDuringBuild?: boolean;
|
|
35
|
+
/** Hooks to extend SDK behavior at key lifecycle points */
|
|
36
|
+
hooks?: Hook[];
|
|
32
37
|
}
|
|
33
38
|
/**
|
|
34
39
|
* Map of feature flag keys to their boolean values
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { TogglyIntegrationOptions, createTogglyMiddleware, default as togglyInte
|
|
|
2
2
|
export { TogglyServer, createTogglyServerClient } from './server/toggly-server.js';
|
|
3
3
|
export { allFeaturesEnabled, anyFeatureEnabled, getTogglyFromAstroGlobal, withFeatureFlag } from './server/utils.js';
|
|
4
4
|
export { $error, $flag, $flags, $gate, $isReady, clearIdentity, initTogglyClient, refreshFlags, setIdentity, stopRefreshInterval } from './client/store.js';
|
|
5
|
-
export { c as FeatureClientProps, b as FeatureProps, F as Flags, P as PageFeatureMapping, a as TogglyClient, T as TogglyConfig } from './index-
|
|
5
|
+
export { c as FeatureClientProps, b as FeatureProps, F as Flags, P as PageFeatureMapping, a as TogglyClient, T as TogglyConfig } from './index-Cjy13d0b.js';
|
|
6
6
|
import 'astro';
|
|
7
7
|
import 'nanostores';
|
|
8
|
+
import '@ops-ai/toggly-hooks-types';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { T as TogglyConfig, a as TogglyClient, F as Flags } from '../index-
|
|
1
|
+
import { T as TogglyConfig, a as TogglyClient, F as Flags } from '../index-Cjy13d0b.js';
|
|
2
|
+
import '@ops-ai/toggly-hooks-types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Toggly Server-Side Client for Astro SSR/SSG
|
package/dist/server/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ops-ai/astro-feature-flags-toggly",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Toggly feature flags SDK for Astro with SSR, SSG, and framework support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"url": "https://github.com/ops-ai/Toggly.FeatureManagement"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
+
"@ops-ai/toggly-hooks-types": "^1.0.0",
|
|
71
72
|
"glob": "^10.3.10"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/store.ts"],"sourcesContent":["/**\n * Toggly Client-Side Store using Nanostores\n * \n * Provides reactive state management for feature flags on the client side.\n * This module includes its own embedded Toggly client implementation.\n */\n\nimport { atom, computed, type ReadableAtom } from 'nanostores';\nimport type { TogglyConfig, Flags } from '../types/index.js';\n\n/**\n * Atom containing all feature flags\n */\nexport const $flags = atom<Flags>({});\n\n/**\n * Atom indicating if flags are loaded and ready\n */\nexport const $isReady = atom<boolean>(false);\n\n/**\n * Atom containing any error that occurred during initialization\n */\nexport const $error = atom<Error | null>(null);\n\n/**\n * Internal client instance storage\n */\nlet clientInstance: TogglyClientInstance | null = null;\n\n/**\n * Internal client implementation\n */\nclass TogglyClientInstance {\n private config: TogglyConfig;\n private cache: Flags | null = null;\n private refreshInterval: NodeJS.Timeout | null = null;\n\n constructor(config: TogglyConfig) {\n this.config = {\n baseURI: 'https://client.toggly.io',\n environment: 'Production',\n flagDefaults: {},\n featureFlagsRefreshInterval: 3 * 60 * 1000,\n isDebug: false,\n connectTimeout: 5 * 1000,\n ...config,\n };\n }\n\n private getApiUrl(): string {\n const { baseURI, appKey, environment, identity } = this.config;\n\n if (!appKey) {\n return '';\n }\n\n const baseUrl = baseURI!.replace(/\\/$/, '');\n let url = `${baseUrl}/${appKey}-${environment}/defs`;\n\n if (identity) {\n url += `?u=${encodeURIComponent(identity)}`;\n }\n\n return url;\n }\n\n async fetchFlags(): Promise<Flags> {\n const url = this.getApiUrl();\n\n if (!url || !this.config.appKey) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults (no appKey):', this.config.flagDefaults);\n }\n return { ...this.config.flagDefaults! };\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.connectTimeout);\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n cache: 'no-store',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status} ${response.statusText}`);\n }\n\n const flags = (await response.json()) as Flags;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Fetched flags:', flags);\n }\n\n return flags;\n } catch (error) {\n if (this.config.isDebug) {\n console.error('[Toggly Client] Error fetching flags:', error);\n }\n\n // Fall back to cached flags or defaults\n if (this.cache) {\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using cached flags');\n }\n return { ...this.cache };\n }\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Using flag defaults');\n }\n\n return { ...this.config.flagDefaults! };\n }\n }\n\n async init(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n $isReady.set(true);\n $error.set(null);\n\n // Start refresh interval if configured\n if (\n this.config.featureFlagsRefreshInterval &&\n this.config.featureFlagsRefreshInterval > 0\n ) {\n this.startRefreshInterval();\n }\n } catch (error) {\n $error.set(error as Error);\n $isReady.set(true); // Still mark as ready even on error\n console.error('[Toggly Client] Initialization error:', error);\n }\n }\n\n async refresh(): Promise<void> {\n try {\n const flags = await this.fetchFlags();\n this.cache = flags;\n $flags.set(flags);\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Flags refreshed');\n }\n } catch (error) {\n console.error('[Toggly Client] Refresh error:', error);\n }\n }\n\n private startRefreshInterval(): void {\n if (this.refreshInterval) {\n return;\n }\n\n this.refreshInterval = setInterval(() => {\n this.refresh();\n }, this.config.featureFlagsRefreshInterval!);\n\n if (this.config.isDebug) {\n console.log(\n `[Toggly Client] Started refresh interval: ${this.config.featureFlagsRefreshInterval}ms`\n );\n }\n }\n\n stopRefreshInterval(): void {\n if (this.refreshInterval) {\n clearInterval(this.refreshInterval);\n this.refreshInterval = null;\n\n if (this.config.isDebug) {\n console.log('[Toggly Client] Stopped refresh interval');\n }\n }\n }\n\n setIdentity(identity: string): void {\n this.config.identity = identity;\n this.refresh(); // Refresh with new identity\n }\n\n clearIdentity(): void {\n this.config.identity = undefined;\n this.refresh(); // Refresh without identity\n }\n}\n\n/**\n * Initialize Toggly client with configuration\n * \n * @param config - Toggly configuration\n */\nexport async function initTogglyClient(config: TogglyConfig): Promise<void> {\n if (clientInstance) {\n console.warn('[Toggly Client] Client already initialized');\n return;\n }\n\n clientInstance = new TogglyClientInstance(config);\n await clientInstance.init();\n}\n\n/**\n * Manually refresh feature flags\n */\nexport async function refreshFlags(): Promise<void> {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n await clientInstance.refresh();\n}\n\n/**\n * Set user identity for targeting\n * \n * @param identity - User identifier\n */\nexport function setIdentity(identity: string): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.setIdentity(identity);\n}\n\n/**\n * Clear user identity\n */\nexport function clearIdentity(): void {\n if (!clientInstance) {\n console.error('[Toggly Client] Client not initialized');\n return;\n }\n\n clientInstance.clearIdentity();\n}\n\n/**\n * Stop automatic refresh interval\n */\nexport function stopRefreshInterval(): void {\n if (clientInstance) {\n clientInstance.stopRefreshInterval();\n }\n}\n\n/**\n * Create a computed atom for a specific feature flag\n * \n * @param key - Feature flag key\n * @param defaultValue - Default value if flag not found\n * @returns Readable atom with the flag value\n */\nexport function $flag(key: string, defaultValue: boolean = false): ReadableAtom<boolean> {\n return computed($flags, (flags) => flags[key] ?? defaultValue);\n}\n\n/**\n * Create a computed atom that evaluates multiple feature flags\n * \n * @param keys - Array of feature flag keys\n * @param requirement - 'all' or 'any'\n * @param negate - Whether to negate the result\n * @returns Readable atom with the evaluation result\n */\nexport function $gate(\n keys: string[],\n requirement: 'all' | 'any' = 'all',\n negate: boolean = false\n): ReadableAtom<boolean> {\n return computed($flags, (flags) => {\n if (keys.length === 0) {\n return !negate;\n }\n\n let isEnabled: boolean;\n\n if (requirement === 'any') {\n isEnabled = keys.some((key) => flags[key] === true);\n } else {\n isEnabled = keys.every((key) => flags[key] === true);\n }\n\n return negate ? !isEnabled : isEnabled;\n });\n}\n\n\n"],"mappings":"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","names":[]}
|
|
File without changes
|