improved typing

This commit is contained in:
Seth Trowbridge 2025-08-03 22:03:10 -04:00
parent eb12efae50
commit 836c4bcea8
11 changed files with 147 additions and 192 deletions

View File

@ -104,6 +104,8 @@
"https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
"https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
"https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e",
"https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c" "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c",
"https://vanjs.org/code/van-1.5.5.nomodule.min.js": "32403d4dd6203a46513f000fd64b18e4eaef0823bf757ca0092e70130d059aa3",
"https://vanjs.org/code/van-x-0.6.3.nomodule.min.js": "e4b7de89bf2f84c22669ce7bcef592fb398d9dcf8b8a36f4562b8ac6354b1f2f"
} }
} }

3
dist/bundle_entry.js vendored Normal file

File diff suppressed because one or more lines are too long

103
dist/core.d.ts vendored
View File

@ -1,103 +0,0 @@
export {}
declare global
{
namespace Van {
interface State<T> {
val: T
readonly oldVal: T
readonly rawVal: T
}
// Defining readonly view of State<T> for covariance.
// Basically we want StateView<string> to implement StateView<string | number>
type StateView<T> = Readonly<State<T>>
type Val<T> = State<T> | T
type Primitive = string | number | boolean | bigint
// deno-lint-ignore no-explicit-any
type PropValue = Primitive | ((e: any) => void) | null
type PropValueOrDerived = PropValue | StateView<PropValue> | (() => PropValue)
type Props = Record<string, PropValueOrDerived> & { class?: PropValueOrDerived; is?: string }
type PropsWithKnownKeys<ElementType> = Partial<{[K in keyof ElementType]: PropValueOrDerived}>
type ValidChildDomValue = Primitive | Node | null | undefined
type BindingFunc = ((dom?: Node) => ValidChildDomValue) | ((dom?: Element) => Element)
type ChildDom = ValidChildDomValue | StateView<Primitive | null | undefined> | BindingFunc | readonly ChildDom[]
type TagFunc<Result> = (first?: Props & PropsWithKnownKeys<Result> | ChildDom, ...rest: readonly ChildDom[]) => Result
type Tags = Readonly<Record<string, TagFunc<Element>>> & {
[K in keyof HTMLElementTagNameMap]: TagFunc<HTMLElementTagNameMap[K]>
}
}
const van:{
readonly state: <T>(initVal: T, HMRKey?:string)=> Van.State<T>
readonly derive: <T>(f: () => T) => Van.State<T>
readonly add: (dom: Element, ...children: readonly Van.ChildDom[]) => Element
readonly tags: Van.Tags & ((namespaceURI: string) => Readonly<Record<string, Van.TagFunc<Element>>>)
readonly hydrate: <T extends Node>(dom: T, f: (dom: T) => T | null | undefined) => T
};
namespace VanX
{
type StateOf<T> = { readonly [K in keyof T]: Van.State<T[K]> }
type ValueType<T> = T extends (infer V)[] ? V : T[keyof T]
type KeyType<T> = T extends unknown[] ? number : string
type ReplacementFunc<T> =
T extends (infer V)[] ? (items: V[]) => readonly V[] :
(items: [string, T[keyof T]][]) => readonly [string, T[keyof T]][]
}
const vanX:{
readonly calc: <R>(f: () => R) => R
readonly reactive: <T extends object>(obj: T, HMRKey?:string) => T
readonly noreactive: <T extends object>(obj: T) => T
readonly stateFields: <T extends object>(obj: T) => VanX.StateOf<T>
readonly raw: <T extends object>(obj: T) => T
readonly list: <T extends object, ElementType extends Element>(
container: (() => ElementType) | ElementType,
items: T,itemFunc: (v: Van.State<VanX.ValueType<T>>, deleter: () => void, k: VanX.KeyType<T>) => Node
) => ElementType
readonly replace: <T extends object>(obj: T, replacement: VanX.ReplacementFunc<T> | T) => T
readonly compact: <T extends object>(obj: T) => T
// my addition
readonly Store: <T extends object>(obj:T, key:string)=>T
};
namespace Gale {
type KeyQuery = "@";
type KeyState = ":";
type KeyChild = ".";
type KeyGroup = "^";
type UserStyles = Partial<CSSStyleDeclaration> & {[key: `${KeyQuery|KeyState|KeyChild|KeyGroup}${string}`]: UserStyles }
type UserSheet = Record<string, UserStyles>
type CollectKeys<Obj> = {[Key in keyof Obj]: Obj[Key] extends object ? Key | CollectKeys<Obj[Key]> : Key }[keyof Obj]
type FilterKeys<Keys> = Keys extends `${KeyChild|KeyGroup}${infer Rest}` ? Keys : never
type CrossMultiply<A, B> = A extends string ? B extends string ? `${A}${B}` : never : never
type CrossMultiplyRecord<Rec> = keyof Rec | { [K in keyof Rec]: K extends string ? CrossMultiply<K, FilterKeys<CollectKeys<Rec[K]>>> : never }[keyof Rec]
type Tier = (selector:string, obj:UserStyles, suffix:string)=>string;
type CreateSheet = <T extends UserSheet>(sheet:UserSheet&T, hash?:string)=>{
Tag:(...args:CrossMultiplyRecord<T>[])=>string,
CSS:string,
DOM:Elemental<CrossMultiplyRecord<T>>,
Div:Circular<CrossMultiplyRecord<T>, Van.TagFunc<HTMLDivElement>>
}
type Elemental<T extends string> = {[K in keyof HTMLElementTagNameMap]: Van.TagFunc<HTMLElementTagNameMap[K]>&Circular<T, Van.TagFunc<HTMLElementTagNameMap[K]>>}
type Circular<Keys extends string, Func> = {
[K in Keys]: Circular<Keys, Func>&Func
};
}
const Gale:Gale.CreateSheet
}

