@omnistreamai/data-adapter-webdav 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @omnistreamai/data-adapter-webdav
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add sorting support to getList method with sortBy and sortOrder options.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @omnistreamai/data-core@0.2.0
13
+
3
14
  ## 0.1.2
4
15
 
5
16
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -49,10 +49,26 @@ declare class WebDAVAdapter implements RemoteAdapter {
49
49
  * Get list data (with pagination support)
50
50
  */
51
51
  getList<T extends Record<string, unknown>>(storeName: string, options?: QueryOptions<T>, _idKey?: string): Promise<PaginatedResult<T>>;
52
+ /**
53
+ * Sort entries by specified field
54
+ * @param entries Array of entries to sort
55
+ * @param key Sorting field name
56
+ * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')
57
+ * @returns Sorted array of entries
58
+ */
59
+ private sortEntries;
52
60
  /**
53
61
  * Clear store
54
62
  */
55
63
  clear(storeName: string): Promise<void>;
64
+ /**
65
+ * List directory contents
66
+ */
67
+ listDirectory(path?: string): Promise<string[]>;
68
+ /**
69
+ * Test connection
70
+ */
71
+ testConnection(): Promise<boolean>;
56
72
  }
57
73
  //#endregion
58
74
  export { WebDAVAdapter };
@@ -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;EA4GV,SAAA,IAAA,GAAA,eAAA;EAkBiB,QAAA,OAAA;EAEZ;;;EAiBe,UAAA,CAAA,OAAA,EAzIH,aAyIG,CAAA,EAAA,IAAA;EAGP;;;EAEb,QAAA,UAAA;EAsBiE;;;EAejE,QAAA,QAAA;EAWqB;;;EAIG,QAAA,OAAA;EAAhB;;;EA3MyB,SAAA,CAAA,SAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA6GjC,OA7GiC,CAAA,IAAA,CAAA;EAAa;;;gBA+H7B,kDAEZ,oBAEL,QAAQ;;;;mBAeY,8DAGf,QAAQ,qBAEb,QAAQ;;;;0DAsByD;;;;oBAW5C,0EAIrB,QAAQ;;;;oBAWa,sDAEb,aAAa,sBAErB,QAAQ,gBAAgB;;;;4BAoDK"}
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;EAgHV,SAAA,IAAA,GAAA,eAAA;EAgBiB,QAAA,OAAA;EAEZ;;;EAiBe,UAAA,CAAA,OAAA,EA3IH,aA2IG,CAAA,EAAA,IAAA;EAGP;;;EAEb,QAAA,UAAA;EAoBiE;;;EAejE,QAAA,QAAA;EAeqB;;;EAIG,QAAA,OAAA;EAAhB;;;EAyH6B,SAAA,CAAA,SAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAvNrC,OAuNqC,CAAA,IAAA,CAAA;EAgDhB;;;gBAvPJ,kDAEZ,oBAEL,QAAQ;;;;mBAeY,8DAGf,QAAQ,qBAEb,QAAQ;;;;0DAoByD;;;;oBAW5C,0EAIrB,QAAQ;;;;oBAea,sDAEb,aAAa,sBAErB,QAAQ,gBAAgB;;;;;;;;;;;;4BA6GK;;;;gCAYQ;;;;oBAgDhB"}
package/dist/index.d.ts CHANGED
@@ -49,10 +49,26 @@ declare class WebDAVAdapter implements RemoteAdapter {
49
49
  * Get list data (with pagination support)
50
50
  */
51
51
  getList<T extends Record<string, unknown>>(storeName: string, options?: QueryOptions<T>, _idKey?: string): Promise<PaginatedResult<T>>;
52
+ /**
53
+ * Sort entries by specified field
54
+ * @param entries Array of entries to sort
55
+ * @param key Sorting field name
56
+ * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')
57
+ * @returns Sorted array of entries
58
+ */
59
+ private sortEntries;
52
60
  /**
53
61
  * Clear store
54
62
  */
55
63
  clear(storeName: string): Promise<void>;
64
+ /**
65
+ * List directory contents
66
+ */
67
+ listDirectory(path?: string): Promise<string[]>;
68
+ /**
69
+ * Test connection
70
+ */
71
+ testConnection(): Promise<boolean>;
56
72
  }
