synapse-storage 4.1.0 → 4.1.2

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.
@@ -1,5 +1,25 @@
1
1
  import { FetchBaseQueryArgs, RequestDefinition } from '../types/api.interface';
2
2
  import { QueryOptions, QueryResult } from '../types/query.interface';
3
+ /**
4
+ * Собирает абсолютный URL запроса из `path` и `baseUrl`.
5
+ *
6
+ * Важно: сохраняется поведение конкатенации (`baseUrl + path`), а не
7
+ * резолвинг через `new URL(path, baseUrl)`. Резолвинг отбросил бы префикс
8
+ * базы для абсолютных путей (например, `new URL('/api/x', 'http://host/_api')`
9
+ * даёт `http://host/api/x`, теряя `/_api`), что сломало бы проксирование.
10
+ *
11
+ * Покрываемые кейсы:
12
+ * - абсолютный `path` (`http(s)://...`) → используется как есть;
13
+ * - относительный `path` + абсолютный `baseUrl` → конкатенация даёт абсолютный URL;
14
+ * - относительный `path` + относительный `baseUrl` (браузер, напр. `/_api`) →
15
+ * результат резолвится относительно `window.location.origin`, чтобы
16
+ * `new URL(...)` не падал с `Invalid URL` (same-origin сохраняется).
17
+ *
18
+ * @param path Путь запроса
19
+ * @param baseUrl Базовый URL клиента (может быть абсолютным или относительным)
20
+ * @returns Готовый объект URL
21
+ */
22
+ export declare function buildRequestUrl(path: string, baseUrl: string): URL;
3
23
  /**
4
24
  * Создает базовый fetch-клиент для запросов с поддержкой файлов
5
25
  * @param options Настройки базового клиента
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-base-query.d.ts","sourceRoot":"","sources":["../../../src/api/utils/fetch-base-query.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,kBAAkB,EAAE,iBAAiB,EAAkB,MAAM,wBAAwB,CAAA;AAC1G,OAAO,EAAsB,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAqFxF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,IAGlE,aAAa,EAAE,aAAa,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,KAAK,EAC7F,MAAM,iBAAiB,CAAC,aAAa,CAAC,EACtC,cAAc,YAAY,YAAK,EAC/B,SAAS,OAAO,KACf,OAAO,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAsG1C"}
1
+ {"version":3,"file":"fetch-base-query.d.ts","sourceRoot":"","sources":["../../../src/api/utils/fetch-base-query.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,kBAAkB,EAAE,iBAAiB,EAAkB,MAAM,wBAAwB,CAAA;AAC1G,OAAO,EAAsB,YAAY,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAqFxF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,CAqBlE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,IAGlE,aAAa,EAAE,aAAa,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,KAAK,EAC7F,MAAM,iBAAiB,CAAC,aAAa,CAAC,EACtC,cAAc,YAAY,YAAK,EAC/B,SAAS,OAAO,KACf,OAAO,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAsG1C"}
@@ -136,6 +136,42 @@ import { getFileMetadataFromHeaders, getResponseFormatForMimeType, isFileRespons
136
136
  };
137
137
  }
138
138
  }
139
+ /**
140
+ * Собирает абсолютный URL запроса из `path` и `baseUrl`.
141
+ *
142
+ * Важно: сохраняется поведение конкатенации (`baseUrl + path`), а не
143
+ * резолвинг через `new URL(path, baseUrl)`. Резолвинг отбросил бы префикс
144
+ * базы для абсолютных путей (например, `new URL('/api/x', 'http://host/_api')`
145
+ * даёт `http://host/api/x`, теряя `/_api`), что сломало бы проксирование.
146
+ *
147
+ * Покрываемые кейсы:
148
+ * - абсолютный `path` (`http(s)://...`) → используется как есть;
149
+ * - относительный `path` + абсолютный `baseUrl` → конкатенация даёт абсолютный URL;
150
+ * - относительный `path` + относительный `baseUrl` (браузер, напр. `/_api`) →
151
+ * результат резолвится относительно `window.location.origin`, чтобы
152
+ * `new URL(...)` не падал с `Invalid URL` (same-origin сохраняется).
153
+ *
154
+ * @param path Путь запроса
155
+ * @param baseUrl Базовый URL клиента (может быть абсолютным или относительным)
156
+ * @returns Готовый объект URL
157
+ */ function buildRequestUrl(path, baseUrl) {
158
+ // Путь уже абсолютный — база не нужна.
159
+ if (/^https?:\/\//i.test(path)) {
160
+ return new URL(path);
161
+ }
162
+ const combined = `${baseUrl}${path}`;
163
+ // Конкатенация уже дала абсолютный URL (абсолютный baseUrl).
164
+ if (/^https?:\/\//i.test(combined)) {
165
+ return new URL(combined);
166
+ }
167
+ // Относительный результат — резолвим относительно origin'а в браузере.
168
+ if (typeof window !== 'undefined' && window.location?.origin) {
169
+ return new URL(combined, window.location.origin);
170
+ }
171
+ // Сервер с относительной базой: явный resolve невозможен. Пробуем как есть —
172
+ // даст понятную ошибку, если база действительно относительна.
173
+ return new URL(combined);
174
+ }
139
175
  /**
140
176
  * Создает базовый fetch-клиент для запросов с поддержкой файлов
141
177
  * @param options Настройки базового клиента
@@ -148,7 +184,7 @@ import { getFileMetadataFromHeaders, getResponseFormatForMimeType, isFileRespons
148
184
  // Определяем формат ответа с приоритетом от options
149
185
  const responseFormat = optResponseFormat || reqResponseFormat;
150
186
  // Строим URL с учетом api параметров
151
- const url = new URL(path.startsWith('http') ? path : `${baseUrl}${path}`);
187
+ const url = buildRequestUrl(path, baseUrl);
152
188
  // Добавляем query-параметры в URL
153
189
  if (query) {
154
190
  Object.entries(query).forEach(([key, value])=>{
@@ -237,6 +273,6 @@ import { getFileMetadataFromHeaders, getResponseFormatForMimeType, isFileRespons
237
273
  };
238
274
  }
239
275
 
240
- export { fetchBaseQuery };
276
+ export { buildRequestUrl, fetchBaseQuery };
241
277
 
242
278
  //# sourceMappingURL=fetch-base-query.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api/utils/fetch-base-query.js","sources":["../../../src/api/utils/fetch-base-query.ts"],"sourcesContent":["import { handleCallbackError, logError } from '../../_utils/error-handling.util'\nimport { ApiContext, FetchBaseQueryArgs, RequestDefinition, ResponseFormat } from '../types/api.interface'\nimport { FileDownloadResult, QueryOptions, QueryResult } from '../types/query.interface'\nimport { getFileMetadataFromHeaders, getResponseFormatForMimeType, isFileResponse } from './file-utils'\n\n/**\n * Извлекает данные из response в зависимости от формата\n * @param response Объект Response\n * @param format Формат ответа\n * @returns Объект с данными или ошибкой\n */\nasync function getResponseData<T, E extends Error>(response: Response, format?: ResponseFormat): Promise<{ data?: T; error?: E; fileMetadata?: FileDownloadResult }> {\n let responseFormat = format\n const contentType = response.headers.get('content-type') || ''\n\n // Если формат не указан, пытаемся определить его из MIME-типа\n if (!responseFormat && contentType) {\n // Проверка, является ли ответ файлом на основе заголовков\n if (isFileResponse(response.headers)) {\n responseFormat = ResponseFormat.Blob\n } else {\n responseFormat = getResponseFormatForMimeType(contentType)\n }\n }\n\n // Если формат всё ещё не определен, используем JSON по умолчанию\n if (!responseFormat) {\n responseFormat = ResponseFormat.Json\n }\n\n try {\n // Получение метаданных файла, если формат указывает на файл\n let fileMetadata: any\n if (responseFormat === ResponseFormat.Blob || responseFormat === ResponseFormat.ArrayBuffer) {\n fileMetadata = getFileMetadataFromHeaders(response.headers)\n }\n\n // Обработка данных в зависимости от формата\n switch (responseFormat) {\n case ResponseFormat.Json: {\n // Пробуем получить JSON-данные\n try {\n const data = await response.json()\n return response.ok ? { data: data as T, fileMetadata } : { error: data as E, fileMetadata }\n } catch (error) {\n // Если не удалось разобрать JSON, возвращаем текст\n const text = await response.text()\n return response.ok ? { data: text as unknown as T, fileMetadata } : { error: text as unknown as E, fileMetadata }\n }\n }\n\n case ResponseFormat.Text: {\n const text = await response.text()\n return response.ok ? { data: text as unknown as T, fileMetadata } : { error: text as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.Blob: {\n const blob = await response.blob()\n return response.ok ? { data: blob as unknown as T, fileMetadata } : { error: blob as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.ArrayBuffer: {\n const buffer = await response.arrayBuffer()\n return response.ok ? { data: buffer as unknown as T, fileMetadata } : { error: buffer as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.FormData: {\n const formData = await response.formData()\n return response.ok ? { data: formData as unknown as T, fileMetadata } : { error: formData as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.Raw: {\n return response.ok ? { data: response as unknown as T, fileMetadata } : { error: response as unknown as E, fileMetadata }\n }\n\n default:\n // Если формат неизвестен, возвращаем blob как наиболее универсальный\n // eslint-disable-next-line no-case-declarations\n const blob = await response.blob()\n return response.ok ? { data: blob as unknown as T, fileMetadata } : { error: blob as unknown as E, fileMetadata }\n }\n } catch (err) {\n handleCallbackError(`fetchBaseQuery: error extracting response data (format: ${responseFormat})`, err)\n return response.ok ? { data: undefined } : { error: err as E }\n }\n}\n\n/**\n * Создает базовый fetch-клиент для запросов с поддержкой файлов\n * @param options Настройки базового клиента\n * @returns Функция для выполнения запросов\n */\nexport function fetchBaseQuery(options: Omit<FetchBaseQueryArgs, 'prepareHeaders'>) {\n const { baseUrl, timeout = 30000, fetchFn = fetch, credentials = 'same-origin' } = options\n\n return async <RequestResult, RequestParams extends Record<string, any>, E extends Error = Error>(\n args: RequestDefinition<RequestParams>,\n queryOptions: QueryOptions = {},\n headers: Headers,\n ): Promise<QueryResult<RequestResult, E>> => {\n const { path, method, body, query, responseFormat: reqResponseFormat } = args\n\n const { signal, timeout: requestTimeout = timeout, responseFormat: optResponseFormat } = queryOptions\n\n // Определяем формат ответа с приоритетом от options\n const responseFormat = optResponseFormat || reqResponseFormat\n\n // Строим URL с учетом api параметров\n const url = new URL(path.startsWith('http') ? path : `${baseUrl}${path}`)\n\n // Добавляем query-параметры в URL\n if (query) {\n Object.entries(query).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n value.forEach((item) => url.searchParams.append(key, String(item)))\n } else {\n url.searchParams.append(key, String(value))\n }\n }\n })\n }\n\n // Если body это объект, конвертируем в JSON и устанавливаем Content-Type\n let serializedBody: string | FormData | Blob | undefined\n if (body !== undefined) {\n if (body instanceof FormData || body instanceof Blob) {\n serializedBody = body\n } else if (typeof body === 'object' && body !== null) {\n try {\n serializedBody = JSON.stringify(body)\n if (!headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json')\n }\n } catch (error) {\n logError('fetchBaseQuery: error serializing request body', error)\n serializedBody = String(body)\n }\n } else {\n serializedBody = String(body)\n }\n }\n\n // Создаем таймаут если указан\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n const timeoutPromise = new Promise<never>((_, reject) => {\n if (requestTimeout) {\n timeoutId = globalThis.setTimeout(() => {\n reject(new Error(`Превышено время ожидания запроса (${requestTimeout}мс)`))\n }, requestTimeout)\n }\n })\n\n try {\n // Выполняем запрос\n const fetchPromise = fetchFn(url.toString(), {\n method,\n headers,\n body: serializedBody,\n signal,\n credentials,\n })\n\n // Используем Promise.race для обработки таймаута\n const response = await Promise.race([fetchPromise, timeoutPromise])\n\n // Обрабатываем ответ\n const { data, error, fileMetadata } = await getResponseData<RequestResult, E>(response, responseFormat as ResponseFormat)\n\n // Формируем результат запроса\n const result: QueryResult<RequestResult, E> = {\n data,\n error,\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n fileDownloadResult: fileMetadata,\n }\n\n return result\n } catch (err) {\n // Обрабатываем ошибки сети или таймаута\n const error = err as Error\n handleCallbackError('fetchBaseQuery: request execution error', error)\n\n // Формируем результат с ошибкой\n return {\n error: error as E,\n ok: false,\n status: 0,\n statusText: error.message,\n headers: new Headers(),\n }\n } finally {\n // Очищаем таймер в любом случае\n if (timeoutId) {\n globalThis.clearTimeout(timeoutId)\n }\n }\n }\n}\n"],"names":["handleCallbackError","logError","ResponseFormat","getFileMetadataFromHeaders","getResponseFormatForMimeType","isFileResponse","getResponseData","response","format","responseFormat","contentType","fileMetadata","data","error","text","blob","buffer","formData","err","undefined","fetchBaseQuery","options","baseUrl","timeout","fetchFn","fetch","credentials","args","queryOptions","headers","path","method","body","query","reqResponseFormat","signal","requestTimeout","optResponseFormat","url","URL","Object","key","value","Array","item","String","serializedBody","FormData","Blob","JSON","timeoutId","timeoutPromise","Promise","_","reject","globalThis","Error","fetchPromise","result","Headers"],"mappings":"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"}
1
+ {"version":3,"file":"api/utils/fetch-base-query.js","sources":["../../../src/api/utils/fetch-base-query.ts"],"sourcesContent":["import { handleCallbackError, logError } from '../../_utils/error-handling.util'\nimport { ApiContext, FetchBaseQueryArgs, RequestDefinition, ResponseFormat } from '../types/api.interface'\nimport { FileDownloadResult, QueryOptions, QueryResult } from '../types/query.interface'\nimport { getFileMetadataFromHeaders, getResponseFormatForMimeType, isFileResponse } from './file-utils'\n\n/**\n * Извлекает данные из response в зависимости от формата\n * @param response Объект Response\n * @param format Формат ответа\n * @returns Объект с данными или ошибкой\n */\nasync function getResponseData<T, E extends Error>(response: Response, format?: ResponseFormat): Promise<{ data?: T; error?: E; fileMetadata?: FileDownloadResult }> {\n let responseFormat = format\n const contentType = response.headers.get('content-type') || ''\n\n // Если формат не указан, пытаемся определить его из MIME-типа\n if (!responseFormat && contentType) {\n // Проверка, является ли ответ файлом на основе заголовков\n if (isFileResponse(response.headers)) {\n responseFormat = ResponseFormat.Blob\n } else {\n responseFormat = getResponseFormatForMimeType(contentType)\n }\n }\n\n // Если формат всё ещё не определен, используем JSON по умолчанию\n if (!responseFormat) {\n responseFormat = ResponseFormat.Json\n }\n\n try {\n // Получение метаданных файла, если формат указывает на файл\n let fileMetadata: any\n if (responseFormat === ResponseFormat.Blob || responseFormat === ResponseFormat.ArrayBuffer) {\n fileMetadata = getFileMetadataFromHeaders(response.headers)\n }\n\n // Обработка данных в зависимости от формата\n switch (responseFormat) {\n case ResponseFormat.Json: {\n // Пробуем получить JSON-данные\n try {\n const data = await response.json()\n return response.ok ? { data: data as T, fileMetadata } : { error: data as E, fileMetadata }\n } catch (error) {\n // Если не удалось разобрать JSON, возвращаем текст\n const text = await response.text()\n return response.ok ? { data: text as unknown as T, fileMetadata } : { error: text as unknown as E, fileMetadata }\n }\n }\n\n case ResponseFormat.Text: {\n const text = await response.text()\n return response.ok ? { data: text as unknown as T, fileMetadata } : { error: text as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.Blob: {\n const blob = await response.blob()\n return response.ok ? { data: blob as unknown as T, fileMetadata } : { error: blob as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.ArrayBuffer: {\n const buffer = await response.arrayBuffer()\n return response.ok ? { data: buffer as unknown as T, fileMetadata } : { error: buffer as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.FormData: {\n const formData = await response.formData()\n return response.ok ? { data: formData as unknown as T, fileMetadata } : { error: formData as unknown as E, fileMetadata }\n }\n\n case ResponseFormat.Raw: {\n return response.ok ? { data: response as unknown as T, fileMetadata } : { error: response as unknown as E, fileMetadata }\n }\n\n default:\n // Если формат неизвестен, возвращаем blob как наиболее универсальный\n // eslint-disable-next-line no-case-declarations\n const blob = await response.blob()\n return response.ok ? { data: blob as unknown as T, fileMetadata } : { error: blob as unknown as E, fileMetadata }\n }\n } catch (err) {\n handleCallbackError(`fetchBaseQuery: error extracting response data (format: ${responseFormat})`, err)\n return response.ok ? { data: undefined } : { error: err as E }\n }\n}\n\n/**\n * Собирает абсолютный URL запроса из `path` и `baseUrl`.\n *\n * Важно: сохраняется поведение конкатенации (`baseUrl + path`), а не\n * резолвинг через `new URL(path, baseUrl)`. Резолвинг отбросил бы префикс\n * базы для абсолютных путей (например, `new URL('/api/x', 'http://host/_api')`\n * даёт `http://host/api/x`, теряя `/_api`), что сломало бы проксирование.\n *\n * Покрываемые кейсы:\n * - абсолютный `path` (`http(s)://...`) → используется как есть;\n * - относительный `path` + абсолютный `baseUrl` → конкатенация даёт абсолютный URL;\n * - относительный `path` + относительный `baseUrl` (браузер, напр. `/_api`) →\n * результат резолвится относительно `window.location.origin`, чтобы\n * `new URL(...)` не падал с `Invalid URL` (same-origin сохраняется).\n *\n * @param path Путь запроса\n * @param baseUrl Базовый URL клиента (может быть абсолютным или относительным)\n * @returns Готовый объект URL\n */\nexport function buildRequestUrl(path: string, baseUrl: string): URL {\n // Путь уже абсолютный — база не нужна.\n if (/^https?:\\/\\//i.test(path)) {\n return new URL(path)\n }\n\n const combined = `${baseUrl}${path}`\n\n // Конкатенация уже дала абсолютный URL (абсолютный baseUrl).\n if (/^https?:\\/\\//i.test(combined)) {\n return new URL(combined)\n }\n\n // Относительный результат — резолвим относительно origin'а в браузере.\n if (typeof window !== 'undefined' && window.location?.origin) {\n return new URL(combined, window.location.origin)\n }\n\n // Сервер с относительной базой: явный resolve невозможен. Пробуем как есть —\n // даст понятную ошибку, если база действительно относительна.\n return new URL(combined)\n}\n\n/**\n * Создает базовый fetch-клиент для запросов с поддержкой файлов\n * @param options Настройки базового клиента\n * @returns Функция для выполнения запросов\n */\nexport function fetchBaseQuery(options: Omit<FetchBaseQueryArgs, 'prepareHeaders'>) {\n const { baseUrl, timeout = 30000, fetchFn = fetch, credentials = 'same-origin' } = options\n\n return async <RequestResult, RequestParams extends Record<string, any>, E extends Error = Error>(\n args: RequestDefinition<RequestParams>,\n queryOptions: QueryOptions = {},\n headers: Headers,\n ): Promise<QueryResult<RequestResult, E>> => {\n const { path, method, body, query, responseFormat: reqResponseFormat } = args\n\n const { signal, timeout: requestTimeout = timeout, responseFormat: optResponseFormat } = queryOptions\n\n // Определяем формат ответа с приоритетом от options\n const responseFormat = optResponseFormat || reqResponseFormat\n\n // Строим URL с учетом api параметров\n const url = buildRequestUrl(path, baseUrl)\n\n // Добавляем query-параметры в URL\n if (query) {\n Object.entries(query).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n value.forEach((item) => url.searchParams.append(key, String(item)))\n } else {\n url.searchParams.append(key, String(value))\n }\n }\n })\n }\n\n // Если body это объект, конвертируем в JSON и устанавливаем Content-Type\n let serializedBody: string | FormData | Blob | undefined\n if (body !== undefined) {\n if (body instanceof FormData || body instanceof Blob) {\n serializedBody = body\n } else if (typeof body === 'object' && body !== null) {\n try {\n serializedBody = JSON.stringify(body)\n if (!headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json')\n }\n } catch (error) {\n logError('fetchBaseQuery: error serializing request body', error)\n serializedBody = String(body)\n }\n } else {\n serializedBody = String(body)\n }\n }\n\n // Создаем таймаут если указан\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n const timeoutPromise = new Promise<never>((_, reject) => {\n if (requestTimeout) {\n timeoutId = globalThis.setTimeout(() => {\n reject(new Error(`Превышено время ожидания запроса (${requestTimeout}мс)`))\n }, requestTimeout)\n }\n })\n\n try {\n // Выполняем запрос\n const fetchPromise = fetchFn(url.toString(), {\n method,\n headers,\n body: serializedBody,\n signal,\n credentials,\n })\n\n // Используем Promise.race для обработки таймаута\n const response = await Promise.race([fetchPromise, timeoutPromise])\n\n // Обрабатываем ответ\n const { data, error, fileMetadata } = await getResponseData<RequestResult, E>(response, responseFormat as ResponseFormat)\n\n // Формируем результат запроса\n const result: QueryResult<RequestResult, E> = {\n data,\n error,\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n fileDownloadResult: fileMetadata,\n }\n\n return result\n } catch (err) {\n // Обрабатываем ошибки сети или таймаута\n const error = err as Error\n handleCallbackError('fetchBaseQuery: request execution error', error)\n\n // Формируем результат с ошибкой\n return {\n error: error as E,\n ok: false,\n status: 0,\n statusText: error.message,\n headers: new Headers(),\n }\n } finally {\n // Очищаем таймер в любом случае\n if (timeoutId) {\n globalThis.clearTimeout(timeoutId)\n }\n }\n }\n}\n"],"names":["handleCallbackError","logError","ResponseFormat","getFileMetadataFromHeaders","getResponseFormatForMimeType","isFileResponse","getResponseData","response","format","responseFormat","contentType","fileMetadata","data","error","text","blob","buffer","formData","err","undefined","buildRequestUrl","path","baseUrl","URL","combined","window","fetchBaseQuery","options","timeout","fetchFn","fetch","credentials","args","queryOptions","headers","method","body","query","reqResponseFormat","signal","requestTimeout","optResponseFormat","url","Object","key","value","Array","item","String","serializedBody","FormData","Blob","JSON","timeoutId","timeoutPromise","Promise","_","reject","globalThis","Error","fetchPromise","result","Headers"],"mappings":"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"}
@@ -5,7 +5,7 @@ export declare function isEqual(a: any, b: any): boolean;
5
5
  /**
6
6
  * Finds all changed paths between two objects.
7
7
  */
8
- export declare function findChangedPaths(oldObj: any, newObj: any, prefix?: string, changedPaths?: Set<string>, visited?: WeakMap<any, boolean>): Set<string>;
8
+ export declare function findChangedPaths(oldObj: any, newObj: any, prefix?: string, changedPaths?: Set<string>, visited?: WeakMap<any, WeakSet<any>>): Set<string>;
9
9
  /**
10
10
  * Creates a lazy deep-clone of an object.
11
11
  * Shallow copy initially, structuredClone on first nested property access.
@@ -1 +1 @@
1
- {"version":3,"file":"state-diff.util.d.ts","sourceRoot":"","sources":["../../../../src/core/storage/utils/state-diff.util.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CA0B/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,SAAK,EAAE,YAAY,GAAE,GAAG,CAAC,MAAM,CAAqB,EAAE,OAAO,wBAA8B,GAAG,GAAG,CAAC,MAAM,CAAC,CAkCzK;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAsB3E"}
1
+ {"version":3,"file":"state-diff.util.d.ts","sourceRoot":"","sources":["../../../../src/core/storage/utils/state-diff.util.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CA0B/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,SAAK,EAAE,YAAY,GAAE,GAAG,CAAC,MAAM,CAAqB,EAAE,OAAO,6BAAmC,GAAG,GAAG,CAAC,MAAM,CAAC,CA2C9K;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAsB3E"}
@@ -32,8 +32,19 @@
32
32
  }
33
33
  return changedPaths;
34
34
  }
35
- if (visited.has(oldObj)) return changedPaths;
36
- visited.set(oldObj, true);
35
+ // Cycle/dedup guard keyed by the (oldObj, newObj) PAIR, not by oldObj alone.
36
+ // structuredClone deduplicates shared references, so a single `oldObj` can be
37
+ // reached via different paths leading to DIFFERENT `newObj`s. Keying on oldObj
38
+ // alone would skip the second, genuinely-changed branch entirely.
39
+ const seenNew = visited.get(oldObj);
40
+ if (seenNew) {
41
+ if (seenNew.has(newObj)) return changedPaths;
42
+ seenNew.add(newObj);
43
+ } else {
44
+ visited.set(oldObj, new WeakSet([
45
+ newObj
46
+ ]));
47
+ }
37
48
  const allKeys = new Set([
38
49
  ...Object.keys(oldObj || {}),
39
50
  ...Object.keys(newObj || {})
@@ -1 +1 @@
1
- {"version":3,"file":"core/storage/utils/state-diff.util.js","sources":["../../../../src/core/storage/utils/state-diff.util.ts"],"sourcesContent":["/**\n * Deep equality check for two values.\n */\nexport function isEqual(a: any, b: any): boolean {\n if (a === b) return true\n if (a == null || b == null) return a === b\n\n const typeA = typeof a\n const typeB = typeof b\n if (typeA !== typeB) return false\n if (typeA !== 'object') return a === b\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!isEqual(a[i], b[i])) return false\n }\n return true\n }\n\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n if (keysA.length !== keysB.length) return false\n\n return keysA.every((key) => Object.prototype.hasOwnProperty.call(b, key) && isEqual(a[key], b[key]))\n}\n\n/**\n * Finds all changed paths between two objects.\n */\nexport function findChangedPaths(oldObj: any, newObj: any, prefix = '', changedPaths: Set<string> = new Set<string>(), visited = new WeakMap<any, boolean>()): Set<string> {\n if (oldObj === newObj) return changedPaths\n\n if (typeof oldObj !== 'object' || typeof newObj !== 'object' || oldObj === null || newObj === null) {\n if (oldObj !== newObj) {\n changedPaths.add(prefix || '')\n }\n return changedPaths\n }\n\n if (visited.has(oldObj)) return changedPaths\n visited.set(oldObj, true)\n\n const allKeys = new Set([...Object.keys(oldObj || {}), ...Object.keys(newObj || {})])\n\n for (const key of allKeys) {\n const oldValue = oldObj[key]\n const newValue = newObj[key]\n if (oldValue === newValue) continue\n\n const path = prefix ? `${prefix}.${key}` : key\n\n if (oldValue && newValue && typeof oldValue === 'object' && typeof newValue === 'object' && !Array.isArray(oldValue) && !Array.isArray(newValue)) {\n findChangedPaths(oldValue, newValue, path, changedPaths, visited)\n } else if (Array.isArray(oldValue) && Array.isArray(newValue)) {\n if (!isEqual(oldValue, newValue)) {\n changedPaths.add(path)\n }\n } else if (!isEqual(oldValue, newValue)) {\n changedPaths.add(path)\n }\n }\n\n return changedPaths\n}\n\n/**\n * Creates a lazy deep-clone of an object.\n * Shallow copy initially, structuredClone on first nested property access.\n */\nexport function createLazyClone<T extends Record<string, any>>(source: T): T {\n const cloned = new Set<string>()\n const shallow = { ...source }\n\n return new Proxy(shallow, {\n get(target, prop, receiver) {\n if (typeof prop === 'string' && !cloned.has(prop)) {\n const val = target[prop as keyof typeof target]\n if (val !== null && typeof val === 'object') {\n cloned.add(prop)\n ;(target as any)[prop] = structuredClone(val)\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n set(target, prop, value) {\n if (typeof prop === 'string') {\n cloned.add(prop)\n }\n return Reflect.set(target, prop, value)\n },\n }) as T\n}\n"],"names":["isEqual","a","b","typeA","typeB","Date","Array","i","keysA","Object","keysB","key","findChangedPaths","oldObj","newObj","prefix","changedPaths","Set","visited","WeakMap","allKeys","oldValue","newValue","path","createLazyClone","source","cloned","shallow","Proxy","target","prop","receiver","val","structuredClone","Reflect","value"],"mappings":"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"}
1
+ {"version":3,"file":"core/storage/utils/state-diff.util.js","sources":["../../../../src/core/storage/utils/state-diff.util.ts"],"sourcesContent":["/**\n * Deep equality check for two values.\n */\nexport function isEqual(a: any, b: any): boolean {\n if (a === b) return true\n if (a == null || b == null) return a === b\n\n const typeA = typeof a\n const typeB = typeof b\n if (typeA !== typeB) return false\n if (typeA !== 'object') return a === b\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!isEqual(a[i], b[i])) return false\n }\n return true\n }\n\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n if (keysA.length !== keysB.length) return false\n\n return keysA.every((key) => Object.prototype.hasOwnProperty.call(b, key) && isEqual(a[key], b[key]))\n}\n\n/**\n * Finds all changed paths between two objects.\n */\nexport function findChangedPaths(oldObj: any, newObj: any, prefix = '', changedPaths: Set<string> = new Set<string>(), visited = new WeakMap<any, WeakSet<any>>()): Set<string> {\n if (oldObj === newObj) return changedPaths\n\n if (typeof oldObj !== 'object' || typeof newObj !== 'object' || oldObj === null || newObj === null) {\n if (oldObj !== newObj) {\n changedPaths.add(prefix || '')\n }\n return changedPaths\n }\n\n // Cycle/dedup guard keyed by the (oldObj, newObj) PAIR, not by oldObj alone.\n // structuredClone deduplicates shared references, so a single `oldObj` can be\n // reached via different paths leading to DIFFERENT `newObj`s. Keying on oldObj\n // alone would skip the second, genuinely-changed branch entirely.\n const seenNew = visited.get(oldObj)\n if (seenNew) {\n if (seenNew.has(newObj)) return changedPaths\n seenNew.add(newObj)\n } else {\n visited.set(oldObj, new WeakSet([newObj]))\n }\n\n const allKeys = new Set([...Object.keys(oldObj || {}), ...Object.keys(newObj || {})])\n\n for (const key of allKeys) {\n const oldValue = oldObj[key]\n const newValue = newObj[key]\n if (oldValue === newValue) continue\n\n const path = prefix ? `${prefix}.${key}` : key\n\n if (oldValue && newValue && typeof oldValue === 'object' && typeof newValue === 'object' && !Array.isArray(oldValue) && !Array.isArray(newValue)) {\n findChangedPaths(oldValue, newValue, path, changedPaths, visited)\n } else if (Array.isArray(oldValue) && Array.isArray(newValue)) {\n if (!isEqual(oldValue, newValue)) {\n changedPaths.add(path)\n }\n } else if (!isEqual(oldValue, newValue)) {\n changedPaths.add(path)\n }\n }\n\n return changedPaths\n}\n\n/**\n * Creates a lazy deep-clone of an object.\n * Shallow copy initially, structuredClone on first nested property access.\n */\nexport function createLazyClone<T extends Record<string, any>>(source: T): T {\n const cloned = new Set<string>()\n const shallow = { ...source }\n\n return new Proxy(shallow, {\n get(target, prop, receiver) {\n if (typeof prop === 'string' && !cloned.has(prop)) {\n const val = target[prop as keyof typeof target]\n if (val !== null && typeof val === 'object') {\n cloned.add(prop)\n ;(target as any)[prop] = structuredClone(val)\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n set(target, prop, value) {\n if (typeof prop === 'string') {\n cloned.add(prop)\n }\n return Reflect.set(target, prop, value)\n },\n }) as T\n}\n"],"names":["isEqual","a","b","typeA","typeB","Date","Array","i","keysA","Object","keysB","key","findChangedPaths","oldObj","newObj","prefix","changedPaths","Set","visited","WeakMap","seenNew","WeakSet","allKeys","oldValue","newValue","path","createLazyClone","source","cloned","shallow","Proxy","target","prop","receiver","val","structuredClone","Reflect","value"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synapse-storage",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
4
4
  "description": "Набор инструментов для управления состоянием и апи-запросами",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",