3
dist/core.js vendored

File diff suppressed because one or more lines are too long

1
dist/hmr-NY37ZYM7.js vendored Normal file
View File

@ -0,0 +1 @@
var g=0,c={};function d(){for(let e in c)sessionStorage.setItem(e,c[e]);c={},g=0}function f(e,t){c[e]=t,g||(g=setTimeout(d,500)),console.log("SAVE",e,t)}function m(e){let t=sessionStorage.getItem(e);return console.log("LOAD",e,t),t}var S,u=0;function v(e){u=0,S=e}function h(){return S?S+"_"+u+++"_":""}var I=globalThis.van.state;globalThis.van.state=(e,t="")=>{let n=typeof e,o=l=>l,a=l=>l?.toString()||null;switch(n){case"boolean":o=l=>l==="true";break;case"number":o=parseFloat;break;case"object":o=JSON.parse,a=JSON.stringify;break}let r="HMR_"+h()+t,s=m(r),i=I(s?o(s):e);return van.derive(()=>f(r,a(i.val))),i};var w=globalThis.vanX.reactive;globalThis.vanX.reactive=(e,t)=>{v(t);let n=w(e);return v(),n};new WebSocket("ws://"+window.location.host+"/ws").addEventListener("message",e=>e.data==="reload"&&window.location.reload());vanX.Store=(e,t)=>{let n=JSON.stringify(e),o=localStorage.getItem(t+"check");localStorage.setItem(t+"check",n);let a;if(n==o){let s=localStorage.getItem(t);try{a=JSON.parse(s)||e}catch{a=e}}else a=e;let r=vanX.reactive(a);return van.derive(()=>localStorage.setItem(t,JSON.stringify(vanX.compact(r)))),r};

View File

@ -1,6 +1,5 @@
const bundle = await fetch(import.meta.resolve("../dist/core.js")).then(r=>r.text());
const index = await fetch(import.meta.resolve("../dist/index.html")).then(r=>r.text()); const index = await fetch(import.meta.resolve("../dist/index.html")).then(r=>r.text());
export const HTML = index.replace(`</head>`, `<script>${bundle}</script></head>`); export const HTML = index.replace(`</head>`, `<script type="module" src="/dist/bundle_entry.js?map&hmr"></script></head>`);
export const Root = import.meta.resolve("../"); export const Root = import.meta.resolve("../");
export const Load =async(file:string)=> await fetch(Root + file).then(resp=>resp.text()); export const Load =async(file:string)=> await fetch(Root + file).then(resp=>resp.text());
export const Save =async(text:string, name:string)=> await Deno.writeTextFile(name, text); export const Save =async(text:string, name:string)=> await Deno.writeTextFile(name, text);

