@hotfusion/modeller 0.0.13 → 0.0.16

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.
Files changed (104) hide show
  1. package/README.md +99 -0
  2. package/dist/adapters/cipher.js +51 -0
  3. package/dist/adapters/cipher.js.map +1 -0
  4. package/dist/connector.js +81 -41
  5. package/dist/connector.js.map +1 -1
  6. package/dist/core.js +2 -48
  7. package/dist/core.js.map +1 -1
  8. package/dist/index.js +9 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/model.js +27 -50
  11. package/dist/model.js.map +1 -1
  12. package/dist/oidc/adapter.js +177 -0
  13. package/dist/oidc/adapter.js.map +1 -0
  14. package/dist/oidc/adapters/cipher.js +51 -0
  15. package/dist/oidc/adapters/cipher.js.map +1 -0
  16. package/dist/oidc/client.js +66 -0
  17. package/dist/oidc/client.js.map +1 -0
  18. package/dist/oidc/code.js +37 -0
  19. package/dist/oidc/code.js.map +1 -0
  20. package/dist/oidc/default.config.js +200 -0
  21. package/dist/oidc/default.config.js.map +1 -0
  22. package/dist/oidc/federation.js +51 -0
  23. package/dist/oidc/federation.js.map +1 -0
  24. package/dist/oidc/grant.js +37 -0
  25. package/dist/oidc/grant.js.map +1 -0
  26. package/dist/oidc/interaction.js +36 -0
  27. package/dist/oidc/interaction.js.map +1 -0
  28. package/dist/oidc/oidc.config.js +79 -0
  29. package/dist/oidc/oidc.config.js.map +1 -0
  30. package/dist/oidc/schemas/client.schema.json +62 -0
  31. package/dist/oidc/schemas/code.schema.json +16 -0
  32. package/dist/oidc/schemas/grant.schema.json +13 -0
  33. package/dist/oidc/schemas/interaction.schema.json +26 -0
  34. package/dist/oidc/schemas/session.schema.json +14 -0
  35. package/dist/oidc/schemas/token.schema.json +16 -0
  36. package/dist/oidc/schemas/user.schema.json +44 -0
  37. package/dist/oidc/session.js +36 -0
  38. package/dist/oidc/session.js.map +1 -0
  39. package/dist/oidc/session.token.js +24 -0
  40. package/dist/oidc/session.token.js.map +1 -0
  41. package/dist/oidc/token.js +23 -0
  42. package/dist/oidc/token.js.map +1 -0
  43. package/dist/oidc/user.js +95 -0
  44. package/dist/oidc/user.js.map +1 -0
  45. package/dist/oidc/utils.js +154 -0
  46. package/dist/oidc/utils.js.map +1 -0
  47. package/dist/server.js +722 -113
  48. package/dist/server.js.map +1 -1
  49. package/dist/types/adapters/cipher.d.ts +12 -0
  50. package/dist/types/adapters/cipher.d.ts.map +1 -0
  51. package/dist/types/connector.d.ts +13 -1
  52. package/dist/types/connector.d.ts.map +1 -1
  53. package/dist/types/core.d.ts +2 -2
  54. package/dist/types/core.d.ts.map +1 -1
  55. package/dist/types/index.d.ts +4 -0
  56. package/dist/types/index.d.ts.map +1 -1
  57. package/dist/types/model.d.ts +26 -2
  58. package/dist/types/model.d.ts.map +1 -1
  59. package/dist/types/oidc/adapter.d.ts +16 -0
  60. package/dist/types/oidc/adapter.d.ts.map +1 -0
  61. package/dist/types/oidc/adapters/cipher.d.ts +12 -0
  62. package/dist/types/oidc/adapters/cipher.d.ts.map +1 -0
  63. package/dist/types/oidc/client.d.ts +3 -0
  64. package/dist/types/oidc/client.d.ts.map +1 -0
  65. package/dist/types/oidc/code.d.ts +3 -0
  66. package/dist/types/oidc/code.d.ts.map +1 -0
  67. package/dist/types/oidc/default.config.d.ts +33 -0
  68. package/dist/types/oidc/default.config.d.ts.map +1 -0
  69. package/dist/types/oidc/federation.d.ts +3 -0
  70. package/dist/types/oidc/federation.d.ts.map +1 -0
  71. package/dist/types/oidc/grant.d.ts +3 -0
  72. package/dist/types/oidc/grant.d.ts.map +1 -0
  73. package/dist/types/oidc/interaction.d.ts +3 -0
  74. package/dist/types/oidc/interaction.d.ts.map +1 -0
  75. package/dist/types/oidc/oidc.config.d.ts +7 -0
  76. package/dist/types/oidc/oidc.config.d.ts.map +1 -0
  77. package/dist/types/oidc/session.d.ts +3 -0
  78. package/dist/types/oidc/session.d.ts.map +1 -0
  79. package/dist/types/oidc/session.token.d.ts +3 -0
  80. package/dist/types/oidc/session.token.d.ts.map +1 -0
  81. package/dist/types/oidc/token.d.ts +3 -0
  82. package/dist/types/oidc/token.d.ts.map +1 -0
  83. package/dist/types/oidc/user.d.ts +3 -0
  84. package/dist/types/oidc/user.d.ts.map +1 -0
  85. package/dist/types/oidc/utils.d.ts +56 -0
  86. package/dist/types/oidc/utils.d.ts.map +1 -0
  87. package/dist/types/server.d.ts +8 -3
  88. package/dist/types/server.d.ts.map +1 -1
  89. package/dist/types/types.d.ts +264 -0
  90. package/dist/types/utils/bundler.d.ts.map +1 -1
  91. package/dist/types/utils/display.d.ts +23 -0
  92. package/dist/types/utils/display.d.ts.map +1 -0
  93. package/dist/utils/_secret.key +1 -0
  94. package/dist/utils/bundler.js +48 -8
  95. package/dist/utils/bundler.js.map +1 -1
  96. package/dist/utils/display.js +207 -0
  97. package/dist/utils/display.js.map +1 -0
  98. package/package.json +28 -4
  99. package/docs/CORE.md +0 -191
  100. package/docs/ERRORS.md +0 -90
  101. package/docs/MODEL.md +0 -296
  102. package/docs/PATTERNS.md +0 -182
  103. package/docs/SERVER.md +0 -88
  104. package/docs/UTILITIES.md +0 -111
