@qwanyx/stack 0.2.0 → 0.2.3

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.
@@ -0,0 +1,53 @@
1
+ import type { ApiConfig, QueryParams } from '../types';
2
+ /**
3
+ * Generic API Client
4
+ * Framework-agnostic HTTP client for any API
5
+ */
6
+ export declare class ApiClient {
7
+ private config;
8
+ constructor(config: ApiConfig);
9
+ /**
10
+ * Build query string from params
11
+ */
12
+ private buildQueryString;
13
+ /**
14
+ * Make HTTP request
15
+ */
16
+ private request;
17
+ /**
18
+ * GET request
19
+ */
20
+ get<T = any>(endpoint: string, params?: QueryParams): Promise<T>;
21
+ /**
22
+ * POST request
23
+ */
24
+ post<T = any>(endpoint: string, body?: any, params?: QueryParams): Promise<T>;
25
+ /**
26
+ * PUT request
27
+ */
28
+ put<T = any>(endpoint: string, body?: any, params?: QueryParams): Promise<T>;
29
+ /**
30
+ * PATCH request
31
+ */
32
+ patch<T = any>(endpoint: string, body?: any, params?: QueryParams): Promise<T>;
33
+ /**
34
+ * DELETE request
35
+ */
36
+ delete<T = any>(endpoint: string, params?: QueryParams): Promise<T>;
37
+ /**
38
+ * Update base URL
39
+ */
40
+ setBaseUrl(baseUrl: string): void;
41
+ /**
42
+ * Get current base URL
43
+ */
44
+ getBaseUrl(): string;
45
+ }
46
+ /**
47
+ * Initialize the API client
48
+ */
49
+ export declare function initializeApiClient(config: ApiConfig): ApiClient;
50
+ /**
51
+ * Get the API client instance
52
+ */
53
+ export declare function getApiClient(): ApiClient;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Generic authentication manager
3
+ * Handles token storage and retrieval
4
+ */
5
+ export declare class AuthManager {
6
+ /**
7
+ * Store authentication token
8
+ */
9
+ static setToken(token: string): void;
10
+ /**
11
+ * Get authentication token
12
+ */
13
+ static getToken(): string | null;
14
+ /**
15
+ * Remove authentication token
16
+ */
17
+ static clearToken(): void;
18
+ /**
19
+ * Store refresh token
20
+ */
21
+ static setRefreshToken(token: string): void;
22
+ /**
23
+ * Get refresh token
24
+ */
25
+ static getRefreshToken(): string | null;
26
+ /**
27
+ * Check if user is authenticated
28
+ */
29
+ static isAuthenticated(): boolean;
30
+ /**
31
+ * Get authorization header
32
+ */
33
+ static getAuthHeader(): Record<string, string>;
34
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * GraphClient - Backend Communication Layer
3
+ *
4
+ * Provides a type-safe interface to the qwanyx-brain SPU backend.
5
+ * All operations communicate through the unified `/spu/invoke` endpoint.
6
+ */
7
+ import type { GraphNode, GraphEdge, GraphClientConfig } from '../types';
8
+ export declare class GraphClient {
9
+ private config;
10
+ constructor(config: GraphClientConfig);
11
+ /**
12
+ * Call the Graph coprocessor
13
+ */
14
+ private callGraph;
15
+ /**
16
+ * Get auth token from localStorage (browser) or return empty string
17
+ */
18
+ private getToken;
19
+ /**
20
+ * Get children nodes
21
+ */
22
+ getChildren(parentId?: string): Promise<GraphNode[]>;
23
+ /**
24
+ * Get a specific node by ID
25
+ */
26
+ getNode(nodeId: string): Promise<GraphNode | null>;
27
+ /**
28
+ * Add a new node
29
+ */
30
+ addNode(node: Partial<GraphNode> & {
31
+ p?: string;
32
+ type: string;
33
+ }): Promise<GraphNode>;
34
+ /**
35
+ * Update an existing node
36
+ */
37
+ updateNode(nodeId: string, updates: Partial<GraphNode>): Promise<GraphNode>;
38
+ /**
39
+ * Delete a node
40
+ */
41
+ deleteNode(nodeId: string, cascade?: boolean): Promise<boolean>;
42
+ /**
43
+ * Get children with edges (supports different display modes)
44
+ */
45
+ getChildrenWithEdges(nodeId?: string, displayMode?: 'children' | 'edges' | 'both', edgeType?: string, nodeType?: string): Promise<GraphNode[]>;
46
+ /**
47
+ * Add an edge between two nodes
48
+ */
49
+ addEdge(sourceId: string | null, targetId: string, edgeType?: string, data?: Record<string, any>): Promise<{
50
+ success: boolean;
51
+ }>;
52
+ /**
53
+ * Delete an edge
54
+ */
55
+ deleteEdge(sourceId: string | null, targetId: string, edgeType?: string): Promise<{
56
+ deleted: boolean;
57
+ count: number;
58
+ }>;
59
+ /**
60
+ * Get edges from a source node
61
+ * @param sourceId - Source node ID (null for edges without source)
62
+ * @param edgeType - Optional edge type filter
63
+ * @param targetId - Optional target node ID for reverse lookups
64
+ */
65
+ getEdges(sourceId: string | null, edgeType?: string, targetId?: string): Promise<GraphEdge[]>;
66
+ /**
67
+ * Get ancestors of a node (path from root to node)
68
+ */
69
+ getAncestors(nodeId: string): Promise<GraphNode[]>;
70
+ /**
71
+ * Get tree structure starting from a root
72
+ */
73
+ getTree(rootId?: string, maxDepth?: number): Promise<any>;
74
+ /**
75
+ * Move a node to a new parent
76
+ */
77
+ moveNode(nodeId: string, newParentId?: string): Promise<GraphNode>;
78
+ /**
79
+ * Search nodes by query
80
+ */
81
+ searchNodes(query: string, type?: string): Promise<GraphNode[]>;
82
+ /**
83
+ * Get all nodes of a specific type
84
+ */
85
+ getNodesByType(type: string): Promise<GraphNode[]>;
86
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * MailClient - Email Communication Layer
3
+ *
4
+ * Provides a type-safe interface to the mail coprocessor in qwanyx-brain.
5
+ * Handles email settings (with encrypted passwords) and IMAP operations.
6
+ */
7
+ import type { MailClientConfig, MailAccount, MailListResult, ImapResponse, FolderInfo } from '../types';
8
+ export declare class MailClient {
9
+ private config;
10
+ constructor(config: MailClientConfig);
11
+ /**
12
+ * Call the Mail coprocessor
13
+ */
14
+ private callMail;
15
+ /**
16
+ * Get auth token from localStorage (browser) or return empty string
17
+ */
18
+ private getToken;
19
+ /**
20
+ * Get email settings with decrypted passwords
21
+ */
22
+ getSettings(): Promise<{
23
+ accounts: MailAccount[];
24
+ }>;
25
+ /**
26
+ * Save email settings (passwords encrypted server-side)
27
+ */
28
+ saveSettings(accounts: MailAccount[]): Promise<void>;
29
+ /**
30
+ * List emails from IMAP inbox
31
+ */
32
+ listEmails(accountId?: string, limit?: number): Promise<MailListResult>;
33
+ /**
34
+ * Delete emails by UID
35
+ */
36
+ deleteEmails(accountId: string, uids: number[], expunge?: boolean): Promise<{
37
+ success: boolean;
38
+ deleted: number;
39
+ }>;
40
+ /**
41
+ * Execute raw IMAP commands - thin proxy for stack-side logic
42
+ * Commands are strings like: "SELECT INBOX", "UID STORE 123 +FLAGS (\\Deleted)", "EXPUNGE"
43
+ */
44
+ imapExec(accountId: string, commands: string[]): Promise<{
45
+ success: boolean;
46
+ responses: ImapResponse[];
47
+ }>;
48
+ /**
49
+ * Move emails to trash (soft delete)
50
+ */
51
+ trashEmails(accountId: string, uids: number[]): Promise<{
52
+ success: boolean;
53
+ moved: number;
54
+ }>;
55
+ /**
56
+ * Archive emails (remove from inbox, keep in All Mail)
57
+ */
58
+ archiveEmails(accountId: string, uids: number[]): Promise<{
59
+ success: boolean;
60
+ archived: number;
61
+ }>;
62
+ /**
63
+ * List available folders
64
+ */
65
+ listFolders(accountId: string): Promise<{
66
+ success: boolean;
67
+ folders: FolderInfo[];
68
+ }>;
69
+ }
@@ -0,0 +1,19 @@
1
+ export interface AnimatedCardFlipNode {
2
+ _id: string;
3
+ type: string;
4
+ title: string;
5
+ data: {
6
+ name: string;
7
+ image: string;
8
+ };
9
+ }
10
+ export interface AnimatedCardFlipProps {
11
+ nodes: AnimatedCardFlipNode[];
12
+ cardCount?: number;
13
+ minInterval?: number;
14
+ maxInterval?: number;
15
+ onCardClick?: (node: AnimatedCardFlipNode) => void;
16
+ cardSize?: 'small' | 'medium' | 'large';
17
+ className?: string;
18
+ }
19
+ export declare function AnimatedCardFlip({ nodes, cardCount, minInterval, maxInterval, onCardClick, cardSize, className }: AnimatedCardFlipProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Default Card Component for Stack list items
3
+ * Generic design that works with any data type
4
+ */
5
+ export interface CardProps<T = any> {
6
+ item: T;
7
+ onClick?: () => void;
8
+ title?: (item: T) => string;
9
+ subtitle?: (item: T) => string;
10
+ image?: (item: T) => string | undefined;
11
+ badge?: (item: T) => string | undefined;
12
+ }
13
+ export declare function Card<T = any>({ item, onClick, title, subtitle, image, badge, }: CardProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Default Detail View Component
3
+ * Generic detail view for any item
4
+ */
5
+ export interface DetailProps<T = any> {
6
+ item: T | null;
7
+ onClose?: () => void;
8
+ title?: (item: T) => string;
9
+ image?: (item: T) => string | undefined;
10
+ fields?: Array<{
11
+ label: string;
12
+ value: (item: T) => any;
13
+ }>;
14
+ }
15
+ export declare function Detail<T = any>({ item, onClose, title, image, fields, }: DetailProps<T>): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Mail Component
3
+ * Smart email list with selection, actions, and detail view
4
+ */
5
+ import React from 'react';
6
+ export interface EmailMessage {
7
+ uid: number;
8
+ subject: string;
9
+ from: string;
10
+ date: string;
11
+ seen: boolean;
12
+ }
13
+ export interface MailProps {
14
+ baseUrl: string;
15
+ systemId: string;
16
+ accountId: string;
17
+ limit?: number;
18
+ selectable?: boolean;
19
+ showDetail?: boolean;
20
+ emptyMessage?: string;
21
+ autoLoad?: boolean;
22
+ onSelect?: (email: EmailMessage) => void;
23
+ onSelectionChange?: (uids: number[]) => void;
24
+ onDelete?: (uids: number[]) => void;
25
+ onError?: (error: Error) => void;
26
+ renderItem?: (email: EmailMessage, selected: boolean) => React.ReactNode;
27
+ renderDetail?: (email: EmailMessage) => React.ReactNode;
28
+ renderActions?: (selectedUids: number[], actions: MailActions) => React.ReactNode;
29
+ renderEmpty?: () => React.ReactNode;
30
+ renderLoading?: () => React.ReactNode;
31
+ theme?: MailTheme;
32
+ }
33
+ export interface MailTheme {
34
+ background?: string;
35
+ cardBackground?: string;
36
+ selectedBackground?: string;
37
+ unreadBackground?: string;
38
+ text?: string;
39
+ textSecondary?: string;
40
+ border?: string;
41
+ primary?: string;
42
+ danger?: string;
43
+ }
44
+ export interface MailActions {
45
+ delete: () => Promise<void>;
46
+ archive: () => Promise<void>;
47
+ refresh: () => Promise<void>;
48
+ selectAll: () => void;
49
+ clearSelection: () => void;
50
+ }
51
+ export declare function Mail({ baseUrl, systemId, accountId, limit, selectable, showDetail, emptyMessage, autoLoad, onSelect, onSelectionChange, onDelete, onError, renderItem, renderDetail, renderActions, renderEmpty, renderLoading, theme: themeProp, }: MailProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Stack Component
3
+ * Apple/Superhuman inspired clean design
4
+ */
5
+ import React from 'react';
6
+ import type { QueryParams } from '../types';
7
+ export interface StackProps<T = any> {
8
+ endpoint: string;
9
+ params?: QueryParams;
10
+ layout?: 'list' | 'grid' | 'masonry';
11
+ title?: string;
12
+ emptyMessage?: string;
13
+ renderItem?: (item: T, index: number) => React.ReactNode;
14
+ keyExtractor?: (item: T, index: number) => string;
15
+ searchable?: boolean;
16
+ searchFields?: (keyof T)[];
17
+ searchPlaceholder?: string;
18
+ filters?: Array<{
19
+ field: keyof T;
20
+ label: string;
21
+ options?: Array<{
22
+ value: any;
23
+ label: string;
24
+ }>;
25
+ }>;
26
+ pageSize?: number;
27
+ onItemClick?: (item: T) => void;
28
+ onRefresh?: () => void;
29
+ theme?: {
30
+ background?: string;
31
+ cardBackground?: string;
32
+ text?: string;
33
+ textSecondary?: string;
34
+ border?: string;
35
+ primary?: string;
36
+ };
37
+ }
38
+ export declare function Stack<T = any>({ endpoint, params, layout, title, emptyMessage, renderItem, keyExtractor, searchable, searchFields, searchPlaceholder, filters, pageSize, onItemClick, onRefresh, theme, }: StackProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import type { HttpMethod } from '../types';
2
+ export interface UseMutationOptions<TData, TVariables> {
3
+ onSuccess?: (data: TData, variables: TVariables) => void;
4
+ onError?: (error: Error, variables: TVariables) => void;
5
+ }
6
+ export interface UseMutationResult<TData, TVariables> {
7
+ data: TData | null;
8
+ loading: boolean;
9
+ error: Error | null;
10
+ mutate: (variables: TVariables) => Promise<TData | null>;
11
+ reset: () => void;
12
+ }
13
+ /**
14
+ * Generic hook for mutations (POST, PUT, PATCH, DELETE)
15
+ */
16
+ export declare function useMutation<TData = any, TVariables = any>(endpoint: string, method?: HttpMethod, options?: UseMutationOptions<TData, TVariables>): UseMutationResult<TData, TVariables>;
@@ -0,0 +1,18 @@
1
+ import type { QueryParams } from '../types';
2
+ export interface UseQueryOptions<T> {
3
+ enabled?: boolean;
4
+ refetchOnMount?: boolean;
5
+ onSuccess?: (data: T) => void;
6
+ onError?: (error: Error) => void;
7
+ }
8
+ export interface UseQueryResult<T> {
9
+ data: T | null;
10
+ loading: boolean;
11
+ error: Error | null;
12
+ refetch: () => Promise<void>;
13
+ }
14
+ /**
15
+ * Generic hook for fetching data
16
+ * Similar to React Query but simpler
17
+ */
18
+ export declare function useQuery<T = any>(endpoint: string, params?: QueryParams, options?: UseQueryOptions<T>): UseQueryResult<T>;
package/dist/index.cjs.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var wt=Object.defineProperty;var _t=(f,t,r)=>t in f?wt(f,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):f[t]=r;var be=(f,t,r)=>_t(f,typeof t!="symbol"?t+"":t,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("react");class jt{constructor(t){be(this,"config");if(!t.system_id)throw new Error("GraphClient: system_id is REQUIRED. No system_id → No start.");this.config=t}async callGraph(t,r={}){const n={coprocessor:"graph",method:t,system_id:this.config.system_id,params:r};try{const i=await fetch(`${this.config.baseUrl}/spu/invoke`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.getToken()}`},body:JSON.stringify(n)});if(!i.ok){const l=await i.text();throw new Error(`API error (${i.status}): ${l}`)}const o=await i.json();if(!o.success&&o.error)throw new Error(o.error);return o}catch(i){throw console.error("Graph API call failed:",i),i}}getToken(){return typeof window<"u"&&window.localStorage&&localStorage.getItem("qwanyx_token")||""}async getChildren(t){return(await this.callGraph("get_children",{parent_id:t})).result||[]}async getNode(t){return(await this.callGraph("get_node",{node_id:t})).result||null}async addNode(t){const r={...t,created:t.created||new Date().toISOString(),modified:new Date().toISOString()},n=await this.callGraph("add_node",r);if(!n.result)throw new Error("Failed to add node");return n.result}async updateNode(t,r){const n={node_id:t,updates:{...r,modified:new Date().toISOString()}},i=await this.callGraph("update_node",n);if(!i.result)throw new Error("Failed to update node");return i.result}async deleteNode(t,r=!1){return(await this.callGraph("delete_node",{node_id:t,cascade:r})).success===!0}async getChildrenWithEdges(t,r="children",n,i){return(await this.callGraph("get_children_with_edges",{node_id:t,display_mode:r,edge_type:n,node_type:i})).result||[]}async addEdge(t,r,n="link",i){return await this.callGraph("add_edge",{source_id:t,target_id:r,edge_type:n,data:i})}async deleteEdge(t,r,n){return await this.callGraph("delete_edge",{source_id:t,target_id:r,edge_type:n})}async getEdges(t,r){return(await this.callGraph("get_edges",{source_id:t,edge_type:r})).result||[]}async getAncestors(t){return(await this.callGraph("get_ancestors",{node_id:t})).ancestors||[]}async getTree(t,r=10){return(await this.callGraph("get_tree",{root_id:t,max_depth:r})).tree||[]}async moveNode(t,r){const n=await this.callGraph("move",{node_id:t,new_parent_id:r});if(!n.node)throw new Error("Failed to move node");return n.node}async searchNodes(t,r){return(await this.callGraph("search",{query:t,type:r})).nodes||[]}}class Qe{constructor(t){be(this,"config");if(!t.system_id)throw new Error("MailClient: system_id is REQUIRED");this.config=t}async callMail(t,r={}){const n={coprocessor:"mail",method:t,system_id:this.config.system_id,params:{user_id:this.config.system_id,...r}};try{const i=await fetch(`${this.config.baseUrl}/spu/invoke`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.getToken()}`},body:JSON.stringify(n)});if(!i.ok){const l=await i.text();throw new Error(`API error (${i.status}): ${l}`)}const o=await i.json();if(!o.success&&o.error)throw new Error(o.error);return o.result}catch(i){throw console.error("Mail API call failed:",i),i}}getToken(){return typeof window<"u"&&window.localStorage&&localStorage.getItem("qwanyx_token")||""}async getSettings(){try{return await this.callMail("get_email_settings")||{accounts:[]}}catch{return{accounts:[]}}}async saveSettings(t){await this.callMail("save_email_settings",{accounts:t})}async listEmails(t,r=20){return this.callMail("list_emails",{account_id:t,limit:r})}async deleteEmails(t,r,n=!0){return this.callMail("delete_emails",{account_id:t,uids:r,expunge:n})}async imapExec(t,r){return this.callMail("imap_exec",{account_id:t,commands:r})}async trashEmails(t,r){const n=["SELECT INBOX",...r.map(l=>`UID MOVE ${l} [Gmail]/Trash`)],o=(await this.imapExec(t,n)).responses.filter(l=>l.ok&&l.command==="UID MOVE").length;return{success:o>0,moved:o}}async archiveEmails(t,r){const n=["SELECT INBOX",...r.map(l=>`UID STORE ${l} +FLAGS (\\Deleted)`),"EXPUNGE"],o=(await this.imapExec(t,n)).responses.filter(l=>l.ok&&l.command==="UID STORE").length;return{success:o>0,archived:o}}async listFolders(t){const n=(await this.imapExec(t,['LIST "" *'])).responses.find(i=>i.command==="LIST");return n!=null&&n.ok&&n.folders?{success:!0,folders:n.folders}:{success:!1,folders:[]}}}const Ae="qwanyx_auth_token",$e="qwanyx_refresh_token";class He{static setToken(t){typeof window<"u"&&localStorage.setItem(Ae,t)}static getToken(){return typeof window<"u"?localStorage.getItem(Ae):null}static clearToken(){typeof window<"u"&&(localStorage.removeItem(Ae),localStorage.removeItem($e))}static setRefreshToken(t){typeof window<"u"&&localStorage.setItem($e,t)}static getRefreshToken(){return typeof window<"u"?localStorage.getItem($e):null}static isAuthenticated(){return!!this.getToken()}static getAuthHeader(){const t=this.getToken();return t?{Authorization:`Bearer ${t}`}:{}}}class Ze{constructor(t){be(this,"config");this.config={timeout:3e4,headers:{"Content-Type":"application/json"},...t}}buildQueryString(t){if(!t||Object.keys(t).length===0)return"";const r=new URLSearchParams;Object.entries(t).forEach(([i,o])=>{o!=null&&r.append(i,String(o))});const n=r.toString();return n?`?${n}`:""}async request(t,r={}){const{method:n="GET",headers:i={},body:o,params:l}=r,v=`${this.config.baseUrl}/${t}${this.buildQueryString(l)}`,u={...this.config.headers,...He.getAuthHeader(),...i},h={method:n,headers:u};o&&n!=="GET"&&(h.body=JSON.stringify(o));try{const x=new AbortController,j=setTimeout(()=>x.abort(),this.config.timeout),O=await fetch(v,{...h,signal:x.signal});if(clearTimeout(j),!O.ok){const D=await O.json().catch(()=>({message:O.statusText}));throw new Error(D.message||`HTTP ${O.status}`)}return await O.json()}catch(x){throw x instanceof Error?x:new Error("An unexpected error occurred")}}async get(t,r){return this.request(t,{method:"GET",params:r})}async post(t,r,n){return this.request(t,{method:"POST",body:r,params:n})}async put(t,r,n){return this.request(t,{method:"PUT",body:r,params:n})}async patch(t,r,n){return this.request(t,{method:"PATCH",body:r,params:n})}async delete(t,r){return this.request(t,{method:"DELETE",params:r})}setBaseUrl(t){this.config.baseUrl=t}getBaseUrl(){return this.config.baseUrl}}let je=null;function Et(f){return je=new Ze(f),je}function Me(){if(!je)throw new Error("API client not initialized. Call initializeApiClient() first.");return je}function et(f,t,r={}){const{enabled:n=!0,refetchOnMount:i=!0,onSuccess:o,onError:l}=r,[v,u]=b.useState(null),[h,x]=b.useState(n),[j,O]=b.useState(null),D=b.useCallback(async()=>{if(n){x(!0),O(null);try{const C=await Me().get(f,t);u(C),o==null||o(C)}catch(P){const C=P instanceof Error?P:new Error("Unknown error");O(C),l==null||l(C)}finally{x(!1)}}},[f,JSON.stringify(t),n,o,l]);return b.useEffect(()=>{i&&D()},[D,i]),{data:v,loading:h,error:j,refetch:D}}function St(f,t="POST",r={}){const{onSuccess:n,onError:i}=r,[o,l]=b.useState(null),[v,u]=b.useState(!1),[h,x]=b.useState(null),j=b.useCallback(async D=>{u(!0),x(null);try{const P=Me();let C;switch(t){case"POST":C=await P.post(f,D);break;case"PUT":C=await P.put(f,D);break;case"PATCH":C=await P.patch(f,D);break;case"DELETE":C=await P.delete(f);break;default:throw new Error(`Unsupported method: ${t}`)}return l(C),n==null||n(C,D),C}catch(P){const C=P instanceof Error?P:new Error("Unknown error");return x(C),i==null||i(C,D),null}finally{u(!1)}},[f,t,n,i]),O=b.useCallback(()=>{l(null),x(null),u(!1)},[]);return{data:o,loading:v,error:h,mutate:j,reset:O}}var De={exports:{}},ce={};/**
1
+ "use strict";var wt=Object.defineProperty;var _t=(f,t,r)=>t in f?wt(f,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):f[t]=r;var be=(f,t,r)=>_t(f,typeof t!="symbol"?t+"":t,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("react");class jt{constructor(t){be(this,"config");if(!t.system_id)throw new Error("GraphClient: system_id is REQUIRED. No system_id → No start.");this.config=t}async callGraph(t,r={}){const n={coprocessor:"graph",method:t,system_id:this.config.system_id,params:r};try{const i=await fetch(`${this.config.baseUrl}/spu/invoke`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.getToken()}`},body:JSON.stringify(n)});if(!i.ok){const l=await i.text();throw new Error(`API error (${i.status}): ${l}`)}const o=await i.json();if(!o.success&&o.error)throw new Error(o.error);return o}catch(i){throw console.error("Graph API call failed:",i),i}}getToken(){return typeof window<"u"&&window.localStorage&&localStorage.getItem("qwanyx_token")||""}async getChildren(t){return(await this.callGraph("get_children",{parent_id:t})).result||[]}async getNode(t){return(await this.callGraph("get_node",{node_id:t})).result||null}async addNode(t){const r={...t,created:t.created||new Date().toISOString(),modified:new Date().toISOString()},n=await this.callGraph("add_node",r);if(!n.result)throw new Error("Failed to add node");return n.result}async updateNode(t,r){const n={node_id:t,updates:{...r,modified:new Date().toISOString()}},i=await this.callGraph("update_node",n);if(!i.result)throw new Error("Failed to update node");return i.result}async deleteNode(t,r=!1){return(await this.callGraph("delete_node",{node_id:t,cascade:r})).success===!0}async getChildrenWithEdges(t,r="children",n,i){return(await this.callGraph("get_children_with_edges",{node_id:t,display_mode:r,edge_type:n,node_type:i})).result||[]}async addEdge(t,r,n="link",i){return await this.callGraph("add_edge",{source_id:t,target_id:r,edge_type:n,data:i})}async deleteEdge(t,r,n){return await this.callGraph("delete_edge",{source_id:t,target_id:r,edge_type:n})}async getEdges(t,r,n){return(await this.callGraph("get_edges",{source_id:t,edge_type:r,target_id:n})).result||[]}async getAncestors(t){return(await this.callGraph("get_ancestors",{node_id:t})).ancestors||[]}async getTree(t,r=10){return(await this.callGraph("get_tree",{root_id:t,max_depth:r})).tree||[]}async moveNode(t,r){const n=await this.callGraph("move",{node_id:t,new_parent_id:r});if(!n.node)throw new Error("Failed to move node");return n.node}async searchNodes(t,r){return(await this.callGraph("search",{query:t,type:r})).nodes||[]}async getNodesByType(t){return(await this.callGraph("get_nodes_by_type",{type:t})).result||[]}}class Qe{constructor(t){be(this,"config");if(!t.system_id)throw new Error("MailClient: system_id is REQUIRED");this.config=t}async callMail(t,r={}){const n={coprocessor:"mail",method:t,system_id:this.config.system_id,params:{user_id:this.config.system_id,...r}};try{const i=await fetch(`${this.config.baseUrl}/spu/invoke`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.getToken()}`},body:JSON.stringify(n)});if(!i.ok){const l=await i.text();throw new Error(`API error (${i.status}): ${l}`)}const o=await i.json();if(!o.success&&o.error)throw new Error(o.error);return o.result}catch(i){throw console.error("Mail API call failed:",i),i}}getToken(){return typeof window<"u"&&window.localStorage&&localStorage.getItem("qwanyx_token")||""}async getSettings(){try{return await this.callMail("get_email_settings")||{accounts:[]}}catch{return{accounts:[]}}}async saveSettings(t){await this.callMail("save_email_settings",{accounts:t})}async listEmails(t,r=20){return this.callMail("list_emails",{account_id:t,limit:r})}async deleteEmails(t,r,n=!0){return this.callMail("delete_emails",{account_id:t,uids:r,expunge:n})}async imapExec(t,r){return this.callMail("imap_exec",{account_id:t,commands:r})}async trashEmails(t,r){const n=["SELECT INBOX",...r.map(l=>`UID MOVE ${l} [Gmail]/Trash`)],o=(await this.imapExec(t,n)).responses.filter(l=>l.ok&&l.command==="UID MOVE").length;return{success:o>0,moved:o}}async archiveEmails(t,r){const n=["SELECT INBOX",...r.map(l=>`UID STORE ${l} +FLAGS (\\Deleted)`),"EXPUNGE"],o=(await this.imapExec(t,n)).responses.filter(l=>l.ok&&l.command==="UID STORE").length;return{success:o>0,archived:o}}async listFolders(t){const n=(await this.imapExec(t,['LIST "" *'])).responses.find(i=>i.command==="LIST");return n!=null&&n.ok&&n.folders?{success:!0,folders:n.folders}:{success:!1,folders:[]}}}const Ae="qwanyx_auth_token",$e="qwanyx_refresh_token";class He{static setToken(t){typeof window<"u"&&localStorage.setItem(Ae,t)}static getToken(){return typeof window<"u"?localStorage.getItem(Ae):null}static clearToken(){typeof window<"u"&&(localStorage.removeItem(Ae),localStorage.removeItem($e))}static setRefreshToken(t){typeof window<"u"&&localStorage.setItem($e,t)}static getRefreshToken(){return typeof window<"u"?localStorage.getItem($e):null}static isAuthenticated(){return!!this.getToken()}static getAuthHeader(){const t=this.getToken();return t?{Authorization:`Bearer ${t}`}:{}}}class Ze{constructor(t){be(this,"config");this.config={timeout:3e4,headers:{"Content-Type":"application/json"},...t}}buildQueryString(t){if(!t||Object.keys(t).length===0)return"";const r=new URLSearchParams;Object.entries(t).forEach(([i,o])=>{o!=null&&r.append(i,String(o))});const n=r.toString();return n?`?${n}`:""}async request(t,r={}){const{method:n="GET",headers:i={},body:o,params:l}=r,v=`${this.config.baseUrl}/${t}${this.buildQueryString(l)}`,u={...this.config.headers,...He.getAuthHeader(),...i},h={method:n,headers:u};o&&n!=="GET"&&(h.body=JSON.stringify(o));try{const x=new AbortController,j=setTimeout(()=>x.abort(),this.config.timeout),O=await fetch(v,{...h,signal:x.signal});if(clearTimeout(j),!O.ok){const D=await O.json().catch(()=>({message:O.statusText}));throw new Error(D.message||`HTTP ${O.status}`)}return await O.json()}catch(x){throw x instanceof Error?x:new Error("An unexpected error occurred")}}async get(t,r){return this.request(t,{method:"GET",params:r})}async post(t,r,n){return this.request(t,{method:"POST",body:r,params:n})}async put(t,r,n){return this.request(t,{method:"PUT",body:r,params:n})}async patch(t,r,n){return this.request(t,{method:"PATCH",body:r,params:n})}async delete(t,r){return this.request(t,{method:"DELETE",params:r})}setBaseUrl(t){this.config.baseUrl=t}getBaseUrl(){return this.config.baseUrl}}let je=null;function Et(f){return je=new Ze(f),je}function Me(){if(!je)throw new Error("API client not initialized. Call initializeApiClient() first.");return je}function et(f,t,r={}){const{enabled:n=!0,refetchOnMount:i=!0,onSuccess:o,onError:l}=r,[v,u]=b.useState(null),[h,x]=b.useState(n),[j,O]=b.useState(null),D=b.useCallback(async()=>{if(n){x(!0),O(null);try{const C=await Me().get(f,t);u(C),o==null||o(C)}catch(P){const C=P instanceof Error?P:new Error("Unknown error");O(C),l==null||l(C)}finally{x(!1)}}},[f,JSON.stringify(t),n,o,l]);return b.useEffect(()=>{i&&D()},[D,i]),{data:v,loading:h,error:j,refetch:D}}function St(f,t="POST",r={}){const{onSuccess:n,onError:i}=r,[o,l]=b.useState(null),[v,u]=b.useState(!1),[h,x]=b.useState(null),j=b.useCallback(async D=>{u(!0),x(null);try{const P=Me();let C;switch(t){case"POST":C=await P.post(f,D);break;case"PUT":C=await P.put(f,D);break;case"PATCH":C=await P.patch(f,D);break;case"DELETE":C=await P.delete(f);break;default:throw new Error(`Unsupported method: ${t}`)}return l(C),n==null||n(C,D),C}catch(P){const C=P instanceof Error?P:new Error("Unknown error");return x(C),i==null||i(C,D),null}finally{u(!1)}},[f,t,n,i]),O=b.useCallback(()=>{l(null),x(null),u(!1)},[]);return{data:o,loading:v,error:h,mutate:j,reset:O}}var De={exports:{}},ce={};/**
2
2
  * @license React
3
3
  * react-jsx-runtime.production.min.js
4
4
  *
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @qwanyx/stack
3
+ *
4
+ * All-in-one data management system for React applications
5
+ * Combines Graph API + REST API + UI components
6
+ *
7
+ * Like HyperCard, but with React instead of HyperTalk
8
+ */
9
+ export { GraphClient } from './client/GraphClient';
10
+ export { MailClient } from './client/MailClient';
11
+ export { ApiClient, initializeApiClient, getApiClient } from './api/client';
12
+ export { AuthManager } from './auth';
13
+ export { useQuery } from './hooks/useQuery';
14
+ export type { UseQueryOptions, UseQueryResult } from './hooks/useQuery';
15
+ export { useMutation } from './hooks/useMutation';
16
+ export type { UseMutationOptions, UseMutationResult } from './hooks/useMutation';
17
+ export type { GraphNode, GraphEdge, GraphClientConfig, StackItem, StackConfig, StackContainerProps, StackContext, ViewContext, CardRenderer, ViewRenderer, Editor, EditorProps, StackTheme, LayoutMode, ApiConfig, ApiResponse, PaginationParams, SortParams, FilterParams, QueryParams, PaginatedResponse, HttpMethod, RequestOptions, MailClientConfig, MailServer, MailAccount, EmailMessage, MailListResult, ImapResponse, FolderInfo, APIResponse, } from './types';
18
+ export { Stack } from './components/Stack';
19
+ export type { StackProps } from './components/Stack';
20
+ export { Card } from './components/Card';
21
+ export type { CardProps } from './components/Card';
22
+ export { Detail } from './components/Detail';
23
+ export type { DetailProps } from './components/Detail';
24
+ export { AnimatedCardFlip } from './components/AnimatedCardFlip';
25
+ export type { AnimatedCardFlipProps, AnimatedCardFlipNode } from './components/AnimatedCardFlip';
26
+ export { Mail } from './components/Mail';
27
+ export type { MailProps, MailTheme, MailActions } from './components/Mail';
28
+ export { DataOperations } from './operations';
package/dist/index.esm.js CHANGED
@@ -135,11 +135,15 @@ class $t {
135
135
  }
136
136
  /**
137
137
  * Get edges from a source node
138
+ * @param sourceId - Source node ID (null for edges without source)
139
+ * @param edgeType - Optional edge type filter
140
+ * @param targetId - Optional target node ID for reverse lookups
138
141
  */
139
- async getEdges(t, r) {
142
+ async getEdges(t, r, n) {
140
143
  return (await this.callGraph("get_edges", {
141
144
  source_id: t,
142
- edge_type: r
145
+ edge_type: r,
146
+ target_id: n
143
147
  })).result || [];
144
148
  }
145
149
  // ===== HIERARCHY OPERATIONS =====
@@ -182,6 +186,14 @@ class $t {
182
186
  type: r
183
187
  })).nodes || [];
184
188
  }
189
+ /**
190
+ * Get all nodes of a specific type
191
+ */
192
+ async getNodesByType(t) {
193
+ return (await this.callGraph("get_nodes_by_type", {
194
+ type: t
195
+ })).result || [];
196
+ }
185
197
  }
186
198
  class Et {
187
199
  constructor(t) {
@@ -353,7 +365,7 @@ class Rt {
353
365
  return t ? { Authorization: `Bearer ${t}` } : {};
354
366
  }
355
367
  }
356
- class kt {
368
+ class Tt {
357
369
  constructor(t) {
358
370
  _e(this, "config");
359
371
  this.config = {
@@ -456,14 +468,14 @@ class kt {
456
468
  }
457
469
  let Ee = null;
458
470
  function Dt(f) {
459
- return Ee = new kt(f), Ee;
471
+ return Ee = new Tt(f), Ee;
460
472
  }
461
473
  function tt() {
462
474
  if (!Ee)
463
475
  throw new Error("API client not initialized. Call initializeApiClient() first.");
464
476
  return Ee;
465
477
  }
466
- function Tt(f, t, r = {}) {
478
+ function kt(f, t, r = {}) {
467
479
  const {
468
480
  enabled: n = !0,
469
481
  refetchOnMount: i = !0,
@@ -597,10 +609,10 @@ function Nt() {
597
609
  A.unshift("Warning: " + a), Function.prototype.apply.call(console[e], console, A);
598
610
  }
599
611
  }
600
- var V = !1, Q = !1, m = !1, T = !1, C = !1, p;
612
+ var V = !1, Q = !1, m = !1, k = !1, C = !1, p;
601
613
  p = Symbol.for("react.module.reference");
602
- function k(e) {
603
- return !!(typeof e == "string" || typeof e == "function" || e === n || e === o || C || e === i || e === h || e === x || T || e === $ || V || Q || m || typeof e == "object" && e !== null && (e.$$typeof === S || e.$$typeof === _ || e.$$typeof === l || e.$$typeof === v || e.$$typeof === d || // This needs to include all possible module reference object
614
+ function T(e) {
615
+ return !!(typeof e == "string" || typeof e == "function" || e === n || e === o || C || e === i || e === h || e === x || k || e === $ || V || Q || m || typeof e == "object" && e !== null && (e.$$typeof === S || e.$$typeof === _ || e.$$typeof === l || e.$$typeof === v || e.$$typeof === d || // This needs to include all possible module reference object
604
616
  // types supported by any Flight configuration anywhere since
605
617
  // we don't know which Flight build this will end up being used
606
618
  // with.
@@ -688,7 +700,7 @@ function Nt() {
688
700
  D++;
689
701
  }
690
702
  }
691
- function ke() {
703
+ function Te() {
692
704
  {
693
705
  if (D--, D === 0) {
694
706
  var e = {
@@ -739,8 +751,8 @@ function Nt() {
739
751
  }
740
752
  var le = !1, ne;
741
753
  {
742
- var Te = typeof WeakMap == "function" ? WeakMap : Map;
743
- ne = new Te();
754
+ var ke = typeof WeakMap == "function" ? WeakMap : Map;
755
+ ne = new ke();
744
756
  }
745
757
  function be(e, a) {
746
758
  if (!e || le)
@@ -808,7 +820,7 @@ function Nt() {
808
820
  }
809
821
  }
810
822
  } finally {
811
- le = !1, ie.current = A, ke(), Error.prepareStackTrace = O;
823
+ le = !1, ie.current = A, Te(), Error.prepareStackTrace = O;
812
824
  }
813
825
  var ae = e ? e.displayName || e.name : "", te = ae ? re(ae) : "";
814
826
  return typeof e == "function" && ne.set(e, te), te;
@@ -1107,7 +1119,7 @@ Check the top-level render call using <` + c + ">.");
1107
1119
  var Je = {};
1108
1120
  function Ke(e, a, c, y, O, A) {
1109
1121
  {
1110
- var R = k(e);
1122
+ var R = T(e);
1111
1123
  if (!R) {
1112
1124
  var w = "";
1113
1125
  (e === void 0 || typeof e == "object" && e !== null && Object.keys(e).length === 0) && (w += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.");
@@ -1263,15 +1275,15 @@ function It({
1263
1275
  onRefresh: $,
1264
1276
  theme: P = {}
1265
1277
  }) {
1266
- const { data: N, loading: H, error: z, refetch: g } = Tt(f, t), [I, V] = L(""), [Q, m] = L({}), [T, C] = L(1), p = et(() => {
1278
+ const { data: N, loading: H, error: z, refetch: g } = kt(f, t), [I, V] = L(""), [Q, m] = L({}), [k, C] = L(1), p = et(() => {
1267
1279
  if (!N) return { data: [], total: 0, totalPages: 0 };
1268
1280
  let E = N;
1269
- return v && I && d.length > 0 && (E = Fe.search(E, I, d)), Object.keys(Q).length > 0 && (E = Fe.filterByFields(E, Q)), Fe.paginate(E, T, _);
1270
- }, [N, I, Q, T, _, v, d]);
1281
+ return v && I && d.length > 0 && (E = Fe.search(E, I, d)), Object.keys(Q).length > 0 && (E = Fe.filterByFields(E, Q)), Fe.paginate(E, k, _);
1282
+ }, [N, I, Q, k, _, v, d]);
1271
1283
  fe(() => {
1272
1284
  C(1);
1273
1285
  }, [I, Q]);
1274
- const k = () => {
1286
+ const T = () => {
1275
1287
  V(""), m({}), C(1), g(), $ == null || $();
1276
1288
  }, j = {
1277
1289
  background: P.background || "#ffffff",
@@ -1303,7 +1315,7 @@ function It({
1303
1315
  /* @__PURE__ */ s.jsx(
1304
1316
  "button",
1305
1317
  {
1306
- onClick: k,
1318
+ onClick: T,
1307
1319
  style: {
1308
1320
  marginTop: "16px",
1309
1321
  padding: "8px 16px",
@@ -1340,7 +1352,7 @@ function It({
1340
1352
  /* @__PURE__ */ s.jsx(
1341
1353
  "button",
1342
1354
  {
1343
- onClick: k,
1355
+ onClick: T,
1344
1356
  style: {
1345
1357
  padding: "6px 12px",
1346
1358
  background: "transparent",
@@ -1411,7 +1423,7 @@ function It({
1411
1423
  }, children: [
1412
1424
  /* @__PURE__ */ s.jsxs("div", { style: { fontSize: "13px", color: j.textSecondary }, children: [
1413
1425
  "Page ",
1414
- T,
1426
+ k,
1415
1427
  " of ",
1416
1428
  p.totalPages
1417
1429
  ] }),
@@ -1420,14 +1432,14 @@ function It({
1420
1432
  "button",
1421
1433
  {
1422
1434
  onClick: () => C((E) => Math.max(1, E - 1)),
1423
- disabled: T === 1,
1435
+ disabled: k === 1,
1424
1436
  style: {
1425
1437
  padding: "6px 12px",
1426
1438
  border: `1px solid ${j.border}`,
1427
1439
  borderRadius: "6px",
1428
1440
  background: "white",
1429
- cursor: T === 1 ? "not-allowed" : "pointer",
1430
- opacity: T === 1 ? 0.5 : 1
1441
+ cursor: k === 1 ? "not-allowed" : "pointer",
1442
+ opacity: k === 1 ? 0.5 : 1
1431
1443
  },
1432
1444
  children: "Previous"
1433
1445
  }
@@ -1436,14 +1448,14 @@ function It({
1436
1448
  "button",
1437
1449
  {
1438
1450
  onClick: () => C((E) => Math.min(p.totalPages, E + 1)),
1439
- disabled: T === p.totalPages,
1451
+ disabled: k === p.totalPages,
1440
1452
  style: {
1441
1453
  padding: "6px 12px",
1442
1454
  border: `1px solid ${j.border}`,
1443
1455
  borderRadius: "6px",
1444
1456
  background: "white",
1445
- cursor: T === p.totalPages ? "not-allowed" : "pointer",
1446
- opacity: T === p.totalPages ? 0.5 : 1
1457
+ cursor: k === p.totalPages ? "not-allowed" : "pointer",
1458
+ opacity: k === p.totalPages ? 0.5 : 1
1447
1459
  },
1448
1460
  children: "Next"
1449
1461
  }
@@ -1555,49 +1567,49 @@ function Bt({
1555
1567
  className: l = ""
1556
1568
  }) {
1557
1569
  const v = Math.min(Math.max(t, 1), 5), [d, h] = L([]), [x, _] = L([]), [S, $] = L(Array(v).fill(!1)), [P, N] = L(Array(v).fill(!1)), H = Xe([]), z = X((m) => {
1558
- const T = f.filter((p) => !m.includes(p._id));
1559
- if (T.length === 0) return null;
1560
- const C = Math.floor(Math.random() * T.length);
1561
- return T[C];
1570
+ const k = f.filter((p) => !m.includes(p._id));
1571
+ if (k.length === 0) return null;
1572
+ const C = Math.floor(Math.random() * k.length);
1573
+ return k[C];
1562
1574
  }, [f]), g = X(() => Math.random() * (n - r) + r, [r, n]);
1563
1575
  fe(() => {
1564
1576
  if (f.length === 0) {
1565
1577
  h([]), _([]);
1566
1578
  return;
1567
1579
  }
1568
- const m = [], T = [], C = [];
1580
+ const m = [], k = [], C = [];
1569
1581
  for (let p = 0; p < v && p < f.length; p++) {
1570
- const k = z(C);
1571
- k && (m.push(k), C.push(k._id));
1582
+ const T = z(C);
1583
+ T && (m.push(T), C.push(T._id));
1572
1584
  }
1573
1585
  for (let p = 0; p < m.length; p++) {
1574
- const k = [
1586
+ const T = [
1575
1587
  m[p]._id,
1576
1588
  ...m.filter((Z, U) => U !== p).map((Z) => Z._id)
1577
- ], j = z(k);
1578
- j ? T.push(j) : T.push(m[p]);
1589
+ ], j = z(T);
1590
+ j ? k.push(j) : k.push(m[p]);
1579
1591
  }
1580
- h(m), _(T);
1592
+ h(m), _(k);
1581
1593
  }, [f, v, z]);
1582
1594
  const I = X((m) => {
1583
- const T = g(), C = setTimeout(() => {
1595
+ const k = g(), C = setTimeout(() => {
1584
1596
  $((p) => {
1585
- const k = [...p];
1586
- return k[m] = !k[m], k;
1597
+ const T = [...p];
1598
+ return T[m] = !T[m], T;
1587
1599
  }), setTimeout(() => {
1588
1600
  N((p) => {
1589
- const k = [...p];
1590
- return k[m] = !k[m], k;
1601
+ const T = [...p];
1602
+ return T[m] = !T[m], T;
1591
1603
  }), setTimeout(() => {
1592
1604
  const p = !P[m];
1593
- p && h((k) => {
1594
- const j = [...k];
1605
+ p && h((T) => {
1606
+ const j = [...T];
1595
1607
  return j[m] = x[m], j;
1596
- }), _((k) => {
1597
- const j = [...k], U = [
1608
+ }), _((T) => {
1609
+ const j = [...T], U = [
1598
1610
  (p ? x[m] : d[m])._id,
1599
1611
  ...d.filter((D, Y) => Y !== m).map((D) => D._id),
1600
- ...k.filter((D, Y) => Y !== m).map((D) => D._id)
1612
+ ...T.filter((D, Y) => Y !== m).map((D) => D._id)
1601
1613
  ], E = z(U);
1602
1614
  return E && (j[m] = E), j;
1603
1615
  }), setTimeout(() => {
@@ -1605,7 +1617,7 @@ function Bt({
1605
1617
  }, 150);
1606
1618
  }, 200);
1607
1619
  }, 150);
1608
- }, T);
1620
+ }, k);
1609
1621
  H.current[m] = C;
1610
1622
  }, [g, z, d, x, P]), V = Xe(!1);
1611
1623
  fe(() => {
@@ -1621,8 +1633,8 @@ function Bt({
1621
1633
  const Q = (m) => {
1622
1634
  i && i(m);
1623
1635
  };
1624
- return f.length === 0 ? /* @__PURE__ */ s.jsx("div", { className: `flex items-center justify-center p-8 ${l}`, children: /* @__PURE__ */ s.jsx("p", { className: "text-gray-500", children: "No nodes available" }) }) : d.length === 0 ? /* @__PURE__ */ s.jsx("div", { className: `flex items-center justify-center p-8 ${l}`, children: /* @__PURE__ */ s.jsx("p", { className: "text-gray-500", children: "Loading..." }) }) : /* @__PURE__ */ s.jsx("div", { className: `flex gap-4 justify-center items-center flex-wrap ${l}`, children: d.map((m, T) => {
1625
- const C = x[T], p = P[T];
1636
+ return f.length === 0 ? /* @__PURE__ */ s.jsx("div", { className: `flex items-center justify-center p-8 ${l}`, children: /* @__PURE__ */ s.jsx("p", { className: "text-gray-500", children: "No nodes available" }) }) : d.length === 0 ? /* @__PURE__ */ s.jsx("div", { className: `flex items-center justify-center p-8 ${l}`, children: /* @__PURE__ */ s.jsx("p", { className: "text-gray-500", children: "Loading..." }) }) : /* @__PURE__ */ s.jsx("div", { className: `flex gap-4 justify-center items-center flex-wrap ${l}`, children: d.map((m, k) => {
1637
+ const C = x[k], p = P[k];
1626
1638
  return /* @__PURE__ */ s.jsx(
1627
1639
  "div",
1628
1640
  {
@@ -1634,7 +1646,7 @@ function Bt({
1634
1646
  {
1635
1647
  className: "w-full h-full rounded-lg shadow-lg overflow-hidden cursor-pointer hover:shadow-xl",
1636
1648
  style: {
1637
- transform: `rotateY(${S[T] ? 180 : 0}deg)`,
1649
+ transform: `rotateY(${S[k] ? 180 : 0}deg)`,
1638
1650
  transition: "transform 0.5s",
1639
1651
  transformStyle: "preserve-3d"
1640
1652
  },
@@ -1650,7 +1662,7 @@ function Bt({
1650
1662
  "div",
1651
1663
  {
1652
1664
  style: {
1653
- transform: S[T] ? "scaleX(-1)" : "scaleX(1)",
1665
+ transform: S[k] ? "scaleX(-1)" : "scaleX(1)",
1654
1666
  width: "100%",
1655
1667
  height: "100%"
1656
1668
  },
@@ -1680,7 +1692,7 @@ function Bt({
1680
1692
  "div",
1681
1693
  {
1682
1694
  style: {
1683
- transform: S[T] ? "scaleX(-1)" : "scaleX(1)",
1695
+ transform: S[k] ? "scaleX(-1)" : "scaleX(1)",
1684
1696
  width: "100%",
1685
1697
  height: "100%"
1686
1698
  },
@@ -1703,7 +1715,7 @@ function Bt({
1703
1715
  }
1704
1716
  )
1705
1717
  },
1706
- `slot-${T}`
1718
+ `slot-${k}`
1707
1719
  );
1708
1720
  }) });
1709
1721
  }
@@ -1759,7 +1771,7 @@ function Lt({
1759
1771
  renderLoading: H,
1760
1772
  theme: z = {}
1761
1773
  }) {
1762
- const g = { ...Pt, ...z }, [I, V] = L([]), [Q, m] = L(!1), [T, C] = L(null), [p, k] = L(/* @__PURE__ */ new Set()), [j, Z] = L(null), [U, E] = L(null), D = et(() => t ? new Et({ baseUrl: f, system_id: t }) : null, [f, t]), Y = X(async () => {
1774
+ const g = { ...Pt, ...z }, [I, V] = L([]), [Q, m] = L(!1), [k, C] = L(null), [p, T] = L(/* @__PURE__ */ new Set()), [j, Z] = L(null), [U, E] = L(null), D = et(() => t ? new Et({ baseUrl: f, system_id: t }) : null, [f, t]), Y = X(async () => {
1763
1775
  if (D) {
1764
1776
  m(!0), C(null);
1765
1777
  try {
@@ -1788,9 +1800,9 @@ function Lt({
1788
1800
  const W = u.uid;
1789
1801
  if (B.shiftKey && j !== null) {
1790
1802
  const ee = Math.min(j, b), J = Math.max(j, b), Ne = I.slice(ee, J + 1).map((ce) => ce.uid), we = new Set(Ne);
1791
- k(we), h == null || h(Array.from(we));
1803
+ T(we), h == null || h(Array.from(we));
1792
1804
  } else if (B.ctrlKey || B.metaKey)
1793
- k((ee) => {
1805
+ T((ee) => {
1794
1806
  const J = new Set(ee);
1795
1807
  return J.has(W) ? J.delete(W) : J.add(W), h == null || h(Array.from(J)), J;
1796
1808
  }), Z(b);
@@ -1798,7 +1810,7 @@ function Lt({
1798
1810
  d == null || d(u), o && E(u);
1799
1811
  else {
1800
1812
  const ee = /* @__PURE__ */ new Set([W]);
1801
- k(ee), Z(b), h == null || h(Array.from(ee));
1813
+ T(ee), Z(b), h == null || h(Array.from(ee));
1802
1814
  }
1803
1815
  }, [i, j, I, p, d, h, o]), he = X(async () => {
1804
1816
  if (!(!D || p.size === 0))
@@ -1807,7 +1819,7 @@ function Lt({
1807
1819
  if (console.log("Trash result:", u), u.success && u.moved > 0) {
1808
1820
  V((B) => B.filter((W) => !p.has(W.uid)));
1809
1821
  const b = Array.from(p);
1810
- k(/* @__PURE__ */ new Set()), x == null || x(b);
1822
+ T(/* @__PURE__ */ new Set()), x == null || x(b);
1811
1823
  } else
1812
1824
  C("Failed to move emails to trash");
1813
1825
  } catch (u) {
@@ -1821,7 +1833,7 @@ function Lt({
1821
1833
  if (console.log("Archive result:", u), u.success && u.archived > 0) {
1822
1834
  V((B) => B.filter((W) => !p.has(W.uid)));
1823
1835
  const b = Array.from(p);
1824
- k(/* @__PURE__ */ new Set()), x == null || x(b);
1836
+ T(/* @__PURE__ */ new Set()), x == null || x(b);
1825
1837
  } else
1826
1838
  C("Failed to archive emails");
1827
1839
  } catch (u) {
@@ -1830,13 +1842,13 @@ function Lt({
1830
1842
  }
1831
1843
  }, [D, r, p, x, _]), ye = X(() => {
1832
1844
  if (p.size === I.length)
1833
- k(/* @__PURE__ */ new Set()), h == null || h([]);
1845
+ T(/* @__PURE__ */ new Set()), h == null || h([]);
1834
1846
  else {
1835
1847
  const u = new Set(I.map((b) => b.uid));
1836
- k(u), h == null || h(Array.from(u));
1848
+ T(u), h == null || h(Array.from(u));
1837
1849
  }
1838
1850
  }, [I, p.size, h]), xe = X(() => {
1839
- k(/* @__PURE__ */ new Set()), h == null || h([]);
1851
+ T(/* @__PURE__ */ new Set()), h == null || h([]);
1840
1852
  }, [h]), me = {
1841
1853
  delete: he,
1842
1854
  archive: ge,
@@ -1878,7 +1890,7 @@ function Lt({
1878
1890
  color: g.textSecondary,
1879
1891
  flexShrink: 0
1880
1892
  }, children: ve(u.date) })
1881
- ] }) }), ke = (u) => /* @__PURE__ */ s.jsxs("div", { style: { padding: "24px" }, children: [
1893
+ ] }) }), Te = (u) => /* @__PURE__ */ s.jsxs("div", { style: { padding: "24px" }, children: [
1882
1894
  /* @__PURE__ */ s.jsx("h2", { style: { margin: "0 0 8px", fontSize: "20px", color: g.text }, children: je(u.subject) || "(No subject)" }),
1883
1895
  /* @__PURE__ */ s.jsxs("div", { style: { fontSize: "14px", color: g.textSecondary, marginBottom: "16px" }, children: [
1884
1896
  "From: ",
@@ -1986,7 +1998,7 @@ function Lt({
1986
1998
  padding: "48px",
1987
1999
  textAlign: "center",
1988
2000
  color: g.textSecondary
1989
- }, children: "Loading..." }), le = S || Re, ne = $ || ke, Te = P || ie, be = N || oe, Se = H || re;
2001
+ }, children: "Loading..." }), le = S || Re, ne = $ || Te, ke = P || ie, be = N || oe, Se = H || re;
1990
2002
  return Q && I.length === 0 ? /* @__PURE__ */ s.jsx("div", { style: { background: g.background, width: "100%", height: "100%" }, children: Se() }) : /* @__PURE__ */ s.jsxs("div", { style: { display: "flex", background: g.background, width: "100%", height: "100%" }, children: [
1991
2003
  /* @__PURE__ */ s.jsxs("div", { style: {
1992
2004
  flex: o && U ? "0 0 50%" : "1",
@@ -1995,14 +2007,14 @@ function Lt({
1995
2007
  borderRight: o && U ? `1px solid ${g.border}` : "none",
1996
2008
  overflow: "hidden"
1997
2009
  }, children: [
1998
- i && Te(Array.from(p), me),
1999
- T && /* @__PURE__ */ s.jsx("div", { style: {
2010
+ i && ke(Array.from(p), me),
2011
+ k && /* @__PURE__ */ s.jsx("div", { style: {
2000
2012
  padding: "12px 16px",
2001
2013
  background: "#fef2f2",
2002
2014
  color: g.danger,
2003
2015
  fontSize: "14px",
2004
2016
  borderBottom: `1px solid ${g.border}`
2005
- }, children: T }),
2017
+ }, children: k }),
2006
2018
  /* @__PURE__ */ s.jsx("div", { style: { flex: 1, overflowY: "auto" }, children: I.length === 0 ? be() : I.map((u, b) => /* @__PURE__ */ s.jsx(
2007
2019
  "div",
2008
2020
  {
@@ -2017,7 +2029,7 @@ function Lt({
2017
2029
  }
2018
2030
  export {
2019
2031
  Bt as AnimatedCardFlip,
2020
- kt as ApiClient,
2032
+ Tt as ApiClient,
2021
2033
  Rt as AuthManager,
2022
2034
  Mt as Card,
2023
2035
  Fe as DataOperations,
@@ -2029,5 +2041,5 @@ export {
2029
2041
  tt as getApiClient,
2030
2042
  Dt as initializeApiClient,
2031
2043
  Ft as useMutation,
2032
- Tt as useQuery
2044
+ kt as useQuery
2033
2045
  };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Operations Layer
3
+ * Client-side data manipulation utilities
4
+ */
5
+ export declare class DataOperations {
6
+ /**
7
+ * Filter array by predicate function
8
+ */
9
+ static filter<T>(items: T[], predicate: (item: T) => boolean): T[];
10
+ /**
11
+ * Filter by field value
12
+ */
13
+ static filterBy<T>(items: T[], field: keyof T, value: any): T[];
14
+ /**
15
+ * Filter by multiple fields
16
+ */
17
+ static filterByFields<T>(items: T[], filters: Partial<T>): T[];
18
+ /**
19
+ * Sort array by field
20
+ */
21
+ static sort<T>(items: T[], field: keyof T, order?: 'asc' | 'desc'): T[];
22
+ /**
23
+ * Search items by query string in specified fields
24
+ */
25
+ static search<T>(items: T[], query: string, fields: (keyof T)[]): T[];
26
+ /**
27
+ * Paginate array
28
+ */
29
+ static paginate<T>(items: T[], page: number, limit: number): {
30
+ data: T[];
31
+ total: number;
32
+ page: number;
33
+ totalPages: number;
34
+ };
35
+ /**
36
+ * Group items by field value
37
+ */
38
+ static groupBy<T>(items: T[], field: keyof T): Record<string, T[]>;
39
+ /**
40
+ * Get unique values for a field
41
+ */
42
+ static unique<T>(items: T[], field: keyof T): any[];
43
+ /**
44
+ * Count items by field value
45
+ */
46
+ static countBy<T>(items: T[], field: keyof T): Record<string, number>;
47
+ /**
48
+ * Apply multiple operations in sequence
49
+ */
50
+ static pipe<T>(items: T[], operations: Array<(items: T[]) => T[]>): T[];
51
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * TypeScript type definitions for Qwanyx Stack
3
+ */
4
+ import { ReactNode } from 'react';
5
+ export interface GraphNode {
6
+ _id: string;
7
+ p?: string | null;
8
+ type: string;
9
+ title?: string;
10
+ description?: string;
11
+ icon?: string;
12
+ editor?: string;
13
+ hidden?: boolean;
14
+ archive?: boolean;
15
+ data?: Record<string, any>;
16
+ created?: string;
17
+ modified?: string;
18
+ creator?: string;
19
+ owner?: string;
20
+ owner_group?: string | null;
21
+ permissions?: number;
22
+ acl?: string;
23
+ }
24
+ export interface GraphEdge {
25
+ source_id: string | null;
26
+ target_id: string;
27
+ edge_type: string;
28
+ data?: Record<string, any>;
29
+ created: number;
30
+ modified?: number;
31
+ }
32
+ export type StackItem = GraphNode;
33
+ export interface StackContext {
34
+ apiUrl?: string;
35
+ systemId?: string;
36
+ onNavigate?: (item: StackItem) => void;
37
+ }
38
+ export interface ViewContext {
39
+ onBack?: () => void;
40
+ onEdit?: () => void;
41
+ onDelete?: () => void;
42
+ onOpenChat?: (nodeId: string) => void;
43
+ apiUrl?: string;
44
+ userId?: string;
45
+ graphClient?: any;
46
+ }
47
+ export type CardRenderer = (item: StackItem, context?: StackContext) => ReactNode;
48
+ export type ViewRenderer = (item: StackItem, context?: ViewContext) => ReactNode;
49
+ export interface EditorProps {
50
+ item?: StackItem;
51
+ graphClient: any;
52
+ onSave: (item: StackItem) => void;
53
+ onCancel: () => void;
54
+ onDelete?: () => void;
55
+ apiUrl?: string;
56
+ systemId?: string;
57
+ }
58
+ export type Editor = React.ComponentType<EditorProps>;
59
+ export interface StackConfig {
60
+ id: string;
61
+ name?: string;
62
+ system_id: string;
63
+ parentId?: string;
64
+ displayMode?: 'children' | 'edges' | 'both';
65
+ maxWidth?: string;
66
+ minWidth?: string;
67
+ cardRenderer?: CardRenderer;
68
+ viewRenderer?: ViewRenderer;
69
+ items?: StackItem[];
70
+ loadItems?: () => Promise<StackItem[]>;
71
+ metadata?: {
72
+ hidden?: boolean;
73
+ archive?: boolean;
74
+ originalNode?: any;
75
+ isSelected?: boolean;
76
+ };
77
+ onNavigate?: (item: StackItem) => void;
78
+ onAdd?: () => void;
79
+ onAddRequest?: (data: any) => Promise<void>;
80
+ systemMessage?: {
81
+ type: 'error' | 'warning' | 'info' | 'success';
82
+ message: string;
83
+ details?: string;
84
+ };
85
+ }
86
+ export interface StackProps {
87
+ config: StackConfig;
88
+ onItemClick?: (item: StackItem) => void;
89
+ onClose?: () => void;
90
+ onClick?: (event: React.MouseEvent) => void;
91
+ style?: React.CSSProperties;
92
+ currentView?: StackItem | null;
93
+ viewNode?: any;
94
+ onCloseView?: () => void;
95
+ onAdd?: () => void;
96
+ theme?: StackTheme;
97
+ apiUrl?: string;
98
+ userId?: string;
99
+ onCreateColumn?: (item: StackItem, direction: 'left' | 'right') => void;
100
+ }
101
+ export interface StackTheme {
102
+ primary?: string;
103
+ surface?: string;
104
+ surfaceHover?: string;
105
+ muted?: string;
106
+ text?: string;
107
+ textMuted?: string;
108
+ success?: string;
109
+ error?: string;
110
+ }
111
+ export interface StackContainerProps {
112
+ stacks: StackConfig[];
113
+ defaultViewMode?: 'side' | 'fullscreen' | 'modal';
114
+ maxStacks?: number;
115
+ stackSpacing?: string;
116
+ containerPadding?: string;
117
+ onStackClose?: (stackId: string) => void;
118
+ onStackOpen?: (config: StackConfig) => void;
119
+ style?: React.CSSProperties;
120
+ }
121
+ export type LayoutMode = 'list' | 'grid' | 'masonry';
122
+ export interface GraphClientConfig {
123
+ baseUrl: string;
124
+ system_id: string;
125
+ }
126
+ export interface APIResponse<T = any> {
127
+ success: boolean;
128
+ data?: T;
129
+ result?: T;
130
+ error?: string;
131
+ }
132
+ export interface ApiConfig {
133
+ baseUrl: string;
134
+ timeout?: number;
135
+ headers?: Record<string, string>;
136
+ }
137
+ export interface ApiResponse<T = any> {
138
+ data: T;
139
+ success: boolean;
140
+ message?: string;
141
+ error?: string;
142
+ }
143
+ export interface PaginationParams {
144
+ page?: number;
145
+ limit?: number;
146
+ offset?: number;
147
+ }
148
+ export interface SortParams {
149
+ sortBy?: string;
150
+ sortOrder?: 'asc' | 'desc';
151
+ }
152
+ export interface FilterParams {
153
+ [key: string]: any;
154
+ }
155
+ export interface QueryParams extends PaginationParams, SortParams, FilterParams {
156
+ }
157
+ export interface PaginatedResponse<T = any> {
158
+ data: T[];
159
+ total: number;
160
+ page: number;
161
+ limit: number;
162
+ totalPages: number;
163
+ }
164
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
165
+ export interface RequestOptions {
166
+ method?: HttpMethod;
167
+ headers?: Record<string, string>;
168
+ body?: any;
169
+ params?: QueryParams;
170
+ }
171
+ export interface MailClientConfig {
172
+ baseUrl: string;
173
+ system_id: string;
174
+ }
175
+ export interface MailServer {
176
+ host: string;
177
+ port: string;
178
+ user: string;
179
+ password: string;
180
+ }
181
+ export interface MailAccount {
182
+ id: string;
183
+ label: string;
184
+ firstName: string;
185
+ lastName: string;
186
+ email: string;
187
+ signature?: string;
188
+ imap: MailServer;
189
+ smtp: MailServer;
190
+ }
191
+ export interface EmailMessage {
192
+ uid: number;
193
+ subject: string;
194
+ from: string;
195
+ date: string;
196
+ seen: boolean;
197
+ }
198
+ export interface MailListResult {
199
+ account: {
200
+ id: string;
201
+ label: string;
202
+ email: string;
203
+ };
204
+ mailbox: {
205
+ name: string;
206
+ total: number;
207
+ };
208
+ messages: EmailMessage[];
209
+ }
210
+ export interface ImapResponse {
211
+ ok: boolean;
212
+ command: string;
213
+ error?: string;
214
+ mailbox?: string;
215
+ exists?: number;
216
+ recent?: number;
217
+ unseen?: number;
218
+ folders?: FolderInfo[];
219
+ uid?: string;
220
+ }
221
+ export interface FolderInfo {
222
+ name: string;
223
+ delimiter: string | null;
224
+ attributes: string;
225
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qwanyx/stack",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Modern HyperCard for React - All-in-one data management (REST + Graph API + Auth + Hooks + UI)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",