View File

@ -7,8 +7,27 @@ globalThis.vanX = VanX;
import Gale from "../src/gale.js"; import Gale from "../src/gale.js";
globalThis.Gale = Gale; globalThis.Gale = Gale;
import BindHMR from "../src/hmr.js"; const args = new URL(import.meta.url).searchParams;
BindHMR();
import Boot from "../src/boot.js"; //Store
Boot(); vanX.Store=(e,t)=>{const a=localStorage.getItem(t),r=vanX.reactive(a?JSON.parse(a):e);return van.derive((()=>localStorage.setItem(t,JSON.stringify(vanX.compact(r))))),r};
if(args.has("hmr"))
{
await import("../src/hmr.js");
}
const root = args.get("root")||"/";
fetch(root+"deno.json")
.then(text=>text.json())
.then(json=>{
const Script=(t,e)=>{let n=document.createElement("script"); n.type=t; n.textContent=e; document.head.appendChild(n); return n;};
const imports = json.imports;
for(let n in imports)
{
const path=imports[n];
path.startsWith("./")&&(imports[n]=root+path.substring(2))
}
args.has("map") && Script("importmap",JSON.stringify({imports}));
Script("module").src=imports.entry;
});

View File

@ -5,8 +5,14 @@ const command = new Deno.Command("deno", {
"--platform=browser", "--platform=browser",
"--inline-imports=true", "--inline-imports=true",
"--output=dist/core.js", "--output=dist/core.js",
"--outdir=dist",
"--minify", "--minify",
"--code-splitting",
"scripts/bundle_entry.ts" "scripts/bundle_entry.ts"
] ]
}); });
command.outputSync(); const result = command.outputSync();
const textDecoder = new TextDecoder();
console.log("stdout:", textDecoder.decode(result.stdout));
console.log("stderr:", textDecoder.decode(result.stderr));

View File

@ -1,13 +0,0 @@
export default (root="/")=>fetch(root+"deno.json")
.then(text=>text.json())
.then(json=>{
const n=(t,e)=>{let n=document.createElement("script");n.type=t,n.textContent=e,document.head.appendChild(n)};
const imports = json.imports;
for(let n in imports)
{
const path=imports[n];
path.startsWith("./")&&(imports[n]=root+path.substring(2))
}
n("importmap",JSON.stringify({imports})),
n("module",'import "entry"; ')
})

View File

@ -47,75 +47,74 @@ function NextID()
return _ID ? _ID + "_" + (_index++) + "_" : ""; return _ID ? _ID + "_" + (_index++) + "_" : "";
} }
export default()=>{
//bind Van //bind Van
const origninalState = globalThis.van.state; const origninalState = globalThis.van.state;
globalThis.van.state =(value, key="")=> globalThis.van.state =(value, key="")=>
{
const type = typeof value;
let reader =d=>d;
let writer =d=>d?.toString() || null;
switch(type)
{ {
const type = typeof value; case "boolean" :
let reader =d=>d; reader =(data)=> data === "true"; break;
let writer =d=>d?.toString() || null; case "number" :
reader = parseFloat; break;
switch(type) case "object" :
{ reader = JSON.parse;
case "boolean" : writer = JSON.stringify;
reader =(data)=> data === "true"; break; break;
case "number" :
reader = parseFloat; break;
case "object" :
reader = JSON.parse;
writer = JSON.stringify;
break;
}
const fullKey = "HMR_" + NextID() + key;
const stringValue = Load(fullKey);
const signal = origninalState((stringValue ? reader(stringValue) : value));
van.derive(()=>Save(fullKey, writer(signal.val)));
return signal;
};
//bind VanX
const originalReactive = globalThis.vanX.reactive;
globalThis.vanX.reactive =(obj, id)=>
{
StartID(id);
const state = originalReactive(obj);
StartID();
return state;
} }
// added in devmode to index.html const fullKey = "HMR_" + NextID() + key;
new WebSocket('ws://'+window.location.host+'/ws').addEventListener('message',e=>e.data==='reload'&&window.location.reload()); const stringValue = Load(fullKey);
const signal = origninalState((stringValue ? reader(stringValue) : value));
van.derive(()=>Save(fullKey, writer(signal.val)));
vanX.Store =(obj, key)=> return signal;
{ };
let checkInit = JSON.stringify(obj);
let checkStore = localStorage.getItem(key+"check");
localStorage.setItem(key+"check", checkInit);
let recallJSON; //bind VanX
if(checkInit == checkStore) const originalReactive = globalThis.vanX.reactive;
{ globalThis.vanX.reactive =(obj, id)=>
let recallText = localStorage.getItem(key); {
try StartID(id);
{ const state = originalReactive(obj);
recallJSON = JSON.parse(recallText) || obj; StartID();
} return state;
catch(e) }
{
recallJSON = obj; // added in devmode to index.html
} new WebSocket('ws://'+window.location.host+'/ws').addEventListener('message',e=>e.data==='reload'&&window.location.reload());
}
else vanX.Store =(obj, key)=>
{ {
let checkInit = JSON.stringify(obj);
recallJSON = obj; let checkStore = localStorage.getItem(key+"check");
} localStorage.setItem(key+"check", checkInit);
const store = vanX.reactive( recallJSON ); let recallJSON;
van.derive(() => localStorage.setItem(key, JSON.stringify(vanX.compact(store)))); if(checkInit == checkStore)
return store; {
} let recallText = localStorage.getItem(key);
try
{
recallJSON = JSON.parse(recallText) || obj;
}
catch(e)
{
recallJSON = obj;
}
}
else
{
recallJSON = obj;
}
const store = vanX.reactive( recallJSON );
van.derive(() => localStorage.setItem(key, JSON.stringify(vanX.compact(store))));
return store;
} }