package/docs/MODEL.md DELETED
@@ -1,296 +0,0 @@
1
- # Model Layer
2
-
3
- `Model extends Core`. Everything from the [Core layer](./CORE.md) is available on a `Model` instance — `insert`, `update`, `delete`, `get`, `list`, `find`, `getSchema`, `pull`. On top of that, `Model` adds the behavior surface: hooks, workers, methods, uploads, streams, events, subscriptions, logging, and extensions.
4
-
5
- > [← Back to README](../README.md)
6
-
7
- ---
8
-
9
- ## Table of Contents
10
-
11
- - [Hooks](#hooks)
12
- - [Workers](#workers)
13
- - [Methods](#methods)
14
- - [Uploads](#uploads)
15
- - [Streams](#streams)
16
- - [Events & Subscriptions](#events--subscriptions)
17
- - [Internal Event Emitter](#internal-event-emitter)
18
- - [Extensions](#extensions)
19
- - [Logging](#logging)
20
-
21
- ---
22
-
23
- ## Hooks
24
-
25
- Hooks run before or after a CRUD operation. They can short-circuit by throwing, mutate the payload, or perform side effects. Both sync and async callbacks are supported.
26
-
27
- ```ts
28
- users.hook({
29
- id : 'normalize-email',
30
- on : 'before:insert',
31
- callback : async ({ data }) => {
32
- if (data?.email) data.email = data.email.toLowerCase();
33
- }
34
- });
35
-
36
- users.hook({
37
- id : 'audit',
38
- on : ['after:insert', 'after:update', 'after:delete'],
39
- callback : ({ operation, key, data }) => audit.log(operation, key, data)
40
- });
41
-
42
- users.unhook('normalize-email');
43
- ```
44
-
45
- ### Hook events
46
-
47
- | Event | Payload fields |
48
- |-------------------|-----------------------------------------------|
49
- | `before:insert` | `operation`, `timing`, `data` |
50
- | `after:insert` | `operation`, `timing`, `data`, `key: { _id }` |
51
- | `before:update` | `operation`, `timing`, `key`, `data` |
52
- | `after:update` | `operation`, `timing`, `key`, `data` |
53
- | `before:delete` | `operation`, `timing`, `key` |
54
- | `after:delete` | `operation`, `timing`, `key`, `data` |
55
- | `before:list` | `operation`, `timing`, `data` (= query) |
56
- | `after:list` | `operation`, `timing`, `data` (= query) |
57
-
58
- `get` and `find` do **not** fire hooks.
59
-
60
- ### Scheduled hooks
61
-
62
- `schedule: <ms>` defers execution via `setTimeout`. Scheduled hooks are fire-and-forget — they don't block the operation and their errors aren't propagated.
63
-
64
- ```ts
65
- users.hook({
66
- id : 'welcome-email',
67
- on : 'after:insert',
68
- schedule : 5_000,
69
- callback : ({ data }) => mailer.send(data.email, 'welcome')
70
- });
71
- ```
72
-
73
- ### Throwing from a hook
74
-
75
- A non-scheduled hook that throws aborts the operation. The error is logged at `error` level and re-thrown to the caller.
76
-
77
- ---
78
-
79
- ## Workers
80
-
81
- Workers are interval-based background tasks bound to the model. They can be **named** (managed individually) or **unsigned** (managed in bulk).
82
-
83
- ```ts
84
- users.worker({
85
- id : 'sync',
86
- interval : 30_000,
87
- immediate : true, // run once on start, default true
88
- handler : async (model) => { await model.pull(); }
89
- });
90
-
91
- users.worker({ interval: 60_000, handler: () => cleanup() }); // unsigned
92
- ```
93
-
94
- ### Lifecycle
95
-
96
- ```ts
97
- users.start(); // start all (named + unsigned)
98
- users.start('sync'); // start a specific named worker
99
- users.stop(); // stop all
100
- users.stop('sync'); // stop a specific named worker
101
- users.isRunning('sync'); // → boolean
102
- ```
103
-
104
- Errors thrown inside a worker handler are caught and logged — they never crash the interval.
105
-
106
- ---
107
-
108
- ## Methods
109
-
110
- Methods are named, schemaless RPC-style operations. Use them for anything that doesn't fit the CRUD shape.
111
-
112
- ```ts
113
- users.method({
114
- id : 'resetPassword',
115
- handler : async ({ email, newPassword }, model, ctx) => {
116
- const u = await model.get({ email });
117
- if (!u) throw { code: 'NOT_FOUND' };
118
- return model.update({ _id: u._id }, { password: newPassword });
119
- }
120
- });
121
-
122
- await users.call('resetPassword', { email, newPassword }, ctx);
123
- users.getMethods(); // → ['resetPassword']
124
- ```
125
-
126
- `call` throws `{ code: 'METHOD_NOT_FOUND', id }` if the method isn't registered.
127
-
128
- ---
129
-
130
- ## Uploads
131
-
132
- Uploads receive a `Buffer` plus a `fields` map (typically multipart form fields).
133
-
134
- ```ts
135
- users.upload({
136
- id : 'avatar',
137
- handler : async (file, fields, model) => {
138
- const path = await storage.put(file, fields.filename);
139
- return model.update({ _id: fields.userId }, { avatar: path });
140
- }
141
- });
142
-
143
- await users.callUpload('avatar', buffer, { userId, filename: 'me.png' });
144
- users.getUploads(); // → ['avatar']
145
- ```
146
-
147
- ---
148
-
149
- ## Streams
150
-
151
- Streams are similar to uploads but expect chunked `Buffer` data with arbitrary `meta`.
152
-
153
- ```ts
154
- users.stream({
155
- id : 'import',
156
- handler : async (chunk, meta, model) => {
157
- for (const row of parseCsv(chunk)) await model.insert(row);
158
- }
159
- });
160
-
161
- await users.callStream('import', chunk, { source: 'crm' });
162
- users.getStreams(); // → ['import']
163
- ```
164
-
165
- ---
166
-
167
- ## Events & Subscriptions
168
-
169
- Events are named, schema-validated handlers that clients **subscribe to**. Where `method` is one-shot RPC, events are designed for fan-out to multiple subscribers, scoped by query.
170
-
171
- ### Defining an event
172
-
173
- ```ts
174
- users.event({
175
- id : 'roleChanged',
176
- schema : {
177
- type : 'object',
178
- required : ['_id', 'role'],
179
- properties: {
180
- _id : { type: 'string' },
181
- role : { type: 'string', enum: ['user', 'admin'] }
182
- }
183
- },
184
- handler: async ({ _id, role }, model, ctx) => {
185
- return model.update({ _id }, { role });
186
- }
187
- });
188
- ```
189
-
190
- If `schema` is set, args are validated before the handler runs. Validation errors throw `{ code: 'VALIDATION_ERROR', errors }`.
191
-
192
- ### Subscribing
193
-
194
- A subscription is keyed by a normalized query — same query keys/values produce the same subscription instance.
195
-
196
- ```ts
197
- const { subscription, subId } = users.subscribe({ role: 'admin' });
198
-
199
- subscription.on('publish', payload => {
200
- console.log('admin update:', payload);
201
- });
202
-
203
- // shorthand — returns just the subscription
204
- const sub = users.subscription({ role: 'admin' });
205
- ```
206
-
207
- ### Dispatching an event
208
-
209
- `dispatch` validates args (if a schema is set) and runs the matching event handler.
210
-
211
- ```ts
212
- await sub.dispatch('roleChanged', { _id, role: 'admin' }, ctx);
213
- ```
214
-
215
- Throws `{ code: 'EVENT_NOT_FOUND', id }` if the event isn't registered.
216
-
217
- ### Publishing to subscribers
218
-
219
- `publish` is your fan-out hook — it emits to every listener attached via `on('publish', ...)` for that subscription. The transport layer ([Server](./SERVER.md)) wires this into a Socket.IO room so browser clients receive it automatically.
220
-
221
- ```ts
222
- sub.publish({ type: 'role-changed', _id, role: 'admin' });
223
- ```
224
-
225
- A common pattern is calling `publish` from inside an `after:update` hook to notify everyone watching that slice of the model. See [Patterns](./PATTERNS.md).
226
-
227
- ### Inspection / cleanup
228
-
229
- ```ts
230
- sub.getEvents(); // → ['roleChanged']
231
- users.unsubscribe({ role: 'admin' });
232
- ```
233
-
234
- ---
235
-
236
- ## Internal Event Emitter
237
-
238
- Separate from the subscription system, every model is also an `EventEmitter`. CRUD operations emit on this emitter directly — useful for in-process listeners that don't need query scoping.
239
-
240
- ```ts
241
- users.on('insert', doc => log('inserted', doc));
242
- users.on('update', ({ key, data }) => log('updated', key, data));
243
- users.on('delete', result => log('deleted', result));
244
- users.on('log', entry => sink.write(entry));
245
- ```
246
-
247
- `on`, `off`, `once`, `emit` are all available. Built-in events: `insert`, `update`, `delete`, `log`.
248
-
249
- ---
250
-
251
- ## Extensions
252
-
253
- Pass `extensions: [...]` in the constructor options to mount sub-models as named properties on the parent. The mount key is `extension.id`.
254
-
255
- ```ts
256
- const ssh = new Model('ssh', sshSchema);
257
- const users = new Model('users', userSchema, { extensions: [ssh] });
258
-
259
- users.ssh.insert({ host: '...', port: 22, username: 'root' });
260
- users.getExtensions(); // → [Model('ssh')]
261
- ```
262
-
263
- This is how Modeller composes namespaces — a `system` model with `system.ssh`, `system.docker`, `system.firewall` extensions, all addressable as `system.ssh.insert(...)`.
264
-
265
- ---
266
-
267
- ## Logging
268
-
269
- A built-in logger surfaces hook, worker, method, event, and CRUD activity.
270
-
271
- ```ts
272
- const users = new Model('users', schema, {
273
- level : 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
274
- timestamp : true,
275
- features : { hook: true, worker: true, method: true, event: false, crud: true },
276
- onEntry : entry => myLogger.log(entry)
277
- });
278
- ```
279
-
280
- Or supply a custom adapter:
281
-
282
- ```ts
283
- { logAdapter: { log: entry => myStore.append(entry) } }
284
- ```
285
-
286
- Listen on the model itself:
287
-
288
- ```ts
289
- users.on('log', entry => sink.write(entry));
290
- ```
291
-
292
- Each entry includes `level`, `feature`, `modelId`, `message`, `callSite`, `timestamp`, and optional `data`. `initial` is `true` for one-shot registration messages (e.g. "Hook registered"), `false` for runtime activity.
293
-
294
- ---
295
-
296
- > **Next:** [Server layer →](./SERVER.md)
package/docs/PATTERNS.md DELETED
@@ -1,182 +0,0 @@
1
- # Common Patterns
2
-
3
- Recipes that show the [Core → Model → Server](../README.md) chain in action. Each pattern is small, self-contained, and reflects how the framework is meant to be composed.
4
-
5
- > [← Back to README](../README.md)
6
-
7
- ---
8
-
9
- ## Publish on update
10
-
11
- Notify every client subscribed to a slice of a model whenever a document in that slice changes. The `after:update` hook is the natural seam.
12
-
13
- ```ts
14
- users.hook({
15
- id: 'broadcast',
16
- on: 'after:update',
17
- callback: ({ key, data }) => {
18
- users.subscription({ _id: key._id }).publish({ type: 'updated', data });
19
- }
20
- });
21
- ```
22
-
23
- The [Server layer](./SERVER.md) wires the subscription into a Socket.IO room, so every browser client subscribed to `{ _id }` receives the payload automatically.
24
-
25
- ---
26
-
27
- ## FK validation across models
28
-
29
- Use the async `model` keyword to require that a field references an existing document in another model.
30
-
31
- ```ts
32
- const orders = new Model('orders', {
33
- type: 'object',
34
- properties: {
35
- userId: { type: 'string', model: 'users.email' }
36
- }
37
- }, { schemes: [users.getSchema()] });
38
- ```
39
-
40
- `Validator` rejects an `insert` whose `userId` doesn't exist in the `users` model. See [Validator](./CORE.md#validator).
41
-
42
- ---
43
-
44
- ## Composing a system root via extensions
45
-
46
- `extensions` mounts sub-models as named properties on the parent — addressable as `parent.childId.method(...)`.
47
-
48
- ```ts
49
- const ssh = new Model('ssh', sshSchema);
50
- const docker = new Model('docker', dockerSchema);
51
- const firewall = new Model('firewall', firewallSchema);
52
-
53
- const system = new Model('system', systemSchema, {
54
- extensions: [ssh, docker, firewall]
55
- });
56
-
57
- await system.ssh.insert({ host: '...', port: 22, username: 'root' });
58
- await system.firewall.list();
59
- ```
60
-
61
- This pattern is how Modeller composes namespaces. Each extension keeps its own hooks, workers, methods, events, and schema — they aren't merged, just mounted.
62
-
63
- See [Extensions](./MODEL.md#extensions).
64
-
65
- ---
66
-
67
- ## Background sync worker
68
-
69
- Reconcile in-memory state with an external source on an interval.
70
-
71
- ```ts
72
- firewall.worker({
73
- id : 'sync-rules',
74
- interval : 30_000,
75
- handler : async (model) => {
76
- const live = await readLiveUFW();
77
- for (const rule of live) {
78
- const existing = await model.get({ id: rule.id });
79
- if (!existing) await model.insert(rule);
80
- }
81
- }
82
- });
83
-
84
- firewall.start('sync-rules');
85
- ```
86
-
87
- Errors inside the handler are logged but never crash the interval. See [Workers](./MODEL.md#workers).
88
-
89
- ---
90
-
91
- ## Quota / authorization in `before:insert`
92
-
93
- Throw from a `before:` hook to abort an operation.
94
-
95
- ```ts
96
- users.hook({
97
- id: 'check-quota',
98
- on: 'before:insert',
99
- callback: async ({ data }) => {
100
- if (await overQuota(data.tenantId)) {
101
- throw { code: 'QUOTA_EXCEEDED' };
102
- }
103
- }
104
- });
105
- ```
106
-
107
- The thrown object propagates back to the caller of `insert()`. See [Errors](./ERRORS.md).
108
-
109
- ---
110
-
111
- ## Schedule a side effect after insert
112
-
113
- Send a welcome email five seconds after a user is created — without blocking the insert.
114
-
115
- ```ts
116
- users.hook({
117
- id : 'welcome-email',
118
- on : 'after:insert',
119
- schedule : 5_000,
120
- callback : ({ data }) => mailer.send(data.email, 'welcome')
121
- });
122
- ```
123
-
124
- `schedule` makes the hook fire-and-forget via `setTimeout`. Errors from scheduled hooks are logged but don't propagate.
125
-
126
- ---
127
-
128
- ## Persist with an adapter, hydrate on boot
129
-
130
- Wire an `Adapter` so writes are durable, then `pull()` once at startup.
131
-
132
- ```ts
133
- class FileAdapter extends Adapter {
134
- async sync(id, doc) { await fs.writeFile(`./data/${id}.json`, JSON.stringify(doc)); }
135
- async pull() { return JSON.parse(await fs.readFile('./data/users.json', 'utf8')); }
136
- }
137
-
138
- const users = new Model('users', schema, { adapter: FileAdapter });
139
- await users.pull(); // hydrate before serving traffic
140
- ```
141
-
142
- See [Adapter](./CORE.md#adapter).
143
-
144
- ---
145
-
146
- ## Method as RPC, event as fan-out
147
-
148
- Two different shapes for "do something":
149
-
150
- - **Method** — one caller, one return value. Use for actions like `resetPassword`, `regenerateApiKey`.
151
- - **Event** — one or many callers via a subscription, validated args, multi-subscriber fan-out. Use when the action should also notify watchers.
152
-
153
- ```ts
154
- // method — RPC
155
- users.method({ id: 'resetPassword', handler: async (...) => { /* ... */ } });
156
-
157
- // event — broadcasts via subscription
158
- users.event({ id: 'roleChanged', schema, handler: async (...) => { /* ... */ } });
159
- ```
160
-
161
- If in doubt, start with a method. Promote to an event only when more than one consumer needs to know it happened.
162
-
163
- ---
164
-
165
- ## Listening in-process vs over the wire
166
-
167
- Two emitters live on every model. Use the right one for the job:
168
-
169
- - **`model.on('insert' | 'update' | 'delete' | 'log', ...)`** — in-process, no query scoping. Use for audit logs, metrics, internal indexers.
170
- - **`model.subscription(query).on('publish', ...)`** — query-scoped fan-out, designed to be bridged to clients via the [Server layer](./SERVER.md).
171
-
172
- ```ts
173
- // in-process: every write
174
- users.on('update', ({ key, data }) => metrics.increment('users.updated'));
175
-
176
- // per-query: clients listening for admin changes
177
- users.subscription({ role: 'admin' }).on('publish', payload => doSomething(payload));
178
- ```
179
-
180
- ---
181
-
182
- > **Related:** [Model →](./MODEL.md) · [Errors →](./ERRORS.md)
package/docs/SERVER.md DELETED
@@ -1,88 +0,0 @@
1
- # Server Layer
2
-
3
- `Server` is a thin transport that exposes models over HTTP and WebSocket. The package abstracts the underlying Koa + Socket.IO + OIDC + busboy machinery — you don't interact with it directly.
4
-
5
- > [← Back to README](../README.md)
6
-
7
- ---
8
-
9
- ## The mental model
10
-
11
- - **Models in, HTTP + WS out.** Methods, uploads, streams, and events become endpoints.
12
- - **Subscriptions become rooms.** `subscription.publish(payload)` reaches every connected client subscribed to that query.
13
- - **A `/@metadata` endpoint** advertises every model, scope, operation, and event so clients can build typed proxies at runtime.
14
- - **Auth is OIDC + JWT.** Routes can opt in with `protected: true`.
15
-
16
- ---
17
-
18
- ## Quick example
19
-
20
- ```ts
21
- import { Server, Model } from '@hotfusion/modeller';
22
-
23
- const server = new Server(3030, { domain: 'api', secret: process.env.SECRET });
24
-
25
- server.route({
26
- method : 'post',
27
- path : '/system/ping',
28
- callback : async (ctx) => ({ ok: true, ts: Date.now() })
29
- });
30
-
31
- server.start();
32
- ```
33
-
34
- ---
35
-
36
- ## Public methods
37
-
38
- | Method | Purpose |
39
- |-------------------------------------------------|-------------------------------------------------------|
40
- | `route({ method, path, callback, protected? })` | Register a custom HTTP route. |
41
- | `upload(path, handler)` | Multipart form-data endpoint, parsed into a Buffer. |
42
- | `stream(handler)` | Single global handler for chunked socket uploads with resume. |
43
- | `setOIDCProviders(path, providers)` | Wire OIDC providers under a base path. |
44
- | `start()` | Bind to the port, mount routes, start Socket.IO. |
45
-
46
- The full HTTP/WS surface is generated from your models — you don't need to wire each operation manually.
47
-
48
- ---
49
-
50
- ## Server events
51
-
52
- ```ts
53
- server.on('mounted', (s) => console.log('listening'));
54
- server.on('connection', ({ socket }) => /* socket.io connection */);
55
- server.on('request', ({ ctx, flags }) => /* every HTTP request */);
56
- server.on('authenticated', ({ profile, provider, ctx, middleware }) => /* OIDC success */);
57
- server.on('provider', ({ token, sid, session }) => /* token issued */);
58
- server.on('uploaded', ({ file, buffer, resolve }) => resolve({ /* meta */ }));
59
- ```
60
-
61
- | Event | When it fires |
62
- |-----------------|------------------------------------------------------------|
63
- | `mounted` | After `start()` has bound the port and mounted routes. |
64
- | `connection` | A new Socket.IO client connects. |
65
- | `request` | Every incoming HTTP request, with `protected` flag. |
66
- | `authenticated` | OIDC profile successfully verified. |
67
- | `provider` | A provider has issued a token; session is established. |
68
- | `uploaded` | A streamed upload finished assembling. Resolve with meta. |
69
-
70
- ---
71
-
72
- ## Auth
73
-
74
- `{ protected: true }` on a route requires a valid `Authorization: Bearer <jwt>` header. The token is verified against the secret passed to the constructor (or auto-generated via `KeyGen.getSystemSecret()` if omitted).
75
-
76
- `setOIDCProviders` wires multiple OIDC providers under a base path. The server handles the redirect, callback, token exchange, and session creation. Listen to `authenticated` and `provider` to react to login.
77
-
78
- ---
79
-
80
- ## Scope
81
-
82
- This package is meant to be used as an abstraction. The Server layer intentionally keeps a narrow surface so consumers don't need to know Koa, Socket.IO, OIDC client config, or busboy internals.
83
-
84
- If you need to dig deeper — custom middleware, custom socket rooms, OIDC introspection — read `src/server.ts` directly. The public API documented here is the contract.
85
-
86
- ---
87
-
88
- > **Related:** [Utilities →](./UTILITIES.md) · [Errors →](./ERRORS.md)
package/docs/UTILITIES.md DELETED
@@ -1,111 +0,0 @@
1
- # Utilities
2
-
3
- Helpers exported from `@hotfusion/modeller` and from `src/utils`.
4
-
5
- > [← Back to README](../README.md)
6
-
7
- ---
8
-
9
- ## Public exports
10
-
11
- | Export | Purpose |
12
- |-----------|------------------------------------------------------------------------|
13
- | `Bundler` | Rollup-based bundler for shipping client code (TS, JSON, LESS, terser).|
14
- | `KeyGen` | System-wide secret generator. |
15
- | `Adapter` | Base class for persistence — extend to plug in a backing store. |
16
-
17
- ---
18
-
19
- ## `Bundler`
20
-
21
- A Rollup wrapper preconfigured for the Modeller stack. Bundles TypeScript, inlines JSON, compiles LESS via `rollup-plugin-postcss`, supports `@rollup/plugin-replace` for build-time substitutions, and minifies with terser.
22
-
23
- ```ts
24
- import { Bundler } from '@hotfusion/modeller';
25
-
26
- const result = await Bundler.bundle({
27
- entry : 'src/client/index.ts',
28
- output : 'dist/client.js'
29
- });
30
- ```
31
-
32
- Use it for shipping client bundles served by the [Server layer](./SERVER.md), or for any pre-built artifact you want to expose at runtime.
33
-
34
- ---
35
-
36
- ## `KeyGen`
37
-
38
- ```ts
39
- import { KeyGen } from '@hotfusion/modeller';
40
-
41
- const secret = KeyGen.getSystemSecret(); // → hex string
42
- const bytes = KeyGen.getSystemSecret('uint8Array'); // → Uint8Array
43
- ```
44
-
45
- `getSystemSecret()` returns a stable 32-byte secret. On first call it generates one and caches it to disk (`_secret.key`); subsequent calls return the same value. Use it as the JWT signing key, the session secret, or wherever a stable per-installation secret is needed.
46
-
47
- `KeyGen.generateSecret(false)` returns a fresh random secret each time — use this for one-off keys (per-document encryption, single-use tokens).
48
-
49
- ---
50
-
51
- ## `Adapter`
52
-
53
- Base class for persistence. See the full contract in the [Core layer doc](./CORE.md#adapter).
54
-
55
- ```ts
56
- import { Adapter } from '@hotfusion/modeller';
57
-
58
- class MyAdapter extends Adapter {
59
- async sync(id, doc) { /* persist */ }
60
- async pull() { /* return docs */ return []; }
61
- }
62
- ```
63
-
64
- ---
65
-
66
- ## Internal utilities
67
-
68
- These live in `src/utils` and aren't re-exported from the package root, but are stable and safe to import directly.
69
-
70
- ### `encrypt` / `decrypt`
71
-
72
- AES-256-GCM wrappers using a `Uint8Array` secret.
73
-
74
- ```ts
75
- import { encrypt, decrypt } from '@hotfusion/modeller/dist/utils/encryption';
76
-
77
- const hash = await encrypt('plaintext', secretBytes);
78
- const plain = await decrypt(hash, secretBytes);
79
- ```
80
-
81
- Pair with `KeyGen.getSystemSecret('uint8Array')` for the secret.
82
-
83
- ### `signJWT` / `decodeJWT`
84
-
85
- `jose`-backed JWT helpers.
86
-
87
- ```ts
88
- import { signJWT } from '@hotfusion/modeller/dist/utils/sign-jwt';
89
- import { decodeJWT } from '@hotfusion/modeller/dist/utils/decode-jwt';
90
-
91
- const token = await signJWT(payload, secret, { exp: '1h' });
92
- const claims = decodeJWT(token);
93
- ```
94
-
95
- ### `request`
96
-
97
- Minimal `fetch` wrapper used internally by the connector. Returns the parsed body, throws on non-2xx.
98
-
99
- ```ts
100
- import { request } from '@hotfusion/modeller/dist/utils/request';
101
-
102
- const data = await request('https://api.example.com/x', { method: 'GET' });
103
- ```
104
-
105
- ### `ObjectCollection`
106
-
107
- A typed `Map`-with-extras used internally for keyed collections. Useful when you need a `Map` plus filtering / iteration helpers.
108
-
109
- ---
110
-
111
- > **Related:** [Core →](./CORE.md) · [Errors →](./ERRORS.md)