@crustjs/store 0.0.2 → 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/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  // @bun
2
- class G extends Error{code;details;cause;constructor(x,z,V){super(z);this.name="CrustStoreError",this.code=x,this.details=V}is(x){return this.code===x}withCause(x){return this.cause=x,this}}import{homedir as y}from"os";import{join as H}from"path";var A="config";function D(){return{platform:process.platform,env:process.env,homedir:y()}}function F(x){if(!x||x.trim().length===0)throw new G("PATH","appName must be a non-empty string",{path:x??""});if(x.includes("/")||x.includes("\\"))throw new G("PATH","appName must not contain path separators",{path:x})}function J(x){if(!x||x.trim().length===0)throw new G("PATH","name must be a non-empty string",{path:x??""});if(x.includes("/")||x.includes("\\"))throw new G("PATH","name must not contain path separators",{path:x});if(x.endsWith(".json"))throw new G("PATH","name must not include the .json extension",{path:x})}function R(x){if(!x||x.trim().length===0)throw new G("PATH","dirPath must be a non-empty string",{path:x??""});if(!(x.startsWith("/")||/^[A-Za-z]:[/\\]/.test(x)))throw new G("PATH","dirPath must be an absolute path",{path:x});if(x.endsWith(".json"))throw new G("PATH","dirPath must be a directory path, not a file path (should not end with .json)",{path:x})}function T(x,z){F(x);let V=z??D();switch(V.platform){case"linux":{let B=V.env.XDG_CONFIG_HOME,q=B&&B.trim().length>0?B:H(V.homedir,".config");return H(q,x)}case"darwin":return H(V.homedir,"Library","Application Support",x);case"win32":{let B=V.env.APPDATA,q=B&&B.trim().length>0?B:H(V.homedir,"AppData","Roaming");return H(q,x)}default:throw new G("PATH",`Unsupported platform: ${V.platform}`,{path:V.platform})}}function Q(x,z){R(x);let V=z??A;if(z!==void 0)J(z);return H(x,`${V}.json`)}function W(x,z){let V={};for(let[B,q]of Object.entries(z))if(x&&B in x)V[B]=x[B];else if("default"in q&&q.default!==void 0)V[B]=Array.isArray(q.default)?[...q.default]:q.default;return V}import{randomUUID as j}from"crypto";import{mkdir as k,readFile as S,rename as g,rm as w,unlink as C,writeFile as c}from"fs/promises";import{dirname as N,join as v}from"path";async function Y(x){let z;try{z=await S(x,"utf-8")}catch(V){if($(V))return;throw new G("IO",`Failed to read config file: ${x}`,{path:x,operation:"read"}).withCause(V)}try{return JSON.parse(z)}catch(V){throw new G("PARSE",`Malformed JSON in config file: ${x}`,{path:x}).withCause(V)}}async function L(x,z){let V=N(x),B=v(V,`.config-${j()}.tmp`);try{await k(V,{recursive:!0})}catch(q){throw new G("IO",`Failed to create config directory: ${V}`,{path:x,operation:"write"}).withCause(q)}try{let q=JSON.stringify(z,null,"\t");await c(B,q,"utf-8")}catch(q){throw await X(B),new G("IO",`Failed to write config file: ${x}`,{path:x,operation:"write"}).withCause(q)}try{await g(B,x)}catch(q){throw await X(B),new G("IO",`Failed to finalize config file: ${x}`,{path:x,operation:"write"}).withCause(q)}}async function Z(x){try{await C(x)}catch(z){if($(z))return;throw new G("IO",`Failed to delete config file: ${x}`,{path:x,operation:"delete"}).withCause(z)}}function $(x){return x!==null&&typeof x==="object"&&"code"in x&&x.code==="ENOENT"}async function X(x){try{await w(x,{force:!0})}catch{}}function E(x){let{dirPath:z,name:V,fields:B}=x,q=Q(z,V);async function M(){let K=await Y(q);return W(K,B)}async function I(K){await L(q,K)}async function O(K){let _=await M(),b=K(_);await L(q,b)}async function U(){await Z(q)}return{read:M,write:I,update:O,reset:U}}export{E as createStore,T as configDir,G as CrustStoreError};
2
+ class Q extends Error{code;details;cause;constructor(q,I,G){super(I);this.name="CrustStoreError",this.code=q,this.details=G}is(q){return this.code===q}withCause(q){return this.cause=q,this}}import{homedir as P}from"os";import{join as $}from"path";var h="config";function V(){return{platform:process.platform,env:process.env,homedir:P()}}function F(q){if(!q||q.trim().length===0)throw new Q("PATH","appName must be a non-empty string",{path:q??""});if(q.includes("/")||q.includes("\\"))throw new Q("PATH","appName must not contain path separators",{path:q})}function u(q){if(!q||q.trim().length===0)throw new Q("PATH","name must be a non-empty string",{path:q??""});if(q.includes("/")||q.includes("\\"))throw new Q("PATH","name must not contain path separators",{path:q});if(q.endsWith(".json"))throw new Q("PATH","name must not include the .json extension",{path:q})}function v(q){if(!q||q.trim().length===0)throw new Q("PATH","dirPath must be a non-empty string",{path:q??""});if(!(q.startsWith("/")||/^[A-Za-z]:[/\\]/.test(q)))throw new Q("PATH","dirPath must be an absolute path",{path:q});if(q.endsWith(".json"))throw new Q("PATH","dirPath must be a directory path, not a file path (should not end with .json)",{path:q})}function T(q,I,G,H){let K=q.env[I],W=K&&K.trim().length>0?K:$(q.homedir,...G);return $(W,H)}function b(q){throw new Q("PATH",`Unsupported platform: ${q}`,{path:q})}function m(q,I){F(q);let G=I??V();switch(G.platform){case"linux":case"darwin":return T(G,"XDG_CONFIG_HOME",[".config"],q);case"win32":{let H=G.env.APPDATA,K=H&&H.trim().length>0?H:$(G.homedir,"AppData","Roaming");return $(K,q)}default:b(G.platform)}}function d(q,I){F(q);let G=I??V();switch(G.platform){case"linux":case"darwin":return T(G,"XDG_DATA_HOME",[".local","share"],q);case"win32":{let H=G.env.LOCALAPPDATA,K=H&&H.trim().length>0?H:$(G.homedir,"AppData","Local");return $(K,q,"Data")}default:b(G.platform)}}function o(q,I){F(q);let G=I??V();switch(G.platform){case"linux":case"darwin":return T(G,"XDG_STATE_HOME",[".local","state"],q);case"win32":{let H=G.env.LOCALAPPDATA,K=H&&H.trim().length>0?H:$(G.homedir,"AppData","Local");return $(K,q,"State")}default:b(G.platform)}}function s(q,I){F(q);let G=I??V();switch(G.platform){case"linux":case"darwin":return T(G,"XDG_CACHE_HOME",[".cache"],q);case"win32":{let H=G.env.LOCALAPPDATA,K=H&&H.trim().length>0?H:$(G.homedir,"AppData","Local");return $(K,q,"Cache")}default:b(G.platform)}}function U(q,I){v(q);let G=I??h;if(I!==void 0)u(I);return $(q,`${G}.json`)}function z(q,I,G=!0){let H={};for(let[K,W]of Object.entries(I))if(q&&K in q){let _=q[K];H[K]=Array.isArray(_)?[..._]:_}else if("default"in W&&W.default!==void 0)H[K]=Array.isArray(W.default)?[...W.default]:W.default;if(!G&&q!==void 0){for(let[K,W]of Object.entries(q))if(!(K in I))H[K]=Array.isArray(W)?[...W]:W}return H}import{randomUUID as p}from"crypto";import{mkdir as n,readFile as i,rename as l,rm as t,unlink as r,writeFile as a}from"fs/promises";import{dirname as e,join as qq}from"path";async function S(q){let I;try{I=await i(q,"utf-8")}catch(G){if(y(G))return;throw new Q("IO",`Failed to read config file: ${q}`,{path:q,operation:"read"}).withCause(G)}try{return JSON.parse(I)}catch(G){throw new Q("PARSE",`Malformed JSON in config file: ${q}`,{path:q}).withCause(G)}}async function R(q,I){let G=e(q),H=qq(G,`.config-${p()}.tmp`);try{await n(G,{recursive:!0})}catch(K){throw new Q("IO",`Failed to create config directory: ${G}`,{path:q,operation:"write"}).withCause(K)}try{let K=JSON.stringify(I,null,"\t");await a(H,K,"utf-8")}catch(K){throw await A(H),new Q("IO",`Failed to write config file: ${q}`,{path:q,operation:"write"}).withCause(K)}try{await l(H,q)}catch(K){throw await A(H),new Q("IO",`Failed to finalize config file: ${q}`,{path:q,operation:"write"}).withCause(K)}}async function k(q){try{await r(q)}catch(I){if(y(I))return;throw new Q("IO",`Failed to delete config file: ${q}`,{path:q,operation:"delete"}).withCause(I)}}function y(q){return q!==null&&typeof q==="object"&&"code"in q&&q.code==="ENOENT"}async function A(q){try{await t(q,{force:!0})}catch{}}function Gq(q){let{dirPath:I,name:G,fields:H,pruneUnknown:K}=q,W=U(I,G),_=K??!0;function C(L,Y){if(Y==="number"&&typeof L==="string"){let X=Number(L);return Number.isNaN(X)?L:X}if(Y==="boolean"&&typeof L==="string")return L==="true"||L==="1";return L}function x(L){let X={...L};for(let[Z,M]of Object.entries(H)){if(!(Z in X))continue;let O=X[Z];if(M.array===!0&&Array.isArray(O)){X[Z]=O.map((B)=>C(B,M.type));continue}X[Z]=C(O,M.type)}return X}async function J(L,Y){let X=[],Z=L;for(let[M,O]of Object.entries(H)){if(!O.validate)continue;let B=Z[M];if(B===void 0)continue;try{await O.validate(B)}catch(D){let f=D instanceof Error?D.message:"Validation failed";X.push({message:f,path:M})}}if(X.length>0){let M=X.map((B)=>` - ${B.path}: ${B.message}`),O=`Store validation failed (${Y})
3
+ ${M.join(`
4
+ `)}`;throw new Q("VALIDATION",O,{operation:Y,issues:X})}}async function j(){let L=await S(W),Y=z(L,H,_);return x(Y)}async function g(){let L=await j();return await J(L,"read"),L}async function w(L){let Y=x(L);await J(Y,"write"),await R(W,Y)}async function c(L){let Y=await j(),X=L(Y),Z=x(X);await J(Z,"update"),await R(W,Z)}async function E(L){let X={...await j(),...L},Z=x(X);await J(Z,"patch"),await R(W,Z)}async function N(){await k(W)}return{read:g,write:w,update:c,patch:E,reset:N}}export{o as stateDir,d as dataDir,Gq as createStore,m as configDir,s as cacheDir,Q as CrustStoreError};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@crustjs/store",
3
- "version": "0.0.2",
4
- "description": "Minimal, type-safe config persistence for CLI apps",
3
+ "version": "0.0.4",
4
+ "description": "DX-first, typed persistence for CLI apps with config/data/state/cache separation",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "chenxin-yan",
@@ -17,8 +17,12 @@
17
17
  "keywords": [
18
18
  "cli",
19
19
  "config",
20
+ "data",
21
+ "state",
22
+ "cache",
20
23
  "store",
21
24
  "persistence",
25
+ "xdg",
22
26
  "bun",
23
27
  "typescript"
24
28
  ],