lumix-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/store.js ADDED
@@ -0,0 +1,147 @@
1
+ import { signal, batch } from "./signals.js";
2
+ // ─── store() ───────────────────────────────────────────────
3
+ export function store(initialState) {
4
+ const initial = structuredClone(initialState);
5
+ const signals = new Map();
6
+ const listeners = new Set();
7
+ function getSignal(key) {
8
+ const k = key;
9
+ if (!signals.has(k)) {
10
+ signals.set(k, signal(initial[key]));
11
+ }
12
+ return signals.get(k);
13
+ }
14
+ function get(key) {
15
+ return getSignal(key)();
16
+ }
17
+ function set(key, value) {
18
+ getSignal(key)(value);
19
+ notifyListeners(key);
20
+ }
21
+ function update(partial) {
22
+ batch(() => {
23
+ for (const [key, value] of Object.entries(partial)) {
24
+ getSignal(key)(value);
25
+ }
26
+ });
27
+ for (const key of Object.keys(partial)) {
28
+ notifyListeners(key);
29
+ }
30
+ }
31
+ function reset() {
32
+ batch(() => {
33
+ for (const [key, value] of Object.entries(initial)) {
34
+ getSignal(key)(structuredClone(value));
35
+ }
36
+ });
37
+ notifyListeners();
38
+ }
39
+ function snapshot() {
40
+ const snap = {};
41
+ // Include all keys from initial state
42
+ for (const key of Object.keys(initial)) {
43
+ const sig = signals.get(key);
44
+ snap[key] = sig ? sig._peek() : initial[key];
45
+ }
46
+ return Object.freeze(snap);
47
+ }
48
+ function subscribe(fn) {
49
+ listeners.add(fn);
50
+ return () => listeners.delete(fn);
51
+ }
52
+ function notifyListeners(key) {
53
+ const state = snapshot();
54
+ for (const fn of listeners) {
55
+ fn(state, key);
56
+ }
57
+ }
58
+ // Reactive proxy
59
+ const stateProxy = new Proxy({}, {
60
+ get(_target, prop) {
61
+ return get(prop);
62
+ },
63
+ set(_target, prop, value) {
64
+ set(prop, value);
65
+ return true;
66
+ },
67
+ ownKeys() {
68
+ return Object.keys(initial);
69
+ },
70
+ getOwnPropertyDescriptor(_target, prop) {
71
+ if (prop in initial) {
72
+ return { configurable: true, enumerable: true, writable: true };
73
+ }
74
+ return undefined;
75
+ },
76
+ has(_target, prop) {
77
+ return prop in initial;
78
+ },
79
+ });
80
+ return {
81
+ get,
82
+ set,
83
+ update,
84
+ reset,
85
+ snapshot,
86
+ subscribe,
87
+ signal: getSignal,
88
+ state: stateProxy,
89
+ };
90
+ }
91
+ // ─── persist() ─────────────────────────────────────────────
92
+ export function persist(st, options) {
93
+ const { key, storage = typeof localStorage !== "undefined" ? localStorage : null, serialize = JSON.stringify, deserialize = JSON.parse, include, exclude, } = options;
94
+ if (!storage)
95
+ return () => { };
96
+ // Hydrate from storage on init
97
+ try {
98
+ const stored = storage.getItem(key);
99
+ if (stored) {
100
+ const parsed = deserialize(stored);
101
+ if (parsed && typeof parsed === "object") {
102
+ st.update(parsed);
103
+ }
104
+ }
105
+ }
106
+ catch {
107
+ // Ignore hydration errors — use initial state
108
+ }
109
+ // Persist on every change
110
+ const unsub = st.subscribe((state) => {
111
+ try {
112
+ let toSave = { ...state };
113
+ if (include) {
114
+ const filtered = {};
115
+ for (const k of include) {
116
+ if (k in toSave)
117
+ filtered[k] = toSave[k];
118
+ }
119
+ toSave = filtered;
120
+ }
121
+ if (exclude) {
122
+ for (const k of exclude) {
123
+ delete toSave[k];
124
+ }
125
+ }
126
+ storage.setItem(key, serialize(toSave));
127
+ }
128
+ catch {
129
+ // Ignore storage write errors
130
+ }
131
+ });
132
+ return unsub;
133
+ }
134
+ // ─── derived() ────────────────────────────────────────────
135
+ export function derived(st, fn) {
136
+ const inner = signal(fn(st.snapshot()));
137
+ // Re-derive whenever any signal in the store changes
138
+ const keys = Object.keys(st.snapshot());
139
+ const unsubs = [];
140
+ for (const key of keys) {
141
+ const sig = st.signal(key);
142
+ unsubs.push(sig._subscribe(() => {
143
+ inner(fn(st.snapshot()));
144
+ }));
145
+ }
146
+ return inner;
147
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "lumix-js",
3
+ "version": "0.1.0",
4
+ "description": "The modular runtime for LuminJS",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist/",
11
+ "bin/",
12
+ "templates/"
13
+ ],
14
+ "bin": {
15
+ "lumix": "./bin/lumix.js"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch"
20
+ },
21
+ "keywords": [
22
+ "lumix-js",
23
+ "runtime",
24
+ "signals",
25
+ "framework",
26
+ "framework-cli"
27
+ ],
28
+ "author": "ZMDev",
29
+ "license": "MIT",
30
+ "devDependencies": {
31
+ "happy-dom": "^20.6.1",
32
+ "typescript": "^5.0.0",
33
+ "@types/node": "^25.2.3",
34
+ "@types/prompts": "^2.4.9",
35
+ "@types/fs-extra": "^11.0.4"
36
+ },
37
+ "dependencies": {
38
+ "@types/bun": "^1.3.9",
39
+ "vite": "^7.3.1",
40
+ "commander": "^12.0.0",
41
+ "prompts": "^2.4.2",
42
+ "picocolors": "^1.0.0",
43
+ "bundle-require": "^5.0.0",
44
+ "vite-plugin-lumix": "workspace:*",
45
+ "fs-extra": "^11.2.0"
46
+ }
47
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "lumix-js";
2
+
3
+ export default defineConfig({
4
+ title: "LuminJS Blank App",
5
+ head: {
6
+ meta: [
7
+ { name: "description", content: "A beautiful app built with LuminJS" },
8
+ ],
9
+ },
10
+ });
@@ -0,0 +1,5 @@
1
+ import { hydrate } from "lumix-js";
2
+ import App from "./src/App.lumix";
3
+
4
+ const root = document.getElementById("app");
5
+ hydrate(root, App);
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "my-lumin-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "lumix dev",
8
+ "build": "lumix build",
9
+ "preview": "lumix preview"
10
+ },
11
+ "dependencies": {
12
+ "lumix-js": "latest"
13
+ },
14
+ "sideEffects": false
15
+ }
@@ -0,0 +1,28 @@
1
+ <script>
2
+ import { signal } from "lumix-js";
3
+ const message = signal("Hello LumixJS!");
4
+ </script>
5
+
6
+ <div class="container">
7
+ <h1>{message()}</h1>
8
+ <p>Edit <code>src/App.lumix</code> to get started.</p>
9
+ </div>
10
+
11
+ <style>
12
+ .container {
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ justify-content: center;
17
+ height: 100vh;
18
+ font-family: system-ui, -apple-system, sans-serif;
19
+ }
20
+ h1 {
21
+ color: #4f46e5;
22
+ }
23
+ code {
24
+ background: #f3f4f6;
25
+ padding: 0.2rem 0.4rem;
26
+ border-radius: 4px;
27
+ }
28
+ </style>
@@ -0,0 +1,4 @@
1
+ declare module "*.lumix" {
2
+ const component: any;
3
+ export default component;
4
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "lumix-js";
2
+
3
+ export default defineConfig({
4
+ title: "LumixJS App",
5
+ head: {
6
+ meta: [
7
+ { name: "description", content: "A beautiful app built with LumixJS" },
8
+ ],
9
+ },
10
+ });
@@ -0,0 +1,5 @@
1
+ import { hydrate } from "lumix-js";
2
+ import App from "./src/App.lumix";
3
+
4
+ const root = document.getElementById("app")!;
5
+ hydrate(root, App);
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "my-lumin-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "lumix dev",
8
+ "build": "lumix build",
9
+ "preview": "lumix preview"
10
+ },
11
+ "dependencies": {
12
+ "lumix-js": "latest"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "^5.0.0"
16
+ }
17
+ }
@@ -0,0 +1,28 @@
1
+ <script>
2
+ import { signal, type Signal } from "lumix-js";
3
+ const message: Signal<string> = signal("Hello LumixJS!");
4
+ </script>
5
+
6
+ <div class="container">
7
+ <h1>{message()}</h1>
8
+ <p>Edit <code>src/App.lumix</code> to get started.</p>
9
+ </div>
10
+
11
+ <style>
12
+ .container {
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ justify-content: center;
17
+ height: 100vh;
18
+ font-family: system-ui, -apple-system, sans-serif;
19
+ }
20
+ h1 {
21
+ color: #4f46e5;
22
+ }
23
+ code {
24
+ background: #f3f4f6;
25
+ padding: 0.2rem 0.4rem;
26
+ border-radius: 4px;
27
+ }
28
+ </style>
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "preserve"
14
+ },
15
+ "include": ["main.ts", "src/**/*", "lumix-env.d.ts"]
16
+ }