@africode/core 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
  2. package/LICENSE +623 -0
  3. package/README.md +442 -0
  4. package/bin/africode.js +73 -0
  5. package/bin/africode.js.1758507140 +343 -0
  6. package/bin/cli.ts +83 -0
  7. package/bin/create-africode.js +158 -0
  8. package/bin/scaffold.ts +219 -0
  9. package/components/accordion.js +183 -0
  10. package/components/alert.js +131 -0
  11. package/components/auth.js +172 -0
  12. package/components/avatar.js +117 -0
  13. package/components/badge.js +104 -0
  14. package/components/base.d.ts +139 -0
  15. package/components/base.js +184 -0
  16. package/components/button.js +164 -0
  17. package/components/card.js +137 -0
  18. package/components/cultural-card.js +243 -0
  19. package/components/divider.js +83 -0
  20. package/components/dropdown.js +171 -0
  21. package/components/error-boundary.js +155 -0
  22. package/components/form.js +131 -0
  23. package/components/grid.js +273 -0
  24. package/components/hero.js +138 -0
  25. package/components/icon.js +36 -0
  26. package/components/index.js +57 -0
  27. package/components/input.js +256 -0
  28. package/components/kanga-card.js +185 -0
  29. package/components/language-switcher.js +108 -0
  30. package/components/loader.js +80 -0
  31. package/components/modal.js +262 -0
  32. package/components/motion.js +84 -0
  33. package/components/navbar.js +236 -0
  34. package/components/pattern-showcase.js +225 -0
  35. package/components/progress.js +134 -0
  36. package/components/react.js +111 -0
  37. package/components/section.js +54 -0
  38. package/components/select.js +322 -0
  39. package/components/sidebar.js +180 -0
  40. package/components/skeleton.js +85 -0
  41. package/components/table.js +181 -0
  42. package/components/tabs.js +202 -0
  43. package/components/theme-toggle.js +82 -0
  44. package/components/toast.js +139 -0
  45. package/components/tooltip.js +167 -0
  46. package/core/a2ui-schema-manager.js +344 -0
  47. package/core/a2ui.js +431 -0
  48. package/core/bun-runtime.js +799 -0
  49. package/core/cli/commands/add.js +23 -0
  50. package/core/cli/commands/audit.js +58 -0
  51. package/core/cli/commands/build.js +137 -0
  52. package/core/cli/commands/create-plugin.js +241 -0
  53. package/core/cli/commands/dev.js +228 -0
  54. package/core/cli/commands/lint.js +23 -0
  55. package/core/cli/commands/test.js +34 -0
  56. package/core/cli/migrator.js +71 -0
  57. package/core/cli/ui.js +46 -0
  58. package/core/compliance.js +628 -0
  59. package/core/config.js +263 -0
  60. package/core/db-advanced.js +481 -0
  61. package/core/db.js +284 -0
  62. package/core/enhanced-hmr.js +404 -0
  63. package/core/errors.js +222 -0
  64. package/core/file-router.js +290 -0
  65. package/core/heartbeat.js +64 -0
  66. package/core/hmr-client.js +204 -0
  67. package/core/hmr.js +196 -0
  68. package/core/html.d.ts +116 -0
  69. package/core/html.js +160 -0
  70. package/core/hydration.js +52 -0
  71. package/core/lipa-namba-journey.js +572 -0
  72. package/core/motion.js +106 -0
  73. package/core/nida-cig-middleware.js +455 -0
  74. package/core/patterns.d.ts +124 -0
  75. package/core/patterns.js +833 -0
  76. package/core/plugins/index.js +312 -0
  77. package/core/router.js +387 -0
  78. package/core/sdk-client.js +62 -0
  79. package/core/sdk.d.ts +133 -0
  80. package/core/sdk.js +123 -0
  81. package/core/seo.js +76 -0
  82. package/core/server/auth-endpoints.js +339 -0
  83. package/core/server/auth.js +180 -0
  84. package/core/server/csrf.js +206 -0
  85. package/core/server/db.js +39 -0
  86. package/core/server/middleware.js +324 -0
  87. package/core/server/rate-limit.js +238 -0
  88. package/core/server/render.js +69 -0
  89. package/core/server/router.js +120 -0
  90. package/core/shim.js +28 -0
  91. package/core/state.d.ts +86 -0
  92. package/core/state.js +242 -0
  93. package/core/store.d.ts +122 -0
  94. package/core/store.js +61 -0
  95. package/core/validation.d.ts +233 -0
  96. package/core/validation.js +590 -0
  97. package/core/websocket.js +639 -0
  98. package/dist/africode.js +2905 -0
  99. package/dist/africode.js.map +61 -0
  100. package/dist/build-info.json +23 -0
  101. package/dist/components.js +2888 -0
  102. package/dist/components.js.map +58 -0
  103. package/dist/styles/africanity.css +322 -0
  104. package/dist/styles/typography.css +141 -0
  105. package/docs/IDE-Guide.md +50 -0
  106. package/package.json +110 -0
  107. package/src/index.ts +196 -0
  108. package/styles/africanity.css +322 -0
  109. package/styles/typography.css +141 -0
  110. package/templates/starter/.env.example +15 -0
  111. package/templates/starter/africode.config.js +40 -0
  112. package/templates/starter/package.json +14 -0
  113. package/templates/starter/src/pages/index.html +46 -0
  114. package/templates/starter/src/pages/index.js +32 -0
  115. package/templates/starter/src/styles/main.css +4 -0
  116. package/templates/starter-3d/.env.example +7 -0
  117. package/templates/starter-3d/africode.config.js +29 -0
  118. package/templates/starter-3d/components/af-model-viewer.js +125 -0
  119. package/templates/starter-3d/package.json +15 -0
  120. package/templates/starter-3d/src/pages/index.html +46 -0
  121. package/templates/starter-3d/src/pages/index.js +50 -0
  122. package/templates/starter-3d/src/styles/main.css +4 -0
  123. package/templates/starter-react/.env.example +15 -0
  124. package/templates/starter-react/africode.config.js +40 -0
  125. package/templates/starter-react/package.json +16 -0
  126. package/templates/starter-react/src/pages/index.html +46 -0
  127. package/templates/starter-react/src/pages/index.js +68 -0
  128. package/templates/starter-react/src/styles/main.css +4 -0
  129. package/templates/starter-tailwind/.env.example +15 -0
  130. package/templates/starter-tailwind/africode.config.js +40 -0
  131. package/templates/starter-tailwind/package.json +20 -0
  132. package/templates/starter-tailwind/src/pages/index.html +46 -0
  133. package/templates/starter-tailwind/src/pages/index.js +37 -0
  134. package/templates/starter-tailwind/src/styles/main.css +4 -0
  135. package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
  136. package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * AfriCode Rate Limiting Middleware