45
types.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
import type * as VAN from "https://vanjs.org/code/van-1.5.5.d.ts";
import type * as VANX from "https://vanjs.org/code/van-x-0.6.3.d.ts";
type Replace<T extends object, K extends keyof T, R> = Omit<T, K> & { readonly [P in K]: R };
declare module "vanjs-core" { export type State<T> = VAN.State<T> }
declare global
{
namespace Van { export type * from "https://vanjs.org/code/van-1.5.5.d.ts"; }
namespace VanX { export type * from "https://vanjs.org/code/van-x-0.6.3.d.ts"; }
const van: Replace<VAN.Van, "state", <T>(arg:T, HMRKey?:string)=>VAN.State<T>>
const vanX: Replace<typeof VANX, "reactive", <T extends object>(obj: T, HMRKey?:string) => T> & {Store:<T>(obj:T, key:string)=>T}
}
declare global {
namespace Gale {
type KeyQuery = "@";
type KeyState = ":";
type KeyChild = ".";
type KeyGroup = "^";
type UserStyles = Partial<CSSStyleDeclaration> & {[key: `${KeyQuery|KeyState|KeyChild|KeyGroup}${string}`]: UserStyles }
type UserSheet = Record<string, UserStyles>
type CollectKeys<Obj> = {[Key in keyof Obj]: Obj[Key] extends object ? Key | CollectKeys<Obj[Key]> : Key }[keyof Obj]
type FilterKeys<Keys> = Keys extends `${KeyChild|KeyGroup}${infer Rest}` ? Keys : never
type CrossMultiply<A, B> = A extends string ? B extends string ? `${A}${B}` : never : never
type CrossMultiplyRecord<Rec> = keyof Rec | { [K in keyof Rec]: K extends string ? CrossMultiply<K, FilterKeys<CollectKeys<Rec[K]>>> : never }[keyof Rec]
type Tier = (selector:string, obj:UserStyles, suffix:string)=>string;
type CreateSheet = <T extends UserSheet>(sheet:UserSheet&T, hash?:string)=>{
Tag:(...args:CrossMultiplyRecord<T>[])=>string,
CSS:string,
DOM:Elemental<CrossMultiplyRecord<T>>,
Div:Circular<CrossMultiplyRecord<T>, Van.TagFunc<HTMLDivElement>>
}
type Elemental<T extends string> = {[K in keyof HTMLElementTagNameMap]: Van.TagFunc<HTMLElementTagNameMap[K]>&Circular<T, Van.TagFunc<HTMLElementTagNameMap[K]>>}
type Circular<Keys extends string, Func> = {
[K in Keys]: Circular<Keys, Func>&Func
};
}
const Gale:Gale.CreateSheet
}