@gjsify/dav 0.64.12 → 0.64.13

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/README.md CHANGED
@@ -37,7 +37,7 @@ await dav.delete(url, { ifMatch: etag });
37
37
 
38
38
  ## API
39
39
 
40
- `discover`, `listCollections`, `listObjects` (calendar-query / addressbook-query), `getObjects` (multiget), `sync` (RFC 6578; a 404 entry is a deletion), `put`, `delete`, plus `parseMultistatus` for custom requests.
40
+ `discover`, `listCollections`, `listObjects` (calendar-query / addressbook-query; calendars take `{ component, range, uid }`, where `uid` fetches one item by iCalendar UID via an RFC 4791 §9.7.5 prop-filter instead of scanning the collection; `text-match` is a substring match, so compare the returned UID exactly), `getObjects` (multiget), `sync` (RFC 6578; a 404 entry is a deletion), `put`, `delete`, plus `parseMultistatus` for custom requests.
41
41
 
42
42
  A stale sync token makes the server answer 403/409/507 as a `DavError`; resync with `null`.
43
43
 
package/lib/esm/client.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{NS_CALDAV as e,NS_CARDDAV as t,NS_CS as n,NS_DAV as r,addressbookQueryBody as i,calendarQueryBody as a,childText as o,directChildren as s,hasChild as c,multigetBody as l,okProp as u,parseMultistatus as d,propfindBody as f,syncCollectionBody as p}from"./xml.js";var DavError=class extends Error{status;url;method;constructor(e,t,n,r){super(r??`DAV ${n} ${t} failed with ${e}`),this.status=e,this.url=t,this.method=n,this.name=`DavError`}get isPreconditionFailed(){return this.status===412}};const defaultFetch=(e,t)=>fetch(e,t),m={caldav:{ns:e,name:`calendar-data`},carddav:{ns:t,name:`address-data`}};var DavClient=class{auth;doFetch;constructor(e={}){this.auth=e.auth,this.doFetch=e.fetch??defaultFetch}async request(e,t,n={}){let r={...await this.auth?.(),...n.headers};return this.doFetch(t,{method:e,headers:r,body:n.body})}async xml(e,t,n,r){let i=await this.request(e,t,{headers:{"Content-Type":`application/xml; charset=utf-8`,Depth:r},body:n});if(i.status!==207)throw await i.text().catch(()=>``),new DavError(i.status,t,e);return{ms:d(await i.text()),url:i.url||t}}async discover(n,r){let i=[`${new URL(r).origin}/.well-known/${n===`caldav`?`caldav`:`carddav`}`,r],a=null,c;for(let e of i)try{let{ms:t,url:n}=await this.xml(`PROPFIND`,e,f([`D:current-user-principal`]),`0`),r=t.responses.map(u).find(e=>e),i=r&&s(r,`DAV:`,`current-user-principal`)[0],c=i&&o(i,`DAV:`,`href`);a=new URL(c?.trim()||n,n).href;break}catch(e){c=e}if(!a)throw c instanceof Error?c:Error(`DAV: discovery failed`);let l=n===`caldav`?`calendar-home-set`:`addressbook-home-set`,d=n===`caldav`?e:t,{ms:p,url:m}=await this.xml(`PROPFIND`,a,f([`${n===`caldav`?`C`:`A`}:${l}`]),`0`),h=p.responses.map(u).find(e=>e),g=h&&s(h,d,l)[0],_=g&&o(g,`DAV:`,`href`);if(!_)throw Error(`DAV: server returned no ${l}`);return{principalUrl:a,homeUrl:new URL(_.trim(),m).href}}async listCollections(i,a){let{ms:l,url:d}=await this.xml(`PROPFIND`,i,f([`D:resourcetype`,`D:displayname`,`CS:getctag`,`D:sync-token`,`C:supported-calendar-component-set`]),`1`),p=a===`caldav`?{ns:e,name:`calendar`}:{ns:t,name:`addressbook`},m=[];for(let t of l.responses){let i=u(t),l=i&&s(i,`DAV:`,`resourcetype`)[0];if(!i||!l||!c(l,p.ns,p.name))continue;let f=s(i,e,`supported-calendar-component-set`)[0];m.push({url:new URL(t.href,d).href,kind:a,displayName:o(i,r,`displayname`),ctag:o(i,n,`getctag`),syncToken:o(i,r,`sync-token`),components:f?s(f,e,`comp`).map(e=>e.getAttribute(`name`)??``):[]})}return m}async listObjects(e,t,n={}){let r=t===`caldav`?a(n.component??`VEVENT`,n.range):i(),{ms:o,url:s}=await this.xml(`REPORT`,e,r,`1`);return this.objects(o.responses,t,s)}async getObjects(e,t,n){if(n.length===0)return[];let r=n.map(t=>new URL(t,e).pathname),{ms:i,url:a}=await this.xml(`REPORT`,e,l(t,r),`1`);return this.objects(i.responses,t,a)}async sync(e,t){let{ms:n,url:i}=await this.xml(`REPORT`,e,p(t),`1`),a=[],s=[];for(let t of n.responses){let n=new URL(t.href,i).href;if(t.status===404||t.propstats.some(e=>e.status===404&&!u(t))){s.push(n);continue}let c=u(t);!c||n===new URL(e,i).href||a.push({url:n,etag:o(c,r,`getetag`)})}return{syncToken:n.syncToken,changed:a,deleted:s}}async put(e,t,n={}){let r={"Content-Type":n.contentType??(e.endsWith(`.vcf`)?`text/vcard; charset=utf-8`:`text/calendar; charset=utf-8`)};n.ifMatch&&(r[`If-Match`]=n.ifMatch),n.ifNoneMatch&&(r[`If-None-Match`]=`*`);let i=await this.request(`PUT`,e,{headers:r,body:t});if(await i.text().catch(()=>``),i.status<200||i.status>=300)throw new DavError(i.status,e,`PUT`);return{etag:i.headers.get(`etag`)}}async delete(e,t={}){let n=t.ifMatch?{"If-Match":t.ifMatch}:{},r=await this.request(`DELETE`,e,{headers:n});if(await r.text().catch(()=>``),r.status<200||r.status>=300)throw new DavError(r.status,e,`DELETE`)}objects(e,t,n){let i=[];for(let a of e){let e=u(a);if(!e)continue;let s=o(e,m[t].ns,m[t].name);i.push({url:new URL(a.href,n).href,etag:o(e,r,`getetag`),...s===null?{}:{data:s}})}return i}};export{DavClient,DavError};
1
+ import"./_virtual/_rolldown/runtime.js";import{NS_CALDAV as e,NS_CARDDAV as t,NS_CS as n,NS_DAV as r,addressbookQueryBody as i,calendarQueryBody as a,childText as o,directChildren as s,hasChild as c,multigetBody as l,okProp as u,parseMultistatus as d,propfindBody as f,syncCollectionBody as p}from"./xml.js";var DavError=class extends Error{status;url;method;constructor(e,t,n,r){super(r??`DAV ${n} ${t} failed with ${e}`),this.status=e,this.url=t,this.method=n,this.name=`DavError`}get isPreconditionFailed(){return this.status===412}};const defaultFetch=(e,t)=>fetch(e,t),m={caldav:{ns:e,name:`calendar-data`},carddav:{ns:t,name:`address-data`}};var DavClient=class{auth;doFetch;constructor(e={}){this.auth=e.auth,this.doFetch=e.fetch??defaultFetch}async request(e,t,n={}){let r={...await this.auth?.(),...n.headers};return this.doFetch(t,{method:e,headers:r,body:n.body})}async xml(e,t,n,r){let i=await this.request(e,t,{headers:{"Content-Type":`application/xml; charset=utf-8`,Depth:r},body:n});if(i.status!==207)throw await i.text().catch(()=>``),new DavError(i.status,t,e);return{ms:d(await i.text()),url:i.url||t}}async discover(n,r){let i=[`${new URL(r).origin}/.well-known/${n===`caldav`?`caldav`:`carddav`}`,r],a=null,c;for(let e of i)try{let{ms:t,url:n}=await this.xml(`PROPFIND`,e,f([`D:current-user-principal`]),`0`),r=t.responses.map(u).find(e=>e),i=r&&s(r,`DAV:`,`current-user-principal`)[0],c=i&&o(i,`DAV:`,`href`);a=new URL(c?.trim()||n,n).href;break}catch(e){c=e}if(!a)throw c instanceof Error?c:Error(`DAV: discovery failed`);let l=n===`caldav`?`calendar-home-set`:`addressbook-home-set`,d=n===`caldav`?e:t,{ms:p,url:m}=await this.xml(`PROPFIND`,a,f([`${n===`caldav`?`C`:`A`}:${l}`]),`0`),h=p.responses.map(u).find(e=>e),g=h&&s(h,d,l)[0],_=g&&o(g,`DAV:`,`href`);if(!_)throw Error(`DAV: server returned no ${l}`);return{principalUrl:a,homeUrl:new URL(_.trim(),m).href}}async listCollections(i,a){let{ms:l,url:d}=await this.xml(`PROPFIND`,i,f([`D:resourcetype`,`D:displayname`,`CS:getctag`,`D:sync-token`,`C:supported-calendar-component-set`]),`1`),p=a===`caldav`?{ns:e,name:`calendar`}:{ns:t,name:`addressbook`},m=[];for(let t of l.responses){let i=u(t),l=i&&s(i,`DAV:`,`resourcetype`)[0];if(!i||!l||!c(l,p.ns,p.name))continue;let f=s(i,e,`supported-calendar-component-set`)[0];m.push({url:new URL(t.href,d).href,kind:a,displayName:o(i,r,`displayname`),ctag:o(i,n,`getctag`),syncToken:o(i,r,`sync-token`),components:f?s(f,e,`comp`).map(e=>e.getAttribute(`name`)??``):[]})}return m}async listObjects(e,t,n={}){let r=t===`caldav`?a(n.component??`VEVENT`,n.range,n.uid):i(),{ms:o,url:s}=await this.xml(`REPORT`,e,r,`1`);return this.objects(o.responses,t,s)}async getObjects(e,t,n){if(n.length===0)return[];let r=n.map(t=>new URL(t,e).pathname),{ms:i,url:a}=await this.xml(`REPORT`,e,l(t,r),`1`);return this.objects(i.responses,t,a)}async sync(e,t){let{ms:n,url:i}=await this.xml(`REPORT`,e,p(t),`1`),a=[],s=[];for(let t of n.responses){let n=new URL(t.href,i).href;if(t.status===404||t.propstats.some(e=>e.status===404&&!u(t))){s.push(n);continue}let c=u(t);!c||n===new URL(e,i).href||a.push({url:n,etag:o(c,r,`getetag`)})}return{syncToken:n.syncToken,changed:a,deleted:s}}async put(e,t,n={}){let r={"Content-Type":n.contentType??(e.endsWith(`.vcf`)?`text/vcard; charset=utf-8`:`text/calendar; charset=utf-8`)};n.ifMatch&&(r[`If-Match`]=n.ifMatch),n.ifNoneMatch&&(r[`If-None-Match`]=`*`);let i=await this.request(`PUT`,e,{headers:r,body:t});if(await i.text().catch(()=>``),i.status<200||i.status>=300)throw new DavError(i.status,e,`PUT`);return{etag:i.headers.get(`etag`)}}async delete(e,t={}){let n=t.ifMatch?{"If-Match":t.ifMatch}:{},r=await this.request(`DELETE`,e,{headers:n});if(await r.text().catch(()=>``),r.status<200||r.status>=300)throw new DavError(r.status,e,`DELETE`)}objects(e,t,n){let i=[];for(let a of e){let e=u(a);if(!e)continue;let s=o(e,m[t].ns,m[t].name);i.push({url:new URL(a.href,n).href,etag:o(e,r,`getetag`),...s===null?{}:{data:s}})}return i}};export{DavClient,DavError};
package/lib/esm/xml.js CHANGED
@@ -1,2 +1,2 @@
1
1
  import"./_virtual/_rolldown/runtime.js";import{DOMParser as e}from"@gjsify/domparser";const t=`DAV:`,n=`urn:ietf:params:xml:ns:caldav`,r=`urn:ietf:params:xml:ns:carddav`,i=`http://calendarserver.org/ns/`;function parseStatus(e){let t=/\s(\d{3})(\s|$)/.exec(e??``);return t?Number(t[1]):0}function directChildren(e,t,n){return e.children.filter(e=>e.namespaceURI===t&&e.localName===n.toLowerCase())}function childText(e,t,n){let r=directChildren(e,t,n)[0];return r?r.textContent??``:null}function hasChild(e,t,n){return directChildren(e,t,n).length>0}function parseXml(t){let n=new e().parseFromString(t,`application/xml`).documentElement;if(!n)throw Error(`DAV: empty XML response`);return n}function parseMultistatus(e){let n=parseXml(e);if(n.namespaceURI!==`DAV:`||n.localName!==`multistatus`)throw Error(`DAV: expected D:multistatus, got ${n.tagName}`);let r=[];for(let e of directChildren(n,t,`response`)){let n=childText(e,t,`status`);r.push({href:(childText(e,`DAV:`,`href`)??``).trim(),status:n===null?null:parseStatus(n),propstats:directChildren(e,t,`propstat`).flatMap(e=>{let n=directChildren(e,t,`prop`)[0];return n?[{status:parseStatus(childText(e,t,`status`)),prop:n}]:[]})})}return{responses:r,syncToken:childText(n,t,`sync-token`)}}function okProp(e){return e.propstats.find(e=>e.status>=200&&e.status<300)?.prop}function escapeXml(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}const a=`<?xml version="1.0" encoding="utf-8"?>
2
- `,o=`xmlns:D="${t}" xmlns:C="${n}" xmlns:A="${r}" xmlns:CS="${i}"`;function propfindBody(e){return`${a}<D:propfind ${o}><D:prop>${e.map(e=>`<${e}/>`).join(``)}</D:prop></D:propfind>`}function calendarQueryBody(e,t){let n=t?`<C:time-range start="${escapeXml(t.start)}"${t.end?` end="${escapeXml(t.end)}"`:``}/>`:``;return`${a}<C:calendar-query ${o}><D:prop><D:getetag/></D:prop><C:filter><C:comp-filter name="VCALENDAR"><C:comp-filter name="${escapeXml(e)}">${n}</C:comp-filter></C:comp-filter></C:filter></C:calendar-query>`}function addressbookQueryBody(){return`${a}<A:addressbook-query ${o}><D:prop><D:getetag/></D:prop></A:addressbook-query>`}function multigetBody(e,t){let n=e===`caldav`?`C`:`A`;return`${a}<${n}:${e===`caldav`?`calendar`:`addressbook`}-multiget ${o}><D:prop><D:getetag/><${e===`caldav`?`C:calendar-data`:`A:address-data`}/></D:prop>${t.map(e=>`<D:href>${escapeXml(e)}</D:href>`).join(``)}</${n}:${e===`caldav`?`calendar`:`addressbook`}-multiget>`}function syncCollectionBody(e){return`${a}<D:sync-collection ${o}><D:sync-token>${e?escapeXml(e):``}</D:sync-token><D:sync-level>1</D:sync-level><D:prop><D:getetag/></D:prop></D:sync-collection>`}export{n as NS_CALDAV,r as NS_CARDDAV,i as NS_CS,t as NS_DAV,addressbookQueryBody,calendarQueryBody,childText,directChildren,escapeXml,hasChild,multigetBody,okProp,parseMultistatus,parseStatus,parseXml,propfindBody,syncCollectionBody};
2
+ `,o=`xmlns:D="${t}" xmlns:C="${n}" xmlns:A="${r}" xmlns:CS="${i}"`;function propfindBody(e){return`${a}<D:propfind ${o}><D:prop>${e.map(e=>`<${e}/>`).join(``)}</D:prop></D:propfind>`}function calendarQueryBody(e,t,n){let r=t?`<C:time-range start="${escapeXml(t.start)}"${t.end?` end="${escapeXml(t.end)}"`:``}/>`:``,i=n===void 0?``:`<C:prop-filter name="UID"><C:text-match collation="i;octet">${escapeXml(n)}</C:text-match></C:prop-filter>`;return`${a}<C:calendar-query ${o}><D:prop><D:getetag/></D:prop><C:filter><C:comp-filter name="VCALENDAR"><C:comp-filter name="${escapeXml(e)}">${r}${i}</C:comp-filter></C:comp-filter></C:filter></C:calendar-query>`}function addressbookQueryBody(){return`${a}<A:addressbook-query ${o}><D:prop><D:getetag/></D:prop></A:addressbook-query>`}function multigetBody(e,t){let n=e===`caldav`?`C`:`A`;return`${a}<${n}:${e===`caldav`?`calendar`:`addressbook`}-multiget ${o}><D:prop><D:getetag/><${e===`caldav`?`C:calendar-data`:`A:address-data`}/></D:prop>${t.map(e=>`<D:href>${escapeXml(e)}</D:href>`).join(``)}</${n}:${e===`caldav`?`calendar`:`addressbook`}-multiget>`}function syncCollectionBody(e){return`${a}<D:sync-collection ${o}><D:sync-token>${e?escapeXml(e):``}</D:sync-token><D:sync-level>1</D:sync-level><D:prop><D:getetag/></D:prop></D:sync-collection>`}export{n as NS_CALDAV,r as NS_CARDDAV,i as NS_CS,t as NS_DAV,addressbookQueryBody,calendarQueryBody,childText,directChildren,escapeXml,hasChild,multigetBody,okProp,parseMultistatus,parseStatus,parseXml,propfindBody,syncCollectionBody};
@@ -58,10 +58,11 @@ export declare class DavClient {
58
58
  */
59
59
  discover(kind: DavKind, serverUrl: string): Promise<Discovery>;
60
60
  listCollections(homeUrl: string, kind: DavKind): Promise<Collection[]>;
61
- /** URLs and ETags of every object (optionally one component type and time range for calendars). */
61
+ /** URLs and ETags of every object (optionally one component type, time range and iCalendar UID for calendars). */
62
62
  listObjects(collectionUrl: string, kind: DavKind, filter?: {
63
63
  component?: string;
64
64
  range?: TimeRange;
65
+ uid?: string;
65
66
  }): Promise<DavObject[]>;
66
67
  /** Fetch full iCalendar/vCard text for the given object URLs (calendar-multiget / addressbook-multiget). */
67
68
  getObjects(collectionUrl: string, kind: DavKind, urls: string[]): Promise<DavObject[]>;
@@ -37,7 +37,8 @@ export interface TimeRange {
37
37
  start: string;
38
38
  end?: string;
39
39
  }
40
- export declare function calendarQueryBody(component: string, range?: TimeRange): string;
40
+ /** `uid` adds an RFC 4791 §9.7.5 prop-filter; `i;octet` because UIDs are case-sensitive. */
41
+ export declare function calendarQueryBody(component: string, range?: TimeRange, uid?: string): string;
41
42
  export declare function addressbookQueryBody(): string;
42
43
  export declare function multigetBody(kind: 'caldav' | 'carddav', hrefs: string[]): string;
43
44
  export declare function syncCollectionBody(token: string | null): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/dav",
3
- "version": "0.64.12",
3
+ "version": "0.64.13",
4
4
  "description": "CalDAV and CardDAV client over fetch: discovery, collections, objects, sync-collection and conditional writes. iCalendar and vCard stay text.",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -39,13 +39,13 @@
39
39
  "vcard"
40
40
  ],
41
41
  "dependencies": {
42
- "@gjsify/domparser": "^0.64.12"
42
+ "@gjsify/domparser": "^0.64.13"
43
43
  },
44
44
  "devDependencies": {
45
- "@gjsify/cli": "^0.64.12",
46
- "@gjsify/fetch": "^0.64.12",
47
- "@gjsify/http": "^0.64.12",
48
- "@gjsify/unit": "^0.64.12",
45
+ "@gjsify/cli": "^0.64.13",
46
+ "@gjsify/fetch": "^0.64.13",
47
+ "@gjsify/http": "^0.64.13",
48
+ "@gjsify/unit": "^0.64.13",
49
49
  "@types/node": "^25.9.2",
50
50
  "typescript": "^6.0.3"
51
51
  },