@iadev93/zuno-express 0.0.13 → 0.0.14

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
@@ -57,7 +57,7 @@ Each call creates isolated server state by default. Pass `server` when several r
57
57
  The optional `authorize` hook runs before SSE/snapshot reads and mutation writes. Returning `false` produces a `403 FORBIDDEN` response without reading or mutating server state.
58
58
 
59
59
  #### `sse` (GET)
60
- Handles persistent SSE connections, heartbeats, and initial synchronization.
60
+ Handles persistent SSE connections, heartbeats, and initial synchronization. Reconnecting clients receive retained events after their last event ID; if that replay range is incomplete, the handler sends an authoritative snapshot carrying the current event ID. Pending writes are bounded by the selected server state's `maxSubscriberBuffer` option, and slow subscribers reconnect to recover.
61
61
 
62
62
  #### `sync` (POST)
63
63
  Validates and applies incoming state events.
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var server=require('@iadev93/zuno/server');function x(u){let{headers:i={},server:o=server.createZunoServerState(),authorize:r=()=>true}=u??{},s=e=>e.status(403).json({ok:false,reason:"FORBIDDEN"}),a={sse:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}server.createSSEConnection(e,n,i,o);},sync:async(e,n)=>{let p=e.body;if(!await r({action:"write",request:e,event:p})){s(n);return}let t=server.applyStateEvent(p,o);if(!t.ok){n.status(t.reason==="VERSION_CONFLICT"?409:400).json({ok:false,reason:t.reason,...t.reason==="VERSION_CONFLICT"?{current:t.current}:{errors:t.errors}});return}n.status(200).json({ok:true,event:t.event});},snapshot:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}server.sendSnapshot(e,n,o);}};return {...a,server:o,mount:(e,n="/zuno")=>{e.get(`${n}/sse`,a.sse),e.get(`${n}/snapshot`,a.snapshot),e.post(`${n}/sync`,a.sync);}}}function S(u,i){let{basePath:o="/zuno",...r}=i??{},s=x(r);return s.mount(u,o),s}exports.createZunoExpress=x;exports.mountZuno=S;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["createZunoExpress","opts","headers","server","createZunoServerState","authorize","forbidden","res","handlers","req","createSSEConnection","incoming","result","applyStateEvent","sendSnapshot","app","basePath","mountZuno","rest","zuno"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { IncomingHttpHeaders } from \"node:http\";\nimport type { ZunoStateEvent } from \"@iadev93/zuno\";\nimport {\n\tapplyStateEvent,\n\tcreateSSEConnection,\n\tcreateZunoServerState,\n\tsendSnapshot,\n\ttype ZunoServerState,\n} from \"@iadev93/zuno/server\";\nimport type { Request, Response } from \"express\";\n\nexport type ZunoExpressAuthorizationContext = {\n\taction: \"read\" | \"write\";\n\trequest: Request;\n\tevent?: unknown;\n};\n\n/**\n * Options for creating an Express router for Zuno.\n */\nexport type CreateZunoExpressOptions = {\n\t/** Optional custom headers to be sent with the SSE response. */\n\theaders?: IncomingHttpHeaders;\n\t/** Isolated authoritative server state. A new instance is created by default. */\n\tserver?: ZunoServerState;\n\t/** Provider-agnostic authorization hook for snapshots, SSE, and mutations. */\n\tauthorize?: (\n\t\tcontext: ZunoExpressAuthorizationContext,\n\t) => boolean | Promise<boolean>;\n};\n\n/**\n * Creates a Zuno Express instance.\n * Returns both granular handlers and a convenience `mount` helper.\n */\nexport function createZunoExpress(opts?: CreateZunoExpressOptions) {\n\tconst {\n\t\theaders = {},\n\t\tserver = createZunoServerState(),\n\t\tauthorize = () => true,\n\t} = opts ?? {};\n\tconst forbidden = (res: Response) =>\n\t\tres.status(403).json({ ok: false, reason: \"FORBIDDEN\" });\n\n\t/**\n\t * Granular handlers for maximum control.\n\t * You can mount these manually to any route or wrap them in custom middleware.\n\t */\n\tconst handlers = {\n\t\t/**\n\t\t * SSE connection handler.\n\t\t * Usage: app.get('/custom/sse', zuno.sse);\n\t\t */\n\t\tsse: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcreateSSEConnection(req, res, headers, server);\n\t\t},\n\n\t\t/**\n\t\t * Sync POST handler.\n\t\t * Usage: app.post('/custom/sync', zuno.sync);\n\t\t */\n\t\tsync: async (req: Request, res: Response) => {\n\t\t\tconst incoming = req.body as ZunoStateEvent;\n\t\t\tif (\n\t\t\t\t!(await authorize({ action: \"write\", request: req, event: incoming }))\n\t\t\t) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = applyStateEvent(incoming, server);\n\n\t\t\tif (!result.ok) {\n\t\t\t\tres.status(result.reason === \"VERSION_CONFLICT\" ? 409 : 400).json({\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: result.reason,\n\t\t\t\t\t...(result.reason === \"VERSION_CONFLICT\"\n\t\t\t\t\t\t? { current: result.current }\n\t\t\t\t\t\t: { errors: result.errors }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.status(200).json({ ok: true, event: result.event });\n\t\t},\n\n\t\t/**\n\t\t * Snapshot GET handler.\n\t\t * Usage: app.get('/custom/snapshot', zuno.snapshot);\n\t\t */\n\t\tsnapshot: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendSnapshot(req, res, server);\n\t\t},\n\t};\n\n\treturn {\n\t\t...handlers,\n\t\tserver,\n\t\t/**\n\t\t * Optional convenience method to mount all Zuno handlers at once.\n\t\t * @param app The Express App or Router to mount the handlers on.\n\t\t * @param basePath The base path for the Zuno routes (defaults to \"/zuno\").\n\t\t */\n\t\tmount: (\n\t\t\tapp: {\n\t\t\t\tget: (...args: unknown[]) => unknown;\n\t\t\t\tpost: (...args: unknown[]) => unknown;\n\t\t\t},\n\t\t\tbasePath = \"/zuno\",\n\t\t) => {\n\t\t\tapp.get(`${basePath}/sse`, handlers.sse);\n\t\t\tapp.get(`${basePath}/snapshot`, handlers.snapshot);\n\t\t\tapp.post(`${basePath}/sync`, handlers.sync);\n\t\t},\n\t};\n}\n\n/**\n * A standalone helper to mount Zuno handlers on an Express app/router.\n */\nexport function mountZuno(\n\tapp: {\n\t\tget: (...args: unknown[]) => unknown;\n\t\tpost: (...args: unknown[]) => unknown;\n\t},\n\topts?: CreateZunoExpressOptions & { basePath?: string },\n) {\n\tconst { basePath = \"/zuno\", ...rest } = opts ?? {};\n\tconst zuno = createZunoExpress(rest);\n\tzuno.mount(app, basePath);\n\treturn zuno;\n}\n"]}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var server=require('@iadev93/zuno/server');function x(u){let{headers:i={},server:o=server.createZunoServerState(),authorize:r=()=>true}=u??{},s=e=>e.status(403).json({ok:false,reason:"FORBIDDEN"}),a={sse:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}server.createSSEConnection(e,n,i,o);},sync:async(e,n)=>{let p=e.body;if(!await r({action:"write",request:e,event:p})){s(n);return}let t=server.applyStateEvent(p,o);if(!t.ok){n.status(t.reason==="VERSION_CONFLICT"?409:400).json({ok:false,reason:t.reason,...t.reason==="VERSION_CONFLICT"?{current:t.current}:{errors:t.errors}});return}n.status(200).json({ok:true,event:t.event});},snapshot:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}server.sendSnapshot(e,n,o);}};return {...a,server:o,mount:(e,n="/zuno")=>{e.get(`${n}/sse`,a.sse),e.get(`${n}/snapshot`,a.snapshot),e.post(`${n}/sync`,a.sync);}}}function S(u,i){let{basePath:o="/zuno",...r}=i??{},s=x(r);return s.mount(u,o),s}exports.createZunoExpress=x;exports.mountZuno=S;//# sourceMappingURL=index.js.map
1
+ import {createZunoServerState,sendSnapshot,applyStateEvent,createSSEConnection}from'@iadev93/zuno/server';function x(u){let{headers:i={},server:o=createZunoServerState(),authorize:r=()=>true}=u??{},s=e=>e.status(403).json({ok:false,reason:"FORBIDDEN"}),a={sse:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}createSSEConnection(e,n,i,o);},sync:async(e,n)=>{let p=e.body;if(!await r({action:"write",request:e,event:p})){s(n);return}let t=applyStateEvent(p,o);if(!t.ok){n.status(t.reason==="VERSION_CONFLICT"?409:400).json({ok:false,reason:t.reason,...t.reason==="VERSION_CONFLICT"?{current:t.current}:{errors:t.errors}});return}n.status(200).json({ok:true,event:t.event});},snapshot:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}sendSnapshot(e,n,o);}};return {...a,server:o,mount:(e,n="/zuno")=>{e.get(`${n}/sse`,a.sse),e.get(`${n}/snapshot`,a.snapshot),e.post(`${n}/sync`,a.sync);}}}function S(u,i){let{basePath:o="/zuno",...r}=i??{},s=x(r);return s.mount(u,o),s}export{x as createZunoExpress,S as mountZuno};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["createZunoExpress","opts","headers","server","createZunoServerState","authorize","forbidden","res","handlers","req","createSSEConnection","incoming","result","applyStateEvent","sendSnapshot","app","basePath","mountZuno","rest","zuno"],"mappings":"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","file":"index.js","sourcesContent":["import type { IncomingHttpHeaders } from \"node:http\";\nimport type { ZunoStateEvent } from \"@iadev93/zuno\";\nimport {\n\tapplyStateEvent,\n\tcreateSSEConnection,\n\tcreateZunoServerState,\n\tsendSnapshot,\n\ttype ZunoServerState,\n} from \"@iadev93/zuno/server\";\nimport type { Request, Response } from \"express\";\n\nexport type ZunoExpressAuthorizationContext = {\n\taction: \"read\" | \"write\";\n\trequest: Request;\n\tevent?: unknown;\n};\n\n/**\n * Options for creating an Express router for Zuno.\n */\nexport type CreateZunoExpressOptions = {\n\t/** Optional custom headers to be sent with the SSE response. */\n\theaders?: IncomingHttpHeaders;\n\t/** Isolated authoritative server state. A new instance is created by default. */\n\tserver?: ZunoServerState;\n\t/** Provider-agnostic authorization hook for snapshots, SSE, and mutations. */\n\tauthorize?: (\n\t\tcontext: ZunoExpressAuthorizationContext,\n\t) => boolean | Promise<boolean>;\n};\n\n/**\n * Creates a Zuno Express instance.\n * Returns both granular handlers and a convenience `mount` helper.\n */\nexport function createZunoExpress(opts?: CreateZunoExpressOptions) {\n\tconst {\n\t\theaders = {},\n\t\tserver = createZunoServerState(),\n\t\tauthorize = () => true,\n\t} = opts ?? {};\n\tconst forbidden = (res: Response) =>\n\t\tres.status(403).json({ ok: false, reason: \"FORBIDDEN\" });\n\n\t/**\n\t * Granular handlers for maximum control.\n\t * You can mount these manually to any route or wrap them in custom middleware.\n\t */\n\tconst handlers = {\n\t\t/**\n\t\t * SSE connection handler.\n\t\t * Usage: app.get('/custom/sse', zuno.sse);\n\t\t */\n\t\tsse: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcreateSSEConnection(req, res, headers, server);\n\t\t},\n\n\t\t/**\n\t\t * Sync POST handler.\n\t\t * Usage: app.post('/custom/sync', zuno.sync);\n\t\t */\n\t\tsync: async (req: Request, res: Response) => {\n\t\t\tconst incoming = req.body as ZunoStateEvent;\n\t\t\tif (\n\t\t\t\t!(await authorize({ action: \"write\", request: req, event: incoming }))\n\t\t\t) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = applyStateEvent(incoming, server);\n\n\t\t\tif (!result.ok) {\n\t\t\t\tres.status(result.reason === \"VERSION_CONFLICT\" ? 409 : 400).json({\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: result.reason,\n\t\t\t\t\t...(result.reason === \"VERSION_CONFLICT\"\n\t\t\t\t\t\t? { current: result.current }\n\t\t\t\t\t\t: { errors: result.errors }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.status(200).json({ ok: true, event: result.event });\n\t\t},\n\n\t\t/**\n\t\t * Snapshot GET handler.\n\t\t * Usage: app.get('/custom/snapshot', zuno.snapshot);\n\t\t */\n\t\tsnapshot: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendSnapshot(req, res, server);\n\t\t},\n\t};\n\n\treturn {\n\t\t...handlers,\n\t\tserver,\n\t\t/**\n\t\t * Optional convenience method to mount all Zuno handlers at once.\n\t\t * @param app The Express App or Router to mount the handlers on.\n\t\t * @param basePath The base path for the Zuno routes (defaults to \"/zuno\").\n\t\t */\n\t\tmount: (\n\t\t\tapp: {\n\t\t\t\tget: (...args: unknown[]) => unknown;\n\t\t\t\tpost: (...args: unknown[]) => unknown;\n\t\t\t},\n\t\t\tbasePath = \"/zuno\",\n\t\t) => {\n\t\t\tapp.get(`${basePath}/sse`, handlers.sse);\n\t\t\tapp.get(`${basePath}/snapshot`, handlers.snapshot);\n\t\t\tapp.post(`${basePath}/sync`, handlers.sync);\n\t\t},\n\t};\n}\n\n/**\n * A standalone helper to mount Zuno handlers on an Express app/router.\n */\nexport function mountZuno(\n\tapp: {\n\t\tget: (...args: unknown[]) => unknown;\n\t\tpost: (...args: unknown[]) => unknown;\n\t},\n\topts?: CreateZunoExpressOptions & { basePath?: string },\n) {\n\tconst { basePath = \"/zuno\", ...rest } = opts ?? {};\n\tconst zuno = createZunoExpress(rest);\n\tzuno.mount(app, basePath);\n\treturn zuno;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["createZunoExpress","opts","headers","server","createZunoServerState","authorize","forbidden","res","handlers","req","createSSEConnection","incoming","result","applyStateEvent","sendSnapshot","app","basePath","mountZuno","rest","zuno"],"mappings":"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","file":"index.js","sourcesContent":["import type { IncomingHttpHeaders } from \"node:http\";\nimport type { ZunoStateEvent } from \"@iadev93/zuno\";\nimport {\n\tapplyStateEvent,\n\tcreateSSEConnection,\n\tcreateZunoServerState,\n\tsendSnapshot,\n\ttype ZunoServerState,\n} from \"@iadev93/zuno/server\";\nimport type { Request, Response } from \"express\";\n\nexport type ZunoExpressAuthorizationContext = {\n\taction: \"read\" | \"write\";\n\trequest: Request;\n\tevent?: unknown;\n};\n\n/**\n * Options for creating an Express router for Zuno.\n */\nexport type CreateZunoExpressOptions = {\n\t/** Optional custom headers to be sent with the SSE response. */\n\theaders?: IncomingHttpHeaders;\n\t/** Isolated authoritative server state. A new instance is created by default. */\n\tserver?: ZunoServerState;\n\t/** Provider-agnostic authorization hook for snapshots, SSE, and mutations. */\n\tauthorize?: (\n\t\tcontext: ZunoExpressAuthorizationContext,\n\t) => boolean | Promise<boolean>;\n};\n\n/**\n * Creates a Zuno Express instance.\n * Returns both granular handlers and a convenience `mount` helper.\n */\nexport function createZunoExpress(opts?: CreateZunoExpressOptions) {\n\tconst {\n\t\theaders = {},\n\t\tserver = createZunoServerState(),\n\t\tauthorize = () => true,\n\t} = opts ?? {};\n\tconst forbidden = (res: Response) =>\n\t\tres.status(403).json({ ok: false, reason: \"FORBIDDEN\" });\n\n\t/**\n\t * Granular handlers for maximum control.\n\t * You can mount these manually to any route or wrap them in custom middleware.\n\t */\n\tconst handlers = {\n\t\t/**\n\t\t * SSE connection handler.\n\t\t * Usage: app.get('/custom/sse', zuno.sse);\n\t\t */\n\t\tsse: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcreateSSEConnection(req, res, headers, server);\n\t\t},\n\n\t\t/**\n\t\t * Sync POST handler.\n\t\t * Usage: app.post('/custom/sync', zuno.sync);\n\t\t */\n\t\tsync: async (req: Request, res: Response) => {\n\t\t\tconst incoming = req.body as ZunoStateEvent;\n\t\t\tif (\n\t\t\t\t!(await authorize({ action: \"write\", request: req, event: incoming }))\n\t\t\t) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = applyStateEvent(incoming, server);\n\n\t\t\tif (!result.ok) {\n\t\t\t\tres.status(result.reason === \"VERSION_CONFLICT\" ? 409 : 400).json({\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: result.reason,\n\t\t\t\t\t...(result.reason === \"VERSION_CONFLICT\"\n\t\t\t\t\t\t? { current: result.current }\n\t\t\t\t\t\t: { errors: result.errors }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.status(200).json({ ok: true, event: result.event });\n\t\t},\n\n\t\t/**\n\t\t * Snapshot GET handler.\n\t\t * Usage: app.get('/custom/snapshot', zuno.snapshot);\n\t\t */\n\t\tsnapshot: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendSnapshot(req, res, server);\n\t\t},\n\t};\n\n\treturn {\n\t\t...handlers,\n\t\tserver,\n\t\t/**\n\t\t * Optional convenience method to mount all Zuno handlers at once.\n\t\t * @param app The Express App or Router to mount the handlers on.\n\t\t * @param basePath The base path for the Zuno routes (defaults to \"/zuno\").\n\t\t */\n\t\tmount: (\n\t\t\tapp: {\n\t\t\t\tget: (...args: unknown[]) => unknown;\n\t\t\t\tpost: (...args: unknown[]) => unknown;\n\t\t\t},\n\t\t\tbasePath = \"/zuno\",\n\t\t) => {\n\t\t\tapp.get(`${basePath}/sse`, handlers.sse);\n\t\t\tapp.get(`${basePath}/snapshot`, handlers.snapshot);\n\t\t\tapp.post(`${basePath}/sync`, handlers.sync);\n\t\t},\n\t};\n}\n\n/**\n * A standalone helper to mount Zuno handlers on an Express app/router.\n */\nexport function mountZuno(\n\tapp: {\n\t\tget: (...args: unknown[]) => unknown;\n\t\tpost: (...args: unknown[]) => unknown;\n\t},\n\topts?: CreateZunoExpressOptions & { basePath?: string },\n) {\n\tconst { basePath = \"/zuno\", ...rest } = opts ?? {};\n\tconst zuno = createZunoExpress(rest);\n\tzuno.mount(app, basePath);\n\treturn zuno;\n}\n"]}
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@iadev93/zuno-express",
3
- "version": "0.0.13",
4
- "main": "./dist/index.js",
3
+ "version": "0.0.14",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
5
7
  "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "engines": {
