@omnistreamai/data-adapter-webdav 0.3.2 → 0.3.4
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/index.d.mts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/webdav-adapter.ts +73 -58
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/webdav-adapter.ts"],"sourcesContent":[],"mappings":";;;;;;AAYA;AACe,cADF,aAAA,YAAyB,aACvB,CAAA;EAQO,SAAA,IAAA,GARP,WAAA,CAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/webdav-adapter.ts"],"sourcesContent":[],"mappings":";;;;;;AAYA;AACe,cADF,aAAA,YAAyB,aACvB,CAAA;EAQO,SAAA,IAAA,GARP,WAAA,CAAA,MAAA;EAqHV,SAAA,IAAA,GAAA,eAAA;EA4BiB,QAAA,OAAA;EAEZ;;;EAiBe,UAAA,CAAA,OAAA,EA5JH,aA4JG,CAAA,EAAA,IAAA;EAGP;;;EAEb,QAAA,UAAA;EAwBA;;;EAeA,QAAA,QAAA;EAmBqB;;;EAIG,QAAA,OAAA;EAAhB;;;EA0H6B,SAAA,CAAA,SAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA5OrC,OA4OqC,CAAA,IAAA,CAAA;EAmDhB;;;gBAnQJ,kDAEZ,oBAEL,QAAQ;;;;mBAeY,8DAGf,QAAQ,qBAEb,QAAQ;;;;0DAwBR;;;;oBAWqB,0EAIrB,QAAQ;;;;oBAmBa,sDAEb,aAAa,sBAErB,QAAQ,gBAAgB;;;;;;;;;;;;4BA8GK;;;;gCAYQ;;;;oBAmDhB"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/webdav-adapter.ts"],"sourcesContent":[],"mappings":";;;;;;AAYA;AACe,cADF,aAAA,YAAyB,aACvB,CAAA;EAQO,SAAA,IAAA,GARP,WAAA,CAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/webdav-adapter.ts"],"sourcesContent":[],"mappings":";;;;;;AAYA;AACe,cADF,aAAA,YAAyB,aACvB,CAAA;EAQO,SAAA,IAAA,GARP,WAAA,CAAA,MAAA;EAqHV,SAAA,IAAA,GAAA,eAAA;EA4BiB,QAAA,OAAA;EAEZ;;;EAiBe,UAAA,CAAA,OAAA,EA5JH,aA4JG,CAAA,EAAA,IAAA;EAGP;;;EAEb,QAAA,UAAA;EAwBA;;;EAeA,QAAA,QAAA;EAmBqB;;;EAIG,QAAA,OAAA;EAAhB;;;EA0H6B,SAAA,CAAA,SAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA5OrC,OA4OqC,CAAA,IAAA,CAAA;EAmDhB;;;gBAnQJ,kDAEZ,oBAEL,QAAQ;;;;mBAeY,8DAGf,QAAQ,qBAEb,QAAQ;;;;0DAwBR;;;;oBAWqB,0EAIrB,QAAQ;;;;oBAmBa,sDAEb,aAAa,sBAErB,QAAQ,gBAAgB;;;;;;;;;;;;4BA8GK;;;;gCAYQ;;;;oBAmDhB"}
|
package/dist/index.js
CHANGED
|
@@ -135,7 +135,7 @@ var WebDAVAdapter = class {
|
|
|
135
135
|
const files = await this.listDirectory(storeName);
|
|
136
136
|
const allData = [];
|
|
137
137
|
for (const file of files) try {
|
|
138
|
-
const data = await this.request(this.buildUrl(
|
|
138
|
+
const data = await this.request(this.buildUrl(file), { method: "GET" });
|
|
139
139
|
if (data) allData.push(data);
|
|
140
140
|
} catch {}
|
|
141
141
|
let filteredData = allData;
|
|
@@ -214,10 +214,13 @@ var WebDAVAdapter = class {
|
|
|
214
214
|
const basePath = new URL(baseUrl).pathname;
|
|
215
215
|
while ((match = hrefRegex.exec(text)) !== null) {
|
|
216
216
|
const fullPath = match[1] || "";
|
|
217
|
+
let relativePath = "";
|
|
217
218
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
relativePath = new URL(fullPath).pathname.replace(basePath, "").replace(/^\//, "");
|
|
220
|
+
} catch {
|
|
221
|
+
relativePath = fullPath.replace(basePath, "").replace(/^\//, "");
|
|
222
|
+
}
|
|
223
|
+
if (relativePath && relativePath !== path.replace(/^\//, "")) files.push(relativePath);
|
|
221
224
|
}
|
|
222
225
|
return files;
|
|
223
226
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["AdapterType","headers: Record<string, string>","AuthType","allData: T[]","files: string[]"],"sources":["../src/webdav-adapter.ts"],"sourcesContent":["import {\n AdapterType,\n type RemoteAdapter,\n type QueryOptions,\n type PaginatedResult,\n type ServiceConfig,\n AuthType,\n} from '@omnistreamai/data-core';\n\n/**\n * WebDAV adapter implementation\n */\nexport class WebDAVAdapter implements RemoteAdapter {\n readonly type = AdapterType.Remote;\n readonly name = 'WebDAVAdapter';\n\n private service: ServiceConfig | null = null;\n\n /**\n * Set service configuration\n */\n setService(service: ServiceConfig): void {\n this.service = service;\n }\n\n /**\n * Get request headers\n */\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (!this.service?.authentication) {\n return headers;\n }\n\n const { authentication } = this.service;\n\n switch (authentication.authType) {\n case AuthType.Token:\n if (authentication.token) {\n headers['Authorization'] = `${authentication.token.token_type} ${authentication.token.access_token}`;\n }\n break;\n case AuthType.Bearer:\n if (authentication.bearer) {\n headers['Authorization'] = `Bearer ${authentication.bearer}`;\n }\n break;\n case AuthType.Basic:\n if (authentication.username && authentication.password) {\n const credentials = btoa(`${authentication.username}:${authentication.password}`);\n headers['Authorization'] = `Basic ${credentials}`;\n }\n break;\n }\n\n return headers;\n }\n\n /**\n * Build URL\n */\n private buildUrl(storeName: string, id?: string): string {\n if (!this.service) {\n throw new Error('Service not configured. Call setService() first.');\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, '');\n const storePath = `/${storeName}`;\n \n if (id) {\n return `${baseUrl}${storePath}/${id}`;\n }\n \n return `${baseUrl}${storePath}`;\n }\n\n /**\n * Send request\n */\n private async request<T>(\n url: string,\n options: RequestInit = {},\n {\n allowNotFound,\n }: {\n allowNotFound?: boolean;\n } = {}\n ): Promise<T | null> {\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.getHeaders(),\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n if (allowNotFound && response.status == 404) {\n return null;\n }\n throw new Error(`HTTP error! status: ${response.status}, message: ${response.statusText}`);\n }\n\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return null;\n }\n }\n\n /**\n * Initialize store\n */\n async initStore(\n storeName: string,\n _indexes: string[] = [],\n _idKey: string = 'id'\n ): Promise<void> {\n const path = storeName.replace(/^\\/+|\\/+$/g, '');\n const parts = path.split('/');\n\n let currentPath = '';\n for (const part of parts) {\n currentPath = currentPath ? `${currentPath}/${part}` : part;\n\n try {\n await this.listDirectory(currentPath);\n } catch {\n const url = this.buildUrl(currentPath);\n\n const response = await fetch(url, {\n method: 'MKCOL',\n headers: this.getHeaders(),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create store: ${response.statusText}`);\n }\n }\n }\n }\n\n /**\n * Add data\n */\n async add<T extends Record<string, unknown>>(\n storeName: string,\n data: T,\n idKey: string = 'id'\n ): Promise<T> {\n const id = String(data[idKey]);\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: 'PUT',\n body: JSON.stringify(data),\n });\n\n return result ?? data;\n }\n\n /**\n * Update data\n */\n async update<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n data: Partial<T>,\n idKey: string = 'id'\n ): Promise<T> {\n const existing = await this.getData<T>(storeName, id, idKey);\n if (!existing) {\n throw new Error('Not found id:' + id);\n }\n\n const updated = { ...existing, ...data };\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: 'PUT',\n body: JSON.stringify(updated),\n });\n\n return result ?? updated;\n }\n\n /**\n * Delete data\n */\n async delete(storeName: string, id: string, _idKey: string = 'id'): Promise<void> {\n const url = this.buildUrl(storeName, id);\n\n await this.request(url, {\n method: 'DELETE',\n });\n }\n\n /**\n * Get data by ID\n */\n async getData<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n _idKey: string = 'id'\n ): Promise<T | null> {\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: 'GET',\n }, {\n allowNotFound: true\n });\n\n return result;\n }\n\n /**\n * Get list data (with pagination support)\n */\n async getList<T extends Record<string, unknown>>(\n storeName: string,\n options: QueryOptions<T> = {},\n _idKey: string = 'id'\n ): Promise<PaginatedResult<T>> {\n const files = await this.listDirectory(storeName);\n\n const allData: T[] = [];\n for (const file of files) {\n try {\n const data = await this.request<T>(this.buildUrl(storeName, file), {\n method: 'GET',\n });\n if (data) {\n allData.push(data);\n }\n } catch {\n }\n }\n\n let filteredData = allData;\n\n if (options.where) {\n filteredData = allData.filter(item => {\n for (const key in options.where) {\n const itemValue = item[key];\n const whereValue = options.where![key];\n if (itemValue !== whereValue) {\n return false;\n }\n }\n return true;\n });\n }\n\n // Sorting\n if (options.sortBy) {\n filteredData = this.sortEntries(filteredData, String(options.sortBy), options.sortOrder);\n }\n\n const totalCount = filteredData.length;\n const page = options.page ?? 1;\n const limit = options.limit ?? totalCount;\n\n const startIndex = (page - 1) * limit;\n const endIndex = startIndex + limit;\n const paginatedData = filteredData.slice(startIndex, endIndex);\n\n return {\n data: paginatedData,\n totalCount,\n page,\n limit,\n };\n }\n\n /**\n * Sort entries by specified field\n * @param entries Array of entries to sort\n * @param key Sorting field name\n * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')\n * @returns Sorted array of entries\n */\n private sortEntries<T extends Record<string, unknown>>(\n entries: T[],\n key: string,\n sortOrder: 'asc' | 'desc' = 'asc'\n ): T[] {\n return [...entries].sort((a, b) => {\n const aValue = a[key];\n const bValue = b[key];\n\n // Handle undefined or null values\n if (aValue == null && bValue == null) return 0;\n if (aValue == null) return 1; // null/undefined at the end\n if (bValue == null) return -1; // null/undefined at the end\n\n let comparison = 0;\n\n // Number type sorting\n if (typeof aValue === \"number\" && typeof bValue === \"number\") {\n comparison = aValue - bValue;\n }\n // String type sorting\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n comparison = aValue.localeCompare(bValue);\n }\n // Date type sorting (if string format date)\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n const aDate = new Date(aValue);\n const bDate = new Date(bValue);\n if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) {\n comparison = aDate.getTime() - bDate.getTime();\n } else {\n // Convert other types to string for comparison\n comparison = String(aValue).localeCompare(String(bValue));\n }\n }\n // Convert other types to string for comparison\n else {\n comparison = String(aValue).localeCompare(String(bValue));\n }\n\n // Apply sort order\n return sortOrder === 'desc' ? -comparison : comparison;\n });\n }\n\n\n\n /**\n * Clear store\n */\n async clear(storeName: string): Promise<void> {\n const list = await this.getList(storeName);\n\n for (const item of list.data) {\n const id = String((item as Record<string, unknown>)['id']);\n await this.delete(storeName, id);\n }\n }\n\n /**\n * List directory contents\n */\n async listDirectory(path: string = ''): Promise<string[]> {\n if (!this.service) {\n throw new Error('Service not configured. Call setService() first.');\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, '');\n const url = path ? `${baseUrl}/${path.replace(/^\\//, '')}` : baseUrl;\n\n const response = await fetch(url, {\n method: 'PROPFIND',\n headers: {\n ...this.getHeaders(),\n Depth: '1',\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to list directory: ${response.statusText}`);\n }\n\n const text = await response.text();\n\n const hrefRegex = /<d:href[^>]*>([^<]+)<\\/d:href>/gi;\n const files: string[] = [];\n let match;\n\n const baseUrlObj = new URL(baseUrl);\n const basePath = baseUrlObj.pathname;\n\n while ((match = hrefRegex.exec(text)) !== null) {\n const fullPath = match[1] || '';\n try {\n const hrefUrl = new URL(fullPath);\n let relativePath = hrefUrl.pathname.replace(basePath, '').replace(/^\\//, '');\n\n if (relativePath && relativePath !== path.replace(/^\\//, '')) {\n files.push(relativePath);\n }\n } catch {\n }\n }\n\n return files;\n }\n\n /**\n * Test connection\n */\n async testConnection(): Promise<boolean> {\n try {\n await this.listDirectory('');\n return true;\n } catch {\n return false;\n }\n }\n}\n\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["AdapterType","headers: Record<string, string>","AuthType","allData: T[]","files: string[]"],"sources":["../src/webdav-adapter.ts"],"sourcesContent":["import {\n AdapterType,\n type RemoteAdapter,\n type QueryOptions,\n type PaginatedResult,\n type ServiceConfig,\n AuthType,\n} from \"@omnistreamai/data-core\";\n\n/**\n * WebDAV adapter implementation\n */\nexport class WebDAVAdapter implements RemoteAdapter {\n readonly type = AdapterType.Remote;\n readonly name = \"WebDAVAdapter\";\n\n private service: ServiceConfig | null = null;\n\n /**\n * Set service configuration\n */\n setService(service: ServiceConfig): void {\n this.service = service;\n }\n\n /**\n * Get request headers\n */\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (!this.service?.authentication) {\n return headers;\n }\n\n const { authentication } = this.service;\n\n switch (authentication.authType) {\n case AuthType.Token:\n if (authentication.token) {\n headers[\"Authorization\"] =\n `${authentication.token.token_type} ${authentication.token.access_token}`;\n }\n break;\n case AuthType.Bearer:\n if (authentication.bearer) {\n headers[\"Authorization\"] = `Bearer ${authentication.bearer}`;\n }\n break;\n case AuthType.Basic:\n if (authentication.username && authentication.password) {\n const credentials = btoa(\n `${authentication.username}:${authentication.password}`,\n );\n headers[\"Authorization\"] = `Basic ${credentials}`;\n }\n break;\n }\n\n return headers;\n }\n\n /**\n * Build URL\n */\n private buildUrl(storeName: string, id?: string): string {\n if (!this.service) {\n throw new Error(\"Service not configured. Call setService() first.\");\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, \"\");\n const storePath = `/${storeName}`;\n\n if (id) {\n return `${baseUrl}${storePath}/${id}`;\n }\n\n return `${baseUrl}${storePath}`;\n }\n\n /**\n * Send request\n */\n private async request<T>(\n url: string,\n options: RequestInit = {},\n {\n allowNotFound,\n }: {\n allowNotFound?: boolean;\n } = {},\n ): Promise<T | null> {\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.getHeaders(),\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n if (allowNotFound && response.status == 404) {\n return null;\n }\n throw new Error(\n `HTTP error! status: ${response.status}, message: ${response.statusText}`,\n );\n }\n\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return null;\n }\n }\n\n /**\n * Initialize store\n */\n async initStore(\n storeName: string,\n _indexes: string[] = [],\n _idKey: string = \"id\",\n ): Promise<void> {\n const path = storeName.replace(/^\\/+|\\/+$/g, \"\");\n const parts = path.split(\"/\");\n\n let currentPath = \"\";\n for (const part of parts) {\n currentPath = currentPath ? `${currentPath}/${part}` : part;\n\n try {\n await this.listDirectory(currentPath);\n } catch {\n const url = this.buildUrl(currentPath);\n\n const response = await fetch(url, {\n method: \"MKCOL\",\n headers: this.getHeaders(),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create store: ${response.statusText}`);\n }\n }\n }\n }\n\n /**\n * Add data\n */\n async add<T extends Record<string, unknown>>(\n storeName: string,\n data: T,\n idKey: string = \"id\",\n ): Promise<T> {\n const id = String(data[idKey]);\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: \"PUT\",\n body: JSON.stringify(data),\n });\n\n return result ?? data;\n }\n\n /**\n * Update data\n */\n async update<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n data: Partial<T>,\n idKey: string = \"id\",\n ): Promise<T> {\n const existing = await this.getData<T>(storeName, id, idKey);\n if (!existing) {\n throw new Error(\"Not found id:\" + id);\n }\n\n const updated = { ...existing, ...data };\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: \"PUT\",\n body: JSON.stringify(updated),\n });\n\n return result ?? updated;\n }\n\n /**\n * Delete data\n */\n async delete(\n storeName: string,\n id: string,\n _idKey: string = \"id\",\n ): Promise<void> {\n const url = this.buildUrl(storeName, id);\n\n await this.request(url, {\n method: \"DELETE\",\n });\n }\n\n /**\n * Get data by ID\n */\n async getData<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n _idKey: string = \"id\",\n ): Promise<T | null> {\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(\n url,\n {\n method: \"GET\",\n },\n {\n allowNotFound: true,\n },\n );\n\n return result;\n }\n\n /**\n * Get list data (with pagination support)\n */\n async getList<T extends Record<string, unknown>>(\n storeName: string,\n options: QueryOptions<T> = {},\n _idKey: string = \"id\",\n ): Promise<PaginatedResult<T>> {\n const files = await this.listDirectory(storeName);\n\n const allData: T[] = [];\n for (const file of files) {\n try {\n const data = await this.request<T>(this.buildUrl(file), {\n method: \"GET\",\n });\n if (data) {\n allData.push(data);\n }\n } catch {}\n }\n\n let filteredData = allData;\n\n if (options.where) {\n filteredData = allData.filter((item) => {\n for (const key in options.where) {\n const itemValue = item[key];\n const whereValue = options.where![key];\n if (itemValue !== whereValue) {\n return false;\n }\n }\n return true;\n });\n }\n\n // Sorting\n if (options.sortBy) {\n filteredData = this.sortEntries(\n filteredData,\n String(options.sortBy),\n options.sortOrder,\n );\n }\n\n const totalCount = filteredData.length;\n const page = options.page ?? 1;\n const limit = options.limit ?? totalCount;\n\n const startIndex = (page - 1) * limit;\n const endIndex = startIndex + limit;\n const paginatedData = filteredData.slice(startIndex, endIndex);\n\n return {\n data: paginatedData,\n totalCount,\n page,\n limit,\n };\n }\n\n /**\n * Sort entries by specified field\n * @param entries Array of entries to sort\n * @param key Sorting field name\n * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')\n * @returns Sorted array of entries\n */\n private sortEntries<T extends Record<string, unknown>>(\n entries: T[],\n key: string,\n sortOrder: \"asc\" | \"desc\" = \"asc\",\n ): T[] {\n return [...entries].sort((a, b) => {\n const aValue = a[key];\n const bValue = b[key];\n\n // Handle undefined or null values\n if (aValue == null && bValue == null) return 0;\n if (aValue == null) return 1; // null/undefined at the end\n if (bValue == null) return -1; // null/undefined at the end\n\n let comparison = 0;\n\n // Number type sorting\n if (typeof aValue === \"number\" && typeof bValue === \"number\") {\n comparison = aValue - bValue;\n }\n // String type sorting\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n comparison = aValue.localeCompare(bValue);\n }\n // Date type sorting (if string format date)\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n const aDate = new Date(aValue);\n const bDate = new Date(bValue);\n if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) {\n comparison = aDate.getTime() - bDate.getTime();\n } else {\n // Convert other types to string for comparison\n comparison = String(aValue).localeCompare(String(bValue));\n }\n }\n // Convert other types to string for comparison\n else {\n comparison = String(aValue).localeCompare(String(bValue));\n }\n\n // Apply sort order\n return sortOrder === \"desc\" ? -comparison : comparison;\n });\n }\n\n /**\n * Clear store\n */\n async clear(storeName: string): Promise<void> {\n const list = await this.getList(storeName);\n\n for (const item of list.data) {\n const id = String((item as Record<string, unknown>)[\"id\"]);\n await this.delete(storeName, id);\n }\n }\n\n /**\n * List directory contents\n */\n async listDirectory(path: string = \"\"): Promise<string[]> {\n if (!this.service) {\n throw new Error(\"Service not configured. Call setService() first.\");\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, \"\");\n const url = path ? `${baseUrl}/${path.replace(/^\\//, \"\")}` : baseUrl;\n\n const response = await fetch(url, {\n method: \"PROPFIND\",\n headers: {\n ...this.getHeaders(),\n Depth: \"1\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to list directory: ${response.statusText}`);\n }\n\n const text = await response.text();\n\n const hrefRegex = /<d:href[^>]*>([^<]+)<\\/d:href>/gi;\n const files: string[] = [];\n let match;\n\n const baseUrlObj = new URL(baseUrl);\n const basePath = baseUrlObj.pathname;\n\n while ((match = hrefRegex.exec(text)) !== null) {\n const fullPath = match[1] || \"\";\n let relativePath = \"\";\n try {\n const hrefUrl = new URL(fullPath);\n relativePath = hrefUrl.pathname\n .replace(basePath, \"\")\n .replace(/^\\//, \"\");\n } catch {\n relativePath = fullPath.replace(basePath, \"\").replace(/^\\//, \"\");\n }\n if (relativePath && relativePath !== path.replace(/^\\//, \"\")) {\n files.push(relativePath);\n }\n }\n\n return files;\n }\n\n /**\n * Test connection\n */\n async testConnection(): Promise<boolean> {\n try {\n await this.listDirectory(\"\");\n return true;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":";;;;;;AAYA,IAAa,gBAAb,MAAoD;CAClD,AAAS,OAAOA,qCAAY;CAC5B,AAAS,OAAO;CAEhB,AAAQ,UAAgC;;;;CAKxC,WAAW,SAA8B;AACvC,OAAK,UAAU;;;;;CAMjB,AAAQ,aAAqC;EAC3C,MAAMC,UAAkC,EACtC,gBAAgB,oBACjB;AAED,MAAI,CAAC,KAAK,SAAS,eACjB,QAAO;EAGT,MAAM,EAAE,mBAAmB,KAAK;AAEhC,UAAQ,eAAe,UAAvB;GACE,KAAKC,kCAAS;AACZ,QAAI,eAAe,MACjB,SAAQ,mBACN,GAAG,eAAe,MAAM,WAAW,GAAG,eAAe,MAAM;AAE/D;GACF,KAAKA,kCAAS;AACZ,QAAI,eAAe,OACjB,SAAQ,mBAAmB,UAAU,eAAe;AAEtD;GACF,KAAKA,kCAAS;AACZ,QAAI,eAAe,YAAY,eAAe,SAI5C,SAAQ,mBAAmB,SAHP,KAClB,GAAG,eAAe,SAAS,GAAG,eAAe,WAC9C;AAGH;;AAGJ,SAAO;;;;;CAMT,AAAQ,SAAS,WAAmB,IAAqB;AACvD,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,mDAAmD;EAGrE,MAAM,UAAU,KAAK,QAAQ,SAAS,QAAQ,OAAO,GAAG;EACxD,MAAM,YAAY,IAAI;AAEtB,MAAI,GACF,QAAO,GAAG,UAAU,UAAU,GAAG;AAGnC,SAAO,GAAG,UAAU;;;;;CAMtB,MAAc,QACZ,KACA,UAAuB,EAAE,EACzB,EACE,kBAGE,EAAE,EACa;EACnB,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,GAAG;GACH,SAAS;IACP,GAAG,KAAK,YAAY;IACpB,GAAG,QAAQ;IACZ;GACF,CAAC;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,OAAI,iBAAiB,SAAS,UAAU,IACtC,QAAO;AAET,SAAM,IAAI,MACR,uBAAuB,SAAS,OAAO,aAAa,SAAS,aAC9D;;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,MAAI,CAAC,KACH,QAAO;AAGT,MAAI;AACF,UAAO,KAAK,MAAM,KAAK;UACjB;AACN,UAAO;;;;;;CAOX,MAAM,UACJ,WACA,WAAqB,EAAE,EACvB,SAAiB,MACF;EAEf,MAAM,QADO,UAAU,QAAQ,cAAc,GAAG,CAC7B,MAAM,IAAI;EAE7B,IAAI,cAAc;AAClB,OAAK,MAAM,QAAQ,OAAO;AACxB,iBAAc,cAAc,GAAG,YAAY,GAAG,SAAS;AAEvD,OAAI;AACF,UAAM,KAAK,cAAc,YAAY;WAC/B;IACN,MAAM,MAAM,KAAK,SAAS,YAAY;IAEtC,MAAM,WAAW,MAAM,MAAM,KAAK;KAChC,QAAQ;KACR,SAAS,KAAK,YAAY;KAC3B,CAAC;AAEF,QAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,2BAA2B,SAAS,aAAa;;;;;;;CASzE,MAAM,IACJ,WACA,MACA,QAAgB,MACJ;EACZ,MAAM,KAAK,OAAO,KAAK,OAAO;EAC9B,MAAM,MAAM,KAAK,SAAS,WAAW,GAAG;AAOxC,SALe,MAAM,KAAK,QAAW,KAAK;GACxC,QAAQ;GACR,MAAM,KAAK,UAAU,KAAK;GAC3B,CAAC,IAEe;;;;;CAMnB,MAAM,OACJ,WACA,IACA,MACA,QAAgB,MACJ;EACZ,MAAM,WAAW,MAAM,KAAK,QAAW,WAAW,IAAI,MAAM;AAC5D,MAAI,CAAC,SACH,OAAM,IAAI,MAAM,kBAAkB,GAAG;EAGvC,MAAM,UAAU;GAAE,GAAG;GAAU,GAAG;GAAM;EACxC,MAAM,MAAM,KAAK,SAAS,WAAW,GAAG;AAOxC,SALe,MAAM,KAAK,QAAW,KAAK;GACxC,QAAQ;GACR,MAAM,KAAK,UAAU,QAAQ;GAC9B,CAAC,IAEe;;;;;CAMnB,MAAM,OACJ,WACA,IACA,SAAiB,MACF;EACf,MAAM,MAAM,KAAK,SAAS,WAAW,GAAG;AAExC,QAAM,KAAK,QAAQ,KAAK,EACtB,QAAQ,UACT,CAAC;;;;;CAMJ,MAAM,QACJ,WACA,IACA,SAAiB,MACE;EACnB,MAAM,MAAM,KAAK,SAAS,WAAW,GAAG;AAYxC,SAVe,MAAM,KAAK,QACxB,KACA,EACE,QAAQ,OACT,EACD,EACE,eAAe,MAChB,CACF;;;;;CAQH,MAAM,QACJ,WACA,UAA2B,EAAE,EAC7B,SAAiB,MACY;EAC7B,MAAM,QAAQ,MAAM,KAAK,cAAc,UAAU;EAEjD,MAAMC,UAAe,EAAE;AACvB,OAAK,MAAM,QAAQ,MACjB,KAAI;GACF,MAAM,OAAO,MAAM,KAAK,QAAW,KAAK,SAAS,KAAK,EAAE,EACtD,QAAQ,OACT,CAAC;AACF,OAAI,KACF,SAAQ,KAAK,KAAK;UAEd;EAGV,IAAI,eAAe;AAEnB,MAAI,QAAQ,MACV,gBAAe,QAAQ,QAAQ,SAAS;AACtC,QAAK,MAAM,OAAO,QAAQ,MAGxB,KAFkB,KAAK,SACJ,QAAQ,MAAO,KAEhC,QAAO;AAGX,UAAO;IACP;AAIJ,MAAI,QAAQ,OACV,gBAAe,KAAK,YAClB,cACA,OAAO,QAAQ,OAAO,EACtB,QAAQ,UACT;EAGH,MAAM,aAAa,aAAa;EAChC,MAAM,OAAO,QAAQ,QAAQ;EAC7B,MAAM,QAAQ,QAAQ,SAAS;EAE/B,MAAM,cAAc,OAAO,KAAK;EAChC,MAAM,WAAW,aAAa;AAG9B,SAAO;GACL,MAHoB,aAAa,MAAM,YAAY,SAAS;GAI5D;GACA;GACA;GACD;;;;;;;;;CAUH,AAAQ,YACN,SACA,KACA,YAA4B,OACvB;AACL,SAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,MAAM;GACjC,MAAM,SAAS,EAAE;GACjB,MAAM,SAAS,EAAE;AAGjB,OAAI,UAAU,QAAQ,UAAU,KAAM,QAAO;AAC7C,OAAI,UAAU,KAAM,QAAO;AAC3B,OAAI,UAAU,KAAM,QAAO;GAE3B,IAAI,aAAa;AAGjB,OAAI,OAAO,WAAW,YAAY,OAAO,WAAW,SAClD,cAAa,SAAS;YAGf,OAAO,WAAW,YAAY,OAAO,WAAW,SACvD,cAAa,OAAO,cAAc,OAAO;YAGlC,OAAO,WAAW,YAAY,OAAO,WAAW,UAAU;IACjE,MAAM,QAAQ,IAAI,KAAK,OAAO;IAC9B,MAAM,QAAQ,IAAI,KAAK,OAAO;AAC9B,QAAI,CAAC,MAAM,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,MAAM,SAAS,CAAC,CACpD,cAAa,MAAM,SAAS,GAAG,MAAM,SAAS;QAG9C,cAAa,OAAO,OAAO,CAAC,cAAc,OAAO,OAAO,CAAC;SAK3D,cAAa,OAAO,OAAO,CAAC,cAAc,OAAO,OAAO,CAAC;AAI3D,UAAO,cAAc,SAAS,CAAC,aAAa;IAC5C;;;;;CAMJ,MAAM,MAAM,WAAkC;EAC5C,MAAM,OAAO,MAAM,KAAK,QAAQ,UAAU;AAE1C,OAAK,MAAM,QAAQ,KAAK,MAAM;GAC5B,MAAM,KAAK,OAAQ,KAAiC,MAAM;AAC1D,SAAM,KAAK,OAAO,WAAW,GAAG;;;;;;CAOpC,MAAM,cAAc,OAAe,IAAuB;AACxD,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,mDAAmD;EAGrE,MAAM,UAAU,KAAK,QAAQ,SAAS,QAAQ,OAAO,GAAG;EACxD,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,KAAK,QAAQ,OAAO,GAAG,KAAK;EAE7D,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,GAAG,KAAK,YAAY;IACpB,OAAO;IACR;GACF,CAAC;AAEF,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,6BAA6B,SAAS,aAAa;EAGrE,MAAM,OAAO,MAAM,SAAS,MAAM;EAElC,MAAM,YAAY;EAClB,MAAMC,QAAkB,EAAE;EAC1B,IAAI;EAGJ,MAAM,WADa,IAAI,IAAI,QAAQ,CACP;AAE5B,UAAQ,QAAQ,UAAU,KAAK,KAAK,MAAM,MAAM;GAC9C,MAAM,WAAW,MAAM,MAAM;GAC7B,IAAI,eAAe;AACnB,OAAI;AAEF,mBADgB,IAAI,IAAI,SAAS,CACV,SACpB,QAAQ,UAAU,GAAG,CACrB,QAAQ,OAAO,GAAG;WACf;AACN,mBAAe,SAAS,QAAQ,UAAU,GAAG,CAAC,QAAQ,OAAO,GAAG;;AAElE,OAAI,gBAAgB,iBAAiB,KAAK,QAAQ,OAAO,GAAG,CAC1D,OAAM,KAAK,aAAa;;AAI5B,SAAO;;;;;CAMT,MAAM,iBAAmC;AACvC,MAAI;AACF,SAAM,KAAK,cAAc,GAAG;AAC5B,UAAO;UACD;AACN,UAAO"}
|
package/dist/index.mjs
CHANGED
|
@@ -135,7 +135,7 @@ var WebDAVAdapter = class {
|
|
|
135
135
|
const files = await this.listDirectory(storeName);
|
|
136
136
|
const allData = [];
|
|
137
137
|
for (const file of files) try {
|
|
138
|
-
const data = await this.request(this.buildUrl(
|
|
138
|
+
const data = await this.request(this.buildUrl(file), { method: "GET" });
|
|
139
139
|
if (data) allData.push(data);
|
|
140
140
|
} catch {}
|
|
141
141
|
let filteredData = allData;
|
|
@@ -214,10 +214,13 @@ var WebDAVAdapter = class {
|
|
|
214
214
|
const basePath = new URL(baseUrl).pathname;
|
|
215
215
|
while ((match = hrefRegex.exec(text)) !== null) {
|
|
216
216
|
const fullPath = match[1] || "";
|
|
217
|
+
let relativePath = "";
|
|
217
218
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
relativePath = new URL(fullPath).pathname.replace(basePath, "").replace(/^\//, "");
|
|
220
|
+
} catch {
|
|
221
|
+
relativePath = fullPath.replace(basePath, "").replace(/^\//, "");
|
|
222
|
+
}
|
|
223
|
+
if (relativePath && relativePath !== path.replace(/^\//, "")) files.push(relativePath);
|
|
221
224
|
}
|
|
222
225
|
return files;
|
|
223
226
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["headers: Record<string, string>","allData: T[]","files: string[]"],"sources":["../src/webdav-adapter.ts"],"sourcesContent":["import {\n AdapterType,\n type RemoteAdapter,\n type QueryOptions,\n type PaginatedResult,\n type ServiceConfig,\n AuthType,\n} from '@omnistreamai/data-core';\n\n/**\n * WebDAV adapter implementation\n */\nexport class WebDAVAdapter implements RemoteAdapter {\n readonly type = AdapterType.Remote;\n readonly name = 'WebDAVAdapter';\n\n private service: ServiceConfig | null = null;\n\n /**\n * Set service configuration\n */\n setService(service: ServiceConfig): void {\n this.service = service;\n }\n\n /**\n * Get request headers\n */\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (!this.service?.authentication) {\n return headers;\n }\n\n const { authentication } = this.service;\n\n switch (authentication.authType) {\n case AuthType.Token:\n if (authentication.token) {\n headers['Authorization'] = `${authentication.token.token_type} ${authentication.token.access_token}`;\n }\n break;\n case AuthType.Bearer:\n if (authentication.bearer) {\n headers['Authorization'] = `Bearer ${authentication.bearer}`;\n }\n break;\n case AuthType.Basic:\n if (authentication.username && authentication.password) {\n const credentials = btoa(`${authentication.username}:${authentication.password}`);\n headers['Authorization'] = `Basic ${credentials}`;\n }\n break;\n }\n\n return headers;\n }\n\n /**\n * Build URL\n */\n private buildUrl(storeName: string, id?: string): string {\n if (!this.service) {\n throw new Error('Service not configured. Call setService() first.');\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, '');\n const storePath = `/${storeName}`;\n \n if (id) {\n return `${baseUrl}${storePath}/${id}`;\n }\n \n return `${baseUrl}${storePath}`;\n }\n\n /**\n * Send request\n */\n private async request<T>(\n url: string,\n options: RequestInit = {},\n {\n allowNotFound,\n }: {\n allowNotFound?: boolean;\n } = {}\n ): Promise<T | null> {\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.getHeaders(),\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n if (allowNotFound && response.status == 404) {\n return null;\n }\n throw new Error(`HTTP error! status: ${response.status}, message: ${response.statusText}`);\n }\n\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return null;\n }\n }\n\n /**\n * Initialize store\n */\n async initStore(\n storeName: string,\n _indexes: string[] = [],\n _idKey: string = 'id'\n ): Promise<void> {\n const path = storeName.replace(/^\\/+|\\/+$/g, '');\n const parts = path.split('/');\n\n let currentPath = '';\n for (const part of parts) {\n currentPath = currentPath ? `${currentPath}/${part}` : part;\n\n try {\n await this.listDirectory(currentPath);\n } catch {\n const url = this.buildUrl(currentPath);\n\n const response = await fetch(url, {\n method: 'MKCOL',\n headers: this.getHeaders(),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create store: ${response.statusText}`);\n }\n }\n }\n }\n\n /**\n * Add data\n */\n async add<T extends Record<string, unknown>>(\n storeName: string,\n data: T,\n idKey: string = 'id'\n ): Promise<T> {\n const id = String(data[idKey]);\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: 'PUT',\n body: JSON.stringify(data),\n });\n\n return result ?? data;\n }\n\n /**\n * Update data\n */\n async update<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n data: Partial<T>,\n idKey: string = 'id'\n ): Promise<T> {\n const existing = await this.getData<T>(storeName, id, idKey);\n if (!existing) {\n throw new Error('Not found id:' + id);\n }\n\n const updated = { ...existing, ...data };\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: 'PUT',\n body: JSON.stringify(updated),\n });\n\n return result ?? updated;\n }\n\n /**\n * Delete data\n */\n async delete(storeName: string, id: string, _idKey: string = 'id'): Promise<void> {\n const url = this.buildUrl(storeName, id);\n\n await this.request(url, {\n method: 'DELETE',\n });\n }\n\n /**\n * Get data by ID\n */\n async getData<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n _idKey: string = 'id'\n ): Promise<T | null> {\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: 'GET',\n }, {\n allowNotFound: true\n });\n\n return result;\n }\n\n /**\n * Get list data (with pagination support)\n */\n async getList<T extends Record<string, unknown>>(\n storeName: string,\n options: QueryOptions<T> = {},\n _idKey: string = 'id'\n ): Promise<PaginatedResult<T>> {\n const files = await this.listDirectory(storeName);\n\n const allData: T[] = [];\n for (const file of files) {\n try {\n const data = await this.request<T>(this.buildUrl(storeName, file), {\n method: 'GET',\n });\n if (data) {\n allData.push(data);\n }\n } catch {\n }\n }\n\n let filteredData = allData;\n\n if (options.where) {\n filteredData = allData.filter(item => {\n for (const key in options.where) {\n const itemValue = item[key];\n const whereValue = options.where![key];\n if (itemValue !== whereValue) {\n return false;\n }\n }\n return true;\n });\n }\n\n // Sorting\n if (options.sortBy) {\n filteredData = this.sortEntries(filteredData, String(options.sortBy), options.sortOrder);\n }\n\n const totalCount = filteredData.length;\n const page = options.page ?? 1;\n const limit = options.limit ?? totalCount;\n\n const startIndex = (page - 1) * limit;\n const endIndex = startIndex + limit;\n const paginatedData = filteredData.slice(startIndex, endIndex);\n\n return {\n data: paginatedData,\n totalCount,\n page,\n limit,\n };\n }\n\n /**\n * Sort entries by specified field\n * @param entries Array of entries to sort\n * @param key Sorting field name\n * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')\n * @returns Sorted array of entries\n */\n private sortEntries<T extends Record<string, unknown>>(\n entries: T[],\n key: string,\n sortOrder: 'asc' | 'desc' = 'asc'\n ): T[] {\n return [...entries].sort((a, b) => {\n const aValue = a[key];\n const bValue = b[key];\n\n // Handle undefined or null values\n if (aValue == null && bValue == null) return 0;\n if (aValue == null) return 1; // null/undefined at the end\n if (bValue == null) return -1; // null/undefined at the end\n\n let comparison = 0;\n\n // Number type sorting\n if (typeof aValue === \"number\" && typeof bValue === \"number\") {\n comparison = aValue - bValue;\n }\n // String type sorting\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n comparison = aValue.localeCompare(bValue);\n }\n // Date type sorting (if string format date)\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n const aDate = new Date(aValue);\n const bDate = new Date(bValue);\n if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) {\n comparison = aDate.getTime() - bDate.getTime();\n } else {\n // Convert other types to string for comparison\n comparison = String(aValue).localeCompare(String(bValue));\n }\n }\n // Convert other types to string for comparison\n else {\n comparison = String(aValue).localeCompare(String(bValue));\n }\n\n // Apply sort order\n return sortOrder === 'desc' ? -comparison : comparison;\n });\n }\n\n\n\n /**\n * Clear store\n */\n async clear(storeName: string): Promise<void> {\n const list = await this.getList(storeName);\n\n for (const item of list.data) {\n const id = String((item as Record<string, unknown>)['id']);\n await this.delete(storeName, id);\n }\n }\n\n /**\n * List directory contents\n */\n async listDirectory(path: string = ''): Promise<string[]> {\n if (!this.service) {\n throw new Error('Service not configured. Call setService() first.');\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, '');\n const url = path ? `${baseUrl}/${path.replace(/^\\//, '')}` : baseUrl;\n\n const response = await fetch(url, {\n method: 'PROPFIND',\n headers: {\n ...this.getHeaders(),\n Depth: '1',\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to list directory: ${response.statusText}`);\n }\n\n const text = await response.text();\n\n const hrefRegex = /<d:href[^>]*>([^<]+)<\\/d:href>/gi;\n const files: string[] = [];\n let match;\n\n const baseUrlObj = new URL(baseUrl);\n const basePath = baseUrlObj.pathname;\n\n while ((match = hrefRegex.exec(text)) !== null) {\n const fullPath = match[1] || '';\n try {\n const hrefUrl = new URL(fullPath);\n let relativePath = hrefUrl.pathname.replace(basePath, '').replace(/^\\//, '');\n\n if (relativePath && relativePath !== path.replace(/^\\//, '')) {\n files.push(relativePath);\n }\n } catch {\n }\n }\n\n return files;\n }\n\n /**\n * Test connection\n */\n async testConnection(): Promise<boolean> {\n try {\n await this.listDirectory('');\n return true;\n } catch {\n return false;\n }\n }\n}\n\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["headers: Record<string, string>","allData: T[]","files: string[]"],"sources":["../src/webdav-adapter.ts"],"sourcesContent":["import {\n AdapterType,\n type RemoteAdapter,\n type QueryOptions,\n type PaginatedResult,\n type ServiceConfig,\n AuthType,\n} from \"@omnistreamai/data-core\";\n\n/**\n * WebDAV adapter implementation\n */\nexport class WebDAVAdapter implements RemoteAdapter {\n readonly type = AdapterType.Remote;\n readonly name = \"WebDAVAdapter\";\n\n private service: ServiceConfig | null = null;\n\n /**\n * Set service configuration\n */\n setService(service: ServiceConfig): void {\n this.service = service;\n }\n\n /**\n * Get request headers\n */\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (!this.service?.authentication) {\n return headers;\n }\n\n const { authentication } = this.service;\n\n switch (authentication.authType) {\n case AuthType.Token:\n if (authentication.token) {\n headers[\"Authorization\"] =\n `${authentication.token.token_type} ${authentication.token.access_token}`;\n }\n break;\n case AuthType.Bearer:\n if (authentication.bearer) {\n headers[\"Authorization\"] = `Bearer ${authentication.bearer}`;\n }\n break;\n case AuthType.Basic:\n if (authentication.username && authentication.password) {\n const credentials = btoa(\n `${authentication.username}:${authentication.password}`,\n );\n headers[\"Authorization\"] = `Basic ${credentials}`;\n }\n break;\n }\n\n return headers;\n }\n\n /**\n * Build URL\n */\n private buildUrl(storeName: string, id?: string): string {\n if (!this.service) {\n throw new Error(\"Service not configured. Call setService() first.\");\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, \"\");\n const storePath = `/${storeName}`;\n\n if (id) {\n return `${baseUrl}${storePath}/${id}`;\n }\n\n return `${baseUrl}${storePath}`;\n }\n\n /**\n * Send request\n */\n private async request<T>(\n url: string,\n options: RequestInit = {},\n {\n allowNotFound,\n }: {\n allowNotFound?: boolean;\n } = {},\n ): Promise<T | null> {\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.getHeaders(),\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n if (allowNotFound && response.status == 404) {\n return null;\n }\n throw new Error(\n `HTTP error! status: ${response.status}, message: ${response.statusText}`,\n );\n }\n\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return null;\n }\n }\n\n /**\n * Initialize store\n */\n async initStore(\n storeName: string,\n _indexes: string[] = [],\n _idKey: string = \"id\",\n ): Promise<void> {\n const path = storeName.replace(/^\\/+|\\/+$/g, \"\");\n const parts = path.split(\"/\");\n\n let currentPath = \"\";\n for (const part of parts) {\n currentPath = currentPath ? `${currentPath}/${part}` : part;\n\n try {\n await this.listDirectory(currentPath);\n } catch {\n const url = this.buildUrl(currentPath);\n\n const response = await fetch(url, {\n method: \"MKCOL\",\n headers: this.getHeaders(),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create store: ${response.statusText}`);\n }\n }\n }\n }\n\n /**\n * Add data\n */\n async add<T extends Record<string, unknown>>(\n storeName: string,\n data: T,\n idKey: string = \"id\",\n ): Promise<T> {\n const id = String(data[idKey]);\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: \"PUT\",\n body: JSON.stringify(data),\n });\n\n return result ?? data;\n }\n\n /**\n * Update data\n */\n async update<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n data: Partial<T>,\n idKey: string = \"id\",\n ): Promise<T> {\n const existing = await this.getData<T>(storeName, id, idKey);\n if (!existing) {\n throw new Error(\"Not found id:\" + id);\n }\n\n const updated = { ...existing, ...data };\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(url, {\n method: \"PUT\",\n body: JSON.stringify(updated),\n });\n\n return result ?? updated;\n }\n\n /**\n * Delete data\n */\n async delete(\n storeName: string,\n id: string,\n _idKey: string = \"id\",\n ): Promise<void> {\n const url = this.buildUrl(storeName, id);\n\n await this.request(url, {\n method: \"DELETE\",\n });\n }\n\n /**\n * Get data by ID\n */\n async getData<T extends Record<string, unknown>>(\n storeName: string,\n id: string,\n _idKey: string = \"id\",\n ): Promise<T | null> {\n const url = this.buildUrl(storeName, id);\n\n const result = await this.request<T>(\n url,\n {\n method: \"GET\",\n },\n {\n allowNotFound: true,\n },\n );\n\n return result;\n }\n\n /**\n * Get list data (with pagination support)\n */\n async getList<T extends Record<string, unknown>>(\n storeName: string,\n options: QueryOptions<T> = {},\n _idKey: string = \"id\",\n ): Promise<PaginatedResult<T>> {\n const files = await this.listDirectory(storeName);\n\n const allData: T[] = [];\n for (const file of files) {\n try {\n const data = await this.request<T>(this.buildUrl(file), {\n method: \"GET\",\n });\n if (data) {\n allData.push(data);\n }\n } catch {}\n }\n\n let filteredData = allData;\n\n if (options.where) {\n filteredData = allData.filter((item) => {\n for (const key in options.where) {\n const itemValue = item[key];\n const whereValue = options.where![key];\n if (itemValue !== whereValue) {\n return false;\n }\n }\n return true;\n });\n }\n\n // Sorting\n if (options.sortBy) {\n filteredData = this.sortEntries(\n filteredData,\n String(options.sortBy),\n options.sortOrder,\n );\n }\n\n const totalCount = filteredData.length;\n const page = options.page ?? 1;\n const limit = options.limit ?? totalCount;\n\n const startIndex = (page - 1) * limit;\n const endIndex = startIndex + limit;\n const paginatedData = filteredData.slice(startIndex, endIndex);\n\n return {\n data: paginatedData,\n totalCount,\n page,\n limit,\n };\n }\n\n /**\n * Sort entries by specified field\n * @param entries Array of entries to sort\n * @param key Sorting field name\n * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')\n * @returns Sorted array of entries\n */\n private sortEntries<T extends Record<string, unknown>>(\n entries: T[],\n key: string,\n sortOrder: \"asc\" | \"desc\" = \"asc\",\n ): T[] {\n return [...entries].sort((a, b) => {\n const aValue = a[key];\n const bValue = b[key];\n\n // Handle undefined or null values\n if (aValue == null && bValue == null) return 0;\n if (aValue == null) return 1; // null/undefined at the end\n if (bValue == null) return -1; // null/undefined at the end\n\n let comparison = 0;\n\n // Number type sorting\n if (typeof aValue === \"number\" && typeof bValue === \"number\") {\n comparison = aValue - bValue;\n }\n // String type sorting\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n comparison = aValue.localeCompare(bValue);\n }\n // Date type sorting (if string format date)\n else if (typeof aValue === \"string\" && typeof bValue === \"string\") {\n const aDate = new Date(aValue);\n const bDate = new Date(bValue);\n if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) {\n comparison = aDate.getTime() - bDate.getTime();\n } else {\n // Convert other types to string for comparison\n comparison = String(aValue).localeCompare(String(bValue));\n }\n }\n // Convert other types to string for comparison\n else {\n comparison = String(aValue).localeCompare(String(bValue));\n }\n\n // Apply sort order\n return sortOrder === \"desc\" ? -comparison : comparison;\n });\n }\n\n /**\n * Clear store\n */\n async clear(storeName: string): Promise<void> {\n const list = await this.getList(storeName);\n\n for (const item of list.data) {\n const id = String((item as Record<string, unknown>)[\"id\"]);\n await this.delete(storeName, id);\n }\n }\n\n /**\n * List directory contents\n */\n async listDirectory(path: string = \"\"): Promise<string[]> {\n if (!this.service) {\n throw new Error(\"Service not configured. Call setService() first.\");\n }\n\n const baseUrl = this.service.endpoint.replace(/\\/$/, \"\");\n const url = path ? `${baseUrl}/${path.replace(/^\\//, \"\")}` : baseUrl;\n\n const response = await fetch(url, {\n method: \"PROPFIND\",\n headers: {\n ...this.getHeaders(),\n Depth: \"1\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to list directory: ${response.statusText}`);\n }\n\n const text = await response.text();\n\n const hrefRegex = /<d:href[^>]*>([^<]+)<\\/d:href>/gi;\n const files: string[] = [];\n let match;\n\n const baseUrlObj = new URL(baseUrl);\n const basePath = baseUrlObj.pathname;\n\n while ((match = hrefRegex.exec(text)) !== null) {\n const fullPath = match[1] || \"\";\n let relativePath = \"\";\n try {\n const hrefUrl = new URL(fullPath);\n relativePath = hrefUrl.pathname\n .replace(basePath, \"\")\n .replace(/^\\//, \"\");\n } catch {\n relativePath = fullPath.replace(basePath, \"\").replace(/^\\//, \"\");\n }\n if (relativePath && relativePath !== path.replace(/^\\//, \"\")) {\n files.push(relativePath);\n }\n }\n\n return files;\n }\n\n /**\n * Test connection\n */\n async testConnection(): Promise<boolean> {\n try {\n await this.listDirectory(\"\");\n return true;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/webdav-adapter.ts
CHANGED
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
type PaginatedResult,
|
|
6
6
|
type ServiceConfig,
|
|
7
7
|
AuthType,
|
|
8
|
-
} from
|
|
8
|
+
} from "@omnistreamai/data-core";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* WebDAV adapter implementation
|
|
12
12
|
*/
|
|
13
13
|
export class WebDAVAdapter implements RemoteAdapter {
|
|
14
14
|
readonly type = AdapterType.Remote;
|
|
15
|
-
readonly name =
|
|
15
|
+
readonly name = "WebDAVAdapter";
|
|
16
16
|
|
|
17
17
|
private service: ServiceConfig | null = null;
|
|
18
18
|
|
|
@@ -28,7 +28,7 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
28
28
|
*/
|
|
29
29
|
private getHeaders(): Record<string, string> {
|
|
30
30
|
const headers: Record<string, string> = {
|
|
31
|
-
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
if (!this.service?.authentication) {
|
|
@@ -40,18 +40,21 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
40
40
|
switch (authentication.authType) {
|
|
41
41
|
case AuthType.Token:
|
|
42
42
|
if (authentication.token) {
|
|
43
|
-
headers[
|
|
43
|
+
headers["Authorization"] =
|
|
44
|
+
`${authentication.token.token_type} ${authentication.token.access_token}`;
|
|
44
45
|
}
|
|
45
46
|
break;
|
|
46
47
|
case AuthType.Bearer:
|
|
47
48
|
if (authentication.bearer) {
|
|
48
|
-
headers[
|
|
49
|
+
headers["Authorization"] = `Bearer ${authentication.bearer}`;
|
|
49
50
|
}
|
|
50
51
|
break;
|
|
51
52
|
case AuthType.Basic:
|
|
52
53
|
if (authentication.username && authentication.password) {
|
|
53
|
-
const credentials = btoa(
|
|
54
|
-
|
|
54
|
+
const credentials = btoa(
|
|
55
|
+
`${authentication.username}:${authentication.password}`,
|
|
56
|
+
);
|
|
57
|
+
headers["Authorization"] = `Basic ${credentials}`;
|
|
55
58
|
}
|
|
56
59
|
break;
|
|
57
60
|
}
|
|
@@ -64,16 +67,16 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
64
67
|
*/
|
|
65
68
|
private buildUrl(storeName: string, id?: string): string {
|
|
66
69
|
if (!this.service) {
|
|
67
|
-
throw new Error(
|
|
70
|
+
throw new Error("Service not configured. Call setService() first.");
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
const baseUrl = this.service.endpoint.replace(/\/$/,
|
|
73
|
+
const baseUrl = this.service.endpoint.replace(/\/$/, "");
|
|
71
74
|
const storePath = `/${storeName}`;
|
|
72
|
-
|
|
75
|
+
|
|
73
76
|
if (id) {
|
|
74
77
|
return `${baseUrl}${storePath}/${id}`;
|
|
75
78
|
}
|
|
76
|
-
|
|
79
|
+
|
|
77
80
|
return `${baseUrl}${storePath}`;
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -87,7 +90,7 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
87
90
|
allowNotFound,
|
|
88
91
|
}: {
|
|
89
92
|
allowNotFound?: boolean;
|
|
90
|
-
} = {}
|
|
93
|
+
} = {},
|
|
91
94
|
): Promise<T | null> {
|
|
92
95
|
const response = await fetch(url, {
|
|
93
96
|
...options,
|
|
@@ -101,7 +104,9 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
101
104
|
if (allowNotFound && response.status == 404) {
|
|
102
105
|
return null;
|
|
103
106
|
}
|
|
104
|
-
throw new Error(
|
|
107
|
+
throw new Error(
|
|
108
|
+
`HTTP error! status: ${response.status}, message: ${response.statusText}`,
|
|
109
|
+
);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
const text = await response.text();
|
|
@@ -122,12 +127,12 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
122
127
|
async initStore(
|
|
123
128
|
storeName: string,
|
|
124
129
|
_indexes: string[] = [],
|
|
125
|
-
_idKey: string =
|
|
130
|
+
_idKey: string = "id",
|
|
126
131
|
): Promise<void> {
|
|
127
|
-
const path = storeName.replace(/^\/+|\/+$/g,
|
|
128
|
-
const parts = path.split(
|
|
132
|
+
const path = storeName.replace(/^\/+|\/+$/g, "");
|
|
133
|
+
const parts = path.split("/");
|
|
129
134
|
|
|
130
|
-
let currentPath =
|
|
135
|
+
let currentPath = "";
|
|
131
136
|
for (const part of parts) {
|
|
132
137
|
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
133
138
|
|
|
@@ -137,7 +142,7 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
137
142
|
const url = this.buildUrl(currentPath);
|
|
138
143
|
|
|
139
144
|
const response = await fetch(url, {
|
|
140
|
-
method:
|
|
145
|
+
method: "MKCOL",
|
|
141
146
|
headers: this.getHeaders(),
|
|
142
147
|
});
|
|
143
148
|
|
|
@@ -154,13 +159,13 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
154
159
|
async add<T extends Record<string, unknown>>(
|
|
155
160
|
storeName: string,
|
|
156
161
|
data: T,
|
|
157
|
-
idKey: string =
|
|
162
|
+
idKey: string = "id",
|
|
158
163
|
): Promise<T> {
|
|
159
164
|
const id = String(data[idKey]);
|
|
160
165
|
const url = this.buildUrl(storeName, id);
|
|
161
166
|
|
|
162
167
|
const result = await this.request<T>(url, {
|
|
163
|
-
method:
|
|
168
|
+
method: "PUT",
|
|
164
169
|
body: JSON.stringify(data),
|
|
165
170
|
});
|
|
166
171
|
|
|
@@ -174,18 +179,18 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
174
179
|
storeName: string,
|
|
175
180
|
id: string,
|
|
176
181
|
data: Partial<T>,
|
|
177
|
-
idKey: string =
|
|
182
|
+
idKey: string = "id",
|
|
178
183
|
): Promise<T> {
|
|
179
184
|
const existing = await this.getData<T>(storeName, id, idKey);
|
|
180
185
|
if (!existing) {
|
|
181
|
-
throw new Error(
|
|
186
|
+
throw new Error("Not found id:" + id);
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
const updated = { ...existing, ...data };
|
|
185
190
|
const url = this.buildUrl(storeName, id);
|
|
186
191
|
|
|
187
192
|
const result = await this.request<T>(url, {
|
|
188
|
-
method:
|
|
193
|
+
method: "PUT",
|
|
189
194
|
body: JSON.stringify(updated),
|
|
190
195
|
});
|
|
191
196
|
|
|
@@ -195,11 +200,15 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
195
200
|
/**
|
|
196
201
|
* Delete data
|
|
197
202
|
*/
|
|
198
|
-
async delete(
|
|
203
|
+
async delete(
|
|
204
|
+
storeName: string,
|
|
205
|
+
id: string,
|
|
206
|
+
_idKey: string = "id",
|
|
207
|
+
): Promise<void> {
|
|
199
208
|
const url = this.buildUrl(storeName, id);
|
|
200
209
|
|
|
201
210
|
await this.request(url, {
|
|
202
|
-
method:
|
|
211
|
+
method: "DELETE",
|
|
203
212
|
});
|
|
204
213
|
}
|
|
205
214
|
|
|
@@ -209,15 +218,19 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
209
218
|
async getData<T extends Record<string, unknown>>(
|
|
210
219
|
storeName: string,
|
|
211
220
|
id: string,
|
|
212
|
-
_idKey: string =
|
|
221
|
+
_idKey: string = "id",
|
|
213
222
|
): Promise<T | null> {
|
|
214
223
|
const url = this.buildUrl(storeName, id);
|
|
215
224
|
|
|
216
|
-
const result = await this.request<T>(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
const result = await this.request<T>(
|
|
226
|
+
url,
|
|
227
|
+
{
|
|
228
|
+
method: "GET",
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
allowNotFound: true,
|
|
232
|
+
},
|
|
233
|
+
);
|
|
221
234
|
|
|
222
235
|
return result;
|
|
223
236
|
}
|
|
@@ -228,27 +241,26 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
228
241
|
async getList<T extends Record<string, unknown>>(
|
|
229
242
|
storeName: string,
|
|
230
243
|
options: QueryOptions<T> = {},
|
|
231
|
-
_idKey: string =
|
|
244
|
+
_idKey: string = "id",
|
|
232
245
|
): Promise<PaginatedResult<T>> {
|
|
233
246
|
const files = await this.listDirectory(storeName);
|
|
234
247
|
|
|
235
248
|
const allData: T[] = [];
|
|
236
249
|
for (const file of files) {
|
|
237
250
|
try {
|
|
238
|
-
const data = await this.request<T>(this.buildUrl(
|
|
239
|
-
method:
|
|
251
|
+
const data = await this.request<T>(this.buildUrl(file), {
|
|
252
|
+
method: "GET",
|
|
240
253
|
});
|
|
241
254
|
if (data) {
|
|
242
255
|
allData.push(data);
|
|
243
256
|
}
|
|
244
|
-
} catch {
|
|
245
|
-
}
|
|
257
|
+
} catch {}
|
|
246
258
|
}
|
|
247
259
|
|
|
248
260
|
let filteredData = allData;
|
|
249
261
|
|
|
250
262
|
if (options.where) {
|
|
251
|
-
filteredData = allData.filter(item => {
|
|
263
|
+
filteredData = allData.filter((item) => {
|
|
252
264
|
for (const key in options.where) {
|
|
253
265
|
const itemValue = item[key];
|
|
254
266
|
const whereValue = options.where![key];
|
|
@@ -262,7 +274,11 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
262
274
|
|
|
263
275
|
// Sorting
|
|
264
276
|
if (options.sortBy) {
|
|
265
|
-
filteredData = this.sortEntries(
|
|
277
|
+
filteredData = this.sortEntries(
|
|
278
|
+
filteredData,
|
|
279
|
+
String(options.sortBy),
|
|
280
|
+
options.sortOrder,
|
|
281
|
+
);
|
|
266
282
|
}
|
|
267
283
|
|
|
268
284
|
const totalCount = filteredData.length;
|
|
@@ -291,7 +307,7 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
291
307
|
private sortEntries<T extends Record<string, unknown>>(
|
|
292
308
|
entries: T[],
|
|
293
309
|
key: string,
|
|
294
|
-
sortOrder:
|
|
310
|
+
sortOrder: "asc" | "desc" = "asc",
|
|
295
311
|
): T[] {
|
|
296
312
|
return [...entries].sort((a, b) => {
|
|
297
313
|
const aValue = a[key];
|
|
@@ -329,12 +345,10 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
329
345
|
}
|
|
330
346
|
|
|
331
347
|
// Apply sort order
|
|
332
|
-
return sortOrder ===
|
|
348
|
+
return sortOrder === "desc" ? -comparison : comparison;
|
|
333
349
|
});
|
|
334
350
|
}
|
|
335
351
|
|
|
336
|
-
|
|
337
|
-
|
|
338
352
|
/**
|
|
339
353
|
* Clear store
|
|
340
354
|
*/
|
|
@@ -342,7 +356,7 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
342
356
|
const list = await this.getList(storeName);
|
|
343
357
|
|
|
344
358
|
for (const item of list.data) {
|
|
345
|
-
const id = String((item as Record<string, unknown>)[
|
|
359
|
+
const id = String((item as Record<string, unknown>)["id"]);
|
|
346
360
|
await this.delete(storeName, id);
|
|
347
361
|
}
|
|
348
362
|
}
|
|
@@ -350,19 +364,19 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
350
364
|
/**
|
|
351
365
|
* List directory contents
|
|
352
366
|
*/
|
|
353
|
-
async listDirectory(path: string =
|
|
367
|
+
async listDirectory(path: string = ""): Promise<string[]> {
|
|
354
368
|
if (!this.service) {
|
|
355
|
-
throw new Error(
|
|
369
|
+
throw new Error("Service not configured. Call setService() first.");
|
|
356
370
|
}
|
|
357
371
|
|
|
358
|
-
const baseUrl = this.service.endpoint.replace(/\/$/,
|
|
359
|
-
const url = path ? `${baseUrl}/${path.replace(/^\//,
|
|
372
|
+
const baseUrl = this.service.endpoint.replace(/\/$/, "");
|
|
373
|
+
const url = path ? `${baseUrl}/${path.replace(/^\//, "")}` : baseUrl;
|
|
360
374
|
|
|
361
375
|
const response = await fetch(url, {
|
|
362
|
-
method:
|
|
376
|
+
method: "PROPFIND",
|
|
363
377
|
headers: {
|
|
364
378
|
...this.getHeaders(),
|
|
365
|
-
Depth:
|
|
379
|
+
Depth: "1",
|
|
366
380
|
},
|
|
367
381
|
});
|
|
368
382
|
|
|
@@ -380,15 +394,18 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
380
394
|
const basePath = baseUrlObj.pathname;
|
|
381
395
|
|
|
382
396
|
while ((match = hrefRegex.exec(text)) !== null) {
|
|
383
|
-
const fullPath = match[1] ||
|
|
397
|
+
const fullPath = match[1] || "";
|
|
398
|
+
let relativePath = "";
|
|
384
399
|
try {
|
|
385
400
|
const hrefUrl = new URL(fullPath);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
files.push(relativePath);
|
|
390
|
-
}
|
|
401
|
+
relativePath = hrefUrl.pathname
|
|
402
|
+
.replace(basePath, "")
|
|
403
|
+
.replace(/^\//, "");
|
|
391
404
|
} catch {
|
|
405
|
+
relativePath = fullPath.replace(basePath, "").replace(/^\//, "");
|
|
406
|
+
}
|
|
407
|
+
if (relativePath && relativePath !== path.replace(/^\//, "")) {
|
|
408
|
+
files.push(relativePath);
|
|
392
409
|
}
|
|
393
410
|
}
|
|
394
411
|
|
|
@@ -400,12 +417,10 @@ export class WebDAVAdapter implements RemoteAdapter {
|
|
|
400
417
|
*/
|
|
401
418
|
async testConnection(): Promise<boolean> {
|
|
402
419
|
try {
|
|
403
|
-
await this.listDirectory(
|
|
420
|
+
await this.listDirectory("");
|
|
404
421
|
return true;
|
|
405
422
|
} catch {
|
|
406
423
|
return false;
|
|
407
424
|
}
|
|
408
425
|
}
|
|
409
426
|
}
|
|
410
|
-
|
|
411
|
-
|