tina4-nodejs 3.13.137 → 3.13.138
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 +33 -3
- package/COMMERCIAL-LICENSE.md +8 -0
- package/LICENSE +373 -0
- package/NOTICE +11 -0
- package/README.md +24 -2
- package/package.json +44 -6
- package/packages/cli/dist/bin.js +19466 -18048
- package/packages/cli/src/bin.ts +8 -0
- package/packages/cli/src/commands/build.ts +8 -0
- package/packages/cli/src/commands/generate.ts +8 -0
- package/packages/cli/src/commands/init.ts +8 -0
- package/packages/cli/src/commands/lint.ts +8 -0
- package/packages/cli/src/commands/migrate.ts +8 -0
- package/packages/cli/src/commands/migrateCreate.ts +8 -0
- package/packages/cli/src/commands/migrateRollback.ts +8 -0
- package/packages/cli/src/commands/migrateStatus.ts +8 -0
- package/packages/cli/src/commands/queue.ts +8 -0
- package/packages/cli/src/commands/routes.ts +8 -0
- package/packages/cli/src/commands/seed.ts +8 -0
- package/packages/cli/src/commands/serve.ts +11 -0
- package/packages/cli/src/commands/test.ts +8 -0
- package/packages/core/dist/index.js +19458 -18043
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +8 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +8 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +8 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +8 -0
- package/packages/core/gallery/queue/src/lib/queueDb.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/retry/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +8 -0
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +8 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +8 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +8 -0
- package/packages/core/public/THIRD-PARTY-NOTICES.md +55 -0
- package/packages/core/public/bundled-component-provenance.json +48 -0
- package/packages/core/public/js/tina4js.min.js +13 -3
- package/packages/core/public/licenses/swagger-ui-3.10.0-original-notice.txt +11 -0
- package/packages/core/public/licenses/swagger-ui-5.17.14-NOTICE.txt +2 -0
- package/packages/core/public/licenses/swagger-ui-Apache-2.0.txt +202 -0
- package/packages/core/public/licenses/tina4-css-MIT.txt +21 -0
- package/packages/core/public/licenses/tina4-dev-admin-components.json +240 -0
- package/packages/core/public/licenses/tina4-dev-admin-third-party.txt +919 -0
- package/packages/core/public/licenses/tina4-js-MIT.txt +20 -0
- package/packages/core/src/__feedback/widget.js +8 -0
- package/packages/core/src/_missing.ts +8 -0
- package/packages/core/src/ai.ts +8 -0
- package/packages/core/src/aiClient.ts +8 -0
- package/packages/core/src/api.ts +15 -3
- package/packages/core/src/auth.ts +21 -8
- package/packages/core/src/authGate.ts +8 -0
- package/packages/core/src/background.ts +8 -0
- package/packages/core/src/cache.ts +43 -4
- package/packages/core/src/constants.ts +8 -0
- package/packages/core/src/container.ts +8 -0
- package/packages/core/src/context/chunker.ts +8 -0
- package/packages/core/src/context/index.ts +8 -0
- package/packages/core/src/devAdmin.ts +185 -59
- package/packages/core/src/devMailbox.ts +8 -0
- package/packages/core/src/dispatchPipeline.ts +8 -0
- package/packages/core/src/docs.ts +8 -0
- package/packages/core/src/docsAutoDiscovery.ts +8 -0
- package/packages/core/src/docsParser.ts +8 -0
- package/packages/core/src/docsScanner.ts +44 -0
- package/packages/core/src/docsSignatures.ts +8 -0
- package/packages/core/src/dotenv.ts +8 -0
- package/packages/core/src/env.ts +8 -0
- package/packages/core/src/errorOverlay.ts +8 -0
- package/packages/core/src/events.ts +8 -0
- package/packages/core/src/fakeData.ts +8 -0
- package/packages/core/src/feedback.ts +8 -0
- package/packages/core/src/graphql.ts +16 -5
- package/packages/core/src/health.ts +8 -0
- package/packages/core/src/htmlElement.ts +8 -0
- package/packages/core/src/i18n.ts +8 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/job.ts +8 -0
- package/packages/core/src/logger.ts +8 -0
- package/packages/core/src/mcp.test.ts +8 -0
- package/packages/core/src/mcp.ts +8 -0
- package/packages/core/src/messenger.ts +143 -86
- package/packages/core/src/metrics.ts +13 -0
- package/packages/core/src/middleware.ts +1178 -1096
- package/packages/core/src/mqtt.ts +8 -0
- package/packages/core/src/mqttMessage.ts +8 -0
- package/packages/core/src/optionalPackage.ts +48 -0
- package/packages/core/src/plan.ts +8 -0
- package/packages/core/src/portTakeover.ts +8 -0
- package/packages/core/src/projectIndex.ts +8 -0
- package/packages/core/src/projectIndexExtractors.ts +8 -0
- package/packages/core/src/projectIndexStorage.ts +8 -0
- package/packages/core/src/push.ts +8 -0
- package/packages/core/src/queue.ts +8 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +8 -0
- package/packages/core/src/queueBackends/liteBackend.ts +8 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +21 -8
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +8 -0
- package/packages/core/src/rateLimiter.ts +8 -0
- package/packages/core/src/request.ts +59 -10
- package/packages/core/src/response.ts +79 -5
- package/packages/core/src/routeDiscovery.ts +8 -0
- package/packages/core/src/router.ts +8 -0
- package/packages/core/src/server.ts +73 -24
- package/packages/core/src/service.ts +8 -0
- package/packages/core/src/session.ts +8 -0
- package/packages/core/src/sessionHandlers/childError.ts +8 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/respClient.ts +8 -0
- package/packages/core/src/sessionHandlers/sqlClient.ts +8 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +8 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +8 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +8 -0
- package/packages/core/src/sso.ts +8 -0
- package/packages/core/src/static.ts +8 -0
- package/packages/core/src/test.ts +8 -0
- package/packages/core/src/testClient.ts +8 -0
- package/packages/core/src/testing.ts +8 -0
- package/packages/core/src/transport.ts +369 -0
- package/packages/core/src/trustedProxy.ts +8 -0
- package/packages/core/src/types.ts +13 -0
- package/packages/core/src/validator.ts +8 -0
- package/packages/core/src/version.ts +8 -0
- package/packages/core/src/websocket.ts +8 -0
- package/packages/core/src/websocketBackplane.ts +49 -32
- package/packages/core/src/websocketConnection.ts +8 -0
- package/packages/core/src/wsdl.ts +197 -115
- package/packages/core/templates/errors/302.twig +8 -0
- package/packages/core/templates/errors/401.twig +8 -0
- package/packages/core/templates/errors/403.twig +8 -0
- package/packages/core/templates/errors/404.twig +8 -0
- package/packages/core/templates/errors/500.twig +8 -0
- package/packages/core/templates/errors/502.twig +8 -0
- package/packages/core/templates/errors/503.twig +8 -0
- package/packages/core/templates/errors/base.twig +8 -0
- package/packages/frond/dist/index.js +151 -17
- package/packages/frond/src/engine.ts +177 -17
- package/packages/frond/src/index.ts +8 -0
- package/packages/orm/dist/index.js +8125 -6708
- package/packages/orm/src/adapters/firebird.ts +50 -3
- package/packages/orm/src/adapters/mongodb.ts +8 -0
- package/packages/orm/src/adapters/mssql.ts +29 -7
- package/packages/orm/src/adapters/mysql.ts +17 -1
- package/packages/orm/src/adapters/odbc.ts +11 -0
- package/packages/orm/src/adapters/pg-types.d.ts +8 -0
- package/packages/orm/src/adapters/postgres.ts +34 -8
- package/packages/orm/src/adapters/sqlDialect.ts +57 -0
- package/packages/orm/src/adapters/sqlite.ts +26 -0
- package/packages/orm/src/autoCrud.ts +78 -36
- package/packages/orm/src/baseModel.ts +61 -29
- package/packages/orm/src/cachedDatabase.ts +67 -1
- package/packages/orm/src/connectTimeout.ts +8 -0
- package/packages/orm/src/database.ts +649 -401
- package/packages/orm/src/databaseResult.ts +8 -0
- package/packages/orm/src/databaseUrl.ts +8 -0
- package/packages/orm/src/docstore.ts +23 -1
- package/packages/orm/src/fakeData.ts +8 -0
- package/packages/orm/src/graph/adapters/arango.ts +8 -0
- package/packages/orm/src/graph/adapters/bolt.ts +8 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -0
- package/packages/orm/src/graph/connectTimeout.ts +8 -0
- package/packages/orm/src/graph/errors.ts +8 -0
- package/packages/orm/src/graph/graphAdapter.ts +8 -0
- package/packages/orm/src/graph/graphDatabase.ts +8 -0
- package/packages/orm/src/graph/graphUrl.ts +10 -1
- package/packages/orm/src/graph/shapes.ts +8 -0
- package/packages/orm/src/index.ts +147 -139
- package/packages/orm/src/migration.ts +8 -0
- package/packages/orm/src/model.ts +8 -0
- package/packages/orm/src/modelCollection.ts +8 -0
- package/packages/orm/src/point.ts +8 -0
- package/packages/orm/src/query.ts +149 -33
- package/packages/orm/src/queryBuilder.ts +8 -0
- package/packages/orm/src/realtime/index.ts +8 -0
- package/packages/orm/src/realtime/models/attachment.ts +8 -0
- package/packages/orm/src/realtime/models/channel.ts +8 -0
- package/packages/orm/src/realtime/models/channelMember.ts +8 -0
- package/packages/orm/src/realtime/models/message.ts +8 -0
- package/packages/orm/src/realtime/models/workspace.ts +8 -0
- package/packages/orm/src/realtime/realtime.ts +8 -0
- package/packages/orm/src/realtime/storage.ts +43 -12
- package/packages/orm/src/seeder.ts +8 -0
- package/packages/orm/src/seederTable.ts +8 -0
- package/packages/orm/src/seederTypes.ts +8 -0
- package/packages/orm/src/sqlTranslator.ts +185 -8
- package/packages/orm/src/types.ts +15 -0
- package/packages/orm/src/validation.ts +8 -0
- package/packages/swagger/src/generator.ts +8 -0
- package/packages/swagger/src/index.ts +8 -0
- package/packages/swagger/src/ui.ts +8 -0
- package/types/core/src/background.d.ts +0 -16
- package/types/core/src/cache.d.ts +6 -0
- package/types/core/src/devAdmin.d.ts +18 -12
- package/types/core/src/docs.d.ts +0 -17
- package/types/core/src/errorOverlay.d.ts +0 -24
- package/types/core/src/events.d.ts +0 -12
- package/types/core/src/feedback.d.ts +0 -23
- package/types/core/src/graphql.d.ts +0 -21
- package/types/core/src/htmlElement.d.ts +0 -15
- package/types/core/src/messenger.d.ts +12 -1
- package/types/core/src/metrics.d.ts +2 -0
- package/types/core/src/middleware.d.ts +45 -1
- package/types/core/src/mqtt.d.ts +0 -33
- package/types/core/src/optionalPackage.d.ts +25 -0
- package/types/core/src/plan.d.ts +0 -10
- package/types/core/src/projectIndex.d.ts +0 -10
- package/types/core/src/queueBackends/mongoBackend.d.ts +2 -0
- package/types/core/src/request.d.ts +12 -0
- package/types/core/src/response.d.ts +13 -0
- package/types/core/src/server.d.ts +19 -0
- package/types/core/src/sessionHandlers/childError.d.ts +0 -15
- package/types/core/src/transport.d.ts +28 -0
- package/types/core/src/trustedProxy.d.ts +0 -16
- package/types/core/src/types.d.ts +5 -0
- package/types/core/src/websocketBackplane.d.ts +1 -0
- package/types/core/src/wsdl.d.ts +8 -22
- package/types/frond/src/engine.d.ts +7 -1
- package/types/orm/src/adapters/firebird.d.ts +12 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +2 -0
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
- package/types/orm/src/adapters/sqlite.d.ts +7 -0
- package/types/orm/src/baseModel.d.ts +5 -0
- package/types/orm/src/cachedDatabase.d.ts +27 -0
- package/types/orm/src/database.d.ts +46 -7
- package/types/orm/src/graph/graphUrl.d.ts +0 -8
- package/types/orm/src/index.d.ts +1 -1
- package/types/orm/src/query.d.ts +48 -2
- package/types/orm/src/realtime/storage.d.ts +2 -1
- package/types/orm/src/sqlTranslator.d.ts +70 -18
- package/types/orm/src/types.d.ts +6 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Zero-dependency MQTT 3.1.1 client — the protocol every broker and every IoT
|
|
3
11
|
* device already speaks.
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* One MQTT 3.1.1 application message as delivered by the broker.
|
|
3
11
|
*
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Optional peer packages: the database drivers and service clients an
|
|
11
|
+
* APPLICATION installs, never the framework (ADR-0067).
|
|
12
|
+
*
|
|
13
|
+
* tina4-nodejs installs as exactly one package. `pg`, `mongodb`, `redis` and
|
|
14
|
+
* the `@aws-sdk` S3 client are optional peerDependencies, which npm does not
|
|
15
|
+
* install, so for most apps "missing" is the normal state. Every feature that
|
|
16
|
+
* reaches for one must therefore fail at the point of use with the exact
|
|
17
|
+
* command that fixes it - never a bare MODULE_NOT_FOUND, never a silent no-op.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** The actionable "install it" error, one wording for every optional peer. */
|
|
21
|
+
export function optionalPackageMissing(feature: string, packageNames: string[]): Error {
|
|
22
|
+
const quoted = packageNames.map((name) => `'${name}'`).join(" and ");
|
|
23
|
+
const [noun, pronoun] = packageNames.length === 1 ? ["package is", "it"] : ["packages are", "them"];
|
|
24
|
+
return new Error(
|
|
25
|
+
`The ${quoted} ${noun} required for ${feature}. ` +
|
|
26
|
+
`Install ${pronoun} with: npm install ${packageNames.join(" ")}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve an optional peer from the FRAMEWORK's location and return its module
|
|
32
|
+
* URL, or throw the actionable error. Synchronous, so a constructor can refuse
|
|
33
|
+
* immediately (parity with the Python master, which raises ImportError in the
|
|
34
|
+
* constructor) instead of leaving a rejected promise for later.
|
|
35
|
+
*
|
|
36
|
+
* Resolving from here rather than from process.cwd() is what finds the peer an
|
|
37
|
+
* app installed, wherever the server was started from.
|
|
38
|
+
*
|
|
39
|
+
* @param installTogether packages the feature needs as a set (S3 needs the
|
|
40
|
+
* client AND the presigner), all named in the one install command.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveOptionalPackage(packageName: string, feature: string, installTogether: string[] = [packageName]): string {
|
|
43
|
+
try {
|
|
44
|
+
return import.meta.resolve(packageName);
|
|
45
|
+
} catch {
|
|
46
|
+
throw optionalPackageMissing(feature, installTogether);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Project plan management — persistent, human-readable task state.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Identity-checked port takeover, shared by the CLI and the runtime paths.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Project index — lightweight, persistent "where is what" map.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import * as path from "node:path";
|
|
2
10
|
import type { FileEntry, FileRoute } from "./projectIndex.js";
|
|
3
11
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import * as fs from "node:fs";
|
|
2
10
|
import * as path from "node:path";
|
|
3
11
|
import * as crypto from "node:crypto";
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Provider-neutral Web Push delivery using Node's built-in crypto and fetch.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 Queue — Unified job queue with pluggable backends, zero dependencies.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 Kafka Queue Backend — Kafka protocol via raw TCP, zero dependencies.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* LiteBackend — file-based queue backend for Tina4 Queue.
|
|
3
11
|
* Stores jobs as JSON files on disk. Zero dependencies.
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
|
-
* Tina4 MongoDB Queue Backend — uses `mongodb` npm package
|
|
10
|
+
* Tina4 MongoDB Queue Backend — uses the `mongodb` npm package, which the app
|
|
11
|
+
* installs (`npm install mongodb`; an optional peer, ADR-0067).
|
|
3
12
|
*
|
|
4
13
|
* Implements the same interface as the file-based queue but uses MongoDB
|
|
5
14
|
* for message storage and delivery. Atomic pop via findOneAndUpdate.
|
|
@@ -21,6 +30,7 @@
|
|
|
21
30
|
import { randomUUID } from "node:crypto";
|
|
22
31
|
import { execFileSync } from "node:child_process";
|
|
23
32
|
import type { QueueJob } from "../queue.js";
|
|
33
|
+
import { resolveOptionalPackage } from "../optionalPackage.js";
|
|
24
34
|
|
|
25
35
|
// ── Types ────────────────────────────────────────────────────
|
|
26
36
|
|
|
@@ -71,8 +81,17 @@ export class MongoBackend implements QueueBackend {
|
|
|
71
81
|
private collection: string;
|
|
72
82
|
private visibilityTimeout: number;
|
|
73
83
|
private maxRetries: number;
|
|
84
|
+
/** Module URL of the app-installed `mongodb` driver, resolved once. */
|
|
85
|
+
private driverUrl: string;
|
|
74
86
|
|
|
75
87
|
constructor(config?: MongoConfig) {
|
|
88
|
+
// Optional peer (ADR-0067), resolved HERE, on the main thread. Every
|
|
89
|
+
// operation runs in a child process that swallows its own failures, so a
|
|
90
|
+
// driver missing there used to surface as "MongoDB push failed" and a pop
|
|
91
|
+
// that silently returned nothing. The Python master raises in its
|
|
92
|
+
// constructor; so does this. Resolving from the framework's location also
|
|
93
|
+
// means the child finds the driver whatever directory the server runs in.
|
|
94
|
+
this.driverUrl = resolveOptionalPackage("mongodb", "the MongoDB queue backend");
|
|
76
95
|
this.host = config?.host ?? process.env.TINA4_MONGO_HOST ?? "localhost";
|
|
77
96
|
this.port = config?.port
|
|
78
97
|
?? (process.env.TINA4_MONGO_PORT ? parseInt(process.env.TINA4_MONGO_PORT, 10) : 27017);
|
|
@@ -136,13 +155,7 @@ export class MongoBackend implements QueueBackend {
|
|
|
136
155
|
buildScript(operation: string, queue: string, data?: string): string {
|
|
137
156
|
return `
|
|
138
157
|
async function main() {
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
mongodb = await import("mongodb");
|
|
142
|
-
} catch {
|
|
143
|
-
process.stderr.write("mongodb package not installed — run: npm install mongodb");
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
158
|
+
const mongodb = await import(${JSON.stringify(this.driverUrl)});
|
|
146
159
|
|
|
147
160
|
const { MongoClient } = mongodb;
|
|
148
161
|
const uri = ${JSON.stringify(this.uri)};
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 RabbitMQ Queue Backend — AMQP 0-9-1 via raw TCP, zero dependencies.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { Middleware, Tina4Request, Tina4Response } from "./types.js";
|
|
2
10
|
import { resolveClientIp } from "./trustedProxy.js";
|
|
3
11
|
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { IncomingMessage, IncomingHttpHeaders } from "node:http";
|
|
2
10
|
import { mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
11
|
import { dirname, join } from "node:path";
|
|
4
12
|
import type { Tina4Request, UploadedFile } from "./types.js";
|
|
5
|
-
import { resolveClientIp } from "./trustedProxy.js";
|
|
13
|
+
import { resolveClientIp, isTrustedProxy } from "./trustedProxy.js";
|
|
14
|
+
import { Log } from "./logger.js";
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
17
|
* Wrap Node's `IncomingHttpHeaders` in a Proxy so mixed-case lookups
|
|
@@ -53,10 +62,11 @@ export function createRequest(req: IncomingMessage): Tina4Request {
|
|
|
53
62
|
makeCaseInsensitiveHeaders(req.headers);
|
|
54
63
|
|
|
55
64
|
// Resolve scheme + host honouring proxy headers — parity with PHP/Python/Ruby.
|
|
56
|
-
const
|
|
65
|
+
const trustedPeer = isTrustedProxy(req.socket?.remoteAddress ?? "");
|
|
66
|
+
const xfProto = trustedPeer ? req.headers["x-forwarded-proto"] : undefined;
|
|
57
67
|
const proto = (Array.isArray(xfProto) ? xfProto[0] : xfProto)
|
|
58
68
|
?? ((req.socket as { encrypted?: boolean })?.encrypted ? "https" : "http");
|
|
59
|
-
const xfHost = req.headers["x-forwarded-host"];
|
|
69
|
+
const xfHost = trustedPeer ? req.headers["x-forwarded-host"] : undefined;
|
|
60
70
|
const host = (Array.isArray(xfHost) ? xfHost[0] : xfHost)
|
|
61
71
|
?? (req.headers.host ?? "localhost");
|
|
62
72
|
|
|
@@ -148,13 +158,50 @@ export function createRequest(req: IncomingMessage): Tina4Request {
|
|
|
148
158
|
return tReq;
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
|
|
162
|
+
// ── Request limits (ADR-0068) ────────────────────────────────────────────────
|
|
163
|
+
// Here rather than in a module of their own: request.ts sits below the server
|
|
164
|
+
// and transport.ts, so both can read them without an import cycle, and the
|
|
165
|
+
// core barrel's eager module graph stays one module smaller.
|
|
166
|
+
|
|
167
|
+
export const DEFAULT_MAX_UPLOAD_SIZE = 10_485_760;
|
|
168
|
+
|
|
169
|
+
const warnedLimits = new Set<string>();
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Read a numeric limit from the environment. A value that is not a usable
|
|
173
|
+
* number falls back to the default with a warning naming the variable (the
|
|
174
|
+
* same rule as PHP's Server::resolveLimit) - a typo must never be what
|
|
175
|
+
* disables a DoS guard.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveLimit(name: string, fallback: number, zeroAllowed = false): number {
|
|
178
|
+
const raw = (process.env[name] ?? "").trim();
|
|
179
|
+
if (raw === "") return fallback;
|
|
180
|
+
const value = /^[0-9]+$/.test(raw) ? Number(raw) : NaN;
|
|
181
|
+
if (Number.isSafeInteger(value) && (value > 0 || (value === 0 && zeroAllowed))) return value;
|
|
182
|
+
if (!warnedLimits.has(`${name}=${raw}`)) {
|
|
183
|
+
warnedLimits.add(`${name}=${raw}`);
|
|
184
|
+
Log.warning(`${name}=${raw} is not a usable limit - using ${fallback}`);
|
|
185
|
+
}
|
|
186
|
+
return fallback;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** TINA4_MAX_UPLOAD_SIZE, read when it is needed so a value from .env counts. */
|
|
190
|
+
export function maxUploadSize(): number {
|
|
191
|
+
return resolveLimit("TINA4_MAX_UPLOAD_SIZE", DEFAULT_MAX_UPLOAD_SIZE);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The one 413 wording (ADR-0068 section 4). */
|
|
195
|
+
export function bodyTooLargeMessage(bytes: number | bigint | string, limit: number): string {
|
|
196
|
+
return `Request body (${bytes} bytes) exceeds TINA4_MAX_UPLOAD_SIZE (${limit} bytes)`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
153
200
|
|
|
154
201
|
export class PayloadTooLargeError extends Error {
|
|
155
202
|
public statusCode = 413;
|
|
156
203
|
constructor(actual: number, limit: number) {
|
|
157
|
-
super(
|
|
204
|
+
super(bodyTooLargeMessage(actual, limit));
|
|
158
205
|
this.name = "PayloadTooLargeError";
|
|
159
206
|
}
|
|
160
207
|
}
|
|
@@ -164,9 +211,11 @@ async function parseBody(req: Tina4Request): Promise<void> {
|
|
|
164
211
|
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return;
|
|
165
212
|
|
|
166
213
|
// Check content-length header against upload size limit before reading body
|
|
214
|
+
const uploadLimit = maxUploadSize();
|
|
215
|
+
|
|
167
216
|
const declaredLength = parseInt(req.headers["content-length"] ?? "0", 10);
|
|
168
|
-
if (declaredLength >
|
|
169
|
-
throw new PayloadTooLargeError(declaredLength,
|
|
217
|
+
if (declaredLength > uploadLimit) {
|
|
218
|
+
throw new PayloadTooLargeError(declaredLength, uploadLimit);
|
|
170
219
|
}
|
|
171
220
|
|
|
172
221
|
const contentType = req.headers["content-type"] ?? "";
|
|
@@ -190,10 +239,10 @@ async function parseBody(req: Tina4Request): Promise<void> {
|
|
|
190
239
|
req.on("data", (chunk: Buffer) => {
|
|
191
240
|
if (refused) return;
|
|
192
241
|
received += chunk.length;
|
|
193
|
-
if (received >
|
|
242
|
+
if (received > uploadLimit) {
|
|
194
243
|
refused = true;
|
|
195
244
|
chunks.length = 0; // drop what we have; the request is dead
|
|
196
|
-
reject(new PayloadTooLargeError(received,
|
|
245
|
+
reject(new PayloadTooLargeError(received, uploadLimit));
|
|
197
246
|
return;
|
|
198
247
|
}
|
|
199
248
|
chunks.push(chunk);
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { ServerResponse } from "node:http";
|
|
2
10
|
import fs from "node:fs";
|
|
3
11
|
import nodePath from "node:path";
|
|
@@ -24,6 +32,39 @@ async function closeSource(source: AsyncIterable<unknown>): Promise<void> {
|
|
|
24
32
|
}
|
|
25
33
|
}
|
|
26
34
|
|
|
35
|
+
/** RFC 9110 `token`: one or more `tchar`. */
|
|
36
|
+
const HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The caller's name as a JSON string literal with non-ASCII escaped, byte-
|
|
40
|
+
* identical to Python's json.dumps (ADR-0068), so a name that is itself broken
|
|
41
|
+
* cannot break the error message or the log line carrying it.
|
|
42
|
+
*/
|
|
43
|
+
export function quoteHeaderName(name: string): string {
|
|
44
|
+
return JSON.stringify(name).replace(/[\u007f-\uffff]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A TypeError carrying node:http's own error code, as setHeader() throws natively. */
|
|
48
|
+
function headerRefusal(message: string, code: string): TypeError {
|
|
49
|
+
return Object.assign(new TypeError(message), { code });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Refuse a response header whose name is not an HTTP token or whose value
|
|
54
|
+
* carries CR, LF or NUL, with the exact ADR-0068 wording. node:http refuses
|
|
55
|
+
* these too, but names a broken header raw (a CR/LF in the name reaches the
|
|
56
|
+
* log line) - this is the ADR's form, checked first.
|
|
57
|
+
*/
|
|
58
|
+
export function assertSafeHeader(name: string, value: unknown): void {
|
|
59
|
+
if (!HEADER_TOKEN.test(name)) {
|
|
60
|
+
throw headerRefusal(`Header name must be a valid HTTP token [${quoteHeaderName(name)}]`, "ERR_INVALID_HTTP_TOKEN");
|
|
61
|
+
}
|
|
62
|
+
const values = Array.isArray(value) ? value : [value];
|
|
63
|
+
if (values.some((item) => /[\r\n\0]/.test(String(item)))) {
|
|
64
|
+
throw headerRefusal(`Invalid character in header content [${quoteHeaderName(name)}]`, "ERR_INVALID_CHAR");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
27
68
|
/** Cache Frond instances by template directory to avoid repeated instantiation. */
|
|
28
69
|
const _frondCache = new Map<string, InstanceType<any>>();
|
|
29
70
|
|
|
@@ -147,10 +188,24 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
147
188
|
res.end(chunk, encoding);
|
|
148
189
|
}
|
|
149
190
|
};
|
|
191
|
+
// Every header this Response sets goes through here, so every one is
|
|
192
|
+
// checked (ADR-0068) - even after the headers are sent, since an unsafe
|
|
193
|
+
// value is a bug in the code that built it either way.
|
|
150
194
|
const safeSetHeader = (name: string, value: string | number | readonly string[]) => {
|
|
195
|
+
assertSafeHeader(name, value);
|
|
151
196
|
if (!res.headersSent) res.setHeader(name, value);
|
|
152
197
|
};
|
|
153
198
|
|
|
199
|
+
// True once the route set Content-Type with header(); response(data) then
|
|
200
|
+
// keeps it instead of detecting one (ADR-0072, #144).
|
|
201
|
+
let contentTypeFromHeader = false;
|
|
202
|
+
const setDetectedContentType = (contentType: string) => {
|
|
203
|
+
if (!contentTypeFromHeader) safeSetHeader("Content-Type", contentType);
|
|
204
|
+
};
|
|
205
|
+
const noteContentTypeHeader = (name: string) => {
|
|
206
|
+
if (name.toLowerCase() === "content-type") contentTypeFromHeader = true;
|
|
207
|
+
};
|
|
208
|
+
|
|
154
209
|
// ── The callable: response(data, status, contentType) ──
|
|
155
210
|
const response = function (data?: unknown, statusCode?: number, contentType?: string): Tina4Response {
|
|
156
211
|
if (res.headersSent) return response;
|
|
@@ -158,6 +213,10 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
158
213
|
// Normalise ORM models / collections / query results so handlers can
|
|
159
214
|
// `return response(model)` without serialising by hand.
|
|
160
215
|
data = toJsonable(data);
|
|
216
|
+
// Any byte array is a binary body: view it as a Buffer (no copy) so every branch below writes it as bytes.
|
|
217
|
+
if (data instanceof Uint8Array && !Buffer.isBuffer(data)) {
|
|
218
|
+
data = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
219
|
+
}
|
|
161
220
|
|
|
162
221
|
if (statusCode !== undefined) {
|
|
163
222
|
res.statusCode = statusCode;
|
|
@@ -166,21 +225,23 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
166
225
|
if (contentType) {
|
|
167
226
|
// Explicit content type
|
|
168
227
|
safeSetHeader("Content-Type", contentType);
|
|
169
|
-
if (
|
|
228
|
+
if (Buffer.isBuffer(data)) {
|
|
229
|
+
safeEnd(data);
|
|
230
|
+
} else if (typeof data === "object" && data !== null) {
|
|
170
231
|
safeEnd(JSON.stringify(data));
|
|
171
232
|
} else {
|
|
172
233
|
safeEnd(data == null ? "" : String(data));
|
|
173
234
|
}
|
|
174
235
|
} else if (typeof data === "object" && data !== null && !Buffer.isBuffer(data)) {
|
|
175
236
|
// dict/array → auto JSON
|
|
176
|
-
|
|
237
|
+
setDetectedContentType("application/json");
|
|
177
238
|
safeEnd(JSON.stringify(data));
|
|
178
239
|
} else if (typeof data === "string") {
|
|
179
240
|
const trimmed = data.trim();
|
|
180
241
|
if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
|
|
181
|
-
|
|
242
|
+
setDetectedContentType("text/html; charset=utf-8");
|
|
182
243
|
} else {
|
|
183
|
-
|
|
244
|
+
setDetectedContentType("text/plain; charset=utf-8");
|
|
184
245
|
}
|
|
185
246
|
safeEnd(data);
|
|
186
247
|
} else if (Buffer.isBuffer(data)) {
|
|
@@ -191,7 +252,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
191
252
|
} else if (data == null) {
|
|
192
253
|
safeEnd("");
|
|
193
254
|
} else {
|
|
194
|
-
|
|
255
|
+
setDetectedContentType("text/plain; charset=utf-8");
|
|
195
256
|
safeEnd(String(data));
|
|
196
257
|
}
|
|
197
258
|
|
|
@@ -246,6 +307,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
246
307
|
|
|
247
308
|
response.header = function (name: string, value: string | number | readonly string[]): Tina4Response {
|
|
248
309
|
safeSetHeader(name, value);
|
|
310
|
+
noteContentTypeHeader(name);
|
|
249
311
|
return response;
|
|
250
312
|
};
|
|
251
313
|
|
|
@@ -254,9 +316,11 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
254
316
|
*/
|
|
255
317
|
response.addHeader = function (name: string, value: string): void {
|
|
256
318
|
safeSetHeader(name, value);
|
|
319
|
+
noteContentTypeHeader(name);
|
|
257
320
|
};
|
|
258
321
|
|
|
259
322
|
response.redirect = function (url: string, code?: number): Tina4Response {
|
|
323
|
+
assertSafeHeader("Location", url); // before the status changes
|
|
260
324
|
if (res.headersSent) return response;
|
|
261
325
|
res.statusCode = code ?? 302;
|
|
262
326
|
safeSetHeader("Location", url);
|
|
@@ -265,6 +329,15 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
265
329
|
};
|
|
266
330
|
|
|
267
331
|
response.cookie = function (name: string, value: string, options?: CookieOptions): Tina4Response {
|
|
332
|
+
// The name and value are percent-encoded below, so they cannot carry CR,
|
|
333
|
+
// LF, NUL or ';' to the wire and keep being encoded (ADR-0068). Attribute
|
|
334
|
+
// values are written as given, so one carrying those is refused: a ';'
|
|
335
|
+
// would add an attribute (say Domain=) the application never set.
|
|
336
|
+
for (const attribute of [options?.path, options?.domain, options?.sameSite, options?.maxAge]) {
|
|
337
|
+
if (attribute !== undefined && /[\r\n\0;]/.test(String(attribute))) {
|
|
338
|
+
throw headerRefusal(`Invalid character in cookie content [${quoteHeaderName(name)}]`, "ERR_INVALID_CHAR");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
268
341
|
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
269
342
|
if (options?.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);
|
|
270
343
|
if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
@@ -419,6 +492,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
419
492
|
source: AsyncIterable<string | Buffer>,
|
|
420
493
|
contentType: string = "text/event-stream",
|
|
421
494
|
): Promise<Tina4Response> {
|
|
495
|
+
assertSafeHeader("Content-Type", contentType);
|
|
422
496
|
if (res.headersSent) return response;
|
|
423
497
|
res.writeHead(200, {
|
|
424
498
|
"Content-Type": contentType,
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import { readdirSync, statSync, mkdirSync, writeFileSync, existsSync } from "node:fs";
|
|
2
10
|
import { join, relative, basename, extname } from "node:path";
|
|
3
11
|
import type { RouteDefinition, RouteHandler, RouteMeta } from "./types.js";
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import type { RouteHandler, RouteDefinition, RouteMeta, Middleware, MiddlewareSpec, Tina4Request, Tina4Response, WebSocketRouteHandler, WebSocketRouteDefinition } from "./types.js";
|
|
2
10
|
import { isTruthy } from "./dotenv.js";
|
|
3
11
|
import { MiddlewareRunner, isMiddlewareClass } from "./middleware.js";
|