3
+ *
4
+ * In-memory sliding window rate limiter for API endpoints.
5
+ * Framework-level middleware — protect by default.
6
+ *
7
+ * Algorithm: Fixed window with sliding expiry
8
+ * - Each client (identified by IP) gets a counter per window
9
+ * - When the window expires, the counter resets
10
+ * - If the counter exceeds the limit, requests are rejected with 429
11
+ *
12
+ * Limitations:
13
+ * - In-memory only (resets on server restart)
14
+ * - Single-process only (not shared across workers/instances)
15
+ * - For production multi-instance environments, use Redis-backed rate limiting
16
+ *
17
+ * Future: This interface is designed so a Redis/KV adapter can be
18
+ * swapped in without changing the middleware API.
19
+ *
20
+ * @module core/server/rate-limit
21
+ */
22
+
23
+ import { RateLimitError } from '../errors.js';
24
+ import { emitRateLimit } from '../config.js';
25
+
26
+ /**
27
+ * @typedef {Object} RateLimitConfig
28
+ * @property {number} [windowMs=60000] - Time window in milliseconds (default: 1 minute)
29
+ * @property {number} [maxRequests=60] - Maximum requests per window (default: 60)
30
+ * @property {string} [message='Too many requests'] - Error message
31
+ * @property {Function} [keyGenerator] - Custom key generator: (request) => string
32
+ * @property {string[]} [excludePaths=[]] - Paths that skip rate limiting
33
+ * @property {Function} [onLimitReached] - Callback when limit is reached: (key, request) => void
34
+ * @property {Object} [headers=true] - Include rate limit headers in response
35
+ */
36
+
37
+ /**
38
+ * In-memory rate limit store.
39
+ * Maps client keys to their request tracking data.
40
+ *
41
+ * @type {Map<string, { count: number, resetAt: number }>}
42
+ */
43
+ const store = new Map();
44
+
45
+ /**
46
+ * Cleanup interval reference.
47
+ * Periodically removes expired entries to prevent memory leaks.
48
+ */
49
+ let cleanupInterval = null;
50
+
51
+ /**
52
+ * Get the client identifier from a request.
53
+ * Default: uses IP address from various headers.
54
+ *
55
+ * @param {Request} request
56
+ * @returns {string} Client identifier
57
+ */
58
+ function defaultKeyGenerator(request) {
59
+ // Check standard headers in priority order
60
+ const forwarded = request.headers.get('X-Forwarded-For');
61
+ if (forwarded) {
62
+ // X-Forwarded-For can contain multiple IPs, take the first (client)
63
+ return forwarded.split(',')[0].trim();
64
+ }
65
+
66
+ const realIp = request.headers.get('X-Real-IP');
67
+ if (realIp) {return realIp;}
68
+
69
+ // Fallback: use the request URL host (not ideal, but safe fallback)
70
+ try {
71
+ const url = new URL(request.url);
72
+ return url.hostname;
73
+ } catch {
74
+ return 'unknown';
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Create a rate limiting middleware.
80
+ *
81
+ * @param {RateLimitConfig} [config] - Configuration
82
+ * @returns {Function} Middleware function: (request) => { response: Response|null, headers: Record<string, string> }
83
+ *
84
+ * @example
85
+ * // Basic usage — 60 requests per minute
86
+ * const limiter = createRateLimiter();
87
+ *
88
+ * // In your request handler:
89
+ * const { response, headers } = limiter(request);
90
+ * if (response) return response; // 429 Too Many Requests
91
+ * // Add headers to your success response for transparency
92
+ *
93
+ * @example
94
+ * // Custom limits for auth endpoints
95
+ * const authLimiter = createRateLimiter({
96
+ * windowMs: 15 * 60 * 1000, // 15 minutes
97
+ * maxRequests: 5, // 5 attempts
98
+ * message: 'Too many login attempts. Please try again later.',
99
+ * onLimitReached: (key) => console.warn(`Rate limit reached for ${key}`)
100
+ * });
101
+ *
102
+ * @example
103
+ * // Skip rate limiting for certain paths
104
+ * const limiter = createRateLimiter({
105
+ * excludePaths: ['/api/health', '/api/status']
106
+ * });
107
+ */
108
+ export function createRateLimiter(config = {}) {
109
+ const {
110
+ windowMs = 60 * 1000, // 1 minute
111
+ maxRequests = 60, // 60 requests per window
112
+ message = 'Too many requests. Please try again later.',
113
+ keyGenerator = defaultKeyGenerator,
114
+ excludePaths = [],
115
+ onLimitReached = null
116
+ } = config;
117
+
118
+ // Start cleanup interval if not already running
119
+ if (!cleanupInterval) {
120
+ cleanupInterval = setInterval(() => {
121
+ const now = Date.now();
122
+ for (const [key, data] of store.entries()) {
123
+ if (now >= data.resetAt) {
124
+ store.delete(key);
125
+ }
126
+ }
127
+ }, windowMs);
128
+
129
+ // Don't keep the process alive just for cleanup
130
+ if (cleanupInterval.unref) {
131
+ cleanupInterval.unref();
132
+ }
133
+ }
134
+
135
+ /**
136
+ * @param {Request} request
137
+ * @returns {{ response: Response|null, headers: Record<string, string> }}
138
+ */
139
+ return function rateLimitMiddleware(request) {
140
+ // Check excluded paths
141
+ try {
142
+ const url = new URL(request.url);
143
+ if (excludePaths.some(path => url.pathname.startsWith(path))) {
144
+ return { response: null, headers: {} };
145
+ }
146
+ } catch {
147
+ // Invalid URL, continue with rate limiting
148
+ }
149
+
150
+ const key = keyGenerator(request);
151
+ const now = Date.now();
152
+
153
+ // Get or create entry
154
+ let entry = store.get(key);
155
+
156
+ if (!entry || now >= entry.resetAt) {
157
+ // Window expired or new client — reset
158
+ entry = { count: 0, resetAt: now + windowMs };
159
+ store.set(key, entry);
160
+ }
161
+
162
+ // Increment count
163
+ entry.count++;
164
+
165
+ // Build rate limit headers (always returned for transparency)
166
+ const remaining = Math.max(0, maxRequests - entry.count);
167
+ const resetAtSeconds = Math.ceil(entry.resetAt / 1000);
168
+ const headers = {
169
+ 'X-RateLimit-Limit': String(maxRequests),
170
+ 'X-RateLimit-Remaining': String(remaining),
171
+ 'X-RateLimit-Reset': String(resetAtSeconds)
172
+ };
173
+
174
+ // Check if over limit
175
+ if (entry.count > maxRequests) {
176
+ const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
177
+
178
+ // Emit hook
179
+ emitRateLimit(key, request);
180
+
181
+ // Call user callback if provided
182
+ if (onLimitReached) {
183
+ onLimitReached(key, request);
184
+ }
185
+
186
+ const error = new RateLimitError(retryAfter, message);
187
+
188
+ return {
189
+ response: new Response(
190
+ JSON.stringify(error.toJSON()),
191
+ {
192
+ status: 429,
193
+ headers: {
194
+ 'Content-Type': 'application/json',
195
+ 'Retry-After': String(retryAfter),
196
+ ...headers
197
+ }
198
+ }
199
+ ),
200
+ headers
201
+ };
202
+ }
203
+
204
+ // Under limit — pass through
205
+ return { response: null, headers };
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Reset the rate limit store.
211
+ * Useful for testing or administrative purposes.
212
+ */
213
+ export function resetRateLimitStore() {
214
+ store.clear();
215
+ }
216
+
217
+ /**
218
+ * Get the current store size (number of tracked clients).
219
+ * Useful for monitoring.
220
+ *
221
+ * @returns {number}
222
+ */
223
+ export function getRateLimitStoreSize() {
224
+ return store.size;
225
+ }
226
+
227
+ /**
228
+ * Stop the cleanup interval.
229
+ * Call this during server shutdown.
230
+ */
231
+ export function stopRateLimitCleanup() {
232
+ if (cleanupInterval) {
233
+ clearInterval(cleanupInterval);
234
+ cleanupInterval = null;
235
+ }
236
+ }
237
+
238
+ export default { createRateLimiter, resetRateLimitStore, getRateLimitStoreSize, stopRateLimitCleanup };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * AfriCode SSR Engine
3
+ * Implements Declarative Shadow DOM (DSD) for instant loading.
4
+ *
5
+ * "The primary requirement... is the optimization of the rendering pipeline." - Blueprint
6
+ */
7
+
8
+ import { file } from "bun";
9
+
10
+ // Registry of component definitions for SSR
11
+ // In a real framework, this would parse the component class
12
+ // For v1, we map tags to their Shadow DOM HTML
13
+ const componentRegistry = {
14
+ 'af-navbar': (attrs) => `
15
+ <template shadowrootmode="open">
16
+ <style>
17
+ :host { display: block; font-family: 'Inter', system-ui; }
18
+ nav { background: #1EB53A; padding: 16px; color: white; display: flex; justify-content: space-between; }
19
+ .logo { font-weight: 800; font-size: 1.2rem; }
20
+ </style>
21
+ <nav>
22
+ <div class="logo">${attrs.logo || 'AfriCode'}</div>
23
+ <div class="links"><slot></slot></div>
24
+ </nav>
25
+ </template>
26
+ `,
27
+ 'af-card': (attrs) => `
28
+ <template shadowrootmode="open">
29
+ <style>
30
+ :host { display: block; background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; color: white; }
31
+ </style>
32
+ <slot></slot>
33
+ </template>
34
+ `
35
+ };
36
+
37
+ // Bun-specific imports moved inside functions to allow browser-side evaluation without crashes
38
+ export async function renderPage(filePath) {
39
+ const { file } = await import("bun");
40
+ const fileRef = file(filePath);
41
+ let html = await fileRef.text();
42
+
43
+ // 1. SSR: Inject Declarative Shadow DOM
44
+ // Matches <af-tag ...></af-tag>
45
+ // Regex is simple for v1, real parser needed for production
46
+ for (const [tag, renderFn] of Object.entries(componentRegistry)) {
47
+ const regex = new RegExp(`<${tag}([^>]*)>`, 'g');
48
+
49
+ html = html.replace(regex, (match, attrString) => {
50
+ // Parse attributes
51
+ const attrs = {};
52
+ attrString.replace(/(\w+)="([^"]*)"/g, (m, key, value) => {
53
+ attrs[key] = value;
54
+ });
55
+
56
+ // Return tag + DSD Template
57
+ return `<${tag}${attrString}>${renderFn(attrs)}`;
58
+ });
59
+ }
60
+
61
+ // 2. SEO: Inject Meta Tags if missing
62
+ if (!html.includes('<meta name="generator"')) {
63
+ html = html.replace('</head>', '<meta name="generator" content="AfriCode v1.0 SSR"></head>');
64
+ }
65
+
66
+ return new Response(html, {
67
+ headers: { 'Content-Type': 'text/html' }
68
+ });
69
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * AfriCode Server-Side Router
3
+ *
4
+ * Handles dynamic API routing by mapping URLs to the file system.
5
+ * Edge-Ready: Uses strictly standard Request/Response objects.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ // Cache routes in memory for performance
12
+ const routeCache = new Map();
13
+
14
+ /**
15
+ * Discovers API routes in the /pages/api directory
16
+ * Note: In a pure Edge environment (Cloudflare), this would be replaced
17
+ * by a build-time manifest injection.
18
+ */
19
+ async function loadRoutes() {
20
+ const apiDir = path.join(process.cwd(), 'pages', 'api');
21
+
22
+ if (!fs.existsSync(apiDir)) {return;}
23
+
24
+ const files = fs.readdirSync(apiDir);
25
+
26
+ for (const file of files) {
27
+ if (file.endsWith('.js')) {
28
+ const routeName = '/api/' + file.replace('.js', '');
29
+ const modulePath = path.join(apiDir, file);
30
+ routeCache.set(routeName, modulePath);
31
+ console.log(`✓ Registered Route: ${routeName}`);
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * HTTP Method Handler
38
+ * @param {object} module - The imported API module
39
+ * @param {Request} req - The incoming request
40
+ * @returns {Promise<Response>}
41
+ */
42
+ async function executeHandler(module, req) {
43
+ const method = req.method.toUpperCase();
44
+
45
+ if (module[method]) {
46
+ return await module[method](req);
47
+ } else if (module.default) {
48
+ return await module.default(req);
49
+ }
50
+
51
+ return new Response('Method Not Allowed', { status: 405 });
52
+ }
53
+
54
+ /**
55
+ * Router Handler
56
+ * @param {Request} req
57
+ * @returns {Promise<Response|null>}
58
+ */
59
+ export async function handleApiRequest(req) {
60
+ const url = new URL(req.url);
61
+ const pathname = url.pathname;
62
+
63
+ // 0. Handle built-in auth endpoints
64
+ if (pathname.startsWith('/api/auth/')) {
65
+ try {
66
+ const { handleAuthRequest } = await import('./auth-endpoints.js');
67
+ const authResponse = await handleAuthRequest(req, pathname);
68
+ if (authResponse) {
69
+ return authResponse;
70
+ }
71
+ } catch (err) {
72
+ console.error(`Auth endpoint error at ${pathname}:`, err);
73
+ return new Response(JSON.stringify({ error: err.message }), {
74
+ status: 500,
75
+ headers: { 'Content-Type': 'application/json' }
76
+ });
77
+ }
78
+ }
79
+
80
+ // Load routes lazily
81
+ if (routeCache.size === 0) {
82
+ await loadRoutes();
83
+ }
84
+
85
+ // 1. Exact Match
86
+ let modulePath = routeCache.get(pathname);
87
+
88
+ // 2. Dynamic Match (Basic implementation)
89
+ // If no exact match, look for dynamic routes [id].js?
90
+ if (!modulePath) {
91
+ for (const [route, path] of routeCache.entries()) {
92
+ if (route.includes('[') && matchDynamicRoute(route, pathname)) {
93
+ modulePath = path;
94
+ // We would inject params into request here
95
+ break;
96
+ }
97
+ }
98
+ }
99
+
100
+ if (modulePath) {
101
+ try {
102
+ // Import the module
103
+ const module = await import(modulePath);
104
+ return await executeHandler(module, req);
105
+ } catch (err) {
106
+ console.error(`Status 500 at ${pathname}:`, err);
107
+ return new Response(JSON.stringify({ error: err.message }), {
108
+ status: 500,
109
+ headers: { 'Content-Type': 'application/json' }
110
+ });
111
+ }
112
+ }
113
+
114
+ return null; // Not captured -> 404 handled by server.js
115
+ }
116
+
117
+ function matchDynamicRoute(routePattern, actualPath) {
118
+ // Placeholder logic for future expansion
119
+ return false;
120
+ }
package/core/shim.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * AfriCode SSR DOM Shim
3
+ * Allows Web Components to be imported in Node/Bun environment.
4
+ */
5
+
6
+ if (typeof globalThis.HTMLElement === 'undefined') {
7
+ globalThis.HTMLElement = class HTMLElement { };
8
+ globalThis.customElements = { define: () => { }, get: () => { } };
9
+ globalThis.document = {
10
+ createElement: () => ({
11
+ classList: { add: () => { } },
12
+ setAttribute: () => { },
13
+ style: {}
14
+ }),
15
+ head: { appendChild: () => { } },
16
+ body: { appendChild: () => { } }
17
+ };
18
+ globalThis.window = globalThis;
19
+ globalThis.CSSStyleSheet = class CSSStyleSheet { replaceSync() { } };
20
+ }
21
+
22
+ // Global scope support for certain libraries
23
+ if (typeof global !== 'undefined') {
24
+ global.HTMLElement = globalThis.HTMLElement;
25
+ global.customElements = globalThis.customElements;
26
+ global.document = globalThis.document;
27
+ global.window = globalThis;
28
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * AfriCode State Management - TypeScript Definitions
3
+ * Reactive state, signals, and effects
4
+ */
5
+
6
+ /**
7
+ * Creates a reactive state object with Proxy-based reactivity
8
+ * @template T - The shape of the state object
9
+ * @param initialState - Initial state values
10
+ * @returns Reactive proxy of the state
11
+ */
12
+ export function createReactiveState<T extends Record<string, any>>(
13
+ initialState: T
14
+ ): T & {
15
+ [K in keyof T]: T[K];
16
+ };
17
+
18
+ /**
19
+ * Subscriber callback type
20
+ */
21
+ export type Subscriber<T extends Record<string, any>> = (
22
+ newValue: T,
23
+ oldValue?: T
24
+ ) => void;
25
+
26
+ /**
27
+ * Subscribe to state changes
28
+ * @param state - Reactive state object to subscribe to
29
+ * @param callback - Called when state changes
30
+ * @returns Unsubscribe function
31
+ */
32
+ export function subscribe<T extends Record<string, any>>(
33
+ state: T,
34
+ callback: Subscriber<T>
35
+ ): () => void;
36
+
37
+ /**
38
+ * Signal getter function
39
+ */
40
+ export type SignalGetter<T> = () => T;
41
+
42
+ /**
43
+ * Signal setter function - accepts value or updater function
44
+ */
45
+ export type SignalSetter<T> = (value: T | ((prev: T) => T)) => void;
46
+
47
+ /**
48
+ * Signal tuple return type
49
+ */
50
+ export type Signal<T> = [getter: SignalGetter<T>, setter: SignalSetter<T>];
51
+
52
+ /**
53
+ * Create a reactive signal with getter and setter
54
+ * @template T - Signal value type
55
+ * @param initialValue - Initial signal value
56
+ * @returns Tuple of [getter, setter]
57
+ */
58
+ export function createSignal<T>(initialValue: T): Signal<T>;
59
+
60
+ /**
61
+ * Effect callback - can optionally return cleanup function
62
+ */
63
+ export type EffectCallback = () => void | (() => void);
64
+
65
+ /**
66
+ * Effect cleanup function
67
+ */
68
+ export type EffectCleanup = () => void;
69
+
70
+ /**
71
+ * Create an effect that runs side effects
72
+ * Effects track dependencies from signals and state
73
+ * @param effect - Effect function to run
74
+ * @returns Cleanup function
75
+ */
76
+ export function createEffect(effect: EffectCallback): EffectCleanup;
77
+
78
+ /**
79
+ * Internal subscriber map (for advanced use)
80
+ */
81
+ export const subscribers: WeakMap<object, Set<Subscriber<any>>>;
82
+
83
+ /**
84
+ * Internal signal registry (for advanced use)
85
+ */
86
+ export const signals: WeakMap<object, any>;