Compare commits
	
		
			4 commits
		
	
	
		
			530a89755c
			...
			f20b5da8e4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f20b5da8e4 | ||
|  | f0456337d5 | ||
|  | 0fca81dc93 | ||
|  | bffa896184 | 
					 10 changed files with 166 additions and 55 deletions
				
			
		
							
								
								
									
										34
									
								
								.forgejo/workflows/checkpr.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.forgejo/workflows/checkpr.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
|     types: [opened, synchronize, reopened] | ||||
| 
 | ||||
| jobs: | ||||
|   depoly: | ||||
|     runs-on: fedora-41 | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
| 
 | ||||
|       - name: Setup Bun | ||||
|         uses: oven-sh/setup-bun@v2 | ||||
|         with: | ||||
|           bun-version-file: 'package.json' | ||||
| 
 | ||||
|       - name: Cache Dependencies | ||||
|         id: dependencies-cache | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: ~/.bun/install/cache | ||||
|           key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-bun- | ||||
| 
 | ||||
|       - name: Install Dependencies | ||||
|         run: bun install | ||||
| 
 | ||||
|       - name: Validate types | ||||
|         run: bun typecheck | ||||
| 
 | ||||
|       - name: Build Dist | ||||
|         run: VITE_CODE_VERSION=$GITHUB_SHA bun dist | ||||
|  | @ -30,6 +30,9 @@ jobs: | |||
|       - name: Install Dependencies | ||||
|         run: bun install | ||||
| 
 | ||||
|       - name: Validate types | ||||
|         run: bun typecheck | ||||
| 
 | ||||
