@nexussdk/flags 0.0.3 → 0.0.4
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 +187 -8
- package/dist/chunk-VC3UFQ47.mjs +767 -0
- package/dist/dev-server/cli.mjs +95 -0
- package/dist/dev-server/index.mjs +11 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.global.js +3 -3
- package/dist/index.mjs +1 -1
- package/package.json +30 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
startFlagsDevServer
|
|
4
|
+
} from "../chunk-VC3UFQ47.mjs";
|
|
5
|
+
|
|
6
|
+
// src/dev-server/cli.ts
|
|
7
|
+
function parseArgs(args) {
|
|
8
|
+
let port = 4568;
|
|
9
|
+
let host = "localhost";
|
|
10
|
+
let flagsFile = "./nexus-flags.json";
|
|
11
|
+
let showHelp = false;
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const arg = args[i];
|
|
14
|
+
if (arg === "--help" || arg === "-h") {
|
|
15
|
+
showHelp = true;
|
|
16
|
+
} else if (arg === "--port" || arg === "-p") {
|
|
17
|
+
const next = args[++i];
|
|
18
|
+
const val = next ? parseInt(next, 10) : NaN;
|
|
19
|
+
if (!isNaN(val) && val > 0 && val < 65536) port = val;
|
|
20
|
+
} else if (arg === "--host") {
|
|
21
|
+
const next = args[++i];
|
|
22
|
+
if (next) host = next;
|
|
23
|
+
} else if (arg === "--file" || arg === "-f") {
|
|
24
|
+
const next = args[++i];
|
|
25
|
+
if (next) flagsFile = next;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { port, host, flagsFile, showHelp };
|
|
29
|
+
}
|
|
30
|
+
async function main() {
|
|
31
|
+
const { port, host, flagsFile, showHelp } = parseArgs(process.argv.slice(2));
|
|
32
|
+
if (showHelp) {
|
|
33
|
+
console.log(`
|
|
34
|
+
Nexus Flags Dev Server \u2014 Local Feature Flag Management
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
nexus-flags-dev [options]
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
-p, --port <number> Port to listen on (default: 4568)
|
|
41
|
+
--host <string> Host interface to bind (default: localhost)
|
|
42
|
+
-f, --file <path> Path to JSON flags file (default: ./nexus-flags.json)
|
|
43
|
+
-h, --help Show this help message
|
|
44
|
+
|
|
45
|
+
Endpoints:
|
|
46
|
+
GET / Web GUI Dashboard (CRUD flags)
|
|
47
|
+
GET /health Health check
|
|
48
|
+
GET /api/v1/flags List all flags
|
|
49
|
+
POST /api/v1/flags Create a flag
|
|
50
|
+
GET /api/v1/flags/:key Get a flag
|
|
51
|
+
PATCH /api/v1/flags/:key Update a flag
|
|
52
|
+
DELETE /api/v1/flags/:key Delete a flag
|
|
53
|
+
POST /api/v1/flags/:key/toggle Toggle enabled state
|
|
54
|
+
GET /api/v1/flags/stream SSE realtime stream (subscribe for flag changes)
|
|
55
|
+
GET /api/v1/flags/eval Evaluation snapshot (used by SDK refresh)
|
|
56
|
+
GET /api/v1/flags/bootstrap Pre-load snapshot for SSR bootstrap
|
|
57
|
+
`);
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const instance = await startFlagsDevServer({ port, host, flagsFile });
|
|
62
|
+
const serverUrl = `http://${host}:${port}`;
|
|
63
|
+
console.log(`
|
|
64
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
65
|
+
\u2551 NEXUS FEATURE FLAGS DEV SERVER \u2551
|
|
66
|
+
\u2551 Local Flag Management Dashboard \u2551
|
|
67
|
+
\u2551 \u2551
|
|
68
|
+
\u2551 Web Dashboard: ${serverUrl.padEnd(39)} \u2551
|
|
69
|
+
\u2551 Flags file: ${flagsFile.padEnd(39)} \u2551
|
|
70
|
+
\u2551 SSE stream: ${(serverUrl + "/api/v1/flags/stream").padEnd(39)} \u2551
|
|
71
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
72
|
+
|
|
73
|
+
[INFO] Configure your SDK to point to this server:
|
|
74
|
+
|
|
75
|
+
new NexusFlagsClient({
|
|
76
|
+
apiKey: 'nxs_dev_any_value',
|
|
77
|
+
baseUrl: '${serverUrl}',
|
|
78
|
+
realtime: true,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
[INFO] Server running. Press Ctrl+C to stop.
|
|
82
|
+
`);
|
|
83
|
+
const onExit = async () => {
|
|
84
|
+
console.log("\n[INFO] Shutting down Nexus Flags Dev Server...");
|
|
85
|
+
await instance.close();
|
|
86
|
+
process.exit(0);
|
|
87
|
+
};
|
|
88
|
+
process.on("SIGINT", onExit);
|
|
89
|
+
process.on("SIGTERM", onExit);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error("[ERROR] Failed to start Nexus Flags Dev Server:", err);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
main();
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var core=require('@nexussdk/core');function
|
|
1
|
+
'use strict';var core=require('@nexussdk/core');function v(n,t=0){let e=t>>>0,s=3432918353,r=461845907,a=0,l=Math.floor(n.length/4)*4;for(;a<l;){let o=n.charCodeAt(a)&255|(n.charCodeAt(a+1)&255)<<8|(n.charCodeAt(a+2)&255)<<16|(n.charCodeAt(a+3)&255)<<24;o=Math.imul(o,s),o=o<<15|o>>>17,o=Math.imul(o,r),e^=o,e=e<<13|e>>>19,e=Math.imul(e,5)+3864292196>>>0,a+=4;}let i=0,g=n.length&3;return g>=3&&(i^=(n.charCodeAt(a+2)&255)<<16),g>=2&&(i^=(n.charCodeAt(a+1)&255)<<8),g>=1&&(i^=n.charCodeAt(a)&255,i=Math.imul(i,s),i=i<<15|i>>>17,i=Math.imul(i,r),e^=i),e^=n.length,e^=e>>>16,e=Math.imul(e,2246822507),e^=e>>>13,e=Math.imul(e,3266489909),e^=e>>>16,e>>>0}function f(n,t){let e=`${n}:${t}`;return v(e)%100}function p(n,t){let e=n.replace(/^v/,"").split(".").map(Number),s=t.replace(/^v/,"").split(".").map(Number);for(let r=0;r<3;r++){let a=(e[r]??0)-(s[r]??0);if(a!==0)return a}return 0}function y(n,t){let e=t.split("."),s=n;for(let r of e){if(s==null||typeof s!="object")return;s=s[r];}return s}function b(n,t){let e=y(t,n.attribute),s=n.values;switch(n.operator){case "EQUALS":return e===s[0];case "NOT_EQUALS":return e!==s[0];case "IN":return s.includes(e);case "NOT_IN":return !s.includes(e);case "CONTAINS":return typeof e=="string"&&e.includes(String(s[0]));case "NOT_CONTAINS":return typeof e=="string"&&!e.includes(String(s[0]));case "STARTS_WITH":return typeof e=="string"&&e.startsWith(String(s[0]));case "ENDS_WITH":return typeof e=="string"&&e.endsWith(String(s[0]));case "GREATER_THAN":return typeof e=="number"&&e>Number(s[0]);case "LESS_THAN":return typeof e=="number"&&e<Number(s[0]);case "SEMVER_GTE":return typeof e=="string"&&typeof s[0]=="string"&&p(e,String(s[0]))>=0;case "SEMVER_LTE":return typeof e=="string"&&typeof s[0]=="string"&&p(e,String(s[0]))<=0;default:return false}}function d(n,t){if(!n.isEnabled)return {key:n.key,enabled:false,variants:{},reason:"KILL_SWITCH",version:n.version};if(n.targetingRules.length>0){if(!n.targetingRules.every(s=>b(s,t)))return {key:n.key,enabled:false,variants:{},reason:"FALLBACK",version:n.version};if(n.rolloutPercentage>=100)return {key:n.key,enabled:true,variants:n.variants,reason:"TARGETING_MATCH",version:n.version}}if(n.rolloutPercentage>0){let e=t.id??"anon";return f(e,n.key)<n.rolloutPercentage?{key:n.key,enabled:true,variants:n.variants,reason:n.targetingRules.length>0?"TARGETING_MATCH":"ROLLOUT_MATCH",version:n.version}:{key:n.key,enabled:false,variants:{},reason:"FALLBACK",version:n.version}}return {key:n.key,enabled:false,variants:{},reason:"KILL_SWITCH",version:n.version}}var c=class{options;eventSource=null;connected=false;destroyed=false;reconnectAttempt=0;reconnectTimer=null;maxReconnects;constructor(t){this.options=t,this.maxReconnects=t.maxReconnects??1/0;}get isConnected(){return this.connected}connect(){this.destroyed||this.connected||this.eventSource||this.openConnection();}disconnect(){this.destroyed=true,this.cleanup();}openConnection(){if(!this.destroyed)try{let t=new URL(this.options.url);t.searchParams.set("apiKey",this.options.apiKey),this.eventSource=new EventSource(t.toString()),this.eventSource.addEventListener("open",()=>{this.connected=!0,this.reconnectAttempt=0,this.options.onStateChange?.(!0);}),this.eventSource.addEventListener("message",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("flag_update",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("FLAG_UPDATE",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("FLAG_DELETE",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("error",()=>{this.connected=!1,this.options.onStateChange?.(!1),this.cleanup(),this.scheduleReconnect();});}catch{this.scheduleReconnect();}}handleMessage(t){try{let e=JSON.parse(t);this.options.onEvent(e);}catch{}}cleanup(){this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);}scheduleReconnect(){if(this.destroyed||this.reconnectAttempt>=this.maxReconnects)return;let t=core.computeBackoffMs(this.reconnectAttempt,1e3,3e4);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.destroyed||this.openConnection();},t);}};var S="nexus_flags_",m="nexus_anon_id",u=class{memory=new Map;storageKey;localStorageAvailable;broadcastChannel=null;constructor(t){this.storageKey=`${S}${t}`,this.localStorageAvailable=this.testLocalStorage(),this.hydrate(),this.setupBroadcastChannel();}set(t,e){this.memory.set(t,e),this.persist(),this.broadcastUpdate(t,e);}get(t){return this.memory.get(t)}setAll(t){for(let[e,s]of Object.entries(t))this.memory.set(e,s);this.persist();}delete(t){this.memory.delete(t),this.persist();}getAll(){return Object.fromEntries(this.memory.entries())}clear(){if(this.memory.clear(),this.localStorageAvailable)try{window.localStorage.removeItem(this.storageKey);}catch{}this.broadcastChannel?.close();}getOrCreateAnonymousId(){if(typeof window>"u")return "anon-ssr-node";if(this.localStorageAvailable)try{let t=window.localStorage.getItem(m);return t||(t=`anon_${Math.random().toString(36).substring(2,11)}`,window.localStorage.setItem(m,t)),t}catch{}return `anon_${Math.random().toString(36).substring(2,11)}`}testLocalStorage(){try{if(typeof window>"u")return !1;let t="__nexus_test__";return window.localStorage.setItem(t,"1"),window.localStorage.removeItem(t),!0}catch{return false}}hydrate(){if(this.localStorageAvailable)try{let t=window.localStorage.getItem(this.storageKey);if(t){let e=JSON.parse(t);for(let[s,r]of Object.entries(e))this.memory.set(s,r);}}catch{}}persist(){if(this.localStorageAvailable)try{window.localStorage.setItem(this.storageKey,JSON.stringify(Object.fromEntries(this.memory.entries())));}catch{}}setupBroadcastChannel(){try{typeof BroadcastChannel<"u"&&(this.broadcastChannel=new BroadcastChannel(`nexus_flags_${this.storageKey}`),this.broadcastChannel.onmessage=t=>{t.data?.key&&t.data?.result&&this.memory.set(t.data.key,t.data.result);});}catch{}}broadcastUpdate(t,e){try{this.broadcastChannel?.postMessage({key:t,result:e});}catch{}}};var h=class{apiKey;baseUrl;timeoutMs;user;storage;listeners=new Map;sseManager;flagDefinitions=new Map;constructor(t={}){if(this.apiKey=core.resolveApiKey(t.apiKey),this.baseUrl=core.resolveBaseUrl(t.baseUrl),this.timeoutMs=t.timeoutMs??3e3,this.storage=new u(this.apiKey.substring(0,16)),this.user=t.user??{id:this.storage.getOrCreateAnonymousId()},t.bootstrap){let e={};for(let[s,r]of Object.entries(t.bootstrap))if(typeof r=="boolean")e[s]={key:s,enabled:r,variants:{},reason:r?"DEFAULT_ENABLED":"KILL_SWITCH",version:1};else if(r&&typeof r=="object"){let a=r,l=typeof a.enabled=="boolean"?a.enabled:typeof a.isEnabled=="boolean"?a.isEnabled:true;e[s]={key:a.key??s,enabled:l,variants:a.variants??{},reason:l?"DEFAULT_ENABLED":"KILL_SWITCH",version:typeof a.version=="number"?a.version:1};}this.storage.setAll(e);}t.realtime!==false&&this.initRealtimeSync(),this.refreshFlags();}isEnabled(t,e=false){let s=this.storage.get(t);return s!==void 0?s.enabled:e}getVariant(t,e,s){let r=this.storage.get(t);if(!r?.enabled||!r.variants)return s;let a=r.variants[e];return a!==void 0?a:s}async identify(t){this.user={...this.user,...t},await this.refreshFlags();}reset(){this.user={id:this.storage.getOrCreateAnonymousId()},this.refreshFlags();}onFlagChange(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>this.listeners.get(t)?.delete(e)}destroy(){this.sseManager?.disconnect(),this.listeners.clear(),this.storage.clear();}initRealtimeSync(){this.sseManager=new c({url:`${this.baseUrl}/api/v1/flags/stream`,apiKey:this.apiKey,onEvent:t=>{if(t.type==="FLAG_UPDATE"&&t.data){let e=this.flagDefinitions.get(t.key),s=e?d(e,this.user):t.data;this.storage.set(t.key,s),this.listeners.get(t.key)?.forEach(r=>r(s));}else t.type==="FLAG_DELETE"&&(this.storage.delete(t.key),this.flagDefinitions.delete(t.key));}}),this.sseManager.connect();}async refreshFlags(){try{let t=await core.fetchWithRetry({url:`${this.baseUrl}/api/v1/flags/eval`,method:"GET",headers:{Authorization:`Bearer ${this.apiKey}`,"X-Nexus-User-Id":this.user.id??"anon","X-Nexus-Country":this.user.country??""},timeoutMs:this.timeoutMs,maxRetries:2}),e=t.data,s=e&&typeof e=="object"&&"data"in e?e.data:t.data;if(s&&typeof s=="object")for(let[r,a]of Object.entries(s)){this.flagDefinitions.set(r,a);let l=d(a,this.user);this.storage.set(r,l),this.listeners.get(r)?.forEach(i=>i(l));}}catch{}}};exports.FlagStorage=u;exports.NexusFlagsClient=h;exports.SSEManager=c;exports.computeRolloutBucket=f;exports.evaluateFlag=d;exports.murmur3=v;
|
package/dist/index.d.mts
CHANGED
|
@@ -32,9 +32,15 @@ interface NexusFlagsOptions {
|
|
|
32
32
|
*/
|
|
33
33
|
user?: UserContext;
|
|
34
34
|
/**
|
|
35
|
-
* Pre-hydrated flags evaluated on the server (SSR) to prevent client-side UI flicker.
|
|
35
|
+
* Pre-hydrated flags evaluated on the server (SSR) or loaded from a local JSON file to prevent client-side UI flicker.
|
|
36
|
+
* Supports FlagEvaluationResult maps, raw boolean dictionaries, or DevFlag objects.
|
|
36
37
|
*/
|
|
37
|
-
bootstrap?: Record<string, FlagEvaluationResult
|
|
38
|
+
bootstrap?: Record<string, FlagEvaluationResult | boolean | {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
isEnabled?: boolean;
|
|
41
|
+
variants?: Record<string, unknown>;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}>;
|
|
38
44
|
/**
|
|
39
45
|
* Toggle real-time SSE updates. Defaults to true.
|
|
40
46
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -32,9 +32,15 @@ interface NexusFlagsOptions {
|
|
|
32
32
|
*/
|
|
33
33
|
user?: UserContext;
|
|
34
34
|
/**
|
|
35
|
-
* Pre-hydrated flags evaluated on the server (SSR) to prevent client-side UI flicker.
|
|
35
|
+
* Pre-hydrated flags evaluated on the server (SSR) or loaded from a local JSON file to prevent client-side UI flicker.
|
|
36
|
+
* Supports FlagEvaluationResult maps, raw boolean dictionaries, or DevFlag objects.
|
|
36
37
|
*/
|
|
37
|
-
bootstrap?: Record<string, FlagEvaluationResult
|
|
38
|
+
bootstrap?: Record<string, FlagEvaluationResult | boolean | {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
isEnabled?: boolean;
|
|
41
|
+
variants?: Record<string, unknown>;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}>;
|
|
38
44
|
/**
|
|
39
45
|
* Toggle real-time SSE updates. Defaults to true.
|
|
40
46
|
*/
|
package/dist/index.global.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var NexusFlags=(function(exports){'use strict';function
|
|
1
|
+
var NexusFlags=(function(exports){'use strict';function v(r,e=1e3,t=3e4){let n=e*Math.pow(2,r),s=Math.min(t,n);return Math.random()*s}function _(r,e){return new Promise((t,n)=>{if(e?.aborted){n(new DOMException("Aborted","AbortError"));return}let s=setTimeout(t,r);e?.addEventListener("abort",()=>{clearTimeout(s),n(new DOMException("Aborted","AbortError"));});})}function U(r){return r>=500||r===429}async function A(r){let{url:e,method:t="GET",headers:n={},body:s,timeoutMs:a=5e3,maxRetries:l=3,retryBaseMs:i=1e3,retryMaxMs:h=3e4,signal:o}=r,m=new Error("Request failed");for(let d=0;d<=l;d++){if(o?.aborted)throw new DOMException("Request aborted by caller.","AbortError");let E=new AbortController,S=setTimeout(()=>E.abort(),a);o?.addEventListener("abort",()=>E.abort());try{let u={method:t,headers:{"Content-Type":"application/json",...n},signal:E.signal};s!==void 0&&(u.body=typeof s=="string"?s:JSON.stringify(s));let c=await fetch(e,u);if(clearTimeout(S),c.ok)return {data:await c.json(),status:c.status,headers:c.headers};if(U(c.status)&&d<l){m=new Error(`HTTP ${c.status}: ${c.statusText}`);let I=v(d,i,h);await _(I,o);continue}let M=await c.text().catch(()=>"");throw new Error(`HTTP ${c.status}: ${M}`)}catch(u){if(clearTimeout(S),u instanceof DOMException&&u.name==="AbortError")throw u;if(m=u instanceof Error?u:new Error(String(u)),d<l){let c=v(d,i,h);await _(c,o);}}}throw m}function p(r){try{let e=globalThis.process;if(typeof e<"u"&&e&&e.env)return e.env[r]??void 0}catch{}}function T(r){try{let e=typeof globalThis<"u"&&globalThis.__import_meta__;if(e&&e.env)return e.env[r]??void 0;let t=new Function("try { return import.meta; } catch(e) { return undefined; }")();if(t&&t.env)return t.env[r]??void 0}catch{}}function k(){try{if(typeof window<"u"&&window.__NEXUS_API_KEY__)return window.__NEXUS_API_KEY__}catch{}}function w(r){let e=r||k()||p("NEXUS_API_KEY")||p("NEXT_PUBLIC_NEXUS_API_KEY")||T("VITE_NEXUS_API_KEY")||p("NUXT_PUBLIC_NEXUS_API_KEY");if(!e||e.trim()==="")throw new Error(`[Nexus SDK] No API key found. Please provide one via:
|
|
2
2
|
1. Nexus.init({ apiKey: "pk_live_..." })
|
|
3
3
|
2. window.__NEXUS_API_KEY__ = "pk_live_..."
|
|
4
4
|
3. NEXUS_API_KEY env var
|
|
5
5
|
4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)
|
|
6
6
|
5. VITE_NEXUS_API_KEY (Vite)
|
|
7
|
-
6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)`);return e.trim()}function
|
|
8
|
-
exports.FlagStorage=
|
|
7
|
+
6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)`);return e.trim()}function x(r,e="https://api.nexus.dev"){return (r||p("NEXT_PUBLIC_NEXUS_URL")||T("VITE_NEXUS_URL")||e).replace(/\/$/,"")}function F(r,e=0){let t=e>>>0,n=3432918353,s=461845907,a=0,l=Math.floor(r.length/4)*4;for(;a<l;){let o=r.charCodeAt(a)&255|(r.charCodeAt(a+1)&255)<<8|(r.charCodeAt(a+2)&255)<<16|(r.charCodeAt(a+3)&255)<<24;o=Math.imul(o,n),o=o<<15|o>>>17,o=Math.imul(o,s),t^=o,t=t<<13|t>>>19,t=Math.imul(t,5)+3864292196>>>0,a+=4;}let i=0,h=r.length&3;return h>=3&&(i^=(r.charCodeAt(a+2)&255)<<16),h>=2&&(i^=(r.charCodeAt(a+1)&255)<<8),h>=1&&(i^=r.charCodeAt(a)&255,i=Math.imul(i,n),i=i<<15|i>>>17,i=Math.imul(i,s),t^=i),t^=r.length,t^=t>>>16,t=Math.imul(t,2246822507),t^=t>>>13,t=Math.imul(t,3266489909),t^=t>>>16,t>>>0}function C(r,e){let t=`${r}:${e}`;return F(t)%100}function R(r,e){let t=r.replace(/^v/,"").split(".").map(Number),n=e.replace(/^v/,"").split(".").map(Number);for(let s=0;s<3;s++){let a=(t[s]??0)-(n[s]??0);if(a!==0)return a}return 0}function L(r,e){let t=e.split("."),n=r;for(let s of t){if(n==null||typeof n!="object")return;n=n[s];}return n}function K(r,e){let t=L(e,r.attribute),n=r.values;switch(r.operator){case "EQUALS":return t===n[0];case "NOT_EQUALS":return t!==n[0];case "IN":return n.includes(t);case "NOT_IN":return !n.includes(t);case "CONTAINS":return typeof t=="string"&&t.includes(String(n[0]));case "NOT_CONTAINS":return typeof t=="string"&&!t.includes(String(n[0]));case "STARTS_WITH":return typeof t=="string"&&t.startsWith(String(n[0]));case "ENDS_WITH":return typeof t=="string"&&t.endsWith(String(n[0]));case "GREATER_THAN":return typeof t=="number"&&t>Number(n[0]);case "LESS_THAN":return typeof t=="number"&&t<Number(n[0]);case "SEMVER_GTE":return typeof t=="string"&&typeof n[0]=="string"&&R(t,String(n[0]))>=0;case "SEMVER_LTE":return typeof t=="string"&&typeof n[0]=="string"&&R(t,String(n[0]))<=0;default:return false}}function y(r,e){if(!r.isEnabled)return {key:r.key,enabled:false,variants:{},reason:"KILL_SWITCH",version:r.version};if(r.targetingRules.length>0){if(!r.targetingRules.every(n=>K(n,e)))return {key:r.key,enabled:false,variants:{},reason:"FALLBACK",version:r.version};if(r.rolloutPercentage>=100)return {key:r.key,enabled:true,variants:r.variants,reason:"TARGETING_MATCH",version:r.version}}if(r.rolloutPercentage>0){let t=e.id??"anon";return C(t,r.key)<r.rolloutPercentage?{key:r.key,enabled:true,variants:r.variants,reason:r.targetingRules.length>0?"TARGETING_MATCH":"ROLLOUT_MATCH",version:r.version}:{key:r.key,enabled:false,variants:{},reason:"FALLBACK",version:r.version}}return {key:r.key,enabled:false,variants:{},reason:"KILL_SWITCH",version:r.version}}var g=class{options;eventSource=null;connected=false;destroyed=false;reconnectAttempt=0;reconnectTimer=null;maxReconnects;constructor(e){this.options=e,this.maxReconnects=e.maxReconnects??1/0;}get isConnected(){return this.connected}connect(){this.destroyed||this.connected||this.eventSource||this.openConnection();}disconnect(){this.destroyed=true,this.cleanup();}openConnection(){if(!this.destroyed)try{let e=new URL(this.options.url);e.searchParams.set("apiKey",this.options.apiKey),this.eventSource=new EventSource(e.toString()),this.eventSource.addEventListener("open",()=>{this.connected=!0,this.reconnectAttempt=0,this.options.onStateChange?.(!0);}),this.eventSource.addEventListener("message",t=>{this.handleMessage(t.data);}),this.eventSource.addEventListener("flag_update",t=>{this.handleMessage(t.data);}),this.eventSource.addEventListener("FLAG_UPDATE",t=>{this.handleMessage(t.data);}),this.eventSource.addEventListener("FLAG_DELETE",t=>{this.handleMessage(t.data);}),this.eventSource.addEventListener("error",()=>{this.connected=!1,this.options.onStateChange?.(!1),this.cleanup(),this.scheduleReconnect();});}catch{this.scheduleReconnect();}}handleMessage(e){try{let t=JSON.parse(e);this.options.onEvent(t);}catch{}}cleanup(){this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);}scheduleReconnect(){if(this.destroyed||this.reconnectAttempt>=this.maxReconnects)return;let e=v(this.reconnectAttempt,1e3,3e4);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.destroyed||this.openConnection();},e);}};var O="nexus_flags_",N="nexus_anon_id",f=class{memory=new Map;storageKey;localStorageAvailable;broadcastChannel=null;constructor(e){this.storageKey=`${O}${e}`,this.localStorageAvailable=this.testLocalStorage(),this.hydrate(),this.setupBroadcastChannel();}set(e,t){this.memory.set(e,t),this.persist(),this.broadcastUpdate(e,t);}get(e){return this.memory.get(e)}setAll(e){for(let[t,n]of Object.entries(e))this.memory.set(t,n);this.persist();}delete(e){this.memory.delete(e),this.persist();}getAll(){return Object.fromEntries(this.memory.entries())}clear(){if(this.memory.clear(),this.localStorageAvailable)try{window.localStorage.removeItem(this.storageKey);}catch{}this.broadcastChannel?.close();}getOrCreateAnonymousId(){if(typeof window>"u")return "anon-ssr-node";if(this.localStorageAvailable)try{let e=window.localStorage.getItem(N);return e||(e=`anon_${Math.random().toString(36).substring(2,11)}`,window.localStorage.setItem(N,e)),e}catch{}return `anon_${Math.random().toString(36).substring(2,11)}`}testLocalStorage(){try{if(typeof window>"u")return !1;let e="__nexus_test__";return window.localStorage.setItem(e,"1"),window.localStorage.removeItem(e),!0}catch{return false}}hydrate(){if(this.localStorageAvailable)try{let e=window.localStorage.getItem(this.storageKey);if(e){let t=JSON.parse(e);for(let[n,s]of Object.entries(t))this.memory.set(n,s);}}catch{}}persist(){if(this.localStorageAvailable)try{window.localStorage.setItem(this.storageKey,JSON.stringify(Object.fromEntries(this.memory.entries())));}catch{}}setupBroadcastChannel(){try{typeof BroadcastChannel<"u"&&(this.broadcastChannel=new BroadcastChannel(`nexus_flags_${this.storageKey}`),this.broadcastChannel.onmessage=e=>{e.data?.key&&e.data?.result&&this.memory.set(e.data.key,e.data.result);});}catch{}}broadcastUpdate(e,t){try{this.broadcastChannel?.postMessage({key:e,result:t});}catch{}}};var b=class{apiKey;baseUrl;timeoutMs;user;storage;listeners=new Map;sseManager;flagDefinitions=new Map;constructor(e={}){if(this.apiKey=w(e.apiKey),this.baseUrl=x(e.baseUrl),this.timeoutMs=e.timeoutMs??3e3,this.storage=new f(this.apiKey.substring(0,16)),this.user=e.user??{id:this.storage.getOrCreateAnonymousId()},e.bootstrap){let t={};for(let[n,s]of Object.entries(e.bootstrap))if(typeof s=="boolean")t[n]={key:n,enabled:s,variants:{},reason:s?"DEFAULT_ENABLED":"KILL_SWITCH",version:1};else if(s&&typeof s=="object"){let a=s,l=typeof a.enabled=="boolean"?a.enabled:typeof a.isEnabled=="boolean"?a.isEnabled:true;t[n]={key:a.key??n,enabled:l,variants:a.variants??{},reason:l?"DEFAULT_ENABLED":"KILL_SWITCH",version:typeof a.version=="number"?a.version:1};}this.storage.setAll(t);}e.realtime!==false&&this.initRealtimeSync(),this.refreshFlags();}isEnabled(e,t=false){let n=this.storage.get(e);return n!==void 0?n.enabled:t}getVariant(e,t,n){let s=this.storage.get(e);if(!s?.enabled||!s.variants)return n;let a=s.variants[t];return a!==void 0?a:n}async identify(e){this.user={...this.user,...e},await this.refreshFlags();}reset(){this.user={id:this.storage.getOrCreateAnonymousId()},this.refreshFlags();}onFlagChange(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>this.listeners.get(e)?.delete(t)}destroy(){this.sseManager?.disconnect(),this.listeners.clear(),this.storage.clear();}initRealtimeSync(){this.sseManager=new g({url:`${this.baseUrl}/api/v1/flags/stream`,apiKey:this.apiKey,onEvent:e=>{if(e.type==="FLAG_UPDATE"&&e.data){let t=this.flagDefinitions.get(e.key),n=t?y(t,this.user):e.data;this.storage.set(e.key,n),this.listeners.get(e.key)?.forEach(s=>s(n));}else e.type==="FLAG_DELETE"&&(this.storage.delete(e.key),this.flagDefinitions.delete(e.key));}}),this.sseManager.connect();}async refreshFlags(){try{let e=await A({url:`${this.baseUrl}/api/v1/flags/eval`,method:"GET",headers:{Authorization:`Bearer ${this.apiKey}`,"X-Nexus-User-Id":this.user.id??"anon","X-Nexus-Country":this.user.country??""},timeoutMs:this.timeoutMs,maxRetries:2}),t=e.data,n=t&&typeof t=="object"&&"data"in t?t.data:e.data;if(n&&typeof n=="object")for(let[s,a]of Object.entries(n)){this.flagDefinitions.set(s,a);let l=y(a,this.user);this.storage.set(s,l),this.listeners.get(s)?.forEach(i=>i(l));}}catch{}}};
|
|
8
|
+
exports.FlagStorage=f;exports.NexusFlagsClient=b;exports.SSEManager=g;exports.computeRolloutBucket=C;exports.evaluateFlag=y;exports.murmur3=F;return exports;})({});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {computeBackoffMs,resolveApiKey,resolveBaseUrl,fetchWithRetry}from'@nexussdk/core';function
|
|
1
|
+
import {computeBackoffMs,resolveApiKey,resolveBaseUrl,fetchWithRetry}from'@nexussdk/core';function v(n,t=0){let e=t>>>0,s=3432918353,r=461845907,a=0,l=Math.floor(n.length/4)*4;for(;a<l;){let o=n.charCodeAt(a)&255|(n.charCodeAt(a+1)&255)<<8|(n.charCodeAt(a+2)&255)<<16|(n.charCodeAt(a+3)&255)<<24;o=Math.imul(o,s),o=o<<15|o>>>17,o=Math.imul(o,r),e^=o,e=e<<13|e>>>19,e=Math.imul(e,5)+3864292196>>>0,a+=4;}let i=0,g=n.length&3;return g>=3&&(i^=(n.charCodeAt(a+2)&255)<<16),g>=2&&(i^=(n.charCodeAt(a+1)&255)<<8),g>=1&&(i^=n.charCodeAt(a)&255,i=Math.imul(i,s),i=i<<15|i>>>17,i=Math.imul(i,r),e^=i),e^=n.length,e^=e>>>16,e=Math.imul(e,2246822507),e^=e>>>13,e=Math.imul(e,3266489909),e^=e>>>16,e>>>0}function f(n,t){let e=`${n}:${t}`;return v(e)%100}function p(n,t){let e=n.replace(/^v/,"").split(".").map(Number),s=t.replace(/^v/,"").split(".").map(Number);for(let r=0;r<3;r++){let a=(e[r]??0)-(s[r]??0);if(a!==0)return a}return 0}function y(n,t){let e=t.split("."),s=n;for(let r of e){if(s==null||typeof s!="object")return;s=s[r];}return s}function b(n,t){let e=y(t,n.attribute),s=n.values;switch(n.operator){case "EQUALS":return e===s[0];case "NOT_EQUALS":return e!==s[0];case "IN":return s.includes(e);case "NOT_IN":return !s.includes(e);case "CONTAINS":return typeof e=="string"&&e.includes(String(s[0]));case "NOT_CONTAINS":return typeof e=="string"&&!e.includes(String(s[0]));case "STARTS_WITH":return typeof e=="string"&&e.startsWith(String(s[0]));case "ENDS_WITH":return typeof e=="string"&&e.endsWith(String(s[0]));case "GREATER_THAN":return typeof e=="number"&&e>Number(s[0]);case "LESS_THAN":return typeof e=="number"&&e<Number(s[0]);case "SEMVER_GTE":return typeof e=="string"&&typeof s[0]=="string"&&p(e,String(s[0]))>=0;case "SEMVER_LTE":return typeof e=="string"&&typeof s[0]=="string"&&p(e,String(s[0]))<=0;default:return false}}function d(n,t){if(!n.isEnabled)return {key:n.key,enabled:false,variants:{},reason:"KILL_SWITCH",version:n.version};if(n.targetingRules.length>0){if(!n.targetingRules.every(s=>b(s,t)))return {key:n.key,enabled:false,variants:{},reason:"FALLBACK",version:n.version};if(n.rolloutPercentage>=100)return {key:n.key,enabled:true,variants:n.variants,reason:"TARGETING_MATCH",version:n.version}}if(n.rolloutPercentage>0){let e=t.id??"anon";return f(e,n.key)<n.rolloutPercentage?{key:n.key,enabled:true,variants:n.variants,reason:n.targetingRules.length>0?"TARGETING_MATCH":"ROLLOUT_MATCH",version:n.version}:{key:n.key,enabled:false,variants:{},reason:"FALLBACK",version:n.version}}return {key:n.key,enabled:false,variants:{},reason:"KILL_SWITCH",version:n.version}}var c=class{options;eventSource=null;connected=false;destroyed=false;reconnectAttempt=0;reconnectTimer=null;maxReconnects;constructor(t){this.options=t,this.maxReconnects=t.maxReconnects??1/0;}get isConnected(){return this.connected}connect(){this.destroyed||this.connected||this.eventSource||this.openConnection();}disconnect(){this.destroyed=true,this.cleanup();}openConnection(){if(!this.destroyed)try{let t=new URL(this.options.url);t.searchParams.set("apiKey",this.options.apiKey),this.eventSource=new EventSource(t.toString()),this.eventSource.addEventListener("open",()=>{this.connected=!0,this.reconnectAttempt=0,this.options.onStateChange?.(!0);}),this.eventSource.addEventListener("message",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("flag_update",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("FLAG_UPDATE",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("FLAG_DELETE",e=>{this.handleMessage(e.data);}),this.eventSource.addEventListener("error",()=>{this.connected=!1,this.options.onStateChange?.(!1),this.cleanup(),this.scheduleReconnect();});}catch{this.scheduleReconnect();}}handleMessage(t){try{let e=JSON.parse(t);this.options.onEvent(e);}catch{}}cleanup(){this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);}scheduleReconnect(){if(this.destroyed||this.reconnectAttempt>=this.maxReconnects)return;let t=computeBackoffMs(this.reconnectAttempt,1e3,3e4);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.destroyed||this.openConnection();},t);}};var S="nexus_flags_",m="nexus_anon_id",u=class{memory=new Map;storageKey;localStorageAvailable;broadcastChannel=null;constructor(t){this.storageKey=`${S}${t}`,this.localStorageAvailable=this.testLocalStorage(),this.hydrate(),this.setupBroadcastChannel();}set(t,e){this.memory.set(t,e),this.persist(),this.broadcastUpdate(t,e);}get(t){return this.memory.get(t)}setAll(t){for(let[e,s]of Object.entries(t))this.memory.set(e,s);this.persist();}delete(t){this.memory.delete(t),this.persist();}getAll(){return Object.fromEntries(this.memory.entries())}clear(){if(this.memory.clear(),this.localStorageAvailable)try{window.localStorage.removeItem(this.storageKey);}catch{}this.broadcastChannel?.close();}getOrCreateAnonymousId(){if(typeof window>"u")return "anon-ssr-node";if(this.localStorageAvailable)try{let t=window.localStorage.getItem(m);return t||(t=`anon_${Math.random().toString(36).substring(2,11)}`,window.localStorage.setItem(m,t)),t}catch{}return `anon_${Math.random().toString(36).substring(2,11)}`}testLocalStorage(){try{if(typeof window>"u")return !1;let t="__nexus_test__";return window.localStorage.setItem(t,"1"),window.localStorage.removeItem(t),!0}catch{return false}}hydrate(){if(this.localStorageAvailable)try{let t=window.localStorage.getItem(this.storageKey);if(t){let e=JSON.parse(t);for(let[s,r]of Object.entries(e))this.memory.set(s,r);}}catch{}}persist(){if(this.localStorageAvailable)try{window.localStorage.setItem(this.storageKey,JSON.stringify(Object.fromEntries(this.memory.entries())));}catch{}}setupBroadcastChannel(){try{typeof BroadcastChannel<"u"&&(this.broadcastChannel=new BroadcastChannel(`nexus_flags_${this.storageKey}`),this.broadcastChannel.onmessage=t=>{t.data?.key&&t.data?.result&&this.memory.set(t.data.key,t.data.result);});}catch{}}broadcastUpdate(t,e){try{this.broadcastChannel?.postMessage({key:t,result:e});}catch{}}};var h=class{apiKey;baseUrl;timeoutMs;user;storage;listeners=new Map;sseManager;flagDefinitions=new Map;constructor(t={}){if(this.apiKey=resolveApiKey(t.apiKey),this.baseUrl=resolveBaseUrl(t.baseUrl),this.timeoutMs=t.timeoutMs??3e3,this.storage=new u(this.apiKey.substring(0,16)),this.user=t.user??{id:this.storage.getOrCreateAnonymousId()},t.bootstrap){let e={};for(let[s,r]of Object.entries(t.bootstrap))if(typeof r=="boolean")e[s]={key:s,enabled:r,variants:{},reason:r?"DEFAULT_ENABLED":"KILL_SWITCH",version:1};else if(r&&typeof r=="object"){let a=r,l=typeof a.enabled=="boolean"?a.enabled:typeof a.isEnabled=="boolean"?a.isEnabled:true;e[s]={key:a.key??s,enabled:l,variants:a.variants??{},reason:l?"DEFAULT_ENABLED":"KILL_SWITCH",version:typeof a.version=="number"?a.version:1};}this.storage.setAll(e);}t.realtime!==false&&this.initRealtimeSync(),this.refreshFlags();}isEnabled(t,e=false){let s=this.storage.get(t);return s!==void 0?s.enabled:e}getVariant(t,e,s){let r=this.storage.get(t);if(!r?.enabled||!r.variants)return s;let a=r.variants[e];return a!==void 0?a:s}async identify(t){this.user={...this.user,...t},await this.refreshFlags();}reset(){this.user={id:this.storage.getOrCreateAnonymousId()},this.refreshFlags();}onFlagChange(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>this.listeners.get(t)?.delete(e)}destroy(){this.sseManager?.disconnect(),this.listeners.clear(),this.storage.clear();}initRealtimeSync(){this.sseManager=new c({url:`${this.baseUrl}/api/v1/flags/stream`,apiKey:this.apiKey,onEvent:t=>{if(t.type==="FLAG_UPDATE"&&t.data){let e=this.flagDefinitions.get(t.key),s=e?d(e,this.user):t.data;this.storage.set(t.key,s),this.listeners.get(t.key)?.forEach(r=>r(s));}else t.type==="FLAG_DELETE"&&(this.storage.delete(t.key),this.flagDefinitions.delete(t.key));}}),this.sseManager.connect();}async refreshFlags(){try{let t=await fetchWithRetry({url:`${this.baseUrl}/api/v1/flags/eval`,method:"GET",headers:{Authorization:`Bearer ${this.apiKey}`,"X-Nexus-User-Id":this.user.id??"anon","X-Nexus-Country":this.user.country??""},timeoutMs:this.timeoutMs,maxRetries:2}),e=t.data,s=e&&typeof e=="object"&&"data"in e?e.data:t.data;if(s&&typeof s=="object")for(let[r,a]of Object.entries(s)){this.flagDefinitions.set(r,a);let l=d(a,this.user);this.storage.set(r,l),this.listeners.get(r)?.forEach(i=>i(l));}}catch{}}};export{u as FlagStorage,h as NexusFlagsClient,c as SSEManager,f as computeRolloutBucket,d as evaluateFlag,v as murmur3};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexussdk/flags",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -21,19 +21,40 @@
|
|
|
21
21
|
"directory": "packages/sdk-flags"
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/Huynhdung295/NexusSDK#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Huynhdung295/NexusSDK/issues"
|
|
26
|
+
},
|
|
24
27
|
"keywords": [
|
|
25
28
|
"nexus",
|
|
26
29
|
"nexussdk",
|
|
27
30
|
"feature-flags",
|
|
31
|
+
"flags",
|
|
32
|
+
"feature-toggles",
|
|
28
33
|
"remote-config",
|
|
29
34
|
"abac",
|
|
30
35
|
"sse",
|
|
31
|
-
"murmur3"
|
|
36
|
+
"murmur3",
|
|
37
|
+
"canary-release",
|
|
38
|
+
"multivariate-testing",
|
|
39
|
+
"offline-first",
|
|
40
|
+
"self-hosted",
|
|
41
|
+
"local-dev-server",
|
|
42
|
+
"zero-dependencies",
|
|
43
|
+
"react",
|
|
44
|
+
"vue",
|
|
45
|
+
"angular",
|
|
46
|
+
"svelte",
|
|
47
|
+
"nextjs",
|
|
48
|
+
"nuxt",
|
|
49
|
+
"edge-runtime"
|
|
32
50
|
],
|
|
33
51
|
"files": [
|
|
34
52
|
"dist",
|
|
35
53
|
"README.md"
|
|
36
54
|
],
|
|
55
|
+
"bin": {
|
|
56
|
+
"nexus-flags-dev": "./dist/dev-server/cli.mjs"
|
|
57
|
+
},
|
|
37
58
|
"main": "./dist/index.cjs",
|
|
38
59
|
"module": "./dist/index.mjs",
|
|
39
60
|
"types": "./dist/index.d.ts",
|
|
@@ -42,13 +63,17 @@
|
|
|
42
63
|
"types": "./dist/index.d.ts",
|
|
43
64
|
"import": "./dist/index.mjs",
|
|
44
65
|
"require": "./dist/index.cjs"
|
|
66
|
+
},
|
|
67
|
+
"./dev-server": {
|
|
68
|
+
"import": "./dist/dev-server/cli.mjs"
|
|
45
69
|
}
|
|
46
70
|
},
|
|
47
71
|
"dependencies": {
|
|
48
|
-
"@nexussdk/
|
|
49
|
-
"@nexussdk/
|
|
72
|
+
"@nexussdk/core": "0.0.4",
|
|
73
|
+
"@nexussdk/contracts": "0.0.4"
|
|
50
74
|
},
|
|
51
75
|
"devDependencies": {
|
|
76
|
+
"@types/node": "^22.13.4",
|
|
52
77
|
"tsup": "^8.0.2",
|
|
53
78
|
"typescript": "^5.4.5",
|
|
54
79
|
"rimraf": "^5.0.5"
|
|
@@ -56,6 +81,7 @@
|
|
|
56
81
|
"scripts": {
|
|
57
82
|
"build": "tsup",
|
|
58
83
|
"dev": "tsup --watch",
|
|
84
|
+
"flags-server": "node dist/dev-server/cli.mjs",
|
|
59
85
|
"lint": "tsc --noEmit",
|
|
60
86
|
"clean": "rimraf dist"
|
|
61
87
|
}
|