10
+ "node": ">=22"
11
+ },
6
12
  "files": [
7
13
  "dist",
8
14
  "README.md",
@@ -11,19 +17,20 @@
11
17
  "exports": {
12
18
  ".": {
13
19
  "types": "./dist/index.d.ts",
14
- "default": "./dist/index.js"
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs"
15
22
  }
16
23
  },
17
24
  "peerDependencies": {
18
25
  "express": "^5.0.0",
19
- "@iadev93/zuno": "0.0.11"
26
+ "@iadev93/zuno": "0.0.15"
20
27
  },
21
28
  "devDependencies": {
22
29
  "@types/express": "^5.0.0",
23
30
  "@types/node": "^20.0.0",
24
31
  "tsup": "^8.5.1",
25
32
  "typescript": "^5.9.3",
26
- "@iadev93/zuno": "0.0.11"
33
+ "@iadev93/zuno": "0.0.15"
27
34
  },
28
35
  "keywords": [
29
36
  "zuno",
package/dist/index.mjs DELETED
@@ -1,2 +0,0 @@
1
- import {createZunoServerState,sendSnapshot,applyStateEvent,createSSEConnection}from'@iadev93/zuno/server';function x(u){let{headers:i={},server:o=createZunoServerState(),authorize:r=()=>true}=u??{},s=e=>e.status(403).json({ok:false,reason:"FORBIDDEN"}),a={sse:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}createSSEConnection(e,n,i,o);},sync:async(e,n)=>{let p=e.body;if(!await r({action:"write",request:e,event:p})){s(n);return}let t=applyStateEvent(p,o);if(!t.ok){n.status(t.reason==="VERSION_CONFLICT"?409:400).json({ok:false,reason:t.reason,...t.reason==="VERSION_CONFLICT"?{current:t.current}:{errors:t.errors}});return}n.status(200).json({ok:true,event:t.event});},snapshot:async(e,n)=>{if(!await r({action:"read",request:e})){s(n);return}sendSnapshot(e,n,o);}};return {...a,server:o,mount:(e,n="/zuno")=>{e.get(`${n}/sse`,a.sse),e.get(`${n}/snapshot`,a.snapshot),e.post(`${n}/sync`,a.sync);}}}function S(u,i){let{basePath:o="/zuno",...r}=i??{},s=x(r);return s.mount(u,o),s}export{x as createZunoExpress,S as mountZuno};//# sourceMappingURL=index.mjs.map
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["createZunoExpress","opts","headers","server","createZunoServerState","authorize","forbidden","res","handlers","req","createSSEConnection","incoming","result","applyStateEvent","sendSnapshot","app","basePath","mountZuno","rest","zuno"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { IncomingHttpHeaders } from \"node:http\";\nimport type { ZunoStateEvent } from \"@iadev93/zuno\";\nimport {\n\tapplyStateEvent,\n\tcreateSSEConnection,\n\tcreateZunoServerState,\n\tsendSnapshot,\n\ttype ZunoServerState,\n} from \"@iadev93/zuno/server\";\nimport type { Request, Response } from \"express\";\n\nexport type ZunoExpressAuthorizationContext = {\n\taction: \"read\" | \"write\";\n\trequest: Request;\n\tevent?: unknown;\n};\n\n/**\n * Options for creating an Express router for Zuno.\n */\nexport type CreateZunoExpressOptions = {\n\t/** Optional custom headers to be sent with the SSE response. */\n\theaders?: IncomingHttpHeaders;\n\t/** Isolated authoritative server state. A new instance is created by default. */\n\tserver?: ZunoServerState;\n\t/** Provider-agnostic authorization hook for snapshots, SSE, and mutations. */\n\tauthorize?: (\n\t\tcontext: ZunoExpressAuthorizationContext,\n\t) => boolean | Promise<boolean>;\n};\n\n/**\n * Creates a Zuno Express instance.\n * Returns both granular handlers and a convenience `mount` helper.\n */\nexport function createZunoExpress(opts?: CreateZunoExpressOptions) {\n\tconst {\n\t\theaders = {},\n\t\tserver = createZunoServerState(),\n\t\tauthorize = () => true,\n\t} = opts ?? {};\n\tconst forbidden = (res: Response) =>\n\t\tres.status(403).json({ ok: false, reason: \"FORBIDDEN\" });\n\n\t/**\n\t * Granular handlers for maximum control.\n\t * You can mount these manually to any route or wrap them in custom middleware.\n\t */\n\tconst handlers = {\n\t\t/**\n\t\t * SSE connection handler.\n\t\t * Usage: app.get('/custom/sse', zuno.sse);\n\t\t */\n\t\tsse: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcreateSSEConnection(req, res, headers, server);\n\t\t},\n\n\t\t/**\n\t\t * Sync POST handler.\n\t\t * Usage: app.post('/custom/sync', zuno.sync);\n\t\t */\n\t\tsync: async (req: Request, res: Response) => {\n\t\t\tconst incoming = req.body as ZunoStateEvent;\n\t\t\tif (\n\t\t\t\t!(await authorize({ action: \"write\", request: req, event: incoming }))\n\t\t\t) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = applyStateEvent(incoming, server);\n\n\t\t\tif (!result.ok) {\n\t\t\t\tres.status(result.reason === \"VERSION_CONFLICT\" ? 409 : 400).json({\n\t\t\t\t\tok: false,\n\t\t\t\t\treason: result.reason,\n\t\t\t\t\t...(result.reason === \"VERSION_CONFLICT\"\n\t\t\t\t\t\t? { current: result.current }\n\t\t\t\t\t\t: { errors: result.errors }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.status(200).json({ ok: true, event: result.event });\n\t\t},\n\n\t\t/**\n\t\t * Snapshot GET handler.\n\t\t * Usage: app.get('/custom/snapshot', zuno.snapshot);\n\t\t */\n\t\tsnapshot: async (req: Request, res: Response) => {\n\t\t\tif (!(await authorize({ action: \"read\", request: req }))) {\n\t\t\t\tforbidden(res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendSnapshot(req, res, server);\n\t\t},\n\t};\n\n\treturn {\n\t\t...handlers,\n\t\tserver,\n\t\t/**\n\t\t * Optional convenience method to mount all Zuno handlers at once.\n\t\t * @param app The Express App or Router to mount the handlers on.\n\t\t * @param basePath The base path for the Zuno routes (defaults to \"/zuno\").\n\t\t */\n\t\tmount: (\n\t\t\tapp: {\n\t\t\t\tget: (...args: unknown[]) => unknown;\n\t\t\t\tpost: (...args: unknown[]) => unknown;\n\t\t\t},\n\t\t\tbasePath = \"/zuno\",\n\t\t) => {\n\t\t\tapp.get(`${basePath}/sse`, handlers.sse);\n\t\t\tapp.get(`${basePath}/snapshot`, handlers.snapshot);\n\t\t\tapp.post(`${basePath}/sync`, handlers.sync);\n\t\t},\n\t};\n}\n\n/**\n * A standalone helper to mount Zuno handlers on an Express app/router.\n */\nexport function mountZuno(\n\tapp: {\n\t\tget: (...args: unknown[]) => unknown;\n\t\tpost: (...args: unknown[]) => unknown;\n\t},\n\topts?: CreateZunoExpressOptions & { basePath?: string },\n) {\n\tconst { basePath = \"/zuno\", ...rest } = opts ?? {};\n\tconst zuno = createZunoExpress(rest);\n\tzuno.mount(app, basePath);\n\treturn zuno;\n}\n"]}
File without changes