sonamu 0.9.17 → 0.9.19
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/dist/ai/providers/rtzr/api.d.ts +4 -0
- package/dist/ai/providers/rtzr/api.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/api.js +5 -1
- package/dist/ai/providers/rtzr/error.d.ts +15 -6
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/error.js +88 -11
- package/dist/ai/providers/rtzr/index.js +2 -2
- package/dist/ai/providers/rtzr/model.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/model.js +20 -9
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +18 -61
- package/dist/migration/index-where-predicate.d.ts +11 -0
- package/dist/migration/index-where-predicate.d.ts.map +1 -0
- package/dist/migration/index-where-predicate.js +305 -0
- package/package.json +4 -4
- package/src/ai/providers/rtzr/__tests__/model.test.ts +284 -0
- package/src/ai/providers/rtzr/api.ts +6 -0
- package/src/ai/providers/rtzr/error.ts +122 -9
- package/src/ai/providers/rtzr/model.ts +39 -8
- package/src/migration/__tests__/code-generation.search-text.test.ts +40 -0
- package/src/migration/code-generation.ts +36 -72
- package/src/migration/index-where-predicate.ts +392 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration DDL 출력에 사용할 index predicate를 정리한다.
|
|
3
|
+
* 원본 SQL 표현은 보존하고, 빈 문자열과 전체를 감싼 괄호만 제거한다.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeIndexWherePredicate(where: string | undefined): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Index diff 비교에 사용할 predicate identity를 만든다.
|
|
8
|
+
* PostgreSQL canonical 표현 차이는 AST 기반 정규화로 흡수한다.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeIndexWherePredicateForComparison(where: string | undefined): string | undefined;
|
|
11
|
+
//# sourceMappingURL=index-where-predicate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-where-predicate.d.ts","sourceRoot":"","sources":["../../src/migration/index-where-predicate.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAY1F;AAED;;;GAGG;AACH,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAQpB"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { __esmMin } from "../_virtual/rolldown_runtime.js";
|
|
2
|
+
import SqlParser from "node-sql-parser";
|
|
3
|
+
|
|
4
|
+
//#region src/migration/index-where-predicate.ts
|
|
5
|
+
/**
|
|
6
|
+
* Migration DDL 출력에 사용할 index predicate를 정리한다.
|
|
7
|
+
* 원본 SQL 표현은 보존하고, 빈 문자열과 전체를 감싼 괄호만 제거한다.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeIndexWherePredicate(where) {
|
|
10
|
+
if (!where) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const trimmed = removeOuterSqlParentheses(where.trim());
|
|
14
|
+
if (trimmed.length === 0) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Index diff 비교에 사용할 predicate identity를 만든다.
|
|
21
|
+
* PostgreSQL canonical 표현 차이는 AST 기반 정규화로 흡수한다.
|
|
22
|
+
*/
|
|
23
|
+
function normalizeIndexWherePredicateForComparison(where) {
|
|
24
|
+
const normalized = normalizeIndexWherePredicate(where);
|
|
25
|
+
if (!normalized) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return normalizeIndexWherePredicateByAst(normalized) ?? normalized;
|
|
29
|
+
}
|
|
30
|
+
function removeOuterSqlParentheses(source) {
|
|
31
|
+
let trimmed = source.trim();
|
|
32
|
+
while (trimmed.startsWith("(") && trimmed.endsWith(")")) {
|
|
33
|
+
const closeIndex = findMatchingParenthesisInSql(trimmed, 0);
|
|
34
|
+
if (closeIndex === trimmed.length - 1) {
|
|
35
|
+
trimmed = trimmed.slice(1, -1).trim();
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
42
|
+
function normalizeIndexWherePredicateByAst(where) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = INDEX_WHERE_SQL_PARSER.astify(`SELECT * FROM __sonamu_index_predicate_source WHERE ${where}`, { database: "postgresql" });
|
|
45
|
+
const statement = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
46
|
+
if (!isSqlAstRecord(statement)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return serializeIndexWhereAst(statement.where);
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function serializeIndexWhereAst(node) {
|
|
55
|
+
const membership = serializeSqlMembershipPredicate(node);
|
|
56
|
+
if (membership) {
|
|
57
|
+
return membership;
|
|
58
|
+
}
|
|
59
|
+
if (!isSqlAstRecord(node)) {
|
|
60
|
+
return JSON.stringify(node);
|
|
61
|
+
}
|
|
62
|
+
const type = getSqlAstString(node, "type");
|
|
63
|
+
switch (type) {
|
|
64
|
+
case "binary_expr": {
|
|
65
|
+
const operator = normalizeSqlOperator(getSqlAstString(node, "operator"));
|
|
66
|
+
const left = serializeIndexWhereAst(node.left);
|
|
67
|
+
const right = serializeIndexWhereAst(node.right);
|
|
68
|
+
if (operator === "AND" || operator === "OR") {
|
|
69
|
+
return `(${[left, right].toSorted().join(` ${operator} `)})`;
|
|
70
|
+
}
|
|
71
|
+
return `(${left} ${operator} ${right})`;
|
|
72
|
+
}
|
|
73
|
+
case "unary_expr": {
|
|
74
|
+
const operator = normalizeSqlOperator(getSqlAstString(node, "operator"));
|
|
75
|
+
return `(${operator} ${serializeIndexWhereAst(node.expr)})`;
|
|
76
|
+
}
|
|
77
|
+
case "column_ref": {
|
|
78
|
+
return serializeSqlColumnRef(node);
|
|
79
|
+
}
|
|
80
|
+
case "single_quote_string": {
|
|
81
|
+
return `string:${JSON.stringify(getSqlAstString(node, "value"))}`;
|
|
82
|
+
}
|
|
83
|
+
case "number": {
|
|
84
|
+
return `number:${String(node.value)}`;
|
|
85
|
+
}
|
|
86
|
+
case "bool": {
|
|
87
|
+
return `bool:${String(node.value).toLowerCase()}`;
|
|
88
|
+
}
|
|
89
|
+
case "null": {
|
|
90
|
+
return "null";
|
|
91
|
+
}
|
|
92
|
+
case "cast": {
|
|
93
|
+
const targetType = getSqlCastTargetType(node);
|
|
94
|
+
const expr = node.expr;
|
|
95
|
+
if (targetType && isTextLikeSqlType(targetType) && isSqlStringLiteralLike(expr)) {
|
|
96
|
+
return serializeIndexWhereAst(stripTextLikeSqlCast(expr));
|
|
97
|
+
}
|
|
98
|
+
return `cast(${serializeIndexWhereAst(expr)} as ${targetType ?? "unknown"})`;
|
|
99
|
+
}
|
|
100
|
+
case "function": {
|
|
101
|
+
return `fn:${getSqlFunctionName(node)}(${serializeSqlExprList(node.args).join(",")})`;
|
|
102
|
+
}
|
|
103
|
+
case "array": {
|
|
104
|
+
return `array:[${serializeSqlArrayElements(node).map(serializeIndexWhereAst).join(",")}]`;
|
|
105
|
+
}
|
|
106
|
+
case "expr_list": {
|
|
107
|
+
return `list:[${serializeSqlExprList(node).map(serializeIndexWhereAst).join(",")}]`;
|
|
108
|
+
}
|
|
109
|
+
case "default": {
|
|
110
|
+
return `ident:${normalizeSqlIdentifier(getSqlAstString(node, "value") ?? "")}`;
|
|
111
|
+
}
|
|
112
|
+
default: {
|
|
113
|
+
return serializeUnknownSqlAstRecord(node);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function serializeSqlMembershipPredicate(node) {
|
|
118
|
+
if (!isSqlAstRecord(node) || getSqlAstString(node, "type") !== "binary_expr") {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const operator = normalizeSqlOperator(getSqlAstString(node, "operator"));
|
|
122
|
+
const left = serializeIndexWhereAst(stripTextLikeSqlCast(node.left));
|
|
123
|
+
const values = (() => {
|
|
124
|
+
if (operator === "IN") {
|
|
125
|
+
return serializeSqlMembershipValues(serializeSqlExprList(node.right));
|
|
126
|
+
}
|
|
127
|
+
if (operator === "=") {
|
|
128
|
+
return serializeSqlMembershipValues(getSqlAnyArrayElements(node.right));
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
})();
|
|
132
|
+
if (!values) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return `membership:${left}:in:[${values.join(",")}]`;
|
|
136
|
+
}
|
|
137
|
+
function serializeSqlMembershipValues(values) {
|
|
138
|
+
if (!values) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const serialized = values.map((value) => serializeIndexWhereAst(stripTextLikeSqlCast(value)));
|
|
142
|
+
return [...new Set(serialized)].toSorted();
|
|
143
|
+
}
|
|
144
|
+
function getSqlAnyArrayElements(node) {
|
|
145
|
+
if (!isSqlAstRecord(node) || getSqlAstString(node, "type") !== "function") {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (getSqlFunctionName(node) !== "any") {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const [arg] = serializeSqlExprList(node.args);
|
|
152
|
+
if (!arg) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return serializeSqlArrayElements(stripTextLikeSqlCast(arg));
|
|
156
|
+
}
|
|
157
|
+
function serializeSqlArrayElements(node) {
|
|
158
|
+
if (!isSqlAstRecord(node)) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
if (getSqlAstString(node, "type") === "array") {
|
|
162
|
+
return serializeSqlExprList(node.expr_list);
|
|
163
|
+
}
|
|
164
|
+
return serializeSqlExprList(node);
|
|
165
|
+
}
|
|
166
|
+
function serializeSqlExprList(node) {
|
|
167
|
+
if (!isSqlAstRecord(node)) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
if (getSqlAstString(node, "type") === "expr_list" && Array.isArray(node.value)) {
|
|
171
|
+
return node.value;
|
|
172
|
+
}
|
|
173
|
+
return [node];
|
|
174
|
+
}
|
|
175
|
+
function stripTextLikeSqlCast(node) {
|
|
176
|
+
if (!isSqlAstRecord(node) || getSqlAstString(node, "type") !== "cast") {
|
|
177
|
+
return node;
|
|
178
|
+
}
|
|
179
|
+
const targetType = getSqlCastTargetType(node);
|
|
180
|
+
if (!targetType || !isTextLikeSqlType(targetType)) {
|
|
181
|
+
return node;
|
|
182
|
+
}
|
|
183
|
+
return stripTextLikeSqlCast(node.expr);
|
|
184
|
+
}
|
|
185
|
+
function isSqlStringLiteralLike(node) {
|
|
186
|
+
const stripped = stripTextLikeSqlCast(node);
|
|
187
|
+
return isSqlAstRecord(stripped) && getSqlAstString(stripped, "type") === "single_quote_string";
|
|
188
|
+
}
|
|
189
|
+
function getSqlCastTargetType(node) {
|
|
190
|
+
if (!Array.isArray(node.target)) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const dataTypes = node.target.map((target) => isSqlAstRecord(target) ? getSqlAstString(target, "dataType") : undefined).filter((dataType) => dataType !== undefined);
|
|
194
|
+
if (dataTypes.length === 0) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
return normalizeSqlDataType(dataTypes.join(" "));
|
|
198
|
+
}
|
|
199
|
+
function isTextLikeSqlType(type) {
|
|
200
|
+
const normalized = normalizeSqlDataType(type).replace(/\[\]$/g, "");
|
|
201
|
+
return normalized === "text" || normalized === "varchar";
|
|
202
|
+
}
|
|
203
|
+
function normalizeSqlDataType(type) {
|
|
204
|
+
return type.trim().toLowerCase().replace(/\s+/g, " ").replace(/^character varying$/, "varchar");
|
|
205
|
+
}
|
|
206
|
+
function serializeSqlColumnRef(node) {
|
|
207
|
+
const table = serializeSqlIdentifierNode(node.table);
|
|
208
|
+
const column = serializeSqlIdentifierNode(node.column);
|
|
209
|
+
return `column:${table ? `${table}.` : ""}${column}`;
|
|
210
|
+
}
|
|
211
|
+
function serializeSqlIdentifierNode(node) {
|
|
212
|
+
if (typeof node === "string") {
|
|
213
|
+
return normalizeSqlIdentifier(node);
|
|
214
|
+
}
|
|
215
|
+
if (isSqlAstRecord(node)) {
|
|
216
|
+
if (typeof node.value === "string") {
|
|
217
|
+
return normalizeSqlIdentifier(node.value);
|
|
218
|
+
}
|
|
219
|
+
if (isSqlAstRecord(node.expr) && typeof node.expr.value === "string") {
|
|
220
|
+
return normalizeSqlIdentifier(node.expr.value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return "";
|
|
224
|
+
}
|
|
225
|
+
function getSqlFunctionName(node) {
|
|
226
|
+
const name = node.name;
|
|
227
|
+
if (typeof name === "string") {
|
|
228
|
+
return normalizeSqlIdentifier(name);
|
|
229
|
+
}
|
|
230
|
+
if (isSqlAstRecord(name)) {
|
|
231
|
+
if (Array.isArray(name.name)) {
|
|
232
|
+
return name.name.map(serializeSqlIdentifierNode).join(".");
|
|
233
|
+
}
|
|
234
|
+
const nameParts = serializeSqlExprList(name);
|
|
235
|
+
if (nameParts.length > 0) {
|
|
236
|
+
return nameParts.map(serializeSqlIdentifierNode).join(".");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return "";
|
|
240
|
+
}
|
|
241
|
+
function normalizeSqlIdentifier(identifier) {
|
|
242
|
+
return identifier.trim().toLowerCase();
|
|
243
|
+
}
|
|
244
|
+
function normalizeSqlOperator(operator) {
|
|
245
|
+
return (operator ?? "").trim().toUpperCase().replace(/\s+/g, " ");
|
|
246
|
+
}
|
|
247
|
+
function serializeUnknownSqlAstRecord(node) {
|
|
248
|
+
const entries = Object.entries(node).filter(([key]) => key !== "parentheses").toSorted(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}:${serializeIndexWhereAst(value)}`);
|
|
249
|
+
return `record:{${entries.join(",")}}`;
|
|
250
|
+
}
|
|
251
|
+
function isSqlAstRecord(value) {
|
|
252
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
253
|
+
}
|
|
254
|
+
function getSqlAstString(node, key) {
|
|
255
|
+
const value = node[key];
|
|
256
|
+
return typeof value === "string" ? value : undefined;
|
|
257
|
+
}
|
|
258
|
+
function findMatchingParenthesisInSql(source, openIndex) {
|
|
259
|
+
let depth = 0;
|
|
260
|
+
let inSingleQuote = false;
|
|
261
|
+
let inDoubleQuote = false;
|
|
262
|
+
for (let index = openIndex; index < source.length; index += 1) {
|
|
263
|
+
const char = source[index];
|
|
264
|
+
const nextChar = source[index + 1];
|
|
265
|
+
if (char === "'" && !inDoubleQuote) {
|
|
266
|
+
if (inSingleQuote && nextChar === "'") {
|
|
267
|
+
index += 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
inSingleQuote = !inSingleQuote;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (char === "\"" && !inSingleQuote) {
|
|
274
|
+
if (inDoubleQuote && nextChar === "\"") {
|
|
275
|
+
index += 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
inDoubleQuote = !inDoubleQuote;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (char === "(") {
|
|
285
|
+
depth += 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (char === ")") {
|
|
289
|
+
depth -= 1;
|
|
290
|
+
if (depth === 0) {
|
|
291
|
+
return index;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return -1;
|
|
296
|
+
}
|
|
297
|
+
var INDEX_WHERE_SQL_PARSER;
|
|
298
|
+
var init_index_where_predicate = __esmMin((() => {
|
|
299
|
+
INDEX_WHERE_SQL_PARSER = new SqlParser.Parser();
|
|
300
|
+
}));
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
init_index_where_predicate();
|
|
304
|
+
export { init_index_where_predicate, normalizeIndexWherePredicate, normalizeIndexWherePredicateForComparison };
|
|
305
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXgtd2hlcmUtcHJlZGljYXRlLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWdyYXRpb24vaW5kZXgtd2hlcmUtcHJlZGljYXRlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBTcWxQYXJzZXIgZnJvbSBcIm5vZGUtc3FsLXBhcnNlclwiO1xuXG5jb25zdCBJTkRFWF9XSEVSRV9TUUxfUEFSU0VSID0gbmV3IFNxbFBhcnNlci5QYXJzZXIoKTtcblxuLyoqXG4gKiBNaWdyYXRpb24gRERMIOy2nOugpeyXkCDsgqzsmqntlaAgaW5kZXggcHJlZGljYXRl66W8IOygleumrO2VnOuLpC5cbiAqIOybkOuzuCBTUUwg7ZGc7ZiE7J2AIOuztOyhtO2VmOqzoCwg67mIIOusuOyekOyXtOqzvCDsoITssrTrpbwg6rCQ7Iu8IOq0hO2YuOunjCDsoJzqsbDtlZzri6QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVJbmRleFdoZXJlUHJlZGljYXRlKHdoZXJlOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBpZiAoIXdoZXJlKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIC8vIOy2nOugpSDqsr3roZzsl5DshJzripQg7IKs7Jqp7J6Q6rCAIOyekeyEse2VnCBwcmVkaWNhdGXrpbwg7LWc64yA7ZWcIOq3uOuMgOuhnCDsnKDsp4DtlZzri6QuXG4gIGNvbnN0IHRyaW1tZWQgPSByZW1vdmVPdXRlclNxbFBhcmVudGhlc2VzKHdoZXJlLnRyaW0oKSk7XG4gIGlmICh0cmltbWVkLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4gdHJpbW1lZDtcbn1cblxuLyoqXG4gKiBJbmRleCBkaWZmIOu5hOq1kOyXkCDsgqzsmqntlaAgcHJlZGljYXRlIGlkZW50aXR566W8IOunjOuToOuLpC5cbiAqIFBvc3RncmVTUUwgY2Fub25pY2FsIO2RnO2YhCDssKjsnbTripQgQVNUIOq4sOuwmCDsoJXqt5ztmZTroZwg7Z2h7IiY7ZWc64ukLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbm9ybWFsaXplSW5kZXhXaGVyZVByZWRpY2F0ZUZvckNvbXBhcmlzb24oXG4gIHdoZXJlOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4pOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBjb25zdCBub3JtYWxpemVkID0gbm9ybWFsaXplSW5kZXhXaGVyZVByZWRpY2F0ZSh3aGVyZSk7XG4gIGlmICghbm9ybWFsaXplZCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICAvLyDtjIzsi7Eg6rCA64ql7ZWcIHByZWRpY2F0ZeunjCBjYW5vbmljYWwgZm9ybeycvOuhnCDrsJTqvrjqs6AsIOyLpO2MqO2VmOuptCDquLDsobQgc3RyaWN0IOu5hOq1kOulvCDsnKDsp4DtlZzri6QuXG4gIHJldHVybiBub3JtYWxpemVJbmRleFdoZXJlUHJlZGljYXRlQnlBc3Qobm9ybWFsaXplZCkgPz8gbm9ybWFsaXplZDtcbn1cblxuZnVuY3Rpb24gcmVtb3ZlT3V0ZXJTcWxQYXJlbnRoZXNlcyhzb3VyY2U6IHN0cmluZyk6IHN0cmluZyB7XG4gIGxldCB0cmltbWVkID0gc291cmNlLnRyaW0oKTtcblxuICAvLyDsoITssrQgcHJlZGljYXRl66W8IOqwkOyLvCDqtITtmLjrp4wg7KCc6rGw7ZWY6rOgIOuCtOu2gCBncm91cGluZ+ydgCDqsbTrk5zrpqzsp4Ag7JWK64qU64ukLlxuICB3aGlsZSAodHJpbW1lZC5zdGFydHNXaXRoKFwiKFwiKSAmJiB0cmltbWVkLmVuZHNXaXRoKFwiKVwiKSkge1xuICAgIGNvbnN0IGNsb3NlSW5kZXggPSBmaW5kTWF0Y2hpbmdQYXJlbnRoZXNpc0luU3FsKHRyaW1tZWQsIDApO1xuICAgIGlmIChjbG9zZUluZGV4ID09PSB0cmltbWVkLmxlbmd0aCAtIDEpIHtcbiAgICAgIHRyaW1tZWQgPSB0cmltbWVkLnNsaWNlKDEsIC0xKS50cmltKCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBicmVhaztcbiAgfVxuXG4gIHJldHVybiB0cmltbWVkO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVJbmRleFdoZXJlUHJlZGljYXRlQnlBc3Qod2hlcmU6IHN0cmluZyk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIHRyeSB7XG4gICAgLy8gbm9kZS1zcWwtcGFyc2Vy64qUIHJhdyBwcmVkaWNhdGXrpbwg7KeB7KCRIO2MjOyLse2VmOyngCDslYrsnLzrr4DroZwg7J6E7IucIFNFTEVDVCBXSEVSReuhnCDqsJDsi7zri6QuXG4gICAgY29uc3QgcGFyc2VkID0gSU5ERVhfV0hFUkVfU1FMX1BBUlNFUi5hc3RpZnkoXG4gICAgICBgU0VMRUNUICogRlJPTSBfX3NvbmFtdV9pbmRleF9wcmVkaWNhdGVfc291cmNlIFdIRVJFICR7d2hlcmV9YCxcbiAgICAgIHsgZGF0YWJhc2U6IFwicG9zdGdyZXNxbFwiIH0sXG4gICAgKTtcbiAgICBjb25zdCBzdGF0ZW1lbnQgPSBBcnJheS5pc0FycmF5KHBhcnNlZCkgPyBwYXJzZWRbMF0gOiBwYXJzZWQ7XG4gICAgaWYgKCFpc1NxbEFzdFJlY29yZChzdGF0ZW1lbnQpKSB7XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cblxuICAgIHJldHVybiBzZXJpYWxpemVJbmRleFdoZXJlQXN0KHN0YXRlbWVudC53aGVyZSk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplSW5kZXhXaGVyZUFzdChub2RlOiB1bmtub3duKTogc3RyaW5nIHtcbiAgLy8gUG9zdGdyZVNRTOydtCBJTuydhCBBTlkoQVJSQVlbLi4uXSnroZwg7J6s7J6R7ISx7ZWY66+A66GcIG1lbWJlcnNoaXDsnYAg66i87KCAIO2KueyImCDsspjrpqztlZzri6QuXG4gIGNvbnN0IG1lbWJlcnNoaXAgPSBzZXJpYWxpemVTcWxNZW1iZXJzaGlwUHJlZGljYXRlKG5vZGUpO1xuICBpZiAobWVtYmVyc2hpcCkge1xuICAgIHJldHVybiBtZW1iZXJzaGlwO1xuICB9XG5cbiAgaWYgKCFpc1NxbEFzdFJlY29yZChub2RlKSkge1xuICAgIHJldHVybiBKU09OLnN0cmluZ2lmeShub2RlKTtcbiAgfVxuXG4gIGNvbnN0IHR5cGUgPSBnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ0eXBlXCIpO1xuICBzd2l0Y2ggKHR5cGUpIHtcbiAgICBjYXNlIFwiYmluYXJ5X2V4cHJcIjoge1xuICAgICAgY29uc3Qgb3BlcmF0b3IgPSBub3JtYWxpemVTcWxPcGVyYXRvcihnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJvcGVyYXRvclwiKSk7XG4gICAgICBjb25zdCBsZWZ0ID0gc2VyaWFsaXplSW5kZXhXaGVyZUFzdChub2RlLmxlZnQpO1xuICAgICAgY29uc3QgcmlnaHQgPSBzZXJpYWxpemVJbmRleFdoZXJlQXN0KG5vZGUucmlnaHQpO1xuICAgICAgaWYgKG9wZXJhdG9yID09PSBcIkFORFwiIHx8IG9wZXJhdG9yID09PSBcIk9SXCIpIHtcbiAgICAgICAgLy8gQU5EL09S64qUIOyInOyEnOqwgCBkaWZmIOydmOuvuOulvCDrsJTqvrjsp4Ag7JWK7Jy866+A66GcIHN0YWJsZSBpZGVudGl0eeuhnCDsoJXroKztlZzri6QuXG4gICAgICAgIHJldHVybiBgKCR7W2xlZnQsIHJpZ2h0XS50b1NvcnRlZCgpLmpvaW4oYCAke29wZXJhdG9yfSBgKX0pYDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBgKCR7bGVmdH0gJHtvcGVyYXRvcn0gJHtyaWdodH0pYDtcbiAgICB9XG4gICAgY2FzZSBcInVuYXJ5X2V4cHJcIjoge1xuICAgICAgY29uc3Qgb3BlcmF0b3IgPSBub3JtYWxpemVTcWxPcGVyYXRvcihnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJvcGVyYXRvclwiKSk7XG4gICAgICByZXR1cm4gYCgke29wZXJhdG9yfSAke3NlcmlhbGl6ZUluZGV4V2hlcmVBc3Qobm9kZS5leHByKX0pYDtcbiAgICB9XG4gICAgY2FzZSBcImNvbHVtbl9yZWZcIjoge1xuICAgICAgcmV0dXJuIHNlcmlhbGl6ZVNxbENvbHVtblJlZihub2RlKTtcbiAgICB9XG4gICAgY2FzZSBcInNpbmdsZV9xdW90ZV9zdHJpbmdcIjoge1xuICAgICAgcmV0dXJuIGBzdHJpbmc6JHtKU09OLnN0cmluZ2lmeShnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ2YWx1ZVwiKSl9YDtcbiAgICB9XG4gICAgY2FzZSBcIm51bWJlclwiOiB7XG4gICAgICByZXR1cm4gYG51bWJlcjoke1N0cmluZyhub2RlLnZhbHVlKX1gO1xuICAgIH1cbiAgICBjYXNlIFwiYm9vbFwiOiB7XG4gICAgICByZXR1cm4gYGJvb2w6JHtTdHJpbmcobm9kZS52YWx1ZSkudG9Mb3dlckNhc2UoKX1gO1xuICAgIH1cbiAgICBjYXNlIFwibnVsbFwiOiB7XG4gICAgICByZXR1cm4gXCJudWxsXCI7XG4gICAgfVxuICAgIGNhc2UgXCJjYXN0XCI6IHtcbiAgICAgIGNvbnN0IHRhcmdldFR5cGUgPSBnZXRTcWxDYXN0VGFyZ2V0VHlwZShub2RlKTtcbiAgICAgIGNvbnN0IGV4cHIgPSBub2RlLmV4cHI7XG4gICAgICBpZiAodGFyZ2V0VHlwZSAmJiBpc1RleHRMaWtlU3FsVHlwZSh0YXJnZXRUeXBlKSAmJiBpc1NxbFN0cmluZ0xpdGVyYWxMaWtlKGV4cHIpKSB7XG4gICAgICAgIHJldHVybiBzZXJpYWxpemVJbmRleFdoZXJlQXN0KHN0cmlwVGV4dExpa2VTcWxDYXN0KGV4cHIpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBgY2FzdCgke3NlcmlhbGl6ZUluZGV4V2hlcmVBc3QoZXhwcil9IGFzICR7dGFyZ2V0VHlwZSA/PyBcInVua25vd25cIn0pYDtcbiAgICB9XG4gICAgY2FzZSBcImZ1bmN0aW9uXCI6IHtcbiAgICAgIHJldHVybiBgZm46JHtnZXRTcWxGdW5jdGlvbk5hbWUobm9kZSl9KCR7c2VyaWFsaXplU3FsRXhwckxpc3Qobm9kZS5hcmdzKS5qb2luKFwiLFwiKX0pYDtcbiAgICB9XG4gICAgY2FzZSBcImFycmF5XCI6IHtcbiAgICAgIHJldHVybiBgYXJyYXk6WyR7c2VyaWFsaXplU3FsQXJyYXlFbGVtZW50cyhub2RlKS5tYXAoc2VyaWFsaXplSW5kZXhXaGVyZUFzdCkuam9pbihcIixcIil9XWA7XG4gICAgfVxuICAgIGNhc2UgXCJleHByX2xpc3RcIjoge1xuICAgICAgcmV0dXJuIGBsaXN0Olske3NlcmlhbGl6ZVNxbEV4cHJMaXN0KG5vZGUpLm1hcChzZXJpYWxpemVJbmRleFdoZXJlQXN0KS5qb2luKFwiLFwiKX1dYDtcbiAgICB9XG4gICAgY2FzZSBcImRlZmF1bHRcIjoge1xuICAgICAgcmV0dXJuIGBpZGVudDoke25vcm1hbGl6ZVNxbElkZW50aWZpZXIoZ2V0U3FsQXN0U3RyaW5nKG5vZGUsIFwidmFsdWVcIikgPz8gXCJcIil9YDtcbiAgICB9XG4gICAgZGVmYXVsdDoge1xuICAgICAgcmV0dXJuIHNlcmlhbGl6ZVVua25vd25TcWxBc3RSZWNvcmQobm9kZSk7XG4gICAgfVxuICB9XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVNxbE1lbWJlcnNoaXBQcmVkaWNhdGUobm9kZTogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICghaXNTcWxBc3RSZWNvcmQobm9kZSkgfHwgZ2V0U3FsQXN0U3RyaW5nKG5vZGUsIFwidHlwZVwiKSAhPT0gXCJiaW5hcnlfZXhwclwiKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIC8vIElOICguLi4p6rO8ID0gQU5ZKEFSUkFZWy4uLl0p66W8IOuPmeydvO2VnCB1bm9yZGVyZWQgbWVtYmVyc2hpcCDtkZztmITsnLzroZwg66ee7LaY64ukLlxuICBjb25zdCBvcGVyYXRvciA9IG5vcm1hbGl6ZVNxbE9wZXJhdG9yKGdldFNxbEFzdFN0cmluZyhub2RlLCBcIm9wZXJhdG9yXCIpKTtcbiAgY29uc3QgbGVmdCA9IHNlcmlhbGl6ZUluZGV4V2hlcmVBc3Qoc3RyaXBUZXh0TGlrZVNxbENhc3Qobm9kZS5sZWZ0KSk7XG4gIGNvbnN0IHZhbHVlcyA9ICgoKSA9PiB7XG4gICAgaWYgKG9wZXJhdG9yID09PSBcIklOXCIpIHtcbiAgICAgIHJldHVybiBzZXJpYWxpemVTcWxNZW1iZXJzaGlwVmFsdWVzKHNlcmlhbGl6ZVNxbEV4cHJMaXN0KG5vZGUucmlnaHQpKTtcbiAgICB9XG5cbiAgICBpZiAob3BlcmF0b3IgPT09IFwiPVwiKSB7XG4gICAgICByZXR1cm4gc2VyaWFsaXplU3FsTWVtYmVyc2hpcFZhbHVlcyhnZXRTcWxBbnlBcnJheUVsZW1lbnRzKG5vZGUucmlnaHQpKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9KSgpO1xuXG4gIGlmICghdmFsdWVzKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHJldHVybiBgbWVtYmVyc2hpcDoke2xlZnR9OmluOlske3ZhbHVlcy5qb2luKFwiLFwiKX1dYDtcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplU3FsTWVtYmVyc2hpcFZhbHVlcyh2YWx1ZXM6IHVua25vd25bXSB8IHVuZGVmaW5lZCk6IHN0cmluZ1tdIHwgdW5kZWZpbmVkIHtcbiAgaWYgKCF2YWx1ZXMpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgLy8gbWVtYmVyc2hpcCDqsJLsnZgg7Iic7ISc7JmAIOykkeuzteydgCDsnZjrr7jqsIAg7JeG7Jy866+A66GcIOyViOygleyggeyduCBpZGVudGl0eeuhnCDsoJXroKztlZzri6QuXG4gIGNvbnN0IHNlcmlhbGl6ZWQgPSB2YWx1ZXMubWFwKCh2YWx1ZSkgPT4gc2VyaWFsaXplSW5kZXhXaGVyZUFzdChzdHJpcFRleHRMaWtlU3FsQ2FzdCh2YWx1ZSkpKTtcbiAgcmV0dXJuIFsuLi5uZXcgU2V0KHNlcmlhbGl6ZWQpXS50b1NvcnRlZCgpO1xufVxuXG5mdW5jdGlvbiBnZXRTcWxBbnlBcnJheUVsZW1lbnRzKG5vZGU6IHVua25vd24pOiB1bmtub3duW10gfCB1bmRlZmluZWQge1xuICBpZiAoIWlzU3FsQXN0UmVjb3JkKG5vZGUpIHx8IGdldFNxbEFzdFN0cmluZyhub2RlLCBcInR5cGVcIikgIT09IFwiZnVuY3Rpb25cIikge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbiAgaWYgKGdldFNxbEZ1bmN0aW9uTmFtZShub2RlKSAhPT0gXCJhbnlcIikge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICAvLyBBTlkoKeydmCDssqsg67KI7Ke4IOyduOyekOunjCBQb3N0Z3JlU1FMIOuwsOyXtCBtZW1iZXJzaGlwIOuMgOyDgeycvOuhnCDtlbTshJ3tlZzri6QuXG4gIGNvbnN0IFthcmddID0gc2VyaWFsaXplU3FsRXhwckxpc3Qobm9kZS5hcmdzKTtcbiAgaWYgKCFhcmcpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgcmV0dXJuIHNlcmlhbGl6ZVNxbEFycmF5RWxlbWVudHMoc3RyaXBUZXh0TGlrZVNxbENhc3QoYXJnKSk7XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVNxbEFycmF5RWxlbWVudHMobm9kZTogdW5rbm93bik6IHVua25vd25bXSB7XG4gIGlmICghaXNTcWxBc3RSZWNvcmQobm9kZSkpIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICAvLyBwYXJzZXIg67KE7KCE7JeQIOuUsOudvCDrsLDsl7Qg7JuQ7IaM6rCAIGFycmF5LmV4cHJfbGlzdCDrmJDripQgZXhwcl9saXN066GcIOuTpOyWtOyYqOuLpC5cbiAgaWYgKGdldFNxbEFzdFN0cmluZyhub2RlLCBcInR5cGVcIikgPT09IFwiYXJyYXlcIikge1xuICAgIHJldHVybiBzZXJpYWxpemVTcWxFeHByTGlzdChub2RlLmV4cHJfbGlzdCk7XG4gIH1cblxuICByZXR1cm4gc2VyaWFsaXplU3FsRXhwckxpc3Qobm9kZSk7XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVNxbEV4cHJMaXN0KG5vZGU6IHVua25vd24pOiB1bmtub3duW10ge1xuICBpZiAoIWlzU3FsQXN0UmVjb3JkKG5vZGUpKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG5cbiAgLy8gZXhwcl9saXN064qUIOyLpOygnCBBU1Qg64W465OcIOuwsOyXtOydtOqzoCwg64uo7J28IO2RnO2YhOyLneydgCDrsLDsl7Qg7ZWY64KY66GcIOqwkOyLuCDthrXsnbztlZzri6QuXG4gIGlmIChnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ0eXBlXCIpID09PSBcImV4cHJfbGlzdFwiICYmIEFycmF5LmlzQXJyYXkobm9kZS52YWx1ZSkpIHtcbiAgICByZXR1cm4gbm9kZS52YWx1ZTtcbiAgfVxuXG4gIHJldHVybiBbbm9kZV07XG59XG5cbmZ1bmN0aW9uIHN0cmlwVGV4dExpa2VTcWxDYXN0KG5vZGU6IHVua25vd24pOiB1bmtub3duIHtcbiAgaWYgKCFpc1NxbEFzdFJlY29yZChub2RlKSB8fCBnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ0eXBlXCIpICE9PSBcImNhc3RcIikge1xuICAgIHJldHVybiBub2RlO1xuICB9XG5cbiAgLy8gUG9zdGdyZVNRTOydgCB2YXJjaGFyL3RleHQgY2FzdOulvCDrsLDsl7Qg65iQ64qUIOybkOyGjCDsqr3snLzroZwg7Jiu6ri4IOyImCDsnojslrQg67mE6rWQ7JeQ7IScIOygnOqxsO2VnOuLpC5cbiAgY29uc3QgdGFyZ2V0VHlwZSA9IGdldFNxbENhc3RUYXJnZXRUeXBlKG5vZGUpO1xuICBpZiAoIXRhcmdldFR5cGUgfHwgIWlzVGV4dExpa2VTcWxUeXBlKHRhcmdldFR5cGUpKSB7XG4gICAgcmV0dXJuIG5vZGU7XG4gIH1cblxuICByZXR1cm4gc3RyaXBUZXh0TGlrZVNxbENhc3Qobm9kZS5leHByKTtcbn1cblxuZnVuY3Rpb24gaXNTcWxTdHJpbmdMaXRlcmFsTGlrZShub2RlOiB1bmtub3duKTogYm9vbGVhbiB7XG4gIC8vIOusuOyekOyXtCBsaXRlcmFs7JeQIOu2meydgCB0ZXh0IOqzhOyXtCBjYXN064qUIG1lbWJlcnNoaXAg67mE6rWQ7JeQ7IScIOydmOuvuOqwgCDqsJnri6QuXG4gIGNvbnN0IHN0cmlwcGVkID0gc3RyaXBUZXh0TGlrZVNxbENhc3Qobm9kZSk7XG4gIHJldHVybiBpc1NxbEFzdFJlY29yZChzdHJpcHBlZCkgJiYgZ2V0U3FsQXN0U3RyaW5nKHN0cmlwcGVkLCBcInR5cGVcIikgPT09IFwic2luZ2xlX3F1b3RlX3N0cmluZ1wiO1xufVxuXG5mdW5jdGlvbiBnZXRTcWxDYXN0VGFyZ2V0VHlwZShub2RlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICghQXJyYXkuaXNBcnJheShub2RlLnRhcmdldCkpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgLy8gY2hhcmFjdGVyIHZhcnlpbmfsspjrn7wg7Jes65+sIHRva2Vu7Jy866GcIOyYpOuKlCDtg4DsnoXrqoXsnYQg7ZWY64KY7J2YIOygleq3nO2ZlCDrrLjsnpDsl7TroZwg7ZWp7Lmc64ukLlxuICBjb25zdCBkYXRhVHlwZXMgPSBub2RlLnRhcmdldFxuICAgIC5tYXAoKHRhcmdldCkgPT4gKGlzU3FsQXN0UmVjb3JkKHRhcmdldCkgPyBnZXRTcWxBc3RTdHJpbmcodGFyZ2V0LCBcImRhdGFUeXBlXCIpIDogdW5kZWZpbmVkKSlcbiAgICAuZmlsdGVyKChkYXRhVHlwZSk6IGRhdGFUeXBlIGlzIHN0cmluZyA9PiBkYXRhVHlwZSAhPT0gdW5kZWZpbmVkKTtcblxuICBpZiAoZGF0YVR5cGVzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4gbm9ybWFsaXplU3FsRGF0YVR5cGUoZGF0YVR5cGVzLmpvaW4oXCIgXCIpKTtcbn1cblxuZnVuY3Rpb24gaXNUZXh0TGlrZVNxbFR5cGUodHlwZTogc3RyaW5nKTogYm9vbGVhbiB7XG4gIC8vIOuwsOyXtCBjYXN0KHRleHRbXSnrj4Qg7JuQ7IaMIG1lbWJlcnNoaXAg67mE6rWQ7JeQ7ISc64qUIHRleHQg6rOE7Je0IGNhc3TroZwg7Leo6riJ7ZWc64ukLlxuICBjb25zdCBub3JtYWxpemVkID0gbm9ybWFsaXplU3FsRGF0YVR5cGUodHlwZSkucmVwbGFjZSgvXFxbXFxdJC9nLCBcIlwiKTtcbiAgcmV0dXJuIG5vcm1hbGl6ZWQgPT09IFwidGV4dFwiIHx8IG5vcm1hbGl6ZWQgPT09IFwidmFyY2hhclwiO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVTcWxEYXRhVHlwZSh0eXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gdHlwZVxuICAgIC50cmltKClcbiAgICAudG9Mb3dlckNhc2UoKVxuICAgIC5yZXBsYWNlKC9cXHMrL2csIFwiIFwiKVxuICAgIC5yZXBsYWNlKC9eY2hhcmFjdGVyIHZhcnlpbmckLywgXCJ2YXJjaGFyXCIpO1xufVxuXG5mdW5jdGlvbiBzZXJpYWxpemVTcWxDb2x1bW5SZWYobm9kZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBzdHJpbmcge1xuICBjb25zdCB0YWJsZSA9IHNlcmlhbGl6ZVNxbElkZW50aWZpZXJOb2RlKG5vZGUudGFibGUpO1xuICBjb25zdCBjb2x1bW4gPSBzZXJpYWxpemVTcWxJZGVudGlmaWVyTm9kZShub2RlLmNvbHVtbik7XG4gIHJldHVybiBgY29sdW1uOiR7dGFibGUgPyBgJHt0YWJsZX0uYCA6IFwiXCJ9JHtjb2x1bW59YDtcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplU3FsSWRlbnRpZmllck5vZGUobm9kZTogdW5rbm93bik6IHN0cmluZyB7XG4gIGlmICh0eXBlb2Ygbm9kZSA9PT0gXCJzdHJpbmdcIikge1xuICAgIHJldHVybiBub3JtYWxpemVTcWxJZGVudGlmaWVyKG5vZGUpO1xuICB9XG5cbiAgLy8gcXVvdGVkIGlkZW50aWZpZXLsmYAgZnVuY3Rpb24gbmFtZeydmCBBU1Qgc2hhcGUg7LCo7J2066W8IOqwmeydgCBpZGVudGlmaWVyIOusuOyekOyXtOuhnCDtnaHsiJjtlZzri6QuXG4gIGlmIChpc1NxbEFzdFJlY29yZChub2RlKSkge1xuICAgIGlmICh0eXBlb2Ygbm9kZS52YWx1ZSA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgcmV0dXJuIG5vcm1hbGl6ZVNxbElkZW50aWZpZXIobm9kZS52YWx1ZSk7XG4gICAgfVxuXG4gICAgaWYgKGlzU3FsQXN0UmVjb3JkKG5vZGUuZXhwcikgJiYgdHlwZW9mIG5vZGUuZXhwci52YWx1ZSA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgcmV0dXJuIG5vcm1hbGl6ZVNxbElkZW50aWZpZXIobm9kZS5leHByLnZhbHVlKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gXCJcIjtcbn1cblxuZnVuY3Rpb24gZ2V0U3FsRnVuY3Rpb25OYW1lKG5vZGU6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogc3RyaW5nIHtcbiAgY29uc3QgbmFtZSA9IG5vZGUubmFtZTtcbiAgaWYgKHR5cGVvZiBuYW1lID09PSBcInN0cmluZ1wiKSB7XG4gICAgcmV0dXJuIG5vcm1hbGl6ZVNxbElkZW50aWZpZXIobmFtZSk7XG4gIH1cblxuICAvLyBBTlnsspjrn7wgbmFtZS5uYW1lIOuwsOyXtOuhnCDsmKTripQgZnVuY3Rpb24gQVNU66W8IHNjaGVtYS1xdWFsaWZpZWQg7J2066aE6rmM7KeAIOyngOybkO2VnOuLpC5cbiAgaWYgKGlzU3FsQXN0UmVjb3JkKG5hbWUpKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkobmFtZS5uYW1lKSkge1xuICAgICAgcmV0dXJuIG5hbWUubmFtZS5tYXAoc2VyaWFsaXplU3FsSWRlbnRpZmllck5vZGUpLmpvaW4oXCIuXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IG5hbWVQYXJ0cyA9IHNlcmlhbGl6ZVNxbEV4cHJMaXN0KG5hbWUpO1xuICAgIGlmIChuYW1lUGFydHMubGVuZ3RoID4gMCkge1xuICAgICAgcmV0dXJuIG5hbWVQYXJ0cy5tYXAoc2VyaWFsaXplU3FsSWRlbnRpZmllck5vZGUpLmpvaW4oXCIuXCIpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBcIlwiO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVTcWxJZGVudGlmaWVyKGlkZW50aWZpZXI6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBpZGVudGlmaWVyLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVTcWxPcGVyYXRvcihvcGVyYXRvcjogc3RyaW5nIHwgdW5kZWZpbmVkKTogc3RyaW5nIHtcbiAgcmV0dXJuIChvcGVyYXRvciA/PyBcIlwiKS50cmltKCkudG9VcHBlckNhc2UoKS5yZXBsYWNlKC9cXHMrL2csIFwiIFwiKTtcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplVW5rbm93blNxbEFzdFJlY29yZChub2RlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IHN0cmluZyB7XG4gIC8vIOyVhOyngSDrqoXsi5zsoIHsnLzroZwg64uk66Oo7KeAIOyViuuKlCBBU1Qg64W465Oc64qUIGtleSDsoJXroKzroZwg7LWc7IaM7ZWcIOyViOygleyggeyduCDruYTqtZDqsJLsnYQg66eM65Og64ukLlxuICBjb25zdCBlbnRyaWVzID0gT2JqZWN0LmVudHJpZXMobm9kZSlcbiAgICAuZmlsdGVyKChba2V5XSkgPT4ga2V5ICE9PSBcInBhcmVudGhlc2VzXCIpXG4gICAgLnRvU29ydGVkKChbbGVmdF0sIFtyaWdodF0pID0+IGxlZnQubG9jYWxlQ29tcGFyZShyaWdodCkpXG4gICAgLm1hcCgoW2tleSwgdmFsdWVdKSA9PiBgJHtrZXl9OiR7c2VyaWFsaXplSW5kZXhXaGVyZUFzdCh2YWx1ZSl9YCk7XG5cbiAgcmV0dXJuIGByZWNvcmQ6eyR7ZW50cmllcy5qb2luKFwiLFwiKX19YDtcbn1cblxuZnVuY3Rpb24gaXNTcWxBc3RSZWNvcmQodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwib2JqZWN0XCIgJiYgdmFsdWUgIT09IG51bGwgJiYgIUFycmF5LmlzQXJyYXkodmFsdWUpO1xufVxuXG5mdW5jdGlvbiBnZXRTcWxBc3RTdHJpbmcobm9kZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sIGtleTogc3RyaW5nKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgdmFsdWUgPSBub2RlW2tleV07XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwic3RyaW5nXCIgPyB2YWx1ZSA6IHVuZGVmaW5lZDtcbn1cblxuZnVuY3Rpb24gZmluZE1hdGNoaW5nUGFyZW50aGVzaXNJblNxbChzb3VyY2U6IHN0cmluZywgb3BlbkluZGV4OiBudW1iZXIpOiBudW1iZXIge1xuICBsZXQgZGVwdGggPSAwO1xuICBsZXQgaW5TaW5nbGVRdW90ZSA9IGZhbHNlO1xuICBsZXQgaW5Eb3VibGVRdW90ZSA9IGZhbHNlO1xuXG4gIC8vIFNRTCDrrLjsnpDsl7Tqs7wgcXVvdGVkIGlkZW50aWZpZXIg64K067aAIOq0hO2YuOuKlCBncm91cGluZyDqtITtmLjroZwg7IS47KeAIOyViuuKlOuLpC5cbiAgZm9yIChsZXQgaW5kZXggPSBvcGVuSW5kZXg7IGluZGV4IDwgc291cmNlLmxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIGNvbnN0IGNoYXIgPSBzb3VyY2VbaW5kZXhdO1xuICAgIGNvbnN0IG5leHRDaGFyID0gc291cmNlW2luZGV4ICsgMV07XG5cbiAgICBpZiAoY2hhciA9PT0gXCInXCIgJiYgIWluRG91YmxlUXVvdGUpIHtcbiAgICAgIGlmIChpblNpbmdsZVF1b3RlICYmIG5leHRDaGFyID09PSBcIidcIikge1xuICAgICAgICBpbmRleCArPSAxO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGluU2luZ2xlUXVvdGUgPSAhaW5TaW5nbGVRdW90ZTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSAnXCInICYmICFpblNpbmdsZVF1b3RlKSB7XG4gICAgICBpZiAoaW5Eb3VibGVRdW90ZSAmJiBuZXh0Q2hhciA9PT0gJ1wiJykge1xuICAgICAgICBpbmRleCArPSAxO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGluRG91YmxlUXVvdGUgPSAhaW5Eb3VibGVRdW90ZTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChpblNpbmdsZVF1b3RlIHx8IGluRG91YmxlUXVvdGUpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSBcIihcIikge1xuICAgICAgZGVwdGggKz0gMTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSBcIilcIikge1xuICAgICAgZGVwdGggLT0gMTtcbiAgICAgIGlmIChkZXB0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm4gaW5kZXg7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIC0xO1xufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7OztBQVFBLFNBQWdCLDZCQUE2QixPQUErQztBQUMxRixLQUFJLENBQUMsT0FBTztBQUNWLFNBQU87O0NBSVQsTUFBTSxVQUFVLDBCQUEwQixNQUFNLE1BQU0sQ0FBQztBQUN2RCxLQUFJLFFBQVEsV0FBVyxHQUFHO0FBQ3hCLFNBQU87O0FBR1QsUUFBTzs7Ozs7O0FBT1QsU0FBZ0IsMENBQ2QsT0FDb0I7Q0FDcEIsTUFBTSxhQUFhLDZCQUE2QixNQUFNO0FBQ3RELEtBQUksQ0FBQyxZQUFZO0FBQ2YsU0FBTzs7QUFJVCxRQUFPLGtDQUFrQyxXQUFXLElBQUk7O0FBRzFELFNBQVMsMEJBQTBCLFFBQXdCO0NBQ3pELElBQUksVUFBVSxPQUFPLE1BQU07QUFHM0IsUUFBTyxRQUFRLFdBQVcsSUFBSSxJQUFJLFFBQVEsU0FBUyxJQUFJLEVBQUU7RUFDdkQsTUFBTSxhQUFhLDZCQUE2QixTQUFTLEVBQUU7QUFDM0QsTUFBSSxlQUFlLFFBQVEsU0FBUyxHQUFHO0FBQ3JDLGFBQVUsUUFBUSxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUMsTUFBTTtBQUNyQzs7QUFHRjs7QUFHRixRQUFPOztBQUdULFNBQVMsa0NBQWtDLE9BQW1DO0FBQzVFLEtBQUk7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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonamu",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.19",
|
|
4
4
|
"description": "Sonamu — TypeScript Fullstack API Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -129,10 +129,10 @@
|
|
|
129
129
|
"tsicli": "^1.0.5",
|
|
130
130
|
"vite": "8.0.5",
|
|
131
131
|
"vitest": "^4.1.2",
|
|
132
|
-
"@sonamu-kit/hmr-
|
|
133
|
-
"@sonamu-kit/tasks": "^0.3.0",
|
|
132
|
+
"@sonamu-kit/hmr-runner": "^0.2.0",
|
|
134
133
|
"@sonamu-kit/ts-loader": "^2.2.0",
|
|
135
|
-
"@sonamu-kit/
|
|
134
|
+
"@sonamu-kit/tasks": "^0.3.0",
|
|
135
|
+
"@sonamu-kit/hmr-hook": "^0.5.1"
|
|
136
136
|
},
|
|
137
137
|
"devDependencies": {
|
|
138
138
|
"@types/bcrypt": "^6",
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
2
|
+
import { type FetchFunction } from "@ai-sdk/provider-utils";
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { RtzrClientError } from "../error";
|
|
6
|
+
import { RtzrTranscriptionModel } from "../model";
|
|
7
|
+
|
|
8
|
+
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
9
|
+
|
|
10
|
+
type MockResponse = {
|
|
11
|
+
body: JsonValue;
|
|
12
|
+
status?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const audio = new Uint8Array([1, 2, 3]);
|
|
16
|
+
|
|
17
|
+
function jsonResponse({ body, status = 200 }: MockResponse): Response {
|
|
18
|
+
return new Response(JSON.stringify(body), {
|
|
19
|
+
status,
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createFetchMock(responses: MockResponse[]): FetchFunction {
|
|
27
|
+
return async () => {
|
|
28
|
+
const response = responses.shift();
|
|
29
|
+
if (response === undefined) {
|
|
30
|
+
throw new Error("Unexpected RTZR request");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return jsonResponse(response);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createModel(fetchMock: FetchFunction) {
|
|
38
|
+
return new RtzrTranscriptionModel("sommers", {
|
|
39
|
+
_internal: {
|
|
40
|
+
currentDate: () => new Date("2026-06-17T00:00:00.000Z"),
|
|
41
|
+
},
|
|
42
|
+
provider: "rtzr.transcription",
|
|
43
|
+
auth: {
|
|
44
|
+
clientId: "client-id",
|
|
45
|
+
clientSecret: "client-secret",
|
|
46
|
+
},
|
|
47
|
+
url: ({ path }) => `https://rtzr.test/v1${path}`,
|
|
48
|
+
headers: () => ({
|
|
49
|
+
"x-test": "1",
|
|
50
|
+
}),
|
|
51
|
+
fetch: fetchMock,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("RtzrTranscriptionModel", () => {
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.useRealTimers();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns completed transcription text, segments, and language", async () => {
|
|
61
|
+
vi.useFakeTimers();
|
|
62
|
+
const model = createModel(
|
|
63
|
+
createFetchMock([
|
|
64
|
+
{
|
|
65
|
+
body: {
|
|
66
|
+
access_token: "token",
|
|
67
|
+
expire_at: 1_797_000_000,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
body: {
|
|
72
|
+
id: "transcribe-1",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
body: {
|
|
77
|
+
id: "transcribe-1",
|
|
78
|
+
status: "completed",
|
|
79
|
+
results: {
|
|
80
|
+
utterances: [
|
|
81
|
+
{
|
|
82
|
+
start_at: 1000,
|
|
83
|
+
duration: 2500,
|
|
84
|
+
msg: "hello",
|
|
85
|
+
spk: 0,
|
|
86
|
+
lang: "en",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
start_at: 3500,
|
|
90
|
+
duration: 1200,
|
|
91
|
+
msg: "world",
|
|
92
|
+
spk: 0,
|
|
93
|
+
lang: "en",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
]),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const resultPromise = model.doGenerate({
|
|
103
|
+
audio,
|
|
104
|
+
mediaType: "audio/wav",
|
|
105
|
+
headers: {},
|
|
106
|
+
});
|
|
107
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
108
|
+
const result = await resultPromise;
|
|
109
|
+
|
|
110
|
+
expect(result.text).toBe("hello\nworld");
|
|
111
|
+
expect(result.language).toBe("en");
|
|
112
|
+
expect(result.segments).toEqual([
|
|
113
|
+
{
|
|
114
|
+
text: "hello",
|
|
115
|
+
startSecond: 1,
|
|
116
|
+
endSecond: 3,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
text: "world",
|
|
120
|
+
startSecond: 3,
|
|
121
|
+
endSecond: 4,
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("throws a detailed RTZR error when polling returns failed", async () => {
|
|
127
|
+
vi.useFakeTimers();
|
|
128
|
+
const model = createModel(
|
|
129
|
+
createFetchMock([
|
|
130
|
+
{
|
|
131
|
+
body: {
|
|
132
|
+
access_token: "token",
|
|
133
|
+
expire_at: 1_797_000_000,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
body: {
|
|
138
|
+
id: "transcribe-2",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
body: {
|
|
143
|
+
id: "transcribe-2",
|
|
144
|
+
status: "failed",
|
|
145
|
+
error: {
|
|
146
|
+
code: "E500",
|
|
147
|
+
message: "internal server error",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const resultPromise = model.doGenerate({
|
|
155
|
+
audio,
|
|
156
|
+
mediaType: "audio/wav",
|
|
157
|
+
headers: {},
|
|
158
|
+
});
|
|
159
|
+
const errorPromise = resultPromise.catch((error: unknown) => error);
|
|
160
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
161
|
+
const error = await errorPromise;
|
|
162
|
+
|
|
163
|
+
expect(error).toBeInstanceOf(RtzrClientError);
|
|
164
|
+
expect(error).toMatchObject({
|
|
165
|
+
message: expect.stringContaining("RTZR transcription failed for id transcribe-2"),
|
|
166
|
+
});
|
|
167
|
+
expect(error).toMatchObject({
|
|
168
|
+
message: expect.stringContaining("code E500"),
|
|
169
|
+
});
|
|
170
|
+
expect(error).toMatchObject({
|
|
171
|
+
message: expect.stringContaining("internal server error"),
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("converts flat RTZR HTTP errors to useful API messages", async () => {
|
|
176
|
+
const model = createModel(
|
|
177
|
+
createFetchMock([
|
|
178
|
+
{
|
|
179
|
+
body: {
|
|
180
|
+
access_token: "token",
|
|
181
|
+
expire_at: 1_797_000_000,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
status: 400,
|
|
186
|
+
body: {
|
|
187
|
+
code: "H0001",
|
|
188
|
+
msg: "unexpected end of JSON input",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
]),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
await expect(
|
|
195
|
+
model.doGenerate({
|
|
196
|
+
audio,
|
|
197
|
+
mediaType: "audio/wav",
|
|
198
|
+
headers: {},
|
|
199
|
+
}),
|
|
200
|
+
).rejects.toMatchObject({
|
|
201
|
+
name: "AI_APICallError",
|
|
202
|
+
message: expect.stringContaining("HTTP 400: code H0001: unexpected end of JSON input"),
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("includes auth response details without secrets", async () => {
|
|
207
|
+
const model = createModel(
|
|
208
|
+
createFetchMock([
|
|
209
|
+
{
|
|
210
|
+
status: 401,
|
|
211
|
+
body: {
|
|
212
|
+
code: "H401",
|
|
213
|
+
msg: "invalid client credentials",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
]),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const resultPromise = model.doGenerate({
|
|
220
|
+
audio,
|
|
221
|
+
mediaType: "audio/wav",
|
|
222
|
+
headers: {},
|
|
223
|
+
});
|
|
224
|
+
const error = await resultPromise.catch((caughtError: unknown) => caughtError);
|
|
225
|
+
|
|
226
|
+
expect(error).toBeInstanceOf(RtzrClientError);
|
|
227
|
+
expect(error).toMatchObject({
|
|
228
|
+
message: expect.stringContaining(
|
|
229
|
+
"Failed to authorize RTZR: HTTP 401: code H401: invalid client credentials",
|
|
230
|
+
),
|
|
231
|
+
});
|
|
232
|
+
expect(error).toMatchObject({
|
|
233
|
+
message: expect.not.stringContaining("client-secret"),
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("includes auth schema details for invalid auth payloads", async () => {
|
|
238
|
+
const model = createModel(
|
|
239
|
+
createFetchMock([
|
|
240
|
+
{
|
|
241
|
+
body: {
|
|
242
|
+
token: "wrong-field",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
]),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
await expect(
|
|
249
|
+
model.doGenerate({
|
|
250
|
+
audio,
|
|
251
|
+
mediaType: "audio/wav",
|
|
252
|
+
headers: {},
|
|
253
|
+
}),
|
|
254
|
+
).rejects.toThrow("Invalid RTZR auth response");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("keeps RTZR HTTP errors as API call errors", async () => {
|
|
258
|
+
const model = createModel(
|
|
259
|
+
createFetchMock([
|
|
260
|
+
{
|
|
261
|
+
body: {
|
|
262
|
+
access_token: "token",
|
|
263
|
+
expire_at: 1_797_000_000,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
status: 404,
|
|
268
|
+
body: {
|
|
269
|
+
code: "H0004",
|
|
270
|
+
msg: "not found",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
]),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
await expect(
|
|
277
|
+
model.doGenerate({
|
|
278
|
+
audio,
|
|
279
|
+
mediaType: "audio/wav",
|
|
280
|
+
headers: {},
|
|
281
|
+
}),
|
|
282
|
+
).rejects.toThrow(APICallError);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -19,6 +19,12 @@ export const rtzrTranscriptionResultResponseSchema = lazySchema(() =>
|
|
|
19
19
|
z.object({
|
|
20
20
|
id: z.string(),
|
|
21
21
|
status: z.enum(["transcribing", "completed", "failed"]),
|
|
22
|
+
error: z
|
|
23
|
+
.object({
|
|
24
|
+
code: z.union([z.string(), z.number()]).nullish(),
|
|
25
|
+
message: z.string().nullish(),
|
|
26
|
+
})
|
|
27
|
+
.nullish(),
|
|
22
28
|
results: z
|
|
23
29
|
.object({
|
|
24
30
|
utterances: z.array(
|