57
73
  //#endregion
58
74
  export { WebDAVAdapter };
@@ -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;EA4GV,SAAA,IAAA,GAAA,eAAA;EAkBiB,QAAA,OAAA;EAEZ;;;EAiBe,UAAA,CAAA,OAAA,EAzIH,aAyIG,CAAA,EAAA,IAAA;EAGP;;;EAEb,QAAA,UAAA;EAsBiE;;;EAejE,QAAA,QAAA;EAWqB;;;EAIG,QAAA,OAAA;EAAhB;;;EA3MyB,SAAA,CAAA,SAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA6GjC,OA7GiC,CAAA,IAAA,CAAA;EAAa;;;gBA+H7B,kDAEZ,oBAEL,QAAQ;;;;mBAeY,8DAGf,QAAQ,qBAEb,QAAQ;;;;0DAsByD;;;;oBAW5C,0EAIrB,QAAQ;;;;oBAWa,sDAEb,aAAa,sBAErB,QAAQ,gBAAgB;;;;4BAoDK"}
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;EAgHV,SAAA,IAAA,GAAA,eAAA;EAgBiB,QAAA,OAAA;EAEZ;;;EAiBe,UAAA,CAAA,OAAA,EA3IH,aA2IG,CAAA,EAAA,IAAA;EAGP;;;EAEb,QAAA,UAAA;EAoBiE;;;EAejE,QAAA,QAAA;EAeqB;;;EAIG,QAAA,OAAA;EAAhB;;;EAyH6B,SAAA,CAAA,SAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAvNrC,OAuNqC,CAAA,IAAA,CAAA;EAgDhB;;;gBAvPJ,kDAEZ,oBAEL,QAAQ;;;;mBAeY,8DAGf,QAAQ,qBAEb,QAAQ;;;;0DAoByD;;;;oBAW5C,0EAIrB,QAAQ;;;;oBAea,sDAEb,aAAa,sBAErB,QAAQ,gBAAgB;;;;;;;;;;;;4BA6GK;;;;gCAYQ;;;;oBAgDhB"}
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ var WebDAVAdapter = class {
47
47
  /**
48
48
  * Send request
49
49
  */
50
- async request(url, options = {}) {
50
+ async request(url, options = {}, { allowNotFound } = {}) {
51
51
  const response = await fetch(url, {
52
52
  ...options,
53
53
  headers: {
@@ -56,8 +56,8 @@ var WebDAVAdapter = class {
56
56
  }
57
57
  });
58
58
  if (!response.ok) {
59
- if (response.status === 404) return null;
60
- throw new Error(`HTTP error! status: ${response.status}`);
59
+ if (allowNotFound && response.status == 404) return null;
60
+ throw new Error(`HTTP error! status: ${response.status}, message: ${response.statusText}`);
61
61
  }
62
62
  const text = await response.text();
63
63
  if (!text) return null;
@@ -94,7 +94,7 @@ var WebDAVAdapter = class {
94
94
  */
95
95
  async update(storeName, id, data, idKey = "id") {
96
96
  const existing = await this.getData(storeName, id, idKey);
97
- if (!existing) throw new Error(`Data with id ${id} not found`);
97
+ if (!existing) throw new Error("Not found id:" + id);
98
98
  const updated = {
99
99
  ...existing,
100
100
  ...data
@@ -117,43 +117,111 @@ var WebDAVAdapter = class {
117
117
  */
118
118
  async getData(storeName, id, _idKey = "id") {
119
119
  const url = this.buildUrl(storeName, id);
120
- return await this.request(url, { method: "GET" });
120
+ return await this.request(url, { method: "GET" }, { allowNotFound: true });
121
121
  }
122
122
  /**
123
123
  * Get list data (with pagination support)
124
124
  */
125
125
  async getList(storeName, options = {}, _idKey = "id") {
126
- const url = this.buildUrl(storeName);
127
- const params = new URLSearchParams();
128
- if (options.page !== void 0) params.append("page", String(options.page));
129
- if (options.limit !== void 0) params.append("limit", String(options.limit));
130
- if (options.where) params.append("where", JSON.stringify(options.where));
131
- const queryString = params.toString();
132
- const fullUrl = queryString ? `${url}?${queryString}` : url;
133
- const result = await this.request(fullUrl, { method: "GET" });
134
- if (result && "data" in result) return result;
135
- if (Array.isArray(result)) {
136
- const dataArray = result;
137
- return {
138
- data: dataArray,
139
- totalCount: dataArray.length,
140
- page: options.page ?? 1,
141
- limit: options.limit ?? dataArray.length
142
- };
143
- }
126
+ const files = await this.listDirectory(storeName);
127
+ const allData = [];
128
+ for (const file of files) try {
129
+ const data = await this.request(this.buildUrl(storeName, file), { method: "GET" });
130
+ if (data) allData.push(data);
131
+ } catch {}
132
+ let filteredData = allData;
133
+ if (options.where) filteredData = allData.filter((item) => {
134
+ for (const key in options.where) if (item[key] !== options.where[key]) return false;
135
+ return true;
136
+ });
137
+ if (options.sortBy) filteredData = this.sortEntries(filteredData, String(options.sortBy), options.sortOrder);
138
+ const totalCount = filteredData.length;
139
+ const page = options.page ?? 1;
140
+ const limit = options.limit ?? totalCount;
141
+ const startIndex = (page - 1) * limit;
142
+ const endIndex = startIndex + limit;
144
143
  return {
145
- data: [],
146
- totalCount: 0,
147
- page: options.page ?? 1,
148
- limit: options.limit ?? 10
144
+ data: filteredData.slice(startIndex, endIndex),
145
+ totalCount,
146
+ page,
147
+ limit
149
148
  };
150
149
  }
151
150
  /**
151
+ * Sort entries by specified field
152
+ * @param entries Array of entries to sort
153
+ * @param key Sorting field name
154
+ * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')
155
+ * @returns Sorted array of entries
156
+ */
157
+ sortEntries(entries, key, sortOrder = "asc") {
158
+ return [...entries].sort((a, b) => {
159
+ const aValue = a[key];
160
+ const bValue = b[key];
161
+ if (aValue == null && bValue == null) return 0;
162
+ if (aValue == null) return 1;
163
+ if (bValue == null) return -1;
164
+ let comparison = 0;
165
+ if (typeof aValue === "number" && typeof bValue === "number") comparison = aValue - bValue;
166
+ else if (typeof aValue === "string" && typeof bValue === "string") comparison = aValue.localeCompare(bValue);
167
+ else if (typeof aValue === "string" && typeof bValue === "string") {
168
+ const aDate = new Date(aValue);
169
+ const bDate = new Date(bValue);
170
+ if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) comparison = aDate.getTime() - bDate.getTime();
171
+ else comparison = String(aValue).localeCompare(String(bValue));
172
+ } else comparison = String(aValue).localeCompare(String(bValue));
173
+ return sortOrder === "desc" ? -comparison : comparison;
174
+ });
175
+ }
176
+ /**
152
177
  * Clear store
153
178
  */
154
179
  async clear(storeName) {
155
- const url = this.buildUrl(storeName);
156
- await this.request(url, { method: "DELETE" });
180
+ const list = await this.getList(storeName);
181
+ for (const item of list.data) {
182
+ const id = String(item["id"]);
183
+ await this.delete(storeName, id);
184
+ }
185
+ }
186
+ /**
187
+ * List directory contents
188
+ */
189
+ async listDirectory(path = "") {
190
+ if (!this.service) throw new Error("Service not configured. Call setService() first.");
191
+ const baseUrl = this.service.endpoint.replace(/\/$/, "");
192
+ const url = path ? `${baseUrl}/${path.replace(/^\//, "")}` : baseUrl;
193
+ const response = await fetch(url, {
194
+ method: "PROPFIND",
195
+ headers: {
196
+ ...this.getHeaders(),
197
+ Depth: "1"
198
+ }
199
+ });
200
+ if (!response.ok) throw new Error(`Failed to list directory: ${response.statusText}`);
201
+ const text = await response.text();
202
+ const hrefRegex = /<d:href[^>]*>([^<]+)<\/d:href>/g;
203
+ const files = [];
204
+ let match;
205
+ const basePath = new URL(baseUrl).pathname;
206
+ while ((match = hrefRegex.exec(text)) !== null) {
207
+ const fullPath = match[1] || "";
208
+ try {
209
+ let relativePath = new URL(fullPath).pathname.replace(basePath, "").replace(/^\//, "");
210
+ if (relativePath && relativePath !== path.replace(/^\//, "")) files.push(relativePath);
211
+ } catch {}
212
+ }
213
+ return files;
214
+ }
215
+ /**
216
+ * Test connection
217
+ */
218
+ async testConnection() {
219
+ try {
220
+ await this.listDirectory("");
221
+ return true;
222
+ } catch {
223
+ return false;
224
+ }
157
225
  }
158
226
  };
159
227
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["AdapterType","headers: Record<string, string>","AuthType"],"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 ): Promise<T> {\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 (response.status === 404) {\n return null as T;\n }\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n // If response is empty, return null\n const text = await response.text();\n if (!text) {\n return null as T;\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return null as T;\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 url = this.buildUrl(storeName);\n \n // Try to create directory (using MKCOL method)\n const response = await fetch(url, {\n method: 'MKCOL',\n headers: this.getHeaders(),\n });\n\n // If directory already exists (409 Conflict), ignore error\n if (!response.ok && response.status !== 409) {\n throw new Error(`Failed to create store: ${response.statusText}`);\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 // First get existing data\n const existing = await this.getData<T>(storeName, id, idKey);\n if (!existing) {\n throw new Error(`Data with id ${id} not found`);\n }\n\n // Merge data\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 return await this.request<T | null>(url, {\n method: 'GET',\n });\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 url = this.buildUrl(storeName);\n \n // Build query parameters\n const params = new URLSearchParams();\n if (options.page !== undefined) {\n params.append('page', String(options.page));\n }\n if (options.limit !== undefined) {\n params.append('limit', String(options.limit));\n }\n if (options.where) {\n params.append('where', JSON.stringify(options.where));\n }\n\n const queryString = params.toString();\n const fullUrl = queryString ? `${url}?${queryString}` : url;\n\n const result = await this.request<PaginatedResult<T>>(fullUrl, {\n method: 'GET',\n });\n\n // If server returns non-paginated format, convert to paginated format\n if (result && 'data' in result) {\n return result;\n }\n\n // If return is an array, convert to paginated format\n if (Array.isArray(result)) {\n const dataArray = result as T[];\n return {\n data: dataArray,\n totalCount: dataArray.length,\n page: options.page ?? 1,\n limit: options.limit ?? dataArray.length,\n };\n }\n\n // Default return empty result\n return {\n data: [],\n totalCount: 0,\n page: options.page ?? 1,\n limit: options.limit ?? 10,\n };\n }\n\n\n\n /**\n * Clear store\n */\n async clear(storeName: string): Promise<void> {\n const url = this.buildUrl(storeName);\n\n await this.request(url, {\n method: 'DELETE',\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'] = `${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 url = this.buildUrl(storeName);\n\n const response = await fetch(url, {\n method: 'MKCOL',\n headers: this.getHeaders(),\n });\n\n if (!response.ok && response.status !== 409) {\n throw new Error(`Failed to create store: ${response.statusText}`);\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>/g;\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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"}
package/dist/index.mjs CHANGED
@@ -47,7 +47,7 @@ var WebDAVAdapter = class {
47
47
  /**
48
48
  * Send request
49
49
  */
50
- async request(url, options = {}) {
50
+ async request(url, options = {}, { allowNotFound } = {}) {
51
51
  const response = await fetch(url, {
52
52
  ...options,
53
53
  headers: {
@@ -56,8 +56,8 @@ var WebDAVAdapter = class {
56
56
  }
57
57
  });
58
58
  if (!response.ok) {
59
- if (response.status === 404) return null;
60
- throw new Error(`HTTP error! status: ${response.status}`);
59
+ if (allowNotFound && response.status == 404) return null;
60
+ throw new Error(`HTTP error! status: ${response.status}, message: ${response.statusText}`);
61
61
  }
62
62
  const text = await response.text();
63
63
  if (!text) return null;
@@ -94,7 +94,7 @@ var WebDAVAdapter = class {
94
94
  */
95
95
  async update(storeName, id, data, idKey = "id") {
96
96
  const existing = await this.getData(storeName, id, idKey);
97
- if (!existing) throw new Error(`Data with id ${id} not found`);
97
+ if (!existing) throw new Error("Not found id:" + id);
98
98
  const updated = {
99
99
  ...existing,
100
100
  ...data
@@ -117,43 +117,111 @@ var WebDAVAdapter = class {
117
117
  */
118
118
  async getData(storeName, id, _idKey = "id") {
119
119
  const url = this.buildUrl(storeName, id);
120
- return await this.request(url, { method: "GET" });
120
+ return await this.request(url, { method: "GET" }, { allowNotFound: true });
121
121
  }
122
122
  /**
123
123
  * Get list data (with pagination support)
124
124
  */
125
125
  async getList(storeName, options = {}, _idKey = "id") {
126
- const url = this.buildUrl(storeName);
127
- const params = new URLSearchParams();
128
- if (options.page !== void 0) params.append("page", String(options.page));
129
- if (options.limit !== void 0) params.append("limit", String(options.limit));
130
- if (options.where) params.append("where", JSON.stringify(options.where));
131
- const queryString = params.toString();
132
- const fullUrl = queryString ? `${url}?${queryString}` : url;
133
- const result = await this.request(fullUrl, { method: "GET" });
134
- if (result && "data" in result) return result;
135
- if (Array.isArray(result)) {
136
- const dataArray = result;
137
- return {
138
- data: dataArray,
139
- totalCount: dataArray.length,
140
- page: options.page ?? 1,
141
- limit: options.limit ?? dataArray.length
142
- };
143
- }
126
+ const files = await this.listDirectory(storeName);
127
+ const allData = [];
128
+ for (const file of files) try {
129
+ const data = await this.request(this.buildUrl(storeName, file), { method: "GET" });
130
+ if (data) allData.push(data);
131
+ } catch {}
132
+ let filteredData = allData;
133
+ if (options.where) filteredData = allData.filter((item) => {
134
+ for (const key in options.where) if (item[key] !== options.where[key]) return false;
135
+ return true;
136
+ });
137
+ if (options.sortBy) filteredData = this.sortEntries(filteredData, String(options.sortBy), options.sortOrder);
138
+ const totalCount = filteredData.length;
139
+ const page = options.page ?? 1;
140
+ const limit = options.limit ?? totalCount;
141
+ const startIndex = (page - 1) * limit;
142
+ const endIndex = startIndex + limit;
144
143
  return {
145
- data: [],
146
- totalCount: 0,
147
- page: options.page ?? 1,
148
- limit: options.limit ?? 10
144
+ data: filteredData.slice(startIndex, endIndex),
145
+ totalCount,
146
+ page,
147
+ limit
149
148
  };
150
149
  }
151
150
  /**
151
+ * Sort entries by specified field
152
+ * @param entries Array of entries to sort
153
+ * @param key Sorting field name
154
+ * @param sortOrder Sort order: 'asc' or 'desc' (default: 'asc')
155
+ * @returns Sorted array of entries
156
+ */
157
+ sortEntries(entries, key, sortOrder = "asc") {
158
+ return [...entries].sort((a, b) => {
159
+ const aValue = a[key];
160
+ const bValue = b[key];
161
+ if (aValue == null && bValue == null) return 0;
162
+ if (aValue == null) return 1;
163
+ if (bValue == null) return -1;
164
+ let comparison = 0;
165
+ if (typeof aValue === "number" && typeof bValue === "number") comparison = aValue - bValue;
166
+ else if (typeof aValue === "string" && typeof bValue === "string") comparison = aValue.localeCompare(bValue);
167
+ else if (typeof aValue === "string" && typeof bValue === "string") {
168
+ const aDate = new Date(aValue);
169
+ const bDate = new Date(bValue);
170
+ if (!isNaN(aDate.getTime()) && !isNaN(bDate.getTime())) comparison = aDate.getTime() - bDate.getTime();
171
+ else comparison = String(aValue).localeCompare(String(bValue));
172
+ } else comparison = String(aValue).localeCompare(String(bValue));
173
+ return sortOrder === "desc" ? -comparison : comparison;
174
+ });
175
+ }
176
+ /**
152
177
  * Clear store
153
178
  */
154
179
  async clear(storeName) {
155
- const url = this.buildUrl(storeName);
156
- await this.request(url, { method: "DELETE" });
180
+ const list = await this.getList(storeName);
181
+ for (const item of list.data) {
182
+ const id = String(item["id"]);
183
+ await this.delete(storeName, id);
184
+ }
185
+ }
186
+ /**
187
+ * List directory contents
188
+ */
189
+ async listDirectory(path = "") {
190
+ if (!this.service) throw new Error("Service not configured. Call setService() first.");
191
+ const baseUrl = this.service.endpoint.replace(/\/$/, "");
192
+ const url = path ? `${baseUrl}/${path.replace(/^\//, "")}` : baseUrl;
193
+ const response = await fetch(url, {
194
+ method: "PROPFIND",
195
+ headers: {
196
+ ...this.getHeaders(),
197
+ Depth: "1"
198
+ }
199
+ });
200
+ if (!response.ok) throw new Error(`Failed to list directory: ${response.statusText}`);
201
+ const text = await response.text();
202
+ const hrefRegex = /<d:href[^>]*>([^<]+)<\/d:href>/g;
203
+ const files = [];
204
+ let match;
205
+ const basePath = new URL(baseUrl).pathname;
206
+ while ((match = hrefRegex.exec(text)) !== null) {
207
+ const fullPath = match[1] || "";
208
+ try {
209
+ let relativePath = new URL(fullPath).pathname.replace(basePath, "").replace(/^\//, "");
210
+ if (relativePath && relativePath !== path.replace(/^\//, "")) files.push(relativePath);
211
+ } catch {}
212
+ }
213
+ return files;
214
+ }
215
+ /**
216
+ * Test connection
217
+ */
218
+ async testConnection() {
219
+ try {
220
+ await this.listDirectory("");
221
+ return true;
222
+ } catch {
223
+ return false;
224
+ }
157
225
  }
158
226
  };
159
227
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["headers: Record<string, 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 ): Promise<T> {\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 (response.status === 404) {\n return null as T;\n }\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n // If response is empty, return null\n const text = await response.text();\n if (!text) {\n return null as T;\n }\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return null as T;\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 url = this.buildUrl(storeName);\n \n // Try to create directory (using MKCOL method)\n const response = await fetch(url, {\n method: 'MKCOL',\n headers: this.getHeaders(),\n });\n\n // If directory already exists (409 Conflict), ignore error\n if (!response.ok && response.status !== 409) {\n throw new Error(`Failed to create store: ${response.statusText}`);\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 // First get existing data\n const existing = await this.getData<T>(storeName, id, idKey);\n if (!existing) {\n throw new Error(`Data with id ${id} not found`);\n }\n\n // Merge data\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 return await this.request<T | null>(url, {\n method: 'GET',\n });\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 url = this.buildUrl(storeName);\n \n // Build query parameters\n const params = new URLSearchParams();\n if (options.page !== undefined) {\n params.append('page', String(options.page));\n }\n if (options.limit !== undefined) {\n params.append('limit', String(options.limit));\n }\n if (options.where) {\n params.append('where', JSON.stringify(options.where));\n }\n\n const queryString = params.toString();\n const fullUrl = queryString ? `${url}?${queryString}` : url;\n\n const result = await this.request<PaginatedResult<T>>(fullUrl, {\n method: 'GET',\n });\n\n // If server returns non-paginated format, convert to paginated format\n if (result && 'data' in result) {\n return result;\n }\n\n // If return is an array, convert to paginated format\n if (Array.isArray(result)) {\n const dataArray = result as T[];\n return {\n data: dataArray,\n totalCount: dataArray.length,\n page: options.page ?? 1,\n limit: options.limit ?? dataArray.length,\n };\n }\n\n // Default return empty result\n return {\n data: [],\n totalCount: 0,\n page: options.page ?? 1,\n limit: options.limit ?? 10,\n };\n }\n\n\n\n /**\n * Clear store\n */\n async clear(storeName: string): Promise<void> {\n const url = this.buildUrl(storeName);\n\n await this.request(url, {\n method: 'DELETE',\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'] = `${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 url = this.buildUrl(storeName);\n\n const response = await fetch(url, {\n method: 'MKCOL',\n headers: this.getHeaders(),\n });\n\n if (!response.ok && response.status !== 409) {\n throw new Error(`Failed to create store: ${response.statusText}`);\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>/g;\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":";;;;;;AAYA,IAAa,gBAAb,MAAoD;CAClD,AAAS,OAAO,YAAY;CAC5B,AAAS,OAAO;CAEhB,AAAQ,UAAgC;;;;CAKxC,WAAW,SAA8B;AACvC,OAAK,UAAU;;;;;CAMjB,AAAQ,aAAqC;EAC3C,MAAMA,UAAkC,EACtC,gBAAgB,oBACjB;AAED,MAAI,CAAC,KAAK,SAAS,eACjB,QAAO;EAGT,MAAM,EAAE,mBAAmB,KAAK;AAEhC,UAAQ,eAAe,UAAvB;GACE,KAAK,SAAS;AACZ,QAAI,eAAe,MACjB,SAAQ,mBAAmB,GAAG,eAAe,MAAM,WAAW,GAAG,eAAe,MAAM;AAExF;GACF,KAAK,SAAS;AACZ,QAAI,eAAe,OACjB,SAAQ,mBAAmB,UAAU,eAAe;AAEtD;GACF,KAAK,SAAS;AACZ,QAAI,eAAe,YAAY,eAAe,SAE5C,SAAQ,mBAAmB,SADP,KAAK,GAAG,eAAe,SAAS,GAAG,eAAe,WAAW;AAGnF;;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,MAAM,uBAAuB,SAAS,OAAO,aAAa,SAAS,aAAa;;EAG5F,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;EACf,MAAM,MAAM,KAAK,SAAS,UAAU;EAEpC,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS,KAAK,YAAY;GAC3B,CAAC;AAEF,MAAI,CAAC,SAAS,MAAM,SAAS,WAAW,IACtC,OAAM,IAAI,MAAM,2BAA2B,SAAS,aAAa;;;;;CAOrE,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,OAAO,WAAmB,IAAY,SAAiB,MAAqB;EAChF,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;AAQxC,SANe,MAAM,KAAK,QAAW,KAAK,EACxC,QAAQ,OACT,EAAE,EACD,eAAe,MAChB,CAAC;;;;;CAQJ,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,WAAW,KAAK,EAAE,EACjE,QAAQ,OACT,CAAC;AACF,OAAI,KACF,SAAQ,KAAK,KAAK;UAEd;EAIV,IAAI,eAAe;AAEnB,MAAI,QAAQ,MACV,gBAAe,QAAQ,QAAO,SAAQ;AACpC,QAAK,MAAM,OAAO,QAAQ,MAGxB,KAFkB,KAAK,SACJ,QAAQ,MAAO,KAEhC,QAAO;AAGX,UAAO;IACP;AAIJ,MAAI,QAAQ,OACV,gBAAe,KAAK,YAAY,cAAc,OAAO,QAAQ,OAAO,EAAE,QAAQ,UAAU;EAG1F,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;;;;;CAQJ,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;AAC7B,OAAI;IAEF,IAAI,eADY,IAAI,IAAI,SAAS,CACN,SAAS,QAAQ,UAAU,GAAG,CAAC,QAAQ,OAAO,GAAG;AAE5E,QAAI,gBAAgB,iBAAiB,KAAK,QAAQ,OAAO,GAAG,CAC1D,OAAM,KAAK,aAAa;WAEpB;;AAIV,SAAO;;;;;CAMT,MAAM,iBAAmC;AACvC,MAAI;AACF,SAAM,KAAK,cAAc,GAAG;AAC5B,UAAO;UACD;AACN,UAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnistreamai/data-adapter-webdav",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@omnistreamai/data-core": "0.1.2"
15
+ "@omnistreamai/data-core": "0.2.0"
16
16
  },
17
17
  "scripts": {
18
18
  "build": "tsdown",