|       - name: Build Dist (Staging) | ||||
|         run: VITE_CODE_VERSION=$GITHUB_SHA bun dist -m staging | ||||
|         if: env.GITHUB_REF_NAME == 'master' | ||||
|  |  | |||
|  | @ -9,7 +9,8 @@ | |||
|     "dev": "vite --host 0.0.0.0", | ||||
|     "preview": "vite preview", | ||||
|     "dist": "vite build", | ||||
|     "count-source-lines": "exec scripts/src-lc.sh" | ||||
|     "count-source-lines": "exec scripts/src-lc.sh", | ||||
|     "typecheck": "tsc --noEmit --skipLibCheck" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": "Rubicon", | ||||
|  |  | |||
|  | @ -15,3 +15,24 @@ if (typeof window.crypto.randomUUID === "undefined") { | |||
|       ) as `${string}-${string}-${string}-${string}-${string}`; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| if (typeof Promise.withResolvers === "undefined") { | ||||
|   // Chrome/Edge 119, Firefox 121, Safari/iOS 17.4
 | ||||
| 
 | ||||
|   // Promise.withResolvers is generic and works with subclasses - the typescript built-in decl
 | ||||
|   // could not handle the subclassing case.
 | ||||
|   Promise.withResolvers = function <T>(this: AnyPromiseConstructor<T>) { | ||||
|     let resolve!: PromiseWithResolvers<T>["resolve"], reject!: PromiseWithResolvers<T>["reject"]; | ||||
|     // These variables are expected to be set after `new this()`
 | ||||
| 
 | ||||
|     const promise = new this((resolve0, reject0) => { | ||||
|       resolve = resolve0; | ||||
|       reject = reject0; | ||||
|     }) | ||||
| 
 | ||||
|     return { | ||||
|       promise, resolve, reject | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -4,23 +4,27 @@ import { dispatchCall, isJSONRPCCall, type Call } from "./workerrpc"; | |||
| 
 | ||||
| function isServiceWorker( | ||||
|   self: WorkerGlobalScope, | ||||
|   // @ts-ignore: workaround for workbox logger.d.ts decl
 | ||||
| ): self is ServiceWorkerGlobalScope { | ||||
|   return !!(self as unknown as ServiceWorkerGlobalScope).registration; | ||||
|   return ( | ||||
|     (self as unknown as Record<string, unknown>)["serviceWorker"] instanceof | ||||
|     ServiceWorker | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| if (isServiceWorker(self)) { | ||||
|   cleanupOutdatedCaches(); | ||||
|   precacheAndRoute(self.__WB_MANIFEST, { | ||||
|     cleanURLs: false, | ||||
|   }); | ||||
| 
 | ||||
|   // auto update
 | ||||
|   self.skipWaiting(); | ||||
|   clientsClaim(); | ||||
| } else { | ||||
| if (!isServiceWorker(self)) { | ||||
|   throw new TypeError("This entry point must be run in a service worker"); | ||||
| } | ||||
| 
 | ||||
| cleanupOutdatedCaches(); | ||||
| precacheAndRoute(self.__WB_MANIFEST, { | ||||
|   cleanURLs: false, | ||||
| }); | ||||
| 
 | ||||
| // auto update
 | ||||
| self.skipWaiting(); | ||||
| clientsClaim(); | ||||
| 
 | ||||
| export const Service = { | ||||
|   ping() {}, | ||||
| }; | ||||
|  | @ -29,6 +33,9 @@ self.addEventListener("message", (event: MessageEvent<unknown>) => { | |||
|   const payload = event.data; | ||||
|   if (typeof payload !== "object") return; | ||||
|   if (isJSONRPCCall(payload as Record<string, unknown>)) { | ||||
|     dispatchCall(Service, event as MessageEvent<Call<unknown>>); | ||||
|     dispatchCall( | ||||
|       Service, | ||||
|       event as MessageEvent<Call<unknown>> & { source: MessageEventSource }, | ||||
|     ); | ||||
|   } | ||||
| }); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| { | ||||
|   "compilerOptions": { | ||||
|     "lib": ["ESNext", "WebWorker"], | ||||
|     "lib": ["WebWorker", "ESNext"], | ||||
|   }, | ||||
|   "extends": ["../../tsconfig.super.json"], | ||||
|   "include": ["./**/*.ts"], | ||||
| } | ||||
|  | @ -46,7 +46,7 @@ export type Result<T, E> = JSONRPC & { id: string | number } & ( | |||
| export function isJSONRPCResult( | ||||
|   object: Record<string, unknown>, | ||||
| ): object is Result<unknown, unknown> { | ||||
|   return object["jsonrpc"] === "2.0" && object["id"] && !object["method"]; | ||||
|   return !!(object["jsonrpc"] === "2.0" && object["id"] && !object["method"]); | ||||
| } | ||||
| 
 | ||||
| export function isJSONRPCCall( | ||||
|  | @ -114,6 +114,9 @@ export class ResultDispatcher { | |||
|     { | ||||
|       const callback = this.map.get(id); | ||||
|       if (!callback) return; | ||||
|       // Now the callback is not undefined
 | ||||
| 
 | ||||
|       // Fast path
 | ||||
|       if (typeof callback !== "boolean") { | ||||
|         callback(message); | ||||
|         this.map.delete(id); | ||||
|  | @ -121,15 +124,16 @@ export class ResultDispatcher { | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return new Promise<void>((resolve) => { | ||||
|     const { promise, resolve } = Promise.withResolvers<undefined>(); | ||||
| 
 | ||||
|     let retried = 0; | ||||
| 
 | ||||
|     const checkAndDispatch = () => { | ||||
|       const callback = this.map.get(id); | ||||
|       if (typeof callback !== "boolean") { | ||||
|           callback(message); | ||||
|         callback!(message); // the nullability is already checked before
 | ||||
|         this.map.delete(id); | ||||
|           resolve(); | ||||
|         resolve(undefined); | ||||
|         return; | ||||
|       } | ||||
|       setTimeout(checkAndDispatch, 0); | ||||
|  | @ -142,7 +146,8 @@ export class ResultDispatcher { | |||
| 
 | ||||
|     // start the loop
 | ||||
|     checkAndDispatch(); | ||||
|     }); | ||||
| 
 | ||||
|     return promise; | ||||
|   } | ||||
| 
 | ||||
|   createTypedCall< | ||||
|  | @ -159,14 +164,16 @@ export class ResultDispatcher { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| type AnyService = Record<string, ((...args: unknown[]) => unknown) | undefined> | ||||
| type AnyService = Record<string, (...args: unknown[]) => unknown>; | ||||
| 
 | ||||
| export async function dispatchCall< | ||||
|   S extends AnyService, | ||||
| >(service: S, event: MessageEvent<Call<unknown>>) { | ||||
| export async function dispatchCall<S extends Partial<AnyService>>( | ||||
|   service: S, | ||||
|   event: MessageEvent<Call<unknown>> & { source: MessageEventSource }, | ||||
| ) { | ||||
|   try { | ||||
|     const fn = service[event.data.method]; | ||||
|     if (!fn) { | ||||
|       console.warn("requested unknown method", event.data.method, event.data); | ||||
|       if (event.data.id) | ||||
|         return event.source.postMessage({ | ||||
|           jsonrpc: "2.0", | ||||
|  | @ -176,10 +183,12 @@ export async function dispatchCall< | |||
|             message: "Method not found", | ||||
|           }, | ||||
|         } as Result<void, void>); | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       const result = await fn(...event.data.params as unknown[]); | ||||
|       const result = await fn(...(event.data.params as unknown[])); | ||||
| 
 | ||||
|       if (!event.data.id) return; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,20 +1,11 @@ | |||
| { | ||||
|   "compilerOptions": { | ||||
|     "strict": true, | ||||
|     "target": "ESNext", | ||||
|     "module": "esnext", | ||||
|     "moduleResolution": "node", | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "esModuleInterop": true, | ||||
|     "jsx": "preserve", | ||||
|     "jsxImportSource": "solid-js", | ||||
|     "types": ["vite/client", "vite-plugin-pwa/solid"], | ||||
|     "noEmit": true, | ||||
|     "isolatedModules": true, | ||||
|     "resolveJsonModule": true, | ||||
|     "paths": { | ||||
|       "~platform/*": ["./src/platform/*"], | ||||
|       "~material/*": ["./src/material/*"] | ||||
|     } | ||||
|   } | ||||
|     "lib": ["ESNext", "DOM", "DOM.Iterable"] | ||||
|   }, | ||||
|   "include": ["./src/**/*.ts", "./src/**/*.tsx", "./*.ts", "./types/**.ts"], | ||||
|   "exclude": ["./src/serviceworker/**"], | ||||
|   "extends": ["./tsconfig.super.json"] | ||||
| } | ||||
|  |  | |||
							
								
								
									
										17
									
								
								tsconfig.super.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tsconfig.super.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| { | ||||
|   "compilerOptions": { | ||||
|     "strict": true, | ||||
|     "target": "ESNext", | ||||
|     "module": "esnext", | ||||
|     "moduleResolution": "node", | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "esModuleInterop": true, | ||||
|     "noEmit": true, | ||||
|     "isolatedModules": true, | ||||
|     "resolveJsonModule": true, | ||||
|     "paths": { | ||||
|       "~platform/*": ["./src/platform/*"], | ||||
|       "~material/*": ["./src/material/*"] | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								types/lib.esnext.promise.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								types/lib.esnext.promise.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| interface AnyPromiseWithResolvers<T, Instance> { | ||||
|   promise: Instance; | ||||
|   resolve: (value: T | PromiseLike<T>) => void; | ||||
|   reject: (reason?: any) => void; | ||||
| } | ||||
| 
 | ||||
| type AnyPromiseConstructor<T> = new ( | ||||
|   executor: ( | ||||
|     resolve: PromiseWithResolvers<T>["resolve"], | ||||
|     reject: PromiseWithResolvers<T>["reject"], | ||||
|   ) => void, | ||||
| ) => Promise<T>; | ||||
| 
 | ||||
| interface PromiseConstructor { | ||||
|   /** | ||||
|    * Creates a new Promise and returns it in an object, along with its resolve and reject functions. | ||||
|    * @returns An object with the properties `promise`, `resolve`, and `reject`. | ||||
|    * | ||||
|    * ```ts
 | ||||
|    * const { promise, resolve, reject } = Promise.withResolvers<T>(); | ||||
|    * ``` | ||||
|    */ | ||||
|   withResolvers<T, K extends AnyPromiseConstructor<T>>( | ||||
|     this: K, | ||||
|   ): AnyPromiseWithResolvers<T, InstanceType<K>>; | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue