@danceroutine/tango-core 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/{TangoError-NPkVPfuH.js → TangoError-DdQVQNZU.js} +5 -1
- package/dist/TangoError-DdQVQNZU.js.map +1 -0
- package/dist/errors/AuthenticationError.d.ts +6 -1
- package/dist/errors/ConflictError.d.ts +6 -1
- package/dist/errors/NotFoundError.d.ts +6 -1
- package/dist/errors/PermissionDenied.d.ts +6 -1
- package/dist/errors/TangoError.d.ts +13 -1
- package/dist/errors/ValidationError.d.ts +5 -1
- package/dist/errors/factories/HttpErrorFactory.d.ts +20 -11
- package/dist/errors/factories/index.d.ts +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +4 -4
- package/dist/{errors-CzSQXdgI.js → errors-_tWsmNyZ.js} +81 -34
- package/dist/errors-_tWsmNyZ.js.map +1 -0
- package/dist/http/TangoBody.d.ts +43 -18
- package/dist/http/TangoHeaders.d.ts +10 -5
- package/dist/http/TangoQueryParams.d.ts +69 -0
- package/dist/http/TangoRequest.d.ts +82 -0
- package/dist/http/TangoResponse.d.ts +184 -49
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +4 -4
- package/dist/{http-DOLwwAYt.js → http-D20MQa6p.js} +675 -295
- package/dist/http-D20MQa6p.js.map +1 -0
- package/dist/index.d.ts +9 -7
- package/dist/index.js +7 -6
- package/dist/logging/ConsoleLogger.d.ts +15 -0
- package/dist/logging/Logger.d.ts +13 -0
- package/dist/logging/getLogger.d.ts +23 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging-BWeD4HOO.js +48 -0
- package/dist/logging-BWeD4HOO.js.map +1 -0
- package/dist/runtime/binary/isArrayBuffer.d.ts +3 -0
- package/dist/runtime/binary/isBlob.d.ts +3 -0
- package/dist/runtime/binary/isUint8Array.d.ts +3 -0
- package/dist/runtime/date/isDate.d.ts +3 -0
- package/dist/runtime/error/isError.d.ts +3 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/object/index.d.ts +1 -0
- package/dist/runtime/object/isNil.d.ts +4 -0
- package/dist/runtime/object/isObject.d.ts +3 -0
- package/dist/runtime/web/isFile.d.ts +3 -0
- package/dist/runtime/web/isFormData.d.ts +3 -0
- package/dist/runtime/web/isReadableStream.d.ts +3 -0
- package/dist/runtime/web/isURLSearchParams.d.ts +3 -0
- package/dist/{runtime-DPpCYEe_.js → runtime-B8KkgD3R.js} +14 -4
- package/dist/runtime-B8KkgD3R.js.map +1 -0
- package/dist/sql/SqlDialect.d.ts +5 -0
- package/dist/sql/SqlIdentifierRole.d.ts +13 -0
- package/dist/sql/SqlSafetyEngine.d.ts +50 -0
- package/dist/sql/TrustedSqlFragment.d.ts +5 -0
- package/dist/sql/ValidatedSqlIdentifier.d.ts +7 -0
- package/dist/sql/index.d.ts +13 -0
- package/dist/sql/index.js +3 -0
- package/dist/sql/isTrustedSqlFragment.d.ts +5 -0
- package/dist/sql/quoteSqlIdentifier.d.ts +6 -0
- package/dist/sql/trustedSql.d.ts +5 -0
- package/dist/sql/validateSqlIdentifier.d.ts +6 -0
- package/dist/sql-D3frkfy-.js +116 -0
- package/dist/sql-D3frkfy-.js.map +1 -0
- package/package.json +65 -54
- package/dist/TangoError-NPkVPfuH.js.map +0 -1
- package/dist/errors/factories/HttpErrorFactory.js +0 -91
- package/dist/errors-CzSQXdgI.js.map +0 -1
- package/dist/http/TangoHeaders.js +0 -396
- package/dist/http/TangoResponse.js +0 -556
- package/dist/http-DOLwwAYt.js.map +0 -1
- package/dist/result/Err.d.ts +0 -21
- package/dist/result/Ok.d.ts +0 -21
- package/dist/result/Result.d.ts +0 -33
- package/dist/result/index.d.ts +0 -8
- package/dist/result/index.js +0 -3
- package/dist/result-CBqw9Hlg.js +0 -82
- package/dist/result-CBqw9Hlg.js.map +0 -1
- package/dist/runtime-DPpCYEe_.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const INTERNAL_SQL_IDENTIFIER_ROLE: {
|
|
2
|
+
readonly TABLE: "table";
|
|
3
|
+
readonly COLUMN: "column";
|
|
4
|
+
readonly PRIMARY_KEY: "primaryKey";
|
|
5
|
+
readonly INDEX: "index";
|
|
6
|
+
readonly ALIAS: "alias";
|
|
7
|
+
readonly CONSTRAINT: "constraint";
|
|
8
|
+
readonly SCHEMA: "schema";
|
|
9
|
+
readonly RELATION_TABLE: "relationTable";
|
|
10
|
+
readonly RELATION_FOREIGN_KEY: "relationForeignKey";
|
|
11
|
+
readonly RELATION_TARGET_PRIMARY_KEY: "relationTargetPrimaryKey";
|
|
12
|
+
};
|
|
13
|
+
export type SqlIdentifierRole = (typeof INTERNAL_SQL_IDENTIFIER_ROLE)[keyof typeof INTERNAL_SQL_IDENTIFIER_ROLE];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SqlDialect } from './SqlDialect';
|
|
2
|
+
import type { SqlIdentifierRole } from './SqlIdentifierRole';
|
|
3
|
+
import type { TrustedSqlFragment } from './TrustedSqlFragment';
|
|
4
|
+
import type { ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';
|
|
5
|
+
export type SqlIdentifierRequest = {
|
|
6
|
+
key: string;
|
|
7
|
+
role: SqlIdentifierRole;
|
|
8
|
+
value: string;
|
|
9
|
+
allowlist?: readonly string[];
|
|
10
|
+
};
|
|
11
|
+
export type SqlLookupTokenRequest = {
|
|
12
|
+
key: string;
|
|
13
|
+
lookup: string;
|
|
14
|
+
allowed: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
export type SqlRawFragmentRequest = {
|
|
17
|
+
key: string;
|
|
18
|
+
value: TrustedSqlFragment;
|
|
19
|
+
};
|
|
20
|
+
export type SqlSafetyRequest = {
|
|
21
|
+
dialect?: SqlDialect;
|
|
22
|
+
identifiers?: readonly SqlIdentifierRequest[];
|
|
23
|
+
lookupTokens?: readonly SqlLookupTokenRequest[];
|
|
24
|
+
rawFragments?: readonly SqlRawFragmentRequest[];
|
|
25
|
+
};
|
|
26
|
+
export type ValidatedSqlLookupToken = {
|
|
27
|
+
lookup: string;
|
|
28
|
+
};
|
|
29
|
+
export type ValidatedSqlSafetyResult = {
|
|
30
|
+
identifiers: Record<string, ValidatedSqlIdentifier>;
|
|
31
|
+
lookupTokens: Record<string, ValidatedSqlLookupToken>;
|
|
32
|
+
rawFragments: Record<string, TrustedSqlFragment>;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Canonical SQL safety policy engine shared across Tango packages.
|
|
36
|
+
*/
|
|
37
|
+
export declare class SqlSafetyEngine {
|
|
38
|
+
static readonly BRAND: "tango.core.sql_safety_engine";
|
|
39
|
+
readonly __tangoBrand: typeof SqlSafetyEngine.BRAND;
|
|
40
|
+
/**
|
|
41
|
+
* Narrow an unknown value to `SqlSafetyEngine`.
|
|
42
|
+
*/
|
|
43
|
+
static isSqlSafetyEngine(value: unknown): value is SqlSafetyEngine;
|
|
44
|
+
/**
|
|
45
|
+
* Validate a canonical SQL safety request and return trusted tokens.
|
|
46
|
+
*/
|
|
47
|
+
validate(request: SqlSafetyRequest): ValidatedSqlSafetyResult;
|
|
48
|
+
private validateLookupToken;
|
|
49
|
+
private validateRawFragment;
|
|
50
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SqlIdentifierRole } from './SqlIdentifierRole';
|
|
2
|
+
export declare const VALIDATED_SQL_IDENTIFIER_BRAND: "tango.core.validated_sql_identifier";
|
|
3
|
+
export type ValidatedSqlIdentifier = {
|
|
4
|
+
readonly __tangoBrand: typeof VALIDATED_SQL_IDENTIFIER_BRAND;
|
|
5
|
+
readonly role: SqlIdentifierRole;
|
|
6
|
+
readonly value: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain boundary barrel: centralizes SQL safety primitives and policy helpers.
|
|
3
|
+
*/
|
|
4
|
+
export type { SqlDialect } from './SqlDialect';
|
|
5
|
+
export type { SqlIdentifierRole } from './SqlIdentifierRole';
|
|
6
|
+
export type { TrustedSqlFragment } from './TrustedSqlFragment';
|
|
7
|
+
export type { ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';
|
|
8
|
+
export { SqlSafetyEngine } from './SqlSafetyEngine';
|
|
9
|
+
export type { SqlIdentifierRequest, SqlLookupTokenRequest, SqlRawFragmentRequest, SqlSafetyRequest, ValidatedSqlLookupToken, ValidatedSqlSafetyResult, } from './SqlSafetyEngine';
|
|
10
|
+
export { trustedSql } from './trustedSql';
|
|
11
|
+
export { isTrustedSqlFragment } from './isTrustedSqlFragment';
|
|
12
|
+
export { validateSqlIdentifier } from './validateSqlIdentifier';
|
|
13
|
+
export { quoteSqlIdentifier } from './quoteSqlIdentifier';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SqlDialect } from './SqlDialect';
|
|
2
|
+
import type { ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';
|
|
3
|
+
/**
|
|
4
|
+
* Quote a validated identifier for the target SQL dialect.
|
|
5
|
+
*/
|
|
6
|
+
export declare function quoteSqlIdentifier(identifier: ValidatedSqlIdentifier, _dialect: SqlDialect): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';
|
|
2
|
+
import { type SqlIdentifierRole } from './SqlIdentifierRole';
|
|
3
|
+
/**
|
|
4
|
+
* Validate an identifier against Tango's SQL safety policy.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateSqlIdentifier(value: string, role: SqlIdentifierRole, allowlist?: readonly string[]): ValidatedSqlIdentifier;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { __export } from "./chunk-BkvOhyD0.js";
|
|
2
|
+
|
|
3
|
+
//#region src/sql/TrustedSqlFragment.ts
|
|
4
|
+
const TRUSTED_SQL_FRAGMENT_BRAND = "tango.core.trusted_sql_fragment";
|
|
5
|
+
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/sql/isTrustedSqlFragment.ts
|
|
8
|
+
function isTrustedSqlFragment(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === TRUSTED_SQL_FRAGMENT_BRAND && typeof value.sql === "string";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/sql/ValidatedSqlIdentifier.ts
|
|
14
|
+
const VALIDATED_SQL_IDENTIFIER_BRAND = "tango.core.validated_sql_identifier";
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/sql/SqlIdentifierRole.ts
|
|
18
|
+
const INTERNAL_SQL_IDENTIFIER_ROLE = {
|
|
19
|
+
TABLE: "table",
|
|
20
|
+
COLUMN: "column",
|
|
21
|
+
PRIMARY_KEY: "primaryKey",
|
|
22
|
+
INDEX: "index",
|
|
23
|
+
ALIAS: "alias",
|
|
24
|
+
CONSTRAINT: "constraint",
|
|
25
|
+
SCHEMA: "schema",
|
|
26
|
+
RELATION_TABLE: "relationTable",
|
|
27
|
+
RELATION_FOREIGN_KEY: "relationForeignKey",
|
|
28
|
+
RELATION_TARGET_PRIMARY_KEY: "relationTargetPrimaryKey"
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/sql/validateSqlIdentifier.ts
|
|
33
|
+
const SQL_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
34
|
+
const ROLE_LABELS = {
|
|
35
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.TABLE]: "table name",
|
|
36
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.COLUMN]: "column",
|
|
37
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.PRIMARY_KEY]: "primary key",
|
|
38
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.INDEX]: "index",
|
|
39
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.ALIAS]: "alias",
|
|
40
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.CONSTRAINT]: "constraint",
|
|
41
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.SCHEMA]: "schema",
|
|
42
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.RELATION_TABLE]: "relation table",
|
|
43
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.RELATION_FOREIGN_KEY]: "relation foreign key",
|
|
44
|
+
[INTERNAL_SQL_IDENTIFIER_ROLE.RELATION_TARGET_PRIMARY_KEY]: "relation target primary key"
|
|
45
|
+
};
|
|
46
|
+
function validateSqlIdentifier(value, role, allowlist) {
|
|
47
|
+
const label = ROLE_LABELS[role];
|
|
48
|
+
if (!SQL_IDENTIFIER_PATTERN.test(value)) throw new Error(`Invalid SQL ${label}: '${value}'.`);
|
|
49
|
+
if (allowlist && !allowlist.includes(value)) throw new Error(`Unknown SQL ${label}: '${value}'.`);
|
|
50
|
+
return {
|
|
51
|
+
__tangoBrand: VALIDATED_SQL_IDENTIFIER_BRAND,
|
|
52
|
+
role,
|
|
53
|
+
value
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/sql/SqlSafetyEngine.ts
|
|
59
|
+
var SqlSafetyEngine = class SqlSafetyEngine {
|
|
60
|
+
static BRAND = "tango.core.sql_safety_engine";
|
|
61
|
+
__tangoBrand = SqlSafetyEngine.BRAND;
|
|
62
|
+
/**
|
|
63
|
+
* Narrow an unknown value to `SqlSafetyEngine`.
|
|
64
|
+
*/
|
|
65
|
+
static isSqlSafetyEngine(value) {
|
|
66
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === SqlSafetyEngine.BRAND;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate a canonical SQL safety request and return trusted tokens.
|
|
70
|
+
*/
|
|
71
|
+
validate(request) {
|
|
72
|
+
return {
|
|
73
|
+
identifiers: Object.fromEntries((request.identifiers ?? []).map((entry) => [entry.key, validateSqlIdentifier(entry.value, entry.role, entry.allowlist)])),
|
|
74
|
+
lookupTokens: Object.fromEntries((request.lookupTokens ?? []).map((entry) => [entry.key, this.validateLookupToken(entry)])),
|
|
75
|
+
rawFragments: Object.fromEntries((request.rawFragments ?? []).map((entry) => [entry.key, this.validateRawFragment(entry)]))
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
validateLookupToken(entry) {
|
|
79
|
+
if (!entry.allowed.includes(entry.lookup)) throw new Error(`Unknown lookup: ${entry.lookup}`);
|
|
80
|
+
return { lookup: entry.lookup };
|
|
81
|
+
}
|
|
82
|
+
validateRawFragment(entry) {
|
|
83
|
+
if (!isTrustedSqlFragment(entry.value)) throw new Error(`Untrusted raw SQL fragment for '${entry.key}'.`);
|
|
84
|
+
return entry.value;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/sql/trustedSql.ts
|
|
90
|
+
function trustedSql(sql) {
|
|
91
|
+
return {
|
|
92
|
+
__tangoBrand: TRUSTED_SQL_FRAGMENT_BRAND,
|
|
93
|
+
sql
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/sql/quoteSqlIdentifier.ts
|
|
99
|
+
function quoteSqlIdentifier(identifier, _dialect) {
|
|
100
|
+
return `"${identifier.value.replaceAll("\"", "\"\"")}"`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/sql/index.ts
|
|
105
|
+
var sql_exports = {};
|
|
106
|
+
__export(sql_exports, {
|
|
107
|
+
SqlSafetyEngine: () => SqlSafetyEngine,
|
|
108
|
+
isTrustedSqlFragment: () => isTrustedSqlFragment,
|
|
109
|
+
quoteSqlIdentifier: () => quoteSqlIdentifier,
|
|
110
|
+
trustedSql: () => trustedSql,
|
|
111
|
+
validateSqlIdentifier: () => validateSqlIdentifier
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
export { SqlSafetyEngine, isTrustedSqlFragment, quoteSqlIdentifier, sql_exports, trustedSql, validateSqlIdentifier };
|
|
116
|
+
//# sourceMappingURL=sql-D3frkfy-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-D3frkfy-.js","names":["value: unknown","ROLE_LABELS: Record<SqlIdentifierRole, string>","value: string","role: SqlIdentifierRole","allowlist?: readonly string[]","value: unknown","request: SqlSafetyRequest","entry: SqlLookupTokenRequest","entry: SqlRawFragmentRequest","sql: string","identifier: ValidatedSqlIdentifier","_dialect: SqlDialect"],"sources":["../src/sql/TrustedSqlFragment.ts","../src/sql/isTrustedSqlFragment.ts","../src/sql/ValidatedSqlIdentifier.ts","../src/sql/SqlIdentifierRole.ts","../src/sql/validateSqlIdentifier.ts","../src/sql/SqlSafetyEngine.ts","../src/sql/trustedSql.ts","../src/sql/quoteSqlIdentifier.ts","../src/sql/index.ts"],"sourcesContent":["export const TRUSTED_SQL_FRAGMENT_BRAND = 'tango.core.trusted_sql_fragment' as const;\n\nexport type TrustedSqlFragment = {\n readonly __tangoBrand: typeof TRUSTED_SQL_FRAGMENT_BRAND;\n readonly sql: string;\n};\n","import { TRUSTED_SQL_FRAGMENT_BRAND, type TrustedSqlFragment } from './TrustedSqlFragment';\n\n/**\n * Narrow an unknown value to a trusted raw SQL fragment.\n */\nexport function isTrustedSqlFragment(value: unknown): value is TrustedSqlFragment {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === TRUSTED_SQL_FRAGMENT_BRAND &&\n typeof (value as { sql?: unknown }).sql === 'string'\n );\n}\n","import type { SqlIdentifierRole } from './SqlIdentifierRole';\n\nexport const VALIDATED_SQL_IDENTIFIER_BRAND = 'tango.core.validated_sql_identifier' as const;\n\nexport type ValidatedSqlIdentifier = {\n readonly __tangoBrand: typeof VALIDATED_SQL_IDENTIFIER_BRAND;\n readonly role: SqlIdentifierRole;\n readonly value: string;\n};\n","export const INTERNAL_SQL_IDENTIFIER_ROLE = {\n TABLE: 'table',\n COLUMN: 'column',\n PRIMARY_KEY: 'primaryKey',\n INDEX: 'index',\n ALIAS: 'alias',\n CONSTRAINT: 'constraint',\n SCHEMA: 'schema',\n RELATION_TABLE: 'relationTable',\n RELATION_FOREIGN_KEY: 'relationForeignKey',\n RELATION_TARGET_PRIMARY_KEY: 'relationTargetPrimaryKey',\n} as const;\n\nexport type SqlIdentifierRole = (typeof INTERNAL_SQL_IDENTIFIER_ROLE)[keyof typeof INTERNAL_SQL_IDENTIFIER_ROLE];\n","import { VALIDATED_SQL_IDENTIFIER_BRAND, type ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';\nimport { INTERNAL_SQL_IDENTIFIER_ROLE, type SqlIdentifierRole } from './SqlIdentifierRole';\n\nconst SQL_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nconst ROLE_LABELS: Record<SqlIdentifierRole, string> = {\n [INTERNAL_SQL_IDENTIFIER_ROLE.TABLE]: 'table name',\n [INTERNAL_SQL_IDENTIFIER_ROLE.COLUMN]: 'column',\n [INTERNAL_SQL_IDENTIFIER_ROLE.PRIMARY_KEY]: 'primary key',\n [INTERNAL_SQL_IDENTIFIER_ROLE.INDEX]: 'index',\n [INTERNAL_SQL_IDENTIFIER_ROLE.ALIAS]: 'alias',\n [INTERNAL_SQL_IDENTIFIER_ROLE.CONSTRAINT]: 'constraint',\n [INTERNAL_SQL_IDENTIFIER_ROLE.SCHEMA]: 'schema',\n [INTERNAL_SQL_IDENTIFIER_ROLE.RELATION_TABLE]: 'relation table',\n [INTERNAL_SQL_IDENTIFIER_ROLE.RELATION_FOREIGN_KEY]: 'relation foreign key',\n [INTERNAL_SQL_IDENTIFIER_ROLE.RELATION_TARGET_PRIMARY_KEY]: 'relation target primary key',\n};\n\n/**\n * Validate an identifier against Tango's SQL safety policy.\n */\nexport function validateSqlIdentifier(\n value: string,\n role: SqlIdentifierRole,\n allowlist?: readonly string[]\n): ValidatedSqlIdentifier {\n const label = ROLE_LABELS[role];\n\n if (!SQL_IDENTIFIER_PATTERN.test(value)) {\n throw new Error(`Invalid SQL ${label}: '${value}'.`);\n }\n\n if (allowlist && !allowlist.includes(value)) {\n throw new Error(`Unknown SQL ${label}: '${value}'.`);\n }\n\n return {\n __tangoBrand: VALIDATED_SQL_IDENTIFIER_BRAND,\n role,\n value,\n };\n}\n","import type { SqlDialect } from './SqlDialect';\nimport type { SqlIdentifierRole } from './SqlIdentifierRole';\nimport { isTrustedSqlFragment } from './isTrustedSqlFragment';\nimport type { TrustedSqlFragment } from './TrustedSqlFragment';\nimport { validateSqlIdentifier } from './validateSqlIdentifier';\nimport type { ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';\n\nexport type SqlIdentifierRequest = {\n key: string;\n role: SqlIdentifierRole;\n value: string;\n allowlist?: readonly string[];\n};\n\nexport type SqlLookupTokenRequest = {\n key: string;\n lookup: string;\n allowed: readonly string[];\n};\n\nexport type SqlRawFragmentRequest = {\n key: string;\n value: TrustedSqlFragment;\n};\n\nexport type SqlSafetyRequest = {\n dialect?: SqlDialect;\n identifiers?: readonly SqlIdentifierRequest[];\n lookupTokens?: readonly SqlLookupTokenRequest[];\n rawFragments?: readonly SqlRawFragmentRequest[];\n};\n\nexport type ValidatedSqlLookupToken = {\n lookup: string;\n};\n\nexport type ValidatedSqlSafetyResult = {\n identifiers: Record<string, ValidatedSqlIdentifier>;\n lookupTokens: Record<string, ValidatedSqlLookupToken>;\n rawFragments: Record<string, TrustedSqlFragment>;\n};\n\n/**\n * Canonical SQL safety policy engine shared across Tango packages.\n */\nexport class SqlSafetyEngine {\n static readonly BRAND = 'tango.core.sql_safety_engine' as const;\n readonly __tangoBrand: typeof SqlSafetyEngine.BRAND = SqlSafetyEngine.BRAND;\n\n /**\n * Narrow an unknown value to `SqlSafetyEngine`.\n */\n static isSqlSafetyEngine(value: unknown): value is SqlSafetyEngine {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === SqlSafetyEngine.BRAND\n );\n }\n\n /**\n * Validate a canonical SQL safety request and return trusted tokens.\n */\n validate(request: SqlSafetyRequest): ValidatedSqlSafetyResult {\n return {\n identifiers: Object.fromEntries(\n (request.identifiers ?? []).map((entry) => [\n entry.key,\n validateSqlIdentifier(entry.value, entry.role, entry.allowlist),\n ])\n ),\n lookupTokens: Object.fromEntries(\n (request.lookupTokens ?? []).map((entry) => [entry.key, this.validateLookupToken(entry)])\n ),\n rawFragments: Object.fromEntries(\n (request.rawFragments ?? []).map((entry) => [entry.key, this.validateRawFragment(entry)])\n ),\n };\n }\n\n private validateLookupToken(entry: SqlLookupTokenRequest): ValidatedSqlLookupToken {\n if (!entry.allowed.includes(entry.lookup)) {\n throw new Error(`Unknown lookup: ${entry.lookup}`);\n }\n\n return {\n lookup: entry.lookup,\n };\n }\n\n private validateRawFragment(entry: SqlRawFragmentRequest): TrustedSqlFragment {\n if (!isTrustedSqlFragment(entry.value)) {\n throw new Error(`Untrusted raw SQL fragment for '${entry.key}'.`);\n }\n\n return entry.value;\n }\n}\n","import { TRUSTED_SQL_FRAGMENT_BRAND, type TrustedSqlFragment } from './TrustedSqlFragment';\n\n/**\n * Explicitly opt into embedding a reviewed raw SQL fragment.\n */\nexport function trustedSql(sql: string): TrustedSqlFragment {\n return {\n __tangoBrand: TRUSTED_SQL_FRAGMENT_BRAND,\n sql,\n };\n}\n","import type { SqlDialect } from './SqlDialect';\nimport type { ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';\n\n/**\n * Quote a validated identifier for the target SQL dialect.\n */\nexport function quoteSqlIdentifier(identifier: ValidatedSqlIdentifier, _dialect: SqlDialect): string {\n return `\"${identifier.value.replaceAll('\"', '\"\"')}\"`;\n}\n","/**\n * Domain boundary barrel: centralizes SQL safety primitives and policy helpers.\n */\n\nexport type { SqlDialect } from './SqlDialect';\nexport type { SqlIdentifierRole } from './SqlIdentifierRole';\nexport type { TrustedSqlFragment } from './TrustedSqlFragment';\nexport type { ValidatedSqlIdentifier } from './ValidatedSqlIdentifier';\nexport { SqlSafetyEngine } from './SqlSafetyEngine';\nexport type {\n SqlIdentifierRequest,\n SqlLookupTokenRequest,\n SqlRawFragmentRequest,\n SqlSafetyRequest,\n ValidatedSqlLookupToken,\n ValidatedSqlSafetyResult,\n} from './SqlSafetyEngine';\nexport { trustedSql } from './trustedSql';\nexport { isTrustedSqlFragment } from './isTrustedSqlFragment';\nexport { validateSqlIdentifier } from './validateSqlIdentifier';\nexport { quoteSqlIdentifier } from './quoteSqlIdentifier';\n"],"mappings":";;;MAAa,6BAA6B;;;;ACKnC,SAAS,qBAAqBA,OAA6C;AAC9E,eACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,qCAC/C,MAA4B,QAAQ;AAEnD;;;;MCVY,iCAAiC;;;;MCFjC,+BAA+B;CACxC,OAAO;CACP,QAAQ;CACR,aAAa;CACb,OAAO;CACP,OAAO;CACP,YAAY;CACZ,QAAQ;CACR,gBAAgB;CAChB,sBAAsB;CACtB,6BAA6B;AAChC;;;;ACRD,MAAM,yBAAyB;AAE/B,MAAMC,cAAiD;EAClD,6BAA6B,QAAQ;EACrC,6BAA6B,SAAS;EACtC,6BAA6B,cAAc;EAC3C,6BAA6B,QAAQ;EACrC,6BAA6B,QAAQ;EACrC,6BAA6B,aAAa;EAC1C,6BAA6B,SAAS;EACtC,6BAA6B,iBAAiB;EAC9C,6BAA6B,uBAAuB;EACpD,6BAA6B,8BAA8B;AAC/D;AAKM,SAAS,sBACZC,OACAC,MACAC,WACsB;CACtB,MAAM,QAAQ,YAAY;AAE1B,MAAK,uBAAuB,KAAK,MAAM,CACnC,OAAM,IAAI,OAAO,cAAc,MAAM,KAAK,MAAM;AAGpD,KAAI,cAAc,UAAU,SAAS,MAAM,CACvC,OAAM,IAAI,OAAO,cAAc,MAAM,KAAK,MAAM;AAGpD,QAAO;EACH,cAAc;EACd;EACA;CACH;AACJ;;;;ICIY,kBAAN,MAAM,gBAAgB;CACzB,OAAgB,QAAQ;CACxB,eAAsD,gBAAgB;;;;CAKtE,OAAO,kBAAkBC,OAA0C;AAC/D,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,gBAAgB;CAE9E;;;;CAKD,SAASC,SAAqD;AAC1D,SAAO;GACH,aAAa,OAAO,YAChB,CAAC,QAAQ,eAAe,CAAE,GAAE,IAAI,CAAC,UAAU,CACvC,MAAM,KACN,sBAAsB,MAAM,OAAO,MAAM,MAAM,MAAM,UAAU,AAClE,EAAC,CACL;GACD,cAAc,OAAO,YACjB,CAAC,QAAQ,gBAAgB,CAAE,GAAE,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,oBAAoB,MAAM,AAAC,EAAC,CAC5F;GACD,cAAc,OAAO,YACjB,CAAC,QAAQ,gBAAgB,CAAE,GAAE,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,oBAAoB,MAAM,AAAC,EAAC,CAC5F;EACJ;CACJ;CAED,oBAA4BC,OAAuD;AAC/E,OAAK,MAAM,QAAQ,SAAS,MAAM,OAAO,CACrC,OAAM,IAAI,OAAO,kBAAkB,MAAM,OAAO;AAGpD,SAAO,EACH,QAAQ,MAAM,OACjB;CACJ;CAED,oBAA4BC,OAAkD;AAC1E,OAAK,qBAAqB,MAAM,MAAM,CAClC,OAAM,IAAI,OAAO,kCAAkC,MAAM,IAAI;AAGjE,SAAO,MAAM;CAChB;AACJ;;;;AC5FM,SAAS,WAAWC,KAAiC;AACxD,QAAO;EACH,cAAc;EACd;CACH;AACJ;;;;ACJM,SAAS,mBAAmBC,YAAoCC,UAA8B;AACjG,SAAQ,GAAG,WAAW,MAAM,WAAW,MAAK,OAAK,CAAC;AACrD"}
|
package/package.json
CHANGED
|
@@ -1,59 +1,70 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
"./errors": {
|
|
14
|
-
"types": "./dist/errors/index.d.ts",
|
|
15
|
-
"import": "./dist/errors/index.js"
|
|
16
|
-
},
|
|
17
|
-
"./http": {
|
|
18
|
-
"types": "./dist/http/index.d.ts",
|
|
19
|
-
"import": "./dist/http/index.js"
|
|
20
|
-
},
|
|
21
|
-
"./result": {
|
|
22
|
-
"types": "./dist/result/index.d.ts",
|
|
23
|
-
"import": "./dist/result/index.js"
|
|
24
|
-
},
|
|
25
|
-
"./runtime": {
|
|
26
|
-
"types": "./dist/runtime/index.d.ts",
|
|
27
|
-
"import": "./dist/runtime/index.js"
|
|
28
|
-
}
|
|
2
|
+
"name": "@danceroutine/tango-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core types, errors, and result utilities for Tango",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
29
12
|
},
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"scripts": {
|
|
34
|
-
"build": "tsdown",
|
|
35
|
-
"test": "vitest run --coverage",
|
|
36
|
-
"test:watch": "vitest",
|
|
37
|
-
"typecheck": "tsc --noEmit"
|
|
13
|
+
"./errors": {
|
|
14
|
+
"types": "./dist/errors/index.d.ts",
|
|
15
|
+
"import": "./dist/errors/index.js"
|
|
38
16
|
},
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"core",
|
|
43
|
-
"errors",
|
|
44
|
-
"result"
|
|
45
|
-
],
|
|
46
|
-
"author": "Pedro Del Moral Lopez",
|
|
47
|
-
"license": "MIT",
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "https://github.com/danceroutine/tango.git",
|
|
51
|
-
"directory": "packages/core"
|
|
17
|
+
"./http": {
|
|
18
|
+
"types": "./dist/http/index.d.ts",
|
|
19
|
+
"import": "./dist/http/index.js"
|
|
52
20
|
},
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
21
|
+
"./result": {
|
|
22
|
+
"types": "./dist/result/index.d.ts",
|
|
23
|
+
"import": "./dist/result/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./logging": {
|
|
26
|
+
"types": "./dist/logging/index.d.ts",
|
|
27
|
+
"import": "./dist/logging/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./sql": {
|
|
30
|
+
"types": "./dist/sql/index.d.ts",
|
|
31
|
+
"import": "./dist/sql/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./runtime": {
|
|
34
|
+
"types": "./dist/runtime/index.d.ts",
|
|
35
|
+
"import": "./dist/runtime/index.js"
|
|
58
36
|
}
|
|
59
|
-
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"keywords": [
|
|
42
|
+
"tango",
|
|
43
|
+
"typescript",
|
|
44
|
+
"core",
|
|
45
|
+
"errors",
|
|
46
|
+
"sql"
|
|
47
|
+
],
|
|
48
|
+
"author": "Pedro Del Moral Lopez",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/danceroutine/tango.git",
|
|
53
|
+
"directory": "packages/core"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.9.0",
|
|
57
|
+
"tsdown": "^0.4.0",
|
|
58
|
+
"typescript": "^5.6.3",
|
|
59
|
+
"vitest": "^4.0.6",
|
|
60
|
+
"zod": "^4.0.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsdown",
|
|
64
|
+
"test": "vitest run --coverage",
|
|
65
|
+
"test:watch": "vitest",
|
|
66
|
+
"typecheck": "pnpm run typecheck:prod && pnpm run typecheck:test",
|
|
67
|
+
"typecheck:prod": "tsc --noEmit -p tsconfig.json",
|
|
68
|
+
"typecheck:test": "tsc --noEmit -p tsconfig.tests.json"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TangoError-NPkVPfuH.js","names":["err: unknown"],"sources":["../src/errors/TangoError.ts"],"sourcesContent":["import type { HttpError } from './HttpError';\nexport type ErrorDetails = Record<string, string[]> | null | undefined;\n\nexport type ProblemDetails<TDetails extends ErrorDetails = null> = {\n code: string;\n message: string;\n details?: TDetails;\n fields?: Record<string, string[]>;\n};\n\nexport type ErrorEnvelope<TDetails extends ErrorDetails = null> = {\n error: ProblemDetails<TDetails>;\n};\n\nexport abstract class TangoError extends Error {\n // String brand avoids instanceof and survives cross-package boundaries.\n readonly __tangoErrorBrand: 'tango.error' = 'tango.error';\n abstract status: number;\n\n protected abstract getDetails(): ErrorDetails;\n\n protected abstract getErrorName(): string;\n\n static isTangoError(err: unknown): err is TangoError {\n return !!err && (err as { __tangoErrorBrand?: string }).__tangoErrorBrand === 'tango.error';\n }\n\n static isProblemDetails(err: unknown): err is ProblemDetails {\n return (\n !!err &&\n typeof err === 'object' &&\n err !== null &&\n 'code' in err &&\n typeof (err as { code: unknown }).code === 'string' &&\n 'message' in err &&\n typeof (err as { message: unknown }).message === 'string'\n );\n }\n toErrorEnvelope(): ErrorEnvelope<ErrorDetails> {\n return {\n error: {\n code: this.getErrorName(),\n message: this.message,\n details: this.getDetails(),\n },\n };\n }\n\n toHttpError(): HttpError {\n return {\n status: this.status,\n body: {\n error: this.message,\n details: this.getDetails(),\n },\n };\n }\n}\n"],"mappings":";;IAcsB,aAAf,cAAkC,MAAM;CAE3C,oBAA4C;CAO5C,OAAO,aAAaA,KAAiC;AACjD,WAAS,OAAQ,IAAuC,sBAAsB;CACjF;CAED,OAAO,iBAAiBA,KAAqC;AACzD,WACM,cACK,QAAQ,YACf,QAAQ,QACR,UAAU,cACF,IAA0B,SAAS,YAC3C,aAAa,cACL,IAA6B,YAAY;CAExD;CACD,kBAA+C;AAC3C,SAAO,EACH,OAAO;GACH,MAAM,KAAK,cAAc;GACzB,SAAS,KAAK;GACd,SAAS,KAAK,YAAY;EAC7B,EACJ;CACJ;CAED,cAAyB;AACrB,SAAO;GACH,QAAQ,KAAK;GACb,MAAM;IACF,OAAO,KAAK;IACZ,SAAS,KAAK,YAAY;GAC7B;EACJ;CACJ;AACJ"}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { TangoError } from '../TangoError';
|
|
2
|
-
import { isError, isObject } from '../../runtime/index';
|
|
3
|
-
/**
|
|
4
|
-
* Converts errors into structured HTTP error responses.
|
|
5
|
-
* Supports TangoError subclasses out of the box, and custom error handlers
|
|
6
|
-
* can be registered for third-party or application-specific error types.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```typescript
|
|
10
|
-
* // Development (default) — exposes real error messages
|
|
11
|
-
* const devFactory = new HttpErrorFactory();
|
|
12
|
-
*
|
|
13
|
-
* // Production — hides internal error details
|
|
14
|
-
* const prodFactory = new HttpErrorFactory({ exposeErrors: false });
|
|
15
|
-
*
|
|
16
|
-
* // Register a custom handler for a third-party error
|
|
17
|
-
* prodFactory.registerHandler(ZodError, (err) => ({
|
|
18
|
-
* status: 400,
|
|
19
|
-
* body: { error: 'Validation failed', details: err.flatten().fieldErrors },
|
|
20
|
-
* }));
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export class HttpErrorFactory {
|
|
24
|
-
static BRAND = 'tango.error_factory.http';
|
|
25
|
-
__tangoBrand = HttpErrorFactory.BRAND;
|
|
26
|
-
handlers = new Map();
|
|
27
|
-
exposeErrors;
|
|
28
|
-
constructor(config = {}) {
|
|
29
|
-
this.exposeErrors = config.exposeErrors ?? true;
|
|
30
|
-
}
|
|
31
|
-
static isHttpErrorFactory(value) {
|
|
32
|
-
return (typeof value === 'object' &&
|
|
33
|
-
value !== null &&
|
|
34
|
-
value.__tangoBrand === HttpErrorFactory.BRAND);
|
|
35
|
-
}
|
|
36
|
-
registerHandler(errorClass, handler) {
|
|
37
|
-
this.handlers.set(errorClass, handler);
|
|
38
|
-
return this;
|
|
39
|
-
}
|
|
40
|
-
create(error) {
|
|
41
|
-
if (TangoError.isTangoError(error)) {
|
|
42
|
-
return error.toHttpError();
|
|
43
|
-
}
|
|
44
|
-
for (const [ErrorClass, handler] of this.handlers) {
|
|
45
|
-
if (this.isErrorClassInstance(error, ErrorClass)) {
|
|
46
|
-
return handler(error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (isError(error)) {
|
|
50
|
-
return {
|
|
51
|
-
status: 500,
|
|
52
|
-
body: {
|
|
53
|
-
error: this.exposeErrors ? error.message : 'Internal Server Error',
|
|
54
|
-
details: null,
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
status: 500,
|
|
60
|
-
body: {
|
|
61
|
-
error: 'Unknown error occurred',
|
|
62
|
-
details: null,
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
isErrorClassInstance(error, ErrorClass) {
|
|
67
|
-
if (!isObject(error)) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
let proto = Object.getPrototypeOf(error);
|
|
71
|
-
while (proto) {
|
|
72
|
-
if (proto === ErrorClass.prototype) {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
proto = Object.getPrototypeOf(proto);
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Default factory instance with dev-friendly defaults (exposes error messages).
|
|
82
|
-
* Use this directly or create a custom HttpErrorFactory for production.
|
|
83
|
-
*/
|
|
84
|
-
export const defaultHttpErrorFactory = new HttpErrorFactory();
|
|
85
|
-
/**
|
|
86
|
-
* Convenience function that converts unknown errors to an HttpError
|
|
87
|
-
* using the default factory instance.
|
|
88
|
-
*/
|
|
89
|
-
export function toHttpError(error) {
|
|
90
|
-
return defaultHttpErrorFactory.create(error);
|
|
91
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors-CzSQXdgI.js","names":["config: HttpErrorFactoryConfig","value: unknown","errorClass: new (...args: any[]) => T","handler: (error: T) => HttpError","error: unknown","ErrorClass: new (...args: any[]) => Error","defaultHttpErrorFactory: HttpErrorFactory","value: unknown","message: string","message: string","details?: ErrorDetails","err: unknown","value: unknown","message: string","value: unknown","message: string","value: unknown","message: string"],"sources":["../src/errors/factories/HttpErrorFactory.ts","../src/errors/factories/index.ts","../src/errors/ConflictError.ts","../src/errors/ValidationError.ts","../src/errors/NotFoundError.ts","../src/errors/PermissionDenied.ts","../src/errors/AuthenticationError.ts","../src/errors/index.ts"],"sourcesContent":["import { TangoError } from '../TangoError';\nimport type { HttpError } from '../HttpError';\nimport { isError, isObject } from '../../runtime/index';\n\nexport interface HttpErrorFactoryConfig {\n /**\n * When true, raw error messages are included in HTTP responses.\n * When false, generic messages are returned for non-TangoError exceptions.\n * Defaults to true (dev-friendly). Set to false in production.\n */\n exposeErrors?: boolean;\n}\n\n/**\n * Converts errors into structured HTTP error responses.\n * Supports TangoError subclasses out of the box, and custom error handlers\n * can be registered for third-party or application-specific error types.\n *\n * @example\n * ```typescript\n * // Development (default) — exposes real error messages\n * const devFactory = new HttpErrorFactory();\n *\n * // Production — hides internal error details\n * const prodFactory = new HttpErrorFactory({ exposeErrors: false });\n *\n * // Register a custom handler for a third-party error\n * prodFactory.registerHandler(ZodError, (err) => ({\n * status: 400,\n * body: { error: 'Validation failed', details: err.flatten().fieldErrors },\n * }));\n * ```\n */\nexport class HttpErrorFactory {\n static readonly BRAND = 'tango.error_factory.http' as const;\n readonly __tangoBrand: typeof HttpErrorFactory.BRAND = HttpErrorFactory.BRAND;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private handlers = new Map<new (...args: any[]) => Error, (error: any) => HttpError>();\n\n private exposeErrors: boolean;\n\n constructor(config: HttpErrorFactoryConfig = {}) {\n this.exposeErrors = config.exposeErrors ?? true;\n }\n\n static isHttpErrorFactory(value: unknown): value is HttpErrorFactory {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === HttpErrorFactory.BRAND\n );\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n registerHandler<T extends Error>(errorClass: new (...args: any[]) => T, handler: (error: T) => HttpError): this {\n this.handlers.set(errorClass, handler);\n return this;\n }\n\n create(error: unknown): HttpError {\n if (TangoError.isTangoError(error)) {\n return error.toHttpError();\n }\n\n for (const [ErrorClass, handler] of this.handlers) {\n if (this.isErrorClassInstance(error, ErrorClass)) {\n return handler(error);\n }\n }\n\n if (isError(error)) {\n return {\n status: 500,\n body: {\n error: this.exposeErrors ? error.message : 'Internal Server Error',\n details: null,\n },\n };\n }\n\n return {\n status: 500,\n body: {\n error: 'Unknown error occurred',\n details: null,\n },\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private isErrorClassInstance(error: unknown, ErrorClass: new (...args: any[]) => Error): error is Error {\n if (!isObject(error)) {\n return false;\n }\n let proto = Object.getPrototypeOf(error);\n while (proto) {\n if (proto === ErrorClass.prototype) {\n return true;\n }\n proto = Object.getPrototypeOf(proto);\n }\n return false;\n }\n}\n\n/**\n * Default factory instance with dev-friendly defaults (exposes error messages).\n * Use this directly or create a custom HttpErrorFactory for production.\n */\nexport const defaultHttpErrorFactory: HttpErrorFactory = new HttpErrorFactory();\n\n/**\n * Convenience function that converts any error to an HttpError\n * using the default factory instance.\n */\nexport function toHttpError(error: unknown): HttpError {\n return defaultHttpErrorFactory.create(error);\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n HttpErrorFactory,\n defaultHttpErrorFactory,\n toHttpError,\n type HttpErrorFactoryConfig,\n} from './HttpErrorFactory';\n","import { TangoError, type ErrorDetails } from './TangoError';\nexport class ConflictError extends TangoError {\n static readonly BRAND = 'tango.error.conflict' as const;\n readonly __tangoBrand: typeof ConflictError.BRAND = ConflictError.BRAND;\n status = 409;\n\n static isConflictError(value: unknown): value is ConflictError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === ConflictError.BRAND\n );\n }\n\n constructor(message: string = 'Resource conflict') {\n super(message);\n this.name = 'ConflictError';\n Object.setPrototypeOf(this, ConflictError.prototype);\n }\n\n protected override getErrorName(): string {\n return 'conflict';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\nexport class ValidationError extends TangoError {\n readonly __tangoValidationErrorBrand: 'tango.error.validation' = 'tango.error.validation';\n status = 400;\n\n constructor(\n message: string,\n public details?: ErrorDetails\n ) {\n super(message);\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n\n static isValidationError(err: unknown): err is ValidationError {\n return (\n (!!err &&\n typeof err === 'object' &&\n (err as { __tangoValidationErrorBrand?: string }).__tangoValidationErrorBrand ===\n 'tango.error.validation') ||\n (typeof err === 'object' &&\n err !== null &&\n 'fields' in err &&\n typeof (err as { fields: unknown }).fields === 'object')\n );\n }\n\n protected override getErrorName(): string {\n return 'ValidationError';\n }\n\n protected override getDetails(): ErrorDetails {\n return this.details;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\nexport class NotFoundError extends TangoError {\n static readonly BRAND = 'tango.error.not_found' as const;\n readonly __tangoBrand: typeof NotFoundError.BRAND = NotFoundError.BRAND;\n status = 404;\n\n static isNotFoundError(value: unknown): value is NotFoundError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === NotFoundError.BRAND\n );\n }\n\n constructor(message: string = 'Resource not found') {\n super(message);\n this.name = 'NotFoundError';\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n\n protected override getErrorName(): string {\n return 'not_found';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\nexport class PermissionDenied extends TangoError {\n static readonly BRAND = 'tango.error.permission_denied' as const;\n readonly __tangoBrand: typeof PermissionDenied.BRAND = PermissionDenied.BRAND;\n status = 403;\n\n static isPermissionDenied(value: unknown): value is PermissionDenied {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === PermissionDenied.BRAND\n );\n }\n\n constructor(message: string = 'Permission denied') {\n super(message);\n this.name = 'PermissionDenied';\n Object.setPrototypeOf(this, PermissionDenied.prototype);\n }\n\n protected override getErrorName(): string {\n return 'permission_denied';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","import { TangoError, type ErrorDetails } from './TangoError';\n\nexport class AuthenticationError extends TangoError {\n static readonly BRAND = 'tango.error.authentication' as const;\n readonly __tangoBrand: typeof AuthenticationError.BRAND = AuthenticationError.BRAND;\n status = 401;\n\n static isAuthenticationError(value: unknown): value is AuthenticationError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === AuthenticationError.BRAND\n );\n }\n\n constructor(message: string = 'Authentication required') {\n super(message);\n this.name = 'AuthenticationError';\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n\n protected override getErrorName(): string {\n return 'authentication_error';\n }\n\n protected override getDetails(): ErrorDetails {\n return undefined;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nimport type { HttpError } from './HttpError';\nimport * as factories from './factories/index';\nimport { TangoError, type ErrorDetails, type ErrorEnvelope, type ProblemDetails } from './TangoError';\nimport { ConflictError } from './ConflictError';\nimport { ValidationError } from './ValidationError';\nimport { NotFoundError } from './NotFoundError';\nimport { PermissionDenied } from './PermissionDenied';\nimport { AuthenticationError } from './AuthenticationError';\nimport {\n HttpErrorFactory,\n defaultHttpErrorFactory,\n toHttpError,\n type HttpErrorFactoryConfig,\n} from './factories/HttpErrorFactory';\n\nexport {\n AuthenticationError,\n ConflictError,\n HttpErrorFactory,\n NotFoundError,\n PermissionDenied,\n TangoError,\n ValidationError,\n factories,\n defaultHttpErrorFactory,\n toHttpError,\n};\n\nexport type { ErrorDetails, ErrorEnvelope, HttpError, HttpErrorFactoryConfig, ProblemDetails };\n"],"mappings":"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"}
|