ras-stack 0.12.0 → 0.13.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/README.md CHANGED
@@ -208,6 +208,17 @@ await startTusUpload(upload)
208
208
 
209
209
  Applications retain ownership of email templates, missing-email behavior, upload metadata, authorization, quotas, and completion processing.
210
210
 
211
+ Stateful development servers can reuse one typed resource across HMR without application-specific `globalThis` casts:
212
+
213
+ ```ts
214
+ import { clearGlobalSingleton, globalAsyncSingleton } from 'ras-stack/server'
215
+
216
+ export const app = () => globalAsyncSingleton('my-app.instance', createApp)
217
+ export const resetApp = () => clearGlobalSingleton('my-app.instance', (instance) => instance.close())
218
+ ```
219
+
220
+ Rejected async initialization removes only its own pending value so a later request can retry. Clearing deletes the key before awaiting initialization or disposal, allowing replacement startup without reusing a closing resource.
221
+
211
222
  ## Shared project configuration
212
223
 
213
224
  Extend the supplied configuration and override anything specific to the application:
@@ -1,3 +1,4 @@
1
1
  export * from './canonical-host.js';
2
2
  export * from './health.js';
3
3
  export * from './rpc.js';
4
+ export * from './singleton.js';
@@ -1,4 +1,5 @@
1
1
  export * from './canonical-host.js';
2
2
  export * from './health.js';
3
3
  export * from './rpc.js';
4
+ export * from './singleton.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA","sourcesContent":["export * from './canonical-host.js'\nexport * from './health.js'\nexport * from './rpc.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA","sourcesContent":["export * from './canonical-host.js'\nexport * from './health.js'\nexport * from './rpc.js'\nexport * from './singleton.js'\n"]}
@@ -0,0 +1,4 @@
1
+ export declare function globalSingleton<T>(key: string, create: () => T): T;
2
+ export declare function globalAsyncSingleton<T>(key: string, create: () => T | Promise<T>): Promise<T>;
3
+ export declare function peekGlobalSingleton(key: string): unknown;
4
+ export declare function clearGlobalSingleton<T>(key: string, dispose?: (value: Awaited<T>) => void | Promise<void>): Promise<boolean>;
@@ -0,0 +1,48 @@
1
+ const storeKey = Symbol.for('ras-stack.server.singletons');
2
+ function store() {
3
+ const target = globalThis;
4
+ return (target[storeKey] ??= new Map());
5
+ }
6
+ export function globalSingleton(key, create) {
7
+ const singletons = store();
8
+ const existing = singletons.get(key);
9
+ if (existing) {
10
+ if (existing.kind !== 'sync')
11
+ throw new Error(`singleton ${key} is asynchronous`);
12
+ return existing.value;
13
+ }
14
+ const value = create();
15
+ singletons.set(key, { kind: 'sync', value });
16
+ return value;
17
+ }
18
+ export function globalAsyncSingleton(key, create) {
19
+ const singletons = store();
20
+ const existing = singletons.get(key);
21
+ if (existing) {
22
+ if (existing.kind !== 'async')
23
+ throw new Error(`singleton ${key} is synchronous`);
24
+ return existing.value;
25
+ }
26
+ const pending = Promise.resolve().then(create);
27
+ const guarded = pending.catch((error) => {
28
+ if (singletons.get(key)?.value === guarded)
29
+ singletons.delete(key);
30
+ throw error;
31
+ });
32
+ singletons.set(key, { kind: 'async', value: guarded });
33
+ return guarded;
34
+ }
35
+ export function peekGlobalSingleton(key) {
36
+ return store().get(key)?.value;
37
+ }
38
+ export async function clearGlobalSingleton(key, dispose) {
39
+ const singletons = store();
40
+ if (!singletons.has(key))
41
+ return false;
42
+ const value = singletons.get(key)?.value;
43
+ singletons.delete(key);
44
+ if (dispose)
45
+ await dispose(await value);
46
+ return true;
47
+ }
48
+ //# sourceMappingURL=singleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/server/singleton.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAA;AAI1D,SAAS,KAAK;IACZ,MAAM,MAAM,GAAG,UAA6B,CAAA;IAC5C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,GAAG,EAAE,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,eAAe,CAAI,GAAW,EAAE,MAAe;IAC7D,MAAM,UAAU,GAAG,KAAK,EAAE,CAAA;IAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACpC,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,kBAAkB,CAAC,CAAA;QACjF,OAAO,QAAQ,CAAC,KAAU,CAAA;IAC5B,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAA;IACtB,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5C,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,GAAW,EAAE,MAA4B;IAC/E,MAAM,UAAU,GAAG,KAAK,EAAE,CAAA;IAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACpC,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,iBAAiB,CAAC,CAAA;QACjF,OAAO,QAAQ,CAAC,KAAmB,CAAA;IACrC,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC/C,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,OAAO;YAAE,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAClE,MAAM,KAAK,CAAA;IACb,CAAC,CAAC,CAAA;IACF,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;IACtD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,OAAO,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAA;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAI,GAAW,EAAE,OAAqD;IAC9G,MAAM,UAAU,GAAG,KAAK,EAAE,CAAA;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAU,CAAA;IAC7C,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACtB,IAAI,OAAO;QAAE,MAAM,OAAO,CAAC,MAAM,KAAK,CAAC,CAAA;IACvC,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["const storeKey = Symbol.for('ras-stack.server.singletons')\ntype SingletonEntry = { kind: 'sync' | 'async'; value: unknown }\ntype SingletonGlobal = typeof globalThis & { [storeKey]?: Map<string, SingletonEntry> }\n\nfunction store() {\n const target = globalThis as SingletonGlobal\n return (target[storeKey] ??= new Map())\n}\n\nexport function globalSingleton<T>(key: string, create: () => T): T {\n const singletons = store()\n const existing = singletons.get(key)\n if (existing) {\n if (existing.kind !== 'sync') throw new Error(`singleton ${key} is asynchronous`)\n return existing.value as T\n }\n const value = create()\n singletons.set(key, { kind: 'sync', value })\n return value\n}\n\nexport function globalAsyncSingleton<T>(key: string, create: () => T | Promise<T>): Promise<T> {\n const singletons = store()\n const existing = singletons.get(key)\n if (existing) {\n if (existing.kind !== 'async') throw new Error(`singleton ${key} is synchronous`)\n return existing.value as Promise<T>\n }\n const pending = Promise.resolve().then(create)\n const guarded = pending.catch((error: unknown) => {\n if (singletons.get(key)?.value === guarded) singletons.delete(key)\n throw error\n })\n singletons.set(key, { kind: 'async', value: guarded })\n return guarded\n}\n\nexport function peekGlobalSingleton(key: string): unknown {\n return store().get(key)?.value\n}\n\nexport async function clearGlobalSingleton<T>(key: string, dispose?: (value: Awaited<T>) => void | Promise<void>) {\n const singletons = store()\n if (!singletons.has(key)) return false\n const value = singletons.get(key)?.value as T\n singletons.delete(key)\n if (dispose) await dispose(await value)\n return true\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ras-stack",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Composable full-stack primitives shared across Richard Solomou's applications.",
5
5
  "keywords": [
6
6
  "authentication",