tina4-nodejs 3.13.92 → 3.13.95
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/CLAUDE.md +170 -28
- package/README.md +2 -2
- package/package.json +13 -9
- package/packages/cli/dist/bin.js +33126 -30055
- package/packages/cli/src/commands/metrics.ts +17 -11
- package/packages/cli/src/commands/serve.ts +10 -9
- package/packages/core/dist/index.js +33062 -29908
- package/packages/core/src/ai.ts +7 -1
- package/packages/core/src/auth.ts +191 -39
- package/packages/core/src/background.ts +19 -19
- package/packages/core/src/cache.ts +492 -49
- package/packages/core/src/devAdmin.ts +79 -32
- package/packages/core/src/devMailbox.ts +20 -44
- package/packages/core/src/dispatchPipeline.ts +285 -0
- package/packages/core/src/dotenv.ts +185 -40
- package/packages/core/src/index.ts +7 -6
- package/packages/core/src/logger.ts +257 -36
- package/packages/core/src/mcp.ts +1 -1
- package/packages/core/src/messenger.ts +81 -13
- package/packages/core/src/metrics.ts +199 -961
- package/packages/core/src/middleware.ts +390 -123
- package/packages/core/src/queue.ts +188 -32
- package/packages/core/src/queueBackends/kafkaBackend.ts +109 -13
- package/packages/core/src/queueBackends/liteBackend.ts +13 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
- package/packages/core/src/rateLimiter.ts +10 -5
- package/packages/core/src/request.ts +6 -9
- package/packages/core/src/response.ts +46 -1
- package/packages/core/src/router.ts +29 -4
- package/packages/core/src/server.ts +751 -414
- package/packages/core/src/session.ts +244 -27
- package/packages/core/src/sessionHandlers/childError.ts +72 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +293 -202
- package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
- package/packages/core/src/sessionHandlers/respClient.ts +16 -143
- package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
- package/packages/core/src/testClient.ts +18 -5
- package/packages/core/src/trustedProxy.ts +249 -0
- package/packages/core/src/types.ts +29 -5
- package/packages/core/src/websocket.ts +66 -0
- package/packages/frond/dist/index.js +74 -31
- package/packages/frond/src/engine.ts +99 -33
- package/packages/orm/dist/index.js +26554 -23400
- package/packages/orm/src/adapters/firebird.ts +183 -56
- package/packages/orm/src/adapters/mongodb.ts +25 -4
- package/packages/orm/src/adapters/mssql.ts +114 -29
- package/packages/orm/src/adapters/mysql.ts +103 -40
- package/packages/orm/src/adapters/odbc.ts +44 -21
- package/packages/orm/src/adapters/postgres.ts +118 -26
- package/packages/orm/src/adapters/sqlDialect.ts +120 -0
- package/packages/orm/src/adapters/sqlite.ts +64 -25
- package/packages/orm/src/baseModel.ts +135 -40
- package/packages/orm/src/cachedDatabase.ts +43 -19
- package/packages/orm/src/connectTimeout.ts +265 -0
- package/packages/orm/src/database.ts +338 -198
- package/packages/orm/src/databaseResult.ts +65 -13
- package/packages/orm/src/databaseUrl.ts +484 -0
- package/packages/orm/src/docstore.ts +386 -145
- package/packages/orm/src/index.ts +13 -3
- package/packages/orm/src/migration.ts +18 -3
- package/packages/orm/src/queryBuilder.ts +38 -4
- package/packages/orm/src/sqlTranslator.ts +310 -4
- package/packages/orm/src/types.ts +15 -4
- package/types/cli/src/bin.d.ts +92 -0
- package/types/cli/src/commands/build.d.ts +2 -0
- package/types/cli/src/commands/generate.d.ts +47 -0
- package/types/cli/src/commands/init.d.ts +1 -0
- package/types/cli/src/commands/metrics.d.ts +6 -0
- package/types/cli/src/commands/migrate.d.ts +1 -0
- package/types/cli/src/commands/migrateCreate.d.ts +1 -0
- package/types/cli/src/commands/migrateRollback.d.ts +1 -0
- package/types/cli/src/commands/migrateStatus.d.ts +1 -0
- package/types/cli/src/commands/queue.d.ts +20 -0
- package/types/cli/src/commands/routes.d.ts +1 -0
- package/types/cli/src/commands/seed.d.ts +1 -0
- package/types/cli/src/commands/serve.d.ts +6 -0
- package/types/cli/src/commands/test.d.ts +1 -0
- package/types/core/src/ai.d.ts +64 -0
- package/types/core/src/api.d.ts +262 -0
- package/types/core/src/auth.d.ts +177 -0
- package/types/core/src/authGate.d.ts +20 -0
- package/types/core/src/background.d.ts +34 -0
- package/types/core/src/cache.d.ts +163 -0
- package/types/core/src/constants.d.ts +38 -0
- package/types/core/src/container.d.ts +44 -0
- package/types/core/src/context/chunker.d.ts +31 -0
- package/types/core/src/context/index.d.ts +93 -0
- package/types/core/src/devAdmin.d.ts +179 -0
- package/types/core/src/devMailbox.d.ts +54 -0
- package/types/core/src/dispatchPipeline.d.ts +117 -0
- package/types/core/src/docs.d.ts +141 -0
- package/types/core/src/docsAutoDiscovery.d.ts +6 -0
- package/types/core/src/dotenv.d.ts +87 -0
- package/types/core/src/env.d.ts +28 -0
- package/types/core/src/errorOverlay.d.ts +36 -0
- package/types/core/src/events.d.ts +75 -0
- package/types/core/src/fakeData.d.ts +55 -0
- package/types/core/src/feedback.d.ts +90 -0
- package/types/core/src/graphql.d.ts +207 -0
- package/types/core/src/health.d.ts +22 -0
- package/types/core/src/htmlElement.d.ts +75 -0
- package/types/core/src/i18n.d.ts +37 -0
- package/types/core/src/index.d.ts +92 -0
- package/types/core/src/job.d.ts +39 -0
- package/types/core/src/logger.d.ts +200 -0
- package/types/core/src/mcp.d.ts +248 -0
- package/types/core/src/messenger.d.ts +191 -0
- package/types/core/src/metrics.d.ts +41 -0
- package/types/core/src/middleware.d.ts +330 -0
- package/types/core/src/mqtt.d.ts +257 -0
- package/types/core/src/mqttMessage.d.ts +67 -0
- package/types/core/src/plan.d.ts +96 -0
- package/types/core/src/projectIndex.d.ts +56 -0
- package/types/core/src/queue.d.ts +268 -0
- package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
- package/types/core/src/queueBackends/liteBackend.d.ts +128 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +119 -0
- package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
- package/types/core/src/rateLimiter.d.ts +49 -0
- package/types/core/src/request.d.ts +25 -0
- package/types/core/src/response.d.ts +28 -0
- package/types/core/src/routeDiscovery.d.ts +12 -0
- package/types/core/src/router.d.ts +366 -0
- package/types/core/src/scss.d.ts +19 -0
- package/types/core/src/server.d.ts +146 -0
- package/types/core/src/service.d.ts +115 -0
- package/types/core/src/session.d.ts +341 -0
- package/types/core/src/sessionHandlers/childError.d.ts +34 -0
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +97 -0
- package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
- package/types/core/src/sessionHandlers/mongoClient.d.ts +35 -0
- package/types/core/src/sessionHandlers/mongoHandler.d.ts +109 -0
- package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
- package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
- package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
- package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
- package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
- package/types/core/src/static.d.ts +2 -0
- package/types/core/src/test.d.ts +94 -0
- package/types/core/src/testClient.d.ts +36 -0
- package/types/core/src/testing.d.ts +58 -0
- package/types/core/src/trustedProxy.d.ts +44 -0
- package/types/core/src/types.d.ts +242 -0
- package/types/core/src/validator.d.ts +52 -0
- package/types/core/src/websocket.d.ts +402 -0
- package/types/core/src/websocketBackplane.d.ts +166 -0
- package/types/core/src/websocketConnection.d.ts +54 -0
- package/types/core/src/wsdl.d.ts +101 -0
- package/types/frond/src/engine.d.ts +263 -0
- package/types/frond/src/index.d.ts +2 -0
- package/types/orm/src/adapters/firebird.d.ts +183 -0
- package/types/orm/src/adapters/mongodb.d.ts +81 -0
- package/types/orm/src/adapters/mssql.d.ts +77 -0
- package/types/orm/src/adapters/mysql.d.ts +67 -0
- package/types/orm/src/adapters/odbc.d.ts +94 -0
- package/types/orm/src/adapters/postgres.d.ts +86 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
- package/types/orm/src/adapters/sqlite.d.ts +68 -0
- package/types/orm/src/autoCrud.d.ts +73 -0
- package/types/orm/src/baseModel.d.ts +427 -0
- package/types/orm/src/cachedDatabase.d.ts +190 -0
- package/types/orm/src/connectTimeout.d.ts +100 -0
- package/types/orm/src/database.d.ts +655 -0
- package/types/orm/src/databaseResult.d.ts +109 -0
- package/types/orm/src/databaseUrl.d.ts +125 -0
- package/types/orm/src/docstore.d.ts +241 -0
- package/types/orm/src/fakeData.d.ts +22 -0
- package/types/orm/src/index.d.ts +43 -0
- package/types/orm/src/migration.d.ts +275 -0
- package/types/orm/src/model.d.ts +7 -0
- package/types/orm/src/query.d.ts +14 -0
- package/types/orm/src/queryBuilder.d.ts +193 -0
- package/types/orm/src/realtime/index.d.ts +7 -0
- package/types/orm/src/realtime/models/attachment.d.ts +43 -0
- package/types/orm/src/realtime/models/channel.d.ts +32 -0
- package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
- package/types/orm/src/realtime/models/message.d.ts +36 -0
- package/types/orm/src/realtime/models/workspace.d.ts +26 -0
- package/types/orm/src/realtime/realtime.d.ts +24 -0
- package/types/orm/src/realtime/storage.d.ts +61 -0
- package/types/orm/src/seeder.d.ts +118 -0
- package/types/orm/src/sqlTranslator.d.ts +258 -0
- package/types/orm/src/types.d.ts +148 -0
- package/types/orm/src/validation.d.ts +6 -0
- package/types/swagger/src/generator.d.ts +46 -0
- package/types/swagger/src/index.d.ts +2 -0
- package/types/swagger/src/ui.d.ts +11 -0
- package/packages/core/src/sessionHandlers/redisHandler.ts +0 -206
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import type { Tina4Request, Tina4Response, Middleware } from "./types.js";
|
|
2
|
+
export declare class MiddlewareChain {
|
|
3
|
+
private middlewares;
|
|
4
|
+
use(fn: Middleware): void;
|
|
5
|
+
/**
|
|
6
|
+
* Run the chain in REGISTRATION order — each middleware runs exactly once,
|
|
7
|
+
* in the order it was attached via use(). The chain advances from ONE
|
|
8
|
+
* source only: `next()`. (The old runner double-advanced — a for-loop index
|
|
9
|
+
* AND next() both incremented — so every other middleware was silently
|
|
10
|
+
* skipped. Fixed by driving the chain purely by next(), mirroring Python's
|
|
11
|
+
* _make_mw_continuation Russian-doll continuation.)
|
|
12
|
+
*
|
|
13
|
+
* A middleware may stop the chain by:
|
|
14
|
+
* - not calling next() (it owns the response), or
|
|
15
|
+
* - ending the response (res.raw.writableEnded).
|
|
16
|
+
* Returns true when the whole chain ran to completion (handler may proceed),
|
|
17
|
+
* false when it was short-circuited.
|
|
18
|
+
*/
|
|
19
|
+
run(req: Tina4Request, res: Tina4Response): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* True when a middleware spec is a CLASS (the beforeX/afterX convention)
|
|
23
|
+
* rather than a plain `(req, res, next)` middleware function.
|
|
24
|
+
*
|
|
25
|
+
* A class's `prototype` property is non-writable by the language spec
|
|
26
|
+
* (ClassDefinitionEvaluation); an ordinary function's is writable, and an
|
|
27
|
+
* arrow function, async function or bound function has no `prototype` at all.
|
|
28
|
+
* That is a language-level distinction rather than a name or source-string
|
|
29
|
+
* sniff, so a class named `cors` and a function named `Cors` both classify
|
|
30
|
+
* correctly.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isMiddlewareClass(spec: unknown): boolean;
|
|
33
|
+
export declare class MiddlewareRunner {
|
|
34
|
+
/** Globally registered middleware classes (parity with PHP/Ruby/Python orchestrators). */
|
|
35
|
+
private static globalMiddleware;
|
|
36
|
+
/**
|
|
37
|
+
* Register a middleware class to run on every request.
|
|
38
|
+
* Mirrors Tina4\Middleware::use (PHP), Tina4::Middleware.use (Ruby),
|
|
39
|
+
* and Middleware.use (Python).
|
|
40
|
+
*/
|
|
41
|
+
static use(cls: any): void;
|
|
42
|
+
/** Return the list of globally registered middleware classes. */
|
|
43
|
+
/**
|
|
44
|
+
* Global middleware that runs BEFORE route matching.
|
|
45
|
+
*
|
|
46
|
+
* A middleware opts in with `static preMatch = true`. Everything else stays
|
|
47
|
+
* where it has always run - after matching - so this is additive and no
|
|
48
|
+
* existing middleware changes behaviour.
|
|
49
|
+
*
|
|
50
|
+
* The two groups need opposite things. CORS must run before matching so its
|
|
51
|
+
* headers survive a short-circuited 401/403; a browser shown a 401 without
|
|
52
|
+
* them reports a CORS error and the real status never reaches the developer.
|
|
53
|
+
* CSRF must run AFTER, because it reads the matched route's metadata to
|
|
54
|
+
* honour a route marked noAuth - PHP shipped exactly that bypass as dead
|
|
55
|
+
* code once, because the metadata was not assigned yet.
|
|
56
|
+
*
|
|
57
|
+
* NOT named `beforeMatch` - hook discovery treats every `before*` static as
|
|
58
|
+
* a middleware hook and would call the flag itself with (req, res).
|
|
59
|
+
*/
|
|
60
|
+
static partitionByMatchPhase(all: any[]): {
|
|
61
|
+
pre: any[];
|
|
62
|
+
post: any[];
|
|
63
|
+
};
|
|
64
|
+
static getGlobal(): any[];
|
|
65
|
+
/** Clear all globally registered middleware (primarily for tests). */
|
|
66
|
+
static reset(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Discover the before-prefixed / after-prefixed hook names on a middleware
|
|
69
|
+
* class, INHERITED HOOKS INCLUDED, base class first (M1).
|
|
70
|
+
*
|
|
71
|
+
* `Object.getOwnPropertyNames` returns a class's OWN statics only, in
|
|
72
|
+
* source-declaration order. On its own that silently DROPPED every hook a
|
|
73
|
+
* subclass inherited: for `class Sub extends Base` with `static beforeBase`
|
|
74
|
+
* on the base, discovery returned only ["beforeSub"] even though
|
|
75
|
+
* `Sub.beforeBase` is a live function — so a shared base middleware simply
|
|
76
|
+
* never ran, with no error. Python returns ['before_base','before_sub'] and
|
|
77
|
+
* Ruby [:before_base,:before_sub]; Node was the only one of the four that
|
|
78
|
+
* lost hooks.
|
|
79
|
+
*
|
|
80
|
+
* So walk the prototype chain (the STATIC side: Sub -> Base -> ...) and emit
|
|
81
|
+
* base-class hooks BEFORE the subclass's own, de-duping an override to its
|
|
82
|
+
* first (base) position. That is exactly Python's `_discover_methods`
|
|
83
|
+
* walking `reversed(__mro__)` over each `__dict__`, and Ruby's
|
|
84
|
+
* `discover_methods` walking `ancestors.reverse_each`.
|
|
85
|
+
*
|
|
86
|
+
* Within one class the order is still source-declaration order — we
|
|
87
|
+
* deliberately do NOT sort(), so hooks run in the order they were written
|
|
88
|
+
* (parity with Python walking __dict__, PHP get_class_methods, Ruby
|
|
89
|
+
* instance_methods(false)). Cross-class order is the natural iteration of
|
|
90
|
+
* the registered classes = REGISTRATION order.
|
|
91
|
+
*
|
|
92
|
+
* The chain walk stops at Function.prototype / Object.prototype, so the
|
|
93
|
+
* built-in members are never scanned. A plain object registered as
|
|
94
|
+
* middleware still works: its own keys are level 0.
|
|
95
|
+
*/
|
|
96
|
+
private static methodNames;
|
|
97
|
+
/**
|
|
98
|
+
* Execute every beforeX static method found on the supplied classes.
|
|
99
|
+
*
|
|
100
|
+
* ORDER (M1): cross-class = REGISTRATION order (the order classes were
|
|
101
|
+
* attached via Router.use / MiddlewareRunner.use); within a class =
|
|
102
|
+
* DEFINITION order (source order, never alphabetical). before_* run before
|
|
103
|
+
* the handler.
|
|
104
|
+
*
|
|
105
|
+
* THROW (M2): each before* call is wrapped — a throwing middleware is
|
|
106
|
+
* LOGGED and produces a deterministic clean 500 (it never crashes the
|
|
107
|
+
* worker / leaks an unhandled exception), and the chain short-circuits
|
|
108
|
+
* (skip = true, handler skipped).
|
|
109
|
+
*
|
|
110
|
+
* Short-circuits (skip = true, handler skipped) when a before* sets a
|
|
111
|
+
* status >= 400 or ends/500s the response.
|
|
112
|
+
*
|
|
113
|
+
* ASYNC — each hook is awaited so middleware can perform async work (e.g.
|
|
114
|
+
* the distributed responseCache before-hook awaiting `backend.get`). Awaiting
|
|
115
|
+
* a synchronous hook that returns an array is harmless (the array resolves
|
|
116
|
+
* immediately), so existing sync hooks keep working unchanged.
|
|
117
|
+
*
|
|
118
|
+
* RETURN VALUE — see `interpretHookResult` for the one table every hook at
|
|
119
|
+
* every scope obeys. A returned Response object is the PRIMARY
|
|
120
|
+
* short-circuit; `false` is a deny.
|
|
121
|
+
*
|
|
122
|
+
* Returns [req, res, shouldContinue].
|
|
123
|
+
*/
|
|
124
|
+
static runBefore(classes: any[], req: Tina4Request, res: Tina4Response): Promise<[Tina4Request, Tina4Response, boolean]>;
|
|
125
|
+
/**
|
|
126
|
+
* Execute every afterX static method found on the supplied classes.
|
|
127
|
+
*
|
|
128
|
+
* ORDER (M1): cross-class = REGISTRATION order; within a class = DEFINITION
|
|
129
|
+
* order. after_* run after the handler.
|
|
130
|
+
*
|
|
131
|
+
* THROW (M2): each after* call is wrapped — a throwing after middleware is
|
|
132
|
+
* LOGGED and produces a clean 500, then the remaining after* STILL run
|
|
133
|
+
* (they may add headers / logging). No unhandled exception leaks.
|
|
134
|
+
*
|
|
135
|
+
* AFTER-ON-4xx RULE (M2): after_* ALWAYS run, even when a before_*
|
|
136
|
+
* short-circuited with status >= 400 and the handler was skipped — so they
|
|
137
|
+
* can still add headers / logging. The dispatcher calls runAfter
|
|
138
|
+
* unconditionally after the before/handler block (see server.ts).
|
|
139
|
+
*
|
|
140
|
+
* ASYNC — each hook is awaited (e.g. the responseCache after-hook awaiting
|
|
141
|
+
* `backend.set`). Awaiting a synchronous hook is harmless, so existing sync
|
|
142
|
+
* after-hooks keep working unchanged.
|
|
143
|
+
*
|
|
144
|
+
* RETURN VALUE — the SAME table as runBefore (`interpretHookResult`): the
|
|
145
|
+
* contract is one table for every hook at every scope. There is nothing left
|
|
146
|
+
* to skip after the handler, so a short-circuit here ends the after chain.
|
|
147
|
+
* A THROW is different and unchanged: it is logged, becomes a clean 500, and
|
|
148
|
+
* the remaining after hooks still run.
|
|
149
|
+
*/
|
|
150
|
+
static runAfter(classes: any[], req: Tina4Request, res: Tina4Response): Promise<[Tina4Request, Tina4Response]>;
|
|
151
|
+
}
|
|
152
|
+
/** Configuration for the CORS middleware */
|
|
153
|
+
export interface CorsConfig {
|
|
154
|
+
/** Allowed origins. Default: NONE (deny) — or TINA4_CORS_ORIGINS env, comma-separated. "*" allows any. */
|
|
155
|
+
origins?: string | string[];
|
|
156
|
+
/** Allowed methods. Default: standard REST methods (or TINA4_CORS_METHODS env) */
|
|
157
|
+
methods?: string | string[];
|
|
158
|
+
/** Allowed headers. Default: Content-Type, Authorization (or TINA4_CORS_HEADERS env) */
|
|
159
|
+
headers?: string | string[];
|
|
160
|
+
/** Access-Control-Max-Age in seconds. Default: 86400 (or TINA4_CORS_MAX_AGE env) */
|
|
161
|
+
maxAge?: number;
|
|
162
|
+
/** Send Access-Control-Allow-Credentials. Default: false (or TINA4_CORS_CREDENTIALS env). Never sent with a wildcard origin. */
|
|
163
|
+
credentials?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** Reset the CORS warn-once ledger. Test seam. */
|
|
166
|
+
export declare function resetCorsWarnings(): void;
|
|
167
|
+
/**
|
|
168
|
+
* The resolved CORS policy — ONE implementation of the rules.
|
|
169
|
+
*
|
|
170
|
+
* Both the function middleware `cors()` and the class middleware
|
|
171
|
+
* `CorsMiddleware` build one of these and apply what it returns. They used to
|
|
172
|
+
* be two independent implementations that had already drifted: `cors()` never
|
|
173
|
+
* read TINA4_CORS_CREDENTIALS at all, so the DEFAULT always-on pipeline
|
|
174
|
+
* silently ignored a documented env var (measured 2026-07-31). One feature,
|
|
175
|
+
* one code path.
|
|
176
|
+
*
|
|
177
|
+
* DENY BY DEFAULT (ADR-0018). With no origins configured, NO
|
|
178
|
+
* Access-Control-Allow-Origin is emitted and the browser's own CORS check
|
|
179
|
+
* blocks the cross-origin request. "*" still works, it just has to be asked for.
|
|
180
|
+
*
|
|
181
|
+
* CREDENTIALS AND THE WILDCARD ARE MUTUALLY EXCLUSIVE. The Fetch Standard's
|
|
182
|
+
* CORS check treats "*" as a literal (not a wildcard) once the request's
|
|
183
|
+
* credentials mode is "include", so ACAO: * with
|
|
184
|
+
* Access-Control-Allow-Credentials: true is rejected by every browser.
|
|
185
|
+
*
|
|
186
|
+
* VARY: ORIGIN whenever the ACAO value is COMPUTED from the request's Origin,
|
|
187
|
+
* i.e. whenever an allow-list is configured — on a MISS as well as a match.
|
|
188
|
+
* RFC 9110 s12.5.5: a Vary field name list tells cache recipients they "MUST
|
|
189
|
+
* NOT use this response to satisfy a later request unless the later request
|
|
190
|
+
* has the same values for the listed header fields as the original request".
|
|
191
|
+
* The miss case matters most: without it a shared cache can store the no-ACAO
|
|
192
|
+
* response for origin B and serve it to origin A. A constant "*" genuinely
|
|
193
|
+
* does not vary and gets no Vary, which would only fragment a CDN's cache.
|
|
194
|
+
*
|
|
195
|
+
* Access-Control-Allow-Methods / -Allow-Headers are static configured lists
|
|
196
|
+
* here, never derived from the request's Access-Control-Request-* headers, so
|
|
197
|
+
* those field names do NOT belong in Vary.
|
|
198
|
+
*/
|
|
199
|
+
export declare class CorsPolicy {
|
|
200
|
+
readonly allowedOrigins: string[];
|
|
201
|
+
readonly allowedMethods: string;
|
|
202
|
+
readonly allowedHeaders: string;
|
|
203
|
+
readonly maxAge: number;
|
|
204
|
+
readonly credentials: boolean;
|
|
205
|
+
constructor(config?: CorsConfig);
|
|
206
|
+
/** Whether an operator has actually declared a CORS policy. */
|
|
207
|
+
isConfigured(): boolean;
|
|
208
|
+
/** The origin to send in Access-Control-Allow-Origin, or undefined for none. */
|
|
209
|
+
resolveOrigin(requestOrigin: string): string | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* The CORS headers for a request origin. `isPreflight` adds Max-Age, which
|
|
212
|
+
* the Fetch Standard only defines for a preflight response.
|
|
213
|
+
*/
|
|
214
|
+
headersFor(requestOrigin: string, isPreflight: boolean): Record<string, string>;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Built-in CORS middleware (function form).
|
|
218
|
+
*
|
|
219
|
+
* A thin adapter over CorsPolicy — see that class for the rules and the
|
|
220
|
+
* standards behind them. Reads configuration from env vars when not provided:
|
|
221
|
+
* TINA4_CORS_ORIGINS — comma-separated list of allowed origins, or "*"
|
|
222
|
+
* TINA4_CORS_METHODS — comma-separated list of allowed methods
|
|
223
|
+
* TINA4_CORS_HEADERS — comma-separated list of allowed headers
|
|
224
|
+
* TINA4_CORS_MAX_AGE — preflight cache duration in seconds
|
|
225
|
+
* TINA4_CORS_CREDENTIALS — send Access-Control-Allow-Credentials
|
|
226
|
+
*
|
|
227
|
+
* A real preflight is answered 204. The status is the same whether the origin
|
|
228
|
+
* was allowed or denied — the browser does the blocking.
|
|
229
|
+
*/
|
|
230
|
+
export declare function cors(config?: CorsConfig): Middleware;
|
|
231
|
+
/**
|
|
232
|
+
* Class-based CORS middleware using the before/after convention.
|
|
233
|
+
*
|
|
234
|
+
* The same CorsPolicy as `cors()` — one implementation, one set of semantics.
|
|
235
|
+
*
|
|
236
|
+
* Usage:
|
|
237
|
+
* Router.use(CorsMiddleware);
|
|
238
|
+
*/
|
|
239
|
+
export declare class CorsMiddleware {
|
|
240
|
+
static beforeCors(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
|
|
241
|
+
/**
|
|
242
|
+
* Check if a request is an OPTIONS preflight.
|
|
243
|
+
*
|
|
244
|
+
* NOTE: returns true for ANY OPTIONS, with no Origin check, so the name
|
|
245
|
+
* overstates what it tests. The real short-circuit uses isCorsPreflight().
|
|
246
|
+
* Kept because existing tests pin this meaning.
|
|
247
|
+
*/
|
|
248
|
+
static isPreflight(method: string): boolean;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Class-based rate limiter middleware using the before/after convention.
|
|
252
|
+
* Uses the same sliding-window algorithm as the `rateLimiter()` function.
|
|
253
|
+
*
|
|
254
|
+
* Reads configuration from env vars:
|
|
255
|
+
* TINA4_RATE_LIMIT — max requests per window (default 100)
|
|
256
|
+
* TINA4_RATE_WINDOW — window duration in seconds (default 60)
|
|
257
|
+
*
|
|
258
|
+
* Usage:
|
|
259
|
+
* Router.use(RateLimiterMiddleware);
|
|
260
|
+
*/
|
|
261
|
+
export declare class RateLimiterMiddleware {
|
|
262
|
+
private static store;
|
|
263
|
+
private static cleanupTimer;
|
|
264
|
+
private static ensureCleanup;
|
|
265
|
+
static beforeRateLimit(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
|
|
266
|
+
/**
|
|
267
|
+
* Check if an IP is within rate limits without recording a request.
|
|
268
|
+
* Returns [allowed, info] matching Python/Ruby API.
|
|
269
|
+
*/
|
|
270
|
+
static check(ip: string): [boolean, {
|
|
271
|
+
limit: number;
|
|
272
|
+
remaining: number;
|
|
273
|
+
reset: number;
|
|
274
|
+
window: number;
|
|
275
|
+
}];
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Class-based request logger middleware using the before/after convention.
|
|
279
|
+
* `beforeLog` stamps the request start time.
|
|
280
|
+
* `afterLog` prints the coloured status line.
|
|
281
|
+
*
|
|
282
|
+
* Usage:
|
|
283
|
+
* Router.use(RequestLogger);
|
|
284
|
+
*/
|
|
285
|
+
export declare class RequestLogger {
|
|
286
|
+
static beforeLog(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
|
|
287
|
+
static afterLog(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Class-based security headers middleware using the before/after convention.
|
|
291
|
+
* Auto-injects security headers on every response.
|
|
292
|
+
*
|
|
293
|
+
* Configuration via env vars:
|
|
294
|
+
* TINA4_FRAME_OPTIONS — X-Frame-Options (default: "SAMEORIGIN")
|
|
295
|
+
* TINA4_HSTS — Strict-Transport-Security max-age value
|
|
296
|
+
* (default: "" = off; set to "31536000" to enable)
|
|
297
|
+
* TINA4_CSP — Content-Security-Policy (default: "default-src 'self'")
|
|
298
|
+
* TINA4_REFERRER_POLICY — Referrer-Policy (default: "strict-origin-when-cross-origin")
|
|
299
|
+
* TINA4_PERMISSIONS_POLICY — Permissions-Policy (default: "camera=(), microphone=(), geolocation=()")
|
|
300
|
+
*
|
|
301
|
+
* Usage:
|
|
302
|
+
* Router.use(SecurityHeadersMiddleware);
|
|
303
|
+
*/
|
|
304
|
+
export declare class SecurityHeadersMiddleware {
|
|
305
|
+
static beforeSecurity(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Class-based CSRF middleware using the before/after convention.
|
|
309
|
+
* Validates form tokens on state-changing requests (POST, PUT, PATCH, DELETE).
|
|
310
|
+
*
|
|
311
|
+
* Off by default — only active when TINA4_CSRF=true in .env or when
|
|
312
|
+
* registered explicitly via Router.use(CsrfMiddleware).
|
|
313
|
+
*
|
|
314
|
+
* Behaviour:
|
|
315
|
+
* - Skips GET, HEAD, OPTIONS requests.
|
|
316
|
+
* - Skips routes marked .noAuth().
|
|
317
|
+
* - Skips requests with a valid Authorization: Bearer header (API clients).
|
|
318
|
+
* - Checks request body formToken then X-Form-Token header.
|
|
319
|
+
* - Rejects if token found in query string formToken (log warning, 403).
|
|
320
|
+
* - Validates token with validToken using SECRET env var.
|
|
321
|
+
* - If token payload has session_id, verifies it matches request session.
|
|
322
|
+
* - Returns 403 on failure.
|
|
323
|
+
*
|
|
324
|
+
* Usage:
|
|
325
|
+
* Router.use(CsrfMiddleware);
|
|
326
|
+
*/
|
|
327
|
+
export declare class CsrfMiddleware {
|
|
328
|
+
static beforeCsrf(req: Tina4Request, res: Tina4Response): [Tina4Request, Tina4Response];
|
|
329
|
+
}
|
|
330
|
+
export declare function requestLogger(): Middleware;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency MQTT 3.1.1 client — the protocol every broker and every IoT
|
|
3
|
+
* device already speaks.
|
|
4
|
+
*
|
|
5
|
+
* Built on Node's `node:net` and `node:tls` stdlib modules only: no npm package,
|
|
6
|
+
* so an app that talks to Mosquitto / EMQX / HiveMQ / AWS IoT adds nothing to its
|
|
7
|
+
* dependency tree. Shaped like the Queue on purpose — publish / subscribe /
|
|
8
|
+
* consume:
|
|
9
|
+
*
|
|
10
|
+
* import { Mqtt } from "@tina4stack/tina4-node";
|
|
11
|
+
*
|
|
12
|
+
* const mqtt = new Mqtt({ url: "mqtt://broker:1883" }); // TINA4_MQTT_URL
|
|
13
|
+
* await mqtt.connect();
|
|
14
|
+
* await mqtt.publish("fleet/meter-42/telemetry", '{"kwh":12.5}', 1);
|
|
15
|
+
*
|
|
16
|
+
* for await (const message of mqtt.consume("fleet/+/telemetry", 1)) {
|
|
17
|
+
* if (message.isDuplicate()) continue; // QoS 1 is at-least-once
|
|
18
|
+
* store(message.topic, message.payload);
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Environment: TINA4_MQTT_URL (default mqtt://127.0.0.1:1883),
|
|
22
|
+
* TINA4_MQTT_CLIENT_ID, TINA4_MQTT_KEEPALIVE (seconds, default 60),
|
|
23
|
+
* TINA4_MQTT_CA_FILE, TINA4_MQTT_TLS_VERIFY.
|
|
24
|
+
*
|
|
25
|
+
* Node has no synchronous blocking socket read, so connect()/publish()/
|
|
26
|
+
* subscribe()/receive() are async and consume() is an async generator — the same
|
|
27
|
+
* idiom as the Queue. No background task runs by default; opt in to the
|
|
28
|
+
* cooperative keepalive with startKeepalive(), which registers a background()
|
|
29
|
+
* task exactly like the queue consumers do.
|
|
30
|
+
*
|
|
31
|
+
* Single reader: like every MQTT client the socket has ONE network reader. Call
|
|
32
|
+
* receive()/consume() from one place.
|
|
33
|
+
*/
|
|
34
|
+
import { MqttMessage } from "./mqttMessage.js";
|
|
35
|
+
/** Any MQTT protocol / connection failure. */
|
|
36
|
+
export declare class MqttError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
/** The broker did not answer inside the timeout. */
|
|
40
|
+
export declare class MqttTimeoutError extends MqttError {
|
|
41
|
+
constructor(message: string);
|
|
42
|
+
}
|
|
43
|
+
export interface MqttOptions {
|
|
44
|
+
url?: string;
|
|
45
|
+
clientId?: string;
|
|
46
|
+
username?: string;
|
|
47
|
+
password?: string;
|
|
48
|
+
caFile?: string;
|
|
49
|
+
tlsVerify?: boolean;
|
|
50
|
+
keepalive?: number;
|
|
51
|
+
cleanSession?: boolean;
|
|
52
|
+
willTopic?: string;
|
|
53
|
+
willPayload?: unknown;
|
|
54
|
+
willQos?: number;
|
|
55
|
+
willRetain?: boolean;
|
|
56
|
+
/** Seconds to wait for a control-packet answer (CONNACK / PUBACK / SUBACK). */
|
|
57
|
+
timeout?: number;
|
|
58
|
+
/** Seconds to wait for an application message; null/undefined blocks. */
|
|
59
|
+
readTimeout?: number | null;
|
|
60
|
+
}
|
|
61
|
+
export interface ParsedMqttUrl {
|
|
62
|
+
host: string;
|
|
63
|
+
port: number;
|
|
64
|
+
tls: boolean;
|
|
65
|
+
username: string | null;
|
|
66
|
+
password: string | null;
|
|
67
|
+
}
|
|
68
|
+
export declare class Mqtt {
|
|
69
|
+
readonly host: string;
|
|
70
|
+
readonly port: number;
|
|
71
|
+
readonly clientId: string;
|
|
72
|
+
readonly keepalive: number;
|
|
73
|
+
readonly cleanSession: boolean;
|
|
74
|
+
readonly username: string | null;
|
|
75
|
+
private readonly secure;
|
|
76
|
+
private readonly password;
|
|
77
|
+
private readonly caFile;
|
|
78
|
+
private readonly tlsVerify;
|
|
79
|
+
private readonly willTopic;
|
|
80
|
+
private readonly willPayload;
|
|
81
|
+
private readonly willQos;
|
|
82
|
+
private readonly willRetain;
|
|
83
|
+
private readonly timeout;
|
|
84
|
+
private readonly readTimeout;
|
|
85
|
+
private packetId;
|
|
86
|
+
private inbox;
|
|
87
|
+
private lastWriteAt;
|
|
88
|
+
private socket;
|
|
89
|
+
private keepaliveTask;
|
|
90
|
+
private readBuffer;
|
|
91
|
+
private waiter;
|
|
92
|
+
private socketError;
|
|
93
|
+
constructor(options?: MqttOptions);
|
|
94
|
+
/**
|
|
95
|
+
* Split an MQTT url into { host, port, tls, username, password }.
|
|
96
|
+
*
|
|
97
|
+
* "mqtt://host:port" and "tcp://host:port" are plain TCP (default port 1883);
|
|
98
|
+
* "mqtts://host:port" is TLS (default 8883). A bare "host" or "host:port"
|
|
99
|
+
* works too, and an IPv6 literal is bracketed ("mqtt://[::1]:1883").
|
|
100
|
+
* Credentials ride in the userinfo and are percent-decoded, so a password
|
|
101
|
+
* containing @ : or / survives.
|
|
102
|
+
*/
|
|
103
|
+
static parseUrl(url: string): ParsedMqttUrl;
|
|
104
|
+
/**
|
|
105
|
+
* Decode %XX in url userinfo. NOT decodeURI-style "+"-to-space: a "+" in a
|
|
106
|
+
* password must survive verbatim, and an invalid "%" is left as-is (never throws).
|
|
107
|
+
*/
|
|
108
|
+
private static percentDecode;
|
|
109
|
+
/**
|
|
110
|
+
* Remaining Length varint: 7 bits per byte, high bit means "another byte
|
|
111
|
+
* follows". A single-byte assumption works for every packet under 128 bytes
|
|
112
|
+
* and then fails, so this is exercised directly at 0 / 127 / 128 / 16383.
|
|
113
|
+
*/
|
|
114
|
+
static encodeRemainingLength(value: number): Buffer;
|
|
115
|
+
/**
|
|
116
|
+
* Open the socket and complete the CONNECT / CONNACK handshake. Also the
|
|
117
|
+
* reconnect path: an existing socket is closed first, and a durable session
|
|
118
|
+
* (cleanSession false) resumes with the same clientId. Returns this for
|
|
119
|
+
* chaining (`const c = await new Mqtt(opts).connect()`).
|
|
120
|
+
*/
|
|
121
|
+
connect(): Promise<this>;
|
|
122
|
+
/** Whether a socket is currently open. */
|
|
123
|
+
connected(): boolean;
|
|
124
|
+
/** True when this connection runs over TLS (mqtts://). */
|
|
125
|
+
tls(): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The negotiated cipher suite name, or null on a plain connection. A real name
|
|
128
|
+
* here is proof the TLS handshake actually completed.
|
|
129
|
+
*/
|
|
130
|
+
cipher(): string | null;
|
|
131
|
+
/** The negotiated TLS protocol version ("TLSv1.3"), or null when plain. */
|
|
132
|
+
tlsVersion(): string | null;
|
|
133
|
+
/**
|
|
134
|
+
* Publish an application message. Resolves to the packet identifier for QoS 1
|
|
135
|
+
* (the broker's PUBACK must carry it back) and null for QoS 0.
|
|
136
|
+
*
|
|
137
|
+
* retain=true tells the broker to keep this as the topic's last known value and
|
|
138
|
+
* hand it to every FUTURE subscriber. Publishing an EMPTY payload with
|
|
139
|
+
* retain=true clears a retained value.
|
|
140
|
+
*/
|
|
141
|
+
publish(topic: string, payload: unknown, qos?: number, retain?: boolean): Promise<number | null>;
|
|
142
|
+
/**
|
|
143
|
+
* Subscribe to a topic filter ("fleet/+/telemetry", "fleet/#"). Resolves to the
|
|
144
|
+
* QoS the broker GRANTED, which can be lower than requested.
|
|
145
|
+
*
|
|
146
|
+
* A SUBACK carrying 0x80 is a REFUSAL, not a success -- treating any SUBACK as
|
|
147
|
+
* success means sitting on a dead subscription receiving nothing, so it throws.
|
|
148
|
+
*/
|
|
149
|
+
subscribe(topicFilter: string, qos?: number): Promise<number>;
|
|
150
|
+
/**
|
|
151
|
+
* Read the next application message.
|
|
152
|
+
*
|
|
153
|
+
* ack=true (the default) acknowledges a QoS 1 delivery immediately, which is
|
|
154
|
+
* right for a synchronous read. Pass ack=false when the message must be stored
|
|
155
|
+
* before the broker is allowed to forget it -- an unacknowledged QoS 1 message
|
|
156
|
+
* is redelivered with DUP set. consume() does exactly that.
|
|
157
|
+
*/
|
|
158
|
+
receive(timeout?: number | null, ack?: boolean): Promise<MqttMessage>;
|
|
159
|
+
/**
|
|
160
|
+
* Long-running consumer, mirroring Queue.consume().
|
|
161
|
+
*
|
|
162
|
+
* for await (const message of mqtt.consume("fleet/+/telemetry", 1)) {
|
|
163
|
+
* store(message);
|
|
164
|
+
* }
|
|
165
|
+
*
|
|
166
|
+
* The message is acknowledged AFTER the loop body hands control back to the
|
|
167
|
+
* generator (the next iteration), so a body that throws leaves the message
|
|
168
|
+
* unacknowledged and the broker redelivers it with DUP set -- at-least-once,
|
|
169
|
+
* the point of QoS 1. iterations > 0 stops after that many messages.
|
|
170
|
+
*/
|
|
171
|
+
consume(topicFilter?: string | null, qos?: number, iterations?: number, timeout?: number | null): AsyncGenerator<MqttMessage>;
|
|
172
|
+
/** PUBACK a QoS 1 delivery. Called by MqttMessage.acknowledge(). */
|
|
173
|
+
acknowledge(packetId: number): Promise<boolean>;
|
|
174
|
+
/**
|
|
175
|
+
* PINGREQ and wait for the PINGRESP. Use this when nothing else is reading the
|
|
176
|
+
* socket; under a consume loop use startKeepalive() instead.
|
|
177
|
+
*/
|
|
178
|
+
ping(timeout?: number | null): Promise<boolean>;
|
|
179
|
+
/**
|
|
180
|
+
* Write a PINGREQ without waiting for the answer. The PINGRESP is absorbed by
|
|
181
|
+
* whatever is reading the socket (receive() skips it).
|
|
182
|
+
*/
|
|
183
|
+
sendKeepalive(): Promise<boolean>;
|
|
184
|
+
/**
|
|
185
|
+
* Opt in to the cooperative keepalive. Registers a background() task -- the
|
|
186
|
+
* same mechanism the queue consumers use -- that sends a PINGREQ only when the
|
|
187
|
+
* connection has gone quiet, so an actively publishing client costs no extra
|
|
188
|
+
* packets.
|
|
189
|
+
*/
|
|
190
|
+
startKeepalive(intervalSeconds?: number): {
|
|
191
|
+
stop: () => void;
|
|
192
|
+
};
|
|
193
|
+
/** Stop the cooperative keepalive registered by startKeepalive(). */
|
|
194
|
+
stopKeepalive(): boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Say goodbye properly: DISCONNECT then close. The broker discards the Last
|
|
197
|
+
* Will on a graceful disconnect.
|
|
198
|
+
*/
|
|
199
|
+
disconnect(): Promise<boolean>;
|
|
200
|
+
/**
|
|
201
|
+
* Drop the socket WITHOUT a DISCONNECT -- what a crashed or unplugged device
|
|
202
|
+
* looks like to the broker, and therefore what fires the Last Will.
|
|
203
|
+
*/
|
|
204
|
+
kill(): boolean;
|
|
205
|
+
private connectFlags;
|
|
206
|
+
/**
|
|
207
|
+
* Open a connected socket, upgrading to TLS when mqtts://. Rejects with an
|
|
208
|
+
* MqttError on a connect timeout or a TLS verification failure (the cert error
|
|
209
|
+
* message is preserved so a rejected cert reports WHY). Each connection builds
|
|
210
|
+
* its OWN tls options object, so a CA supplied for one client never leaks into
|
|
211
|
+
* a later client.
|
|
212
|
+
*/
|
|
213
|
+
private openSocket;
|
|
214
|
+
private refuseUnsupportedQos;
|
|
215
|
+
/** Encode an MQTT string: a 2-byte big-endian length followed by UTF-8 bytes. */
|
|
216
|
+
private static mqttString;
|
|
217
|
+
private static uint16;
|
|
218
|
+
private static payloadBytes;
|
|
219
|
+
/** The next packet identifier (1..65535; 0 is invalid). */
|
|
220
|
+
private nextPacketId;
|
|
221
|
+
private writePacket;
|
|
222
|
+
/**
|
|
223
|
+
* Read one control packet. The fixed header is read in exactly 1 + N bytes
|
|
224
|
+
* (N <= 4 for the varint) so the next packet's header is never consumed by a
|
|
225
|
+
* speculative over-read.
|
|
226
|
+
*/
|
|
227
|
+
private readPacket;
|
|
228
|
+
/**
|
|
229
|
+
* Read exactly `need` bytes from the socket buffer, awaiting more data when
|
|
230
|
+
* short. Only one read is ever outstanding (the protocol reads sequentially),
|
|
231
|
+
* so a single waiter slot is enough. The 'data' handler feeds the buffer and
|
|
232
|
+
* services the waiter; a deadline arms a timer that rejects with a timeout.
|
|
233
|
+
*/
|
|
234
|
+
private readExact;
|
|
235
|
+
private take;
|
|
236
|
+
private serviceWaiter;
|
|
237
|
+
private onData;
|
|
238
|
+
private onSocketGone;
|
|
239
|
+
/** Read packets until the next PUBLISH, skipping keepalive PINGRESPs. */
|
|
240
|
+
private readPublish;
|
|
241
|
+
/**
|
|
242
|
+
* Park a PUBLISH that arrives while we wait for a PUBACK/SUBACK/PINGRESP
|
|
243
|
+
* (normal when the same connection both publishes and subscribes) so receive()
|
|
244
|
+
* still delivers it, in order, instead of it being mistaken for the ack.
|
|
245
|
+
*/
|
|
246
|
+
private stashPublish;
|
|
247
|
+
private parsePublish;
|
|
248
|
+
/**
|
|
249
|
+
* Wait for a specific acknowledgement, tolerating interleaved PUBLISH and
|
|
250
|
+
* PINGRESP packets. A mismatched packet identifier is silent data loss if
|
|
251
|
+
* ignored, so it throws.
|
|
252
|
+
*/
|
|
253
|
+
private waitForAcknowledgement;
|
|
254
|
+
private idleFor;
|
|
255
|
+
private deadlineIn;
|
|
256
|
+
private closeSocket;
|
|
257
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One MQTT 3.1.1 application message as delivered by the broker.
|
|
3
|
+
*
|
|
4
|
+
* Shaped like the Queue's job (its unit of work): it carries the payload plus
|
|
5
|
+
* the delivery metadata a consumer needs, and it knows how to acknowledge itself
|
|
6
|
+
* back to the client it came from. Mirrors tina4_python.mqtt.MqttMessage,
|
|
7
|
+
* Tina4::MqttMessage (Ruby), and Tina4\MqttMessage (PHP).
|
|
8
|
+
*
|
|
9
|
+
* The two flags matter for correctness, not decoration:
|
|
10
|
+
*
|
|
11
|
+
* retained — the broker replayed the topic's last known value to us because
|
|
12
|
+
* we subscribed AFTER it was published. It is current state, not a
|
|
13
|
+
* fresh event.
|
|
14
|
+
* duplicate — the DUP flag. The broker is REDELIVERING a QoS 1 message it never
|
|
15
|
+
* saw acknowledged. QoS 1 is at-least-once, so a duplicate is
|
|
16
|
+
* guaranteed eventually; a consumer that treats a DUP delivery as a
|
|
17
|
+
* new sample double-counts energy or mileage. Key the ingest on
|
|
18
|
+
* (deviceId, deviceTimestamp) and it is harmless.
|
|
19
|
+
*
|
|
20
|
+
* The payload is a Buffer of the raw bytes; text() decodes it for JSON/string
|
|
21
|
+
* payloads.
|
|
22
|
+
*/
|
|
23
|
+
/** The slice of an Mqtt client an MqttMessage needs to acknowledge itself. */
|
|
24
|
+
export interface MqttAcknowledger {
|
|
25
|
+
acknowledge(packetId: number): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
export declare class MqttMessage {
|
|
28
|
+
readonly topic: string;
|
|
29
|
+
readonly payload: Buffer;
|
|
30
|
+
readonly qos: number;
|
|
31
|
+
readonly retained: boolean;
|
|
32
|
+
readonly duplicate: boolean;
|
|
33
|
+
readonly packetId: number | null;
|
|
34
|
+
private readonly client;
|
|
35
|
+
private acknowledgedFlag;
|
|
36
|
+
constructor(topic: string, payload: Buffer, qos?: number, retained?: boolean, duplicate?: boolean, packetId?: number | null, client?: MqttAcknowledger | null);
|
|
37
|
+
/** True when the broker replayed this as the topic's retained (last known) value. */
|
|
38
|
+
isRetained(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* True when the broker set the DUP flag — a REDELIVERY of a QoS 1 message we
|
|
41
|
+
* never acknowledged, not a new sample.
|
|
42
|
+
*/
|
|
43
|
+
isDuplicate(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* PUBACK a QoS 1 delivery so the broker stops redelivering it.
|
|
46
|
+
*
|
|
47
|
+
* A QoS 0 message needs no acknowledgement, and a second call is a no-op, so
|
|
48
|
+
* this is always safe to call once processing succeeded. Returns true only
|
|
49
|
+
* when a PUBACK was actually sent.
|
|
50
|
+
*/
|
|
51
|
+
acknowledge(): Promise<boolean>;
|
|
52
|
+
/** True once this message has been acknowledged back to the broker. */
|
|
53
|
+
isAcknowledged(): boolean;
|
|
54
|
+
/** The payload decoded as text (for JSON / string payloads). */
|
|
55
|
+
text(encoding?: BufferEncoding): string;
|
|
56
|
+
/** The message as a plain object. */
|
|
57
|
+
toObject(): {
|
|
58
|
+
topic: string;
|
|
59
|
+
payload: Buffer;
|
|
60
|
+
qos: number;
|
|
61
|
+
retained: boolean;
|
|
62
|
+
duplicate: boolean;
|
|
63
|
+
packetId: number | null;
|
|
64
|
+
};
|
|
65
|
+
/** String form is the payload text (parity with Python __str__ / Ruby to_s). */
|
|
66
|
+
toString(): string;
|
|
67
|
+
}
|