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.
@@ -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.17",
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-hook": "^0.5.1",
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/hmr-runner": "^0.2.0"
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(