notu 0.6.2 → 0.7.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/dist/notu.umd.js CHANGED
@@ -1 +1 @@
1
- (function(global,factory){typeof exports=="object"&&typeof module<"u"?factory(exports):typeof define=="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis<"u"?globalThis:global||self,factory(global.notu={}))})(this,function(exports2){"use strict";var __defProp=Object.defineProperty;var __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:!0,configurable:!0,writable:!0,value}):obj[key]=value;var __publicField=(obj,key,value)=>(__defNormalProp(obj,typeof key!="symbol"?key+"":key,value),value);class ModelWithState{constructor(){__publicField(this,"state","NEW")}new(){return this.state="NEW",this}clean(){return this.state="CLEAN",this}dirty(){return this.state="DIRTY",this}delete(){return this.state="DELETED",this}get isNew(){return this.state=="NEW"}get isClean(){return this.state=="CLEAN"}get isDirty(){return this.state=="DIRTY"}get isDeleted(){return this.state=="DELETED"}validate(throwError=!1){return!0}}class Attr extends ModelWithState{constructor(name,description){super();__publicField(this,"id",0);__publicField(this,"_name","");__publicField(this,"_description","");__publicField(this,"_type","TEXT");__publicField(this,"_spaceId",0);__publicField(this,"_space",null);name&&(this.name=name),description&&(this.description=description)}get name(){return this._name}set name(value){value!==this._name&&(this._name=value,this.isClean&&this.dirty())}get description(){return this._description}set description(value){value!==this._description&&(this._description=value,this.isClean&&this.dirty())}get type(){return this._type}set type(value){if(!this.isNew)throw Error("Cannot change an attribute's type once it has been created.");this._type=value}get isText(){return this.type=="TEXT"}get isNumber(){return this.type=="NUMBER"}get isBoolean(){return this.type=="BOOLEAN"}get isDate(){return this.type=="DATE"}asText(){return this.type="TEXT",this}asNumber(){return this.type="NUMBER",this}asBoolean(){return this.type="BOOLEAN",this}asDate(){return this.type="DATE",this}get spaceId(){return this._spaceId}set spaceId(value){var _a;value!==this._spaceId&&(this._spaceId=value,value!==((_a=this.space)==null?void 0:_a.id)&&(this._space=null),this.isClean&&this.dirty())}get space(){return this._space}set space(value){this._space=value,this.spaceId=(value==null?void 0:value.id)??0}in(space){return typeof space=="number"?this.spaceId=space:this.space=space,this}duplicate(){const output=new Attr;return output.id=this.id,output.name=this.name,output.description=this.description,output.type=this.type,this.space?output.space=this.space:output.spaceId=this.spaceId,output.state=this.state,output}validate(throwError=!1){let output=null;if(this.spaceId<=0?output="Note spaceId must be greater than zero.":!this.isNew&&this.id<=0&&(output="Attr id must be greater than zero if in non-new state."),throwError&&output!=null)throw Error(output);return output==null}get defaultValue(){switch(this.type){case"TEXT":return"";case"NUMBER":return 0;case"BOOLEAN":return!1;case"DATE":return new Date}}toJSON(){return{state:this.state,id:this.id,name:this.name,description:this.description,type:this.type,spaceId:this.spaceId}}static fromJSON(json){const output=new Attr(json.name,json.description);return output.type=json.type,output.spaceId=json.spaceId,output.id=json.id,output.state=json.state,output}}class CachedClient{constructor(internalClient){__publicField(this,"_internalClient");__publicField(this,"_spaces",null);__publicField(this,"_attrs",null);__publicField(this,"_tags",null);this._internalClient=internalClient}_linkTagsToSpaces(){for(const tag of this._tags.values()){const space=this._spaces.get(tag.spaceId);space&&(tag.space=space)}}_linkAttrsToSpaces(){for(const attr of this._attrs.values()){const space=this._spaces.get(attr.spaceId);space&&(attr.space=space)}}async login(username,password){return await this._internalClient.login(username,password)}async getSpaces(){if(this._spaces==null){const spaces=await this._internalClient.getSpaces();this._spaces=new Map;for(const space of spaces)this._spaces.set(space.id,space);this._tags!=null&&this._linkTagsToSpaces(),this._attrs!=null&&this._linkAttrsToSpaces()}return[...this._spaces.values()]}async saveSpace(space){const saveResult=await this._internalClient.saveSpace(space);return this._spaces!=null&&this._spaces.set(saveResult.id,saveResult),saveResult}async getAttrs(spaceId){if(this._attrs==null){const attrs=await this._internalClient.getAttrs(spaceId);this._attrs=new Map;for(const attr of attrs)this._attrs.set(attr.id,attr);this._spaces!=null&&this._linkAttrsToSpaces()}return[...this._attrs.values()]}async saveAttr(attr){const saveResult=await this._internalClient.saveAttr(attr);return this._attrs!=null&&this._attrs.set(saveResult.id,saveResult),saveResult}async getTags(){if(this._tags==null){const tags=await this._internalClient.getTags();this._tags=new Map;for(const tag of tags)this._tags.set(tag.id,tag);this._spaces!=null&&this._linkTagsToSpaces()}return[...this._tags.values()]}async getNotes(query,spaceId){const results=await this._internalClient.getNotes(query,spaceId);if(this._spaces!=null)for(const note of results){const space=this._spaces.get(note.spaceId);space&&(note.space=space)}if(this._attrs!=null)for(const note of results)for(const na of note.allAttrs){const attr=this._attrs.get(na.attrId);attr&&(na.attr=attr,attr.isDate&&!(na.value instanceof Date)&&(na.value=new Date(na.value)),na.clean())}if(this._tags!=null)for(const note of results){{const tag=this._tags.get(note.id);tag&&(note.setOwnTag(tag),note.clean(),note.ownTag.clean())}for(const nt of note.tags){const tag=this._tags.get(nt.tagId);tag&&(nt.tag=tag,nt.clean())}for(const na of note.allAttrs.filter(x=>x.tagId!=null)){const tag=this._tags.get(na.tagId);tag&&(na.tag=tag,na.clean())}}return results}async getNoteCount(query,spaceId){return await this._internalClient.getNoteCount(query,spaceId)}async saveNotes(notes){const saveResults=await this._internalClient.saveNotes(notes);if(this._tags!=null)for(const note of saveResults.filter(x=>!!x.ownTag))this._tags.set(note.ownTag.id,note.ownTag);return saveResults}async customJob(name,data){return await this._internalClient.customJob(name,data)}async cacheAll(spaceId=0){await this.getSpaces();const tagsPromise=this.getTags(),attrsPromise=this.getAttrs(spaceId);await Promise.all([tagsPromise,attrsPromise])}get spaces(){return[...this._spaces.values()]}get tags(){return[...this._tags.values()]}get attrs(){return[...this._attrs.values()]}}class Space extends ModelWithState{constructor(name=""){super();__publicField(this,"id",0);__publicField(this,"_name","");__publicField(this,"_version","0.0.1");this._name=name}get name(){return this._name}set name(value){value!==this._name&&(this._name=value,this.isClean&&this.dirty())}get version(){return this._version}set version(value){value!==this._version&&(this._version=value,this.isClean&&this.dirty())}v(version){return this.version=version,this}duplicate(){const output=new Space;return output.id=this.id,output.name=this.name,output.state=this.state,output.version=this.version,output}validate(throwError=!1){let output=null;if(!this.isNew&&this.id<=0&&(output="Space id must be greater than zero if in non-new state."),throwError&&output!=null)throw Error(output);return output==null}toJSON(){return{state:this.state,id:this.id,name:this.name,version:this.version}}static fromJSON(json){const output=new Space(json.name);return output.id=json.id,output.state=json.state,output.version=json.version,output}}class HttpClient{constructor(url,fetchMethod=null){__publicField(this,"_url",null);__publicField(this,"_token",null);__publicField(this,"_fetch");if(!url)throw Error("Endpoint URL must be passed in to NotuClient constructor");url.endsWith("/")&&(url=url.substring(0,url.length-1)),this._url=url,this._fetch=fetchMethod??window.fetch.bind(window)}get url(){return this._url}get token(){return this._token}set token(value){this._token=value}async login(username,password){const result=await this._fetch(this.url+"/login",{method:"POST",body:JSON.stringify({username,password})});if(result.body!=null){const token=(await result.json()).token;if(token)return this._token=token,{success:!0,error:null,token:this._token}}return{success:!1,error:"Invalid username & password.",token:null}}async getSpaces(){return(await(await this._fetch(this.url+"/spaces",{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()).map(x=>Space.fromJSON(x))}async saveSpace(space){const result=await this._fetch(this.url+"/spaces",{method:"POST",body:JSON.stringify(space),headers:{Authorization:"Bearer "+this.token}});return Space.fromJSON(await result.json())}async getAttrs(spaceId=0){return(await(await this._fetch(this.url+`/attrs?space=${spaceId}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()).map(x=>Attr.fromJSON(x))}async saveAttr(attr){const result=await this._fetch(this.url+"/attrs",{method:"POST",body:JSON.stringify(attr),headers:{Authorization:"Bearer "+this.token}});return Attr.fromJSON(await result.json())}async getTags(){return(await(await this._fetch(this.url+"/tags",{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()).map(x=>Tag.fromJSON(x))}async getNotes(query,spaceId){return(await(await this._fetch(this.url+`/notes?space=${spaceId}&query=${encodeURIComponent(query)}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()).map(x=>Note.fromJSON(x))}async getNoteCount(query,spaceId){return(await(await this._fetch(this.url+`/notes?count=true&space=${spaceId}&query=${encodeURIComponent(query)}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()).count}async saveNotes(notes){return(await(await this._fetch(this.url+"/notes",{method:"POST",body:JSON.stringify(notes),headers:{Authorization:"Bearer "+this.token}})).json()).map(x=>Note.fromJSON(x))}async customJob(name,data){return await(await this._fetch(this.url+"/customjob",{method:"POST",body:JSON.stringify({name,data}),headers:{Authorization:"Bearer "+this.token}})).json()}}class NoteAttr extends ModelWithState{constructor(note,attr,value){super();__publicField(this,"_noteId",0);__publicField(this,"_note",null);__publicField(this,"_attrId",0);__publicField(this,"_attr",null);__publicField(this,"_value",null);__publicField(this,"_tagId",null);__publicField(this,"_tag",null);note!=null&&note!=null&&(typeof note=="number"?this.noteId=note:this.note=note),attr!=null&&attr!=null&&(typeof attr=="number"?this.attrId=attr:this.attr=attr),value!=null&&value!=null&&(this.value=value)}get noteId(){return this._noteId}set noteId(value){var _a;value!==this._noteId&&(this._noteId=value,value!==((_a=this.note)==null?void 0:_a.id)&&(this._note=null),this.isClean&&this.dirty())}get note(){return this._note}set note(value){this._note=value,this.noteId=(value==null?void 0:value.id)??0}get attrId(){return this._attrId}set attrId(value){var _a;value!==this._attrId&&(this._attrId=value,value!==((_a=this.attr)==null?void 0:_a.id)&&(this._attr=null),this.isClean&&this.dirty())}get attr(){return this._attr}set attr(newAttr){const oldAttr=this._attr;this._attr=newAttr,newAttr?newAttr.id!=this.attrId&&(!oldAttr||newAttr.type!=oldAttr.type)&&(this.value=newAttr.defaultValue):this.value=null,this.attrId=(newAttr==null?void 0:newAttr.id)??0}get value(){return this._value}set value(newVal){newVal!=this._value&&(this._value=newVal,this.isClean&&this.dirty())}withValue(value){return this.value=value,this}get tagId(){return this._tagId}set tagId(value){var _a;value!==this._tagId&&(this._tagId=value,value!==((_a=this.tag)==null?void 0:_a.id)&&(this._tag=null),this.isClean&&this.dirty())}get tag(){return this._tag}set tag(value){this._tag=value,this.tagId=(value==null?void 0:value.id)??null}onTag(tag){return typeof tag=="number"?this.tagId=tag:this.tag=tag,this}duplicate(){const output=new NoteAttr;return output.noteId=this.noteId,this.attr?output.attr=this.attr:output.attrId=this.attrId,this.tag?output.tag=this.tag:output.tagId=this.tagId,output.value=this.value,output.state=this.state,output}validate(throwError=!1){let output=null;if(this.noteId<=0&&!this.isNew?output="NoteAttr noteId must be greater than zero":this.attrId<=0&&(output="NoteAttr attrId must be greater than zero"),throwError&&output!=null)throw Error(output);return output==null}toJSON(){return{state:this.state,noteId:this.noteId,attrId:this.attrId,tagId:this.tagId,value:this.value}}static fromJSON(json){const output=new NoteAttr(json.noteId,json.attrId,json.value);return output.tagId=json.tagId,output.state=json.state,output}}class NoteTag extends ModelWithState{constructor(note,tag){super();__publicField(this,"_noteId",0);__publicField(this,"_note",null);__publicField(this,"_tagId",0);__publicField(this,"_tag",null);note!=null&&note!=null&&(typeof note=="number"?this.noteId=note:this.note=note),tag!=null&&tag!=null&&(typeof tag=="number"?this.tagId=tag:this.tag=tag)}get noteId(){return this._noteId}set noteId(value){var _a;value!==this._noteId&&(this._noteId=value,value!==((_a=this.note)==null?void 0:_a.id)&&(this._note=null),this.isClean&&this.dirty())}get note(){return this._note}set note(value){this._note=value,this.noteId=(value==null?void 0:value.id)??0}get tagId(){return this._tagId}set tagId(value){var _a;value!==this._tagId&&(this._tagId=value,value!==((_a=this.tag)==null?void 0:_a.id)&&(this._tag=null),this.isClean&&this.dirty())}get tag(){return this._tag}set tag(value){this._tag=value,this.tagId=(value==null?void 0:value.id)??0}get attrs(){return this.note?this.note.allAttrs.filter(x=>x.tagId==this.tagId):[]}addAttr(attr){if(!this.note)throw new Error("Cannot call addAttr on NoteTag where note property has not been set");const na=this.note.addAttr(attr);return na.tag=this.tag,na}duplicate(){const output=new NoteTag;return output.noteId=this.noteId,this.tag?output.tag=this.tag:output.tagId=this.tagId,output}validate(throwError=!1){let output=null;if(this.noteId<=0&&!this.isNew?output="NoteTag noteId must be greater than zero":this.tagId<=0?output="NoteTag tagId must be greater than zero":this.noteId==this.tagId&&(output="NoteTag cannot link a note to its own tag"),throwError&&output!=null)throw Error(output);return output==null}toJSON(){return{state:this.state,noteId:this.noteId,tagId:this.tagId}}static fromJSON(json){const output=new NoteTag(json.noteId,json.tagId);return output.state=json.state,output}}class Tag extends ModelWithState{constructor(name=""){super();__publicField(this,"_id",0);__publicField(this,"_spaceId",0);__publicField(this,"_space",null);__publicField(this,"_name","");__publicField(this,"_color",null);__publicField(this,"_isPublic",!0);this._name=name}get id(){return this._id}set id(value){value!==this._id&&(this._id=value,this.isClean&&this.dirty())}get spaceId(){return this._spaceId}set spaceId(value){var _a;value!==this._spaceId&&(this._spaceId=value,value!==((_a=this.space)==null?void 0:_a.id)&&(this._space=null),this.isClean&&this.dirty())}get space(){return this._space}set space(value){this._space=value,this.spaceId=(value==null?void 0:value.id)??0}in(space){return typeof space=="number"?this.spaceId=space:this.space=space,this}get name(){return this._name}set name(value){value!==this._name&&(this._name=value,this.isClean&&this.dirty())}getQualifiedName(contextSpaceId){return contextSpaceId==this.spaceId?this.name:`${this.space.name}.${this.name}`}get color(){return this._color}set color(value){value!==this._color&&(this._color=value,this.isClean&&this.dirty())}get isPublic(){return this._isPublic}set isPublic(value){value!==this._isPublic&&(this._isPublic=value,this.isClean&&this.dirty())}asPublic(){return this.isPublic=!0,this}asPrivate(){return this.isPublic=!1,this}duplicate(){const output=new Tag(this.name);return output.id=this.id,output.state=this.state,output.color=this.color,output.space=this.space,output.isPublic=this.isPublic,output}validate(throwError=!1){let output=null;if(!this.isNew&&this.id<=0?output="Tag id must be greater than zero if in non-new state.":!this.name||!/^[a-zA-Z][a-zA-Z0-9 ]*[a-zA-Z0-9]?$/.test(this.name)?output="Tag name is invalid, must only contain letters, numbers, and spaces, starting with a letter":this.color&&!/^#?[A-z0-9]{6}$/.test(this.color)&&(output="Tag color is invalid, must be a 6 character hexadecimal."),throwError&&output!=null)throw Error(output);return output==null}getColorInt(){let hex=this.color;return hex?(hex.startsWith("#")&&(hex=hex.substring(1)),parseInt(hex,16)):null}toJSON(){return{state:this.state,id:this.id,name:this.name,spaceId:this.spaceId,color:this.color,isPublic:this.isPublic}}static fromJSON(json){const output=new Tag(json.name);return output.id=json.id,output.spaceId=json.spaceId,output.color=json.color,output.isPublic=json.isPublic,output.state=json.state,output}}class Note extends ModelWithState{constructor(text){super();__publicField(this,"_id",0);__publicField(this,"_date",new Date);__publicField(this,"_text","");__publicField(this,"_spaceId",0);__publicField(this,"_space",null);__publicField(this,"_ownTag",null);__publicField(this,"_tags",[]);__publicField(this,"_attrs",[]);text&&(this.text=text)}get id(){return this._id}set id(value){this._id=value,this.ownTag&&(this.ownTag.id=value)}get date(){return this._date}set date(value){value!==this._date&&(this._date=value,this.isClean&&this.dirty())}at(value){return this.date=value,this}get text(){return this._text}set text(value){value!==this._text&&(this._text=value,this.isClean&&this.dirty())}get spaceId(){return this._spaceId}set spaceId(value){var _a;value!==this._spaceId&&(this._spaceId=value,value!==((_a=this.space)==null?void 0:_a.id)&&(this._space=null),this.isClean&&this.dirty(),this._setOwnTagSpace())}get space(){return this._space}set space(value){this._space=value,this.spaceId=(value==null?void 0:value.id)??0}in(space){return typeof space=="number"?this.spaceId=space:this.space=space,this}get ownTag(){return this._ownTag}setOwnTag(tag){if(typeof tag=="string")this.ownTag==null&&(this._ownTag=new Tag),this.ownTag.name=tag,this.ownTag.id=this.id,this._setOwnTagSpace();else{if(this.ownTag)throw new Error("Note has already had its tag set. If you would like to change the tag name, call setTag with just a string specifying the new tag name.");if(tag.id!=0&&tag.id!=this.id)throw new Error("Attempted to set tag to note with non-matching ID. Added tag id must either match the note id, which indicates that the tag has already been added to the note. Otherwise the tag id must be zero, indicating that the tag still needs to be added.");this._ownTag=tag}return this}removeOwnTag(){return this.ownTag?(this.ownTag.isNew?this._ownTag=null:this.ownTag.delete(),this):this}_setOwnTagSpace(){this.ownTag&&(this.space?this.ownTag.space=this.space:this.ownTag.spaceId=this.spaceId)}get tags(){return this._tags.filter(x=>!x.isDeleted)}get tagsPendingDeletion(){return this._tags.filter(x=>x.isDeleted)}addTag(tag){if(tag.isDeleted)throw Error("Cannot add a tag marked as deleted to a note");if(tag.isNew)throw Error("Cannot add a tag that hasn't yet been saved to a note");if(tag.id==this.id)throw Error("Note cannot add its own tag as a linked tag");if(!tag.isPublic&&tag.spaceId!=this.spaceId)throw Error("Cannot add a private tag from another space");let nt=this._tags.find(x=>x.tagId==tag.id);return nt?(nt.isDeleted&&nt.dirty(),nt):(nt=new NoteTag,nt.note=this,nt.tag=tag,this._tags.push(nt),nt)}removeTag(tag){const nt=this._tags.find(x=>x.tagId==tag.id);if(!nt)return this;nt.isNew?this._tags=this._tags.filter(x=>x!==nt):nt.delete();for(const na of this._attrs.filter(x=>!x.isDeleted&&x.tagId==tag.id))this.removeAttr(na.attr,na.tag);return this}getTag(tag,space=null){return tag instanceof Tag&&(tag=tag.name),space&&space instanceof Space&&(space=space.id),space!=null?this.tags.find(x=>x.tag.name==tag&&x.tag.spaceId==space):this.tags.find(x=>x.tag.name==tag&&x.tag.spaceId==this.spaceId)}get allAttrs(){return this._attrs.filter(x=>!x.isDeleted)}get noteAttrs(){return this._attrs.filter(x=>!x.isDeleted&&!x.tag)}get allAttrsPendingDeletion(){return this._attrs.filter(x=>x.isDeleted)}addAttr(attr){if(attr.isDeleted)throw Error("Cannot add an attribute marked as deleted to a note");if(attr.isNew)throw Error("Cannot add an attribute that hasn't yet been saved to a note");const na=new NoteAttr(this,attr);return this._attrs.push(na),na}removeAttr(attr,tag=null){const na=this._attrs.find(x=>x.attrId==attr.id&&x.tagId==(tag==null?void 0:tag.id));return na?(na.isNew?this._attrs=this._attrs.filter(x=>x!==na):na.delete(),this):this}getValue(attr){var _a;return attr instanceof Attr&&(attr=attr.name),(_a=this.noteAttrs.find(x=>x.attr.name==attr))==null?void 0:_a.value}getAttr(attr){return attr instanceof Attr&&(attr=attr.name),this.noteAttrs.find(x=>x.attr.name==attr)}duplicate(){const output=new Note;return output.id=this.id,output.date=this.date,output.text=this.text,this.space?output.space=this.space:output.spaceId=this.spaceId,output._tags=this.tags.map(x=>{const ntCopy=x.duplicate();return ntCopy.note=output,ntCopy}),output._attrs=this.allAttrs.map(x=>{const naCopy=x.duplicate();return naCopy.note=output,naCopy}),this.ownTag&&!this.ownTag.isDeleted&&output.setOwnTag(this.ownTag.duplicate()),output.state=this.state,output}toJSON(){return{state:this.state,id:this.id,date:this.date,text:this.text,spaceId:this.spaceId,ownTag:this.ownTag,tags:this.tags,attrs:this._attrs}}static fromJSON(json){const output=new Note(json.text);if(output.id=json.id,output.date=new Date(json.date),output.spaceId=json.spaceId,json.ownTag&&output.setOwnTag(Tag.fromJSON(json.ownTag)),json.tags){output._tags=json.tags.map(x=>NoteTag.fromJSON(x));for(const nt of output._tags)nt.note=output}if(json.attrs){output._attrs=json.attrs.map(x=>NoteAttr.fromJSON(x));for(const na of output._attrs)na.note=output}return output.state=json.state,output}validate(throwError=!1){let output=null;this.spaceId<=0?output="Note spaceId must be greater than zero.":!this.isNew&&this.id<=0?output="Note id must be greater than zero if in non-new state.":this.ownTag&&this.ownTag.spaceId!=this.spaceId&&(output="Note cannot belong to a different space than its own tag");const survivingAttrs=this._attrs.filter(x=>!x.isDeleted);for(let i=0;i<survivingAttrs.length;i++){const na=survivingAttrs[i];for(let j=i+1;j<survivingAttrs.length;j++){const na2=survivingAttrs[j];na.attrId==na2.attrId&&na.tagId==na2.tagId&&(output=`Attr '${na.attr.name}' is duplicated.`)}}if(throwError&&output!=null)throw Error(output);if(this.ownTag&&!this.ownTag.validate(throwError))return!1;for(const nt of this._tags)if(!nt.validate(throwError))return!1;for(const na of this._attrs)if(!na.validate(throwError))return!1;return output==null}}class ParsedQuery{constructor(){__publicField(this,"where",null);__publicField(this,"order",null);__publicField(this,"tags",[]);__publicField(this,"attrs",[])}}class ParsedTag{constructor(){__publicField(this,"space",null);__publicField(this,"name",null);__publicField(this,"searchDepth",0);__publicField(this,"strictSearchDepth",!0);__publicField(this,"includeOwner",!1)}}class ParsedAttr{constructor(){__publicField(this,"name",null);__publicField(this,"exists",!1);__publicField(this,"tagNameFilters",null)}}function parseQuery(query){const output=splitQuery(query);return output.where=identifyTags(output.where,output),output.order=identifyTags(output.order,output),output.where=identifyAttrs(output.where,output),output.order=identifyAttrs(output.order,output),output}function splitQuery(query){query=" "+query+" ";const output=new ParsedQuery,orderByIndex=query.toUpperCase().indexOf(" ORDER BY ");return orderByIndex<0?output.where=query.trim():(output.where=query.substring(0,orderByIndex).trim(),output.order=query.substring(orderByIndex+10).trim()),output.where==""&&(output.where=null),output}function identifyTags(query,parsedQuery){const regexes=[/(#+\??~?|~)([\w\d]+\.)?([\w\d]+)/,/(#+\??~?|~)\[([\w\d\s]+\.)?([\w\d\s]+)\]/];for(const regex of regexes)for(;;){const match=regex.exec(query);if(!match)break;const hashPrefix=match[1],parsedTag=new ParsedTag;parsedTag.space=match[2]?match[2].substring(0,match[2].length-1):null,parsedTag.name=match[3],parsedTag.includeOwner=hashPrefix.includes("~"),parsedTag.searchDepth=(hashPrefix.match(/#/g)||[]).length,parsedTag.strictSearchDepth=!hashPrefix.includes("?");const fullMatch=match[0],matchStart=query.indexOf(fullMatch),matchEnd=matchStart+fullMatch.length;query=query.substring(0,matchStart)+`{tag${parsedQuery.tags.length}}`+query.substring(matchEnd),parsedQuery.tags.push(parsedTag)}return query}function identifyAttrs(query,parsedQuery){const regexes=[/@([\w\d]+)/,/@\[([\w\d\s]+)\]/];for(const regex of regexes)for(;;){const match=regex.exec(query);if(!match)break;const parsedAttr=new ParsedAttr;parsedAttr.name=match[1];const matchStart=query.indexOf(match[0]);let matchEnd=matchStart+match[0].length;if(query.substring(matchEnd,matchEnd+9)==".Exists()"&&(parsedAttr.exists=!0,matchEnd+=9),query.substring(matchEnd,matchEnd+4)==".On("){let tagFilterStart=matchEnd+4;if(matchEnd=query.indexOf(")",tagFilterStart),matchEnd<0)throw Error("Unclosed bracket detected");let tagNameFilters=query.substring(tagFilterStart,matchEnd).split("|");const dummyParsedQuery=new ParsedQuery;for(let tagNameFilter of tagNameFilters)tagNameFilter.startsWith("~")||(tagNameFilter="~"+tagNameFilter),identifyTags(tagNameFilter,dummyParsedQuery);parsedAttr.tagNameFilters=dummyParsedQuery.tags,matchEnd++}query=query.substring(0,matchStart)+`{attr${parsedQuery.attrs.length}}`+query.substring(matchEnd),parsedQuery.attrs.push(parsedAttr)}return query}exports2.Attr=Attr,exports2.CachedClient=CachedClient,exports2.HttpClient=HttpClient,exports2.Note=Note,exports2.NoteAttr=NoteAttr,exports2.NoteTag=NoteTag,exports2.ParsedAttr=ParsedAttr,exports2.ParsedQuery=ParsedQuery,exports2.ParsedTag=ParsedTag,exports2.Space=Space,exports2.Tag=Tag,exports2.parseQuery=parseQuery,Object.defineProperty(exports2,Symbol.toStringTag,{value:"Module"})});
1
+ (function(global,factory){typeof exports=="object"&&typeof module<"u"?factory(exports):typeof define=="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis<"u"?globalThis:global||self,factory(global.notu={}))})(this,function(exports2){"use strict";var __defProp=Object.defineProperty;var __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:!0,configurable:!0,writable:!0,value}):obj[key]=value;var __publicField=(obj,key,value)=>(__defNormalProp(obj,typeof key!="symbol"?key+"":key,value),value);class ModelWithState{constructor(){__publicField(this,"state","NEW")}new(){return this.state="NEW",this}clean(){return this.state="CLEAN",this}dirty(){return this.state="DIRTY",this}delete(){return this.state="DELETED",this}get isNew(){return this.state=="NEW"}get isClean(){return this.state=="CLEAN"}get isDirty(){return this.state=="DIRTY"}get isDeleted(){return this.state=="DELETED"}validate(throwError=!1){return!0}}class Attr extends ModelWithState{constructor(name,description){super();__publicField(this,"id",0);__publicField(this,"_name","");__publicField(this,"_description","");__publicField(this,"_type","TEXT");__publicField(this,"_space",null);name&&(this.name=name),description&&(this.description=description)}get name(){return this._name}set name(value){value!==this._name&&(this._name=value,this.isClean&&this.dirty())}get description(){return this._description}set description(value){value!==this._description&&(this._description=value,this.isClean&&this.dirty())}get type(){return this._type}set type(value){if(!this.isNew)throw Error("Cannot change an attribute's type once it has been created.");this._type=value}get isText(){return this.type=="TEXT"}get isNumber(){return this.type=="NUMBER"}get isBoolean(){return this.type=="BOOLEAN"}get isDate(){return this.type=="DATE"}asText(){return this.type="TEXT",this}asNumber(){return this.type="NUMBER",this}asBoolean(){return this.type="BOOLEAN",this}asDate(){return this.type="DATE",this}get space(){return this._space}set space(value){var _a;if(value!==this._space){const idChanged=(value==null?void 0:value.id)!=((_a=this._space)==null?void 0:_a.id);this._space=value,this.isClean&&idChanged&&this.dirty()}}in(space){return this.space=space,this}duplicate(){const output=new Attr;return output.id=this.id,output.name=this.name,output.description=this.description,output.type=this.type,output.space=this.space,output.state=this.state,output}validate(throwError=!1){let output=null;if(this.space?!this.isNew&&this.id<=0&&(output="Attr id must be greater than zero if in non-new state."):output="Attr must belong to a space.",throwError&&output!=null)throw Error(output);return output==null}get defaultValue(){switch(this.type){case"TEXT":return"";case"NUMBER":return 0;case"BOOLEAN":return!1;case"DATE":return new Date}}toJSON(){var _a;return{state:this.state,id:this.id,name:this.name,description:this.description,type:this.type,spaceId:(_a=this.space)==null?void 0:_a.id}}}class Notu{constructor(client,cache){__publicField(this,"_client");__publicField(this,"_cache");this._client=client,this._cache=cache}get client(){return this._client}get cache(){return this._cache}async login(username,password){return await this.client.login(username,password)}getSpaces(){return this.cache.getSpaces()}getSpace(id){return this.cache.getSpace(id)}getSpaceByName(name){return this.cache.getSpaceByName(name)}async saveSpace(space){const spaceData=await this.client.saveSpace(space);return this.cache.spaceSaved(spaceData)}getAttrs(space){return this.cache.getAttrs(space)}getAttr(id){return this.cache.getAttr(id)}getAttrByName(name,space){return this.cache.getAttrByName(name,space)}async saveAttr(attr){const attrData=await this.client.saveAttr(attr);return this.cache.attrSaved(attrData)}getTags(space=null,includeOtherSpacePublics=!1){return this.cache.getTags(space,includeOtherSpacePublics)}getTag(id){return this.cache.getTag(id)}getTagByName(name,space){return this.cache.getTagByName(name,space)}async getNotes(query,spaceId){return(await this.client.getNotes(query,spaceId)).map(n=>this.cache.noteFromJSON(n))}async getNoteCount(query,spaceId){return await this.client.getNoteCount(query,spaceId)}async saveNotes(notes){const notesData=await this.client.saveNotes(notes);for(const noteData of notesData.filter(x=>!!x.ownTag))this.cache.tagSaved(noteData.ownTag);return notes=notesData.map(n=>this.cache.noteFromJSON(n)),notes}async customJob(name,data){return await this.client.customJob(name,data)}}class Space extends ModelWithState{constructor(name=""){super();__publicField(this,"_id",0);__publicField(this,"_name","");__publicField(this,"_version","0.0.1");this._name=name}get id(){return this._id}set id(value){if(!this.isNew)throw Error("Cannot change the id of a Space once it has already been created.");this._id=value}get name(){return this._name}set name(value){value!==this._name&&(this._name=value,this.isClean&&this.dirty())}get version(){return this._version}set version(value){value!==this._version&&(this._version=value,this.isClean&&this.dirty())}v(version){return this.version=version,this}duplicate(){const output=new Space;return output.id=this.id,output.name=this.name,output.version=this.version,output.state=this.state,output}validate(throwError=!1){let output=null;if(!this.isNew&&this.id<=0&&(output="Space id must be greater than zero if in non-new state."),throwError&&output!=null)throw Error(output);return output==null}toJSON(){return{state:this.state,id:this.id,name:this.name,version:this.version}}}class NotuHttpClient{constructor(url,fetchMethod=null){__publicField(this,"_url",null);__publicField(this,"_token",null);__publicField(this,"_fetch");if(!url)throw Error("Endpoint URL must be passed in to NotuClient constructor");url.endsWith("/")&&(url=url.substring(0,url.length-1)),this._url=url,this._fetch=fetchMethod??window.fetch.bind(window)}get url(){return this._url}get token(){return this._token}set token(value){this._token=value}async login(username,password){const response=await this._fetch(this.url+"/login",{method:"POST",body:JSON.stringify({username,password})});if(response.body!=null){const result=await response.json();return result.token&&(this._token=result.token),result}return{token:null,error:"Unknown error occurred on the server"}}async setup(){await(await this._fetch(this.url+"/setup",{method:"POST",headers:{Authorization:"Bearer "+this.token}})).json()}async saveSpace(space){return await(await this._fetch(this.url+"/spaces",{method:"POST",body:JSON.stringify(space),headers:{Authorization:"Bearer "+this.token}})).json()}async saveAttr(attr){return await(await this._fetch(this.url+"/attrs",{method:"POST",body:JSON.stringify(attr),headers:{Authorization:"Bearer "+this.token}})).json()}async getNotes(query,space){return space instanceof Space&&(space=space.id),await(await this._fetch(this.url+`/notes?space=${space}&query=${encodeURIComponent(query)}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()}async getNoteCount(query,space){return space instanceof Space&&(space=space.id),(await(await this._fetch(this.url+`/notes?count=true&space=${space}&query=${encodeURIComponent(query)}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()).count}async saveNotes(notes){return await(await this._fetch(this.url+"/notes",{method:"POST",body:JSON.stringify(notes),headers:{Authorization:"Bearer "+this.token}})).json()}async customJob(name,data){return await(await this._fetch(this.url+"/customjob",{method:"POST",body:JSON.stringify({name,data}),headers:{Authorization:"Bearer "+this.token}})).json()}}class NoteAttr extends ModelWithState{constructor(attr,tag,value){super();__publicField(this,"_tag",null);__publicField(this,"_attr",null);__publicField(this,"_value",null);if(!attr)throw Error("Cannot instanciate new NoteAttr without a passed in attr.");if(attr.isNew)throw Error("Cannot create a NoteAttr object for an attr that hasn't been saved yet.");if(attr.isDeleted)throw Error("Cannot create a NoteAttr object for an attr marked as deleted.");if(this._attr=attr,tag){if(tag.isNew)throw Error("Cannot create a NoteAttr object linked to a tag that hasn't been saved yet.");if(tag.isDeleted)throw Error("Cannot create a NoteAttr object linked to a tag marked as deleted.");this._tag=tag}value!=null&&value!=null?this.value=value:this.value=attr.defaultValue}get tag(){return this._tag}get attr(){return this._attr}get value(){return this._value}set value(newVal){this.attr.isDate&&!(newVal instanceof Date)&&(newVal=new Date(newVal)),newVal!=this._value&&(this._value=newVal,this.isClean&&this.dirty())}withValue(value){return this.value=value,this}duplicate(){return new NoteAttr(this.attr,this.tag,this.value)}validate(throwError=!1){let output=null;if(throwError&&output!=null)throw Error(output);return output==null}toJSON(){return{state:this.state,attrId:this.attr.id,tagId:this.tag.id,value:this.value}}}class NoteTag extends ModelWithState{constructor(tag){super();__publicField(this,"_tag");__publicField(this,"_attrs",[]);if(!tag)throw Error("Cannot instanciate new NoteTag without a passed in tag.");if(tag.isNew)throw Error("Cannot create a NoteTag object for a tag that hasn't been saved yet.");if(tag.isDeleted)throw Error("Cannot create a NoteTag object for a tag marked as deleted.");this._tag=tag}get tag(){return this._tag}get attrs(){return this._attrs.filter(x=>!x.isDeleted)}get attrsPendingDeletion(){return this._attrs.filter(x=>x.isDeleted)}addAttr(attr,value){if(attr.isDeleted)throw Error("Cannot add an attribute marked as deleted.");if(attr.isNew)throw Error("Cannot add an attribute that hasn't yet been saved.");const na=new NoteAttr(attr,this.tag,value);return this._attrs.push(na),this}removeAttr(attr){const na=this._attrs.find(x=>x.attr.id==attr.id);return na?(na.isNew?this._attrs=this._attrs.filter(x=>x!==na):na.delete(),this):this}getAttr(attr){return attr instanceof Attr&&(attr=attr.name),this.attrs.find(x=>x.attr.name==attr)}getValue(attr){var _a;return(_a=this.getAttr(attr))==null?void 0:_a.value}duplicate(){const output=new NoteTag(this.tag);return output._attrs=this.attrs.map(x=>x.duplicate()),output}validate(throwError=!1){function exit(message){if(throwError&&message!=null)throw Error(message);return message==null}if(!this.tag)return exit("NoteTag must have a tag set.");for(const na of this._attrs)if(!na.validate(throwError))return!1;return!0}toJSON(){return{state:this.state,tagId:this.tag.id,attrs:this._attrs}}}class Tag extends ModelWithState{constructor(name=""){super();__publicField(this,"_id",0);__publicField(this,"_space",null);__publicField(this,"_name","");__publicField(this,"_color",null);__publicField(this,"_isPublic",!0);this._name=name}get id(){return this._id}set id(value){if(!this.isNew)throw Error("Cannot change the id of a Tag once it has already been created.");this._id=value}get space(){return this._space}set space(value){var _a;if(value!==this._space){const idChanged=(value==null?void 0:value.id)!=((_a=this._space)==null?void 0:_a.id);this._space=value,this.isClean&&idChanged&&this.dirty()}}in(space){return this.space=space,this}get name(){return this._name}set name(value){value!==this._name&&(this._name=value,this.isClean&&this.dirty())}getQualifiedName(contextSpaceId){var _a;return contextSpaceId==((_a=this.space)==null?void 0:_a.id)?this.name:`${this.space.name}.${this.name}`}get color(){return this._color}set color(value){value!==this._color&&(this._color=value,this.isClean&&this.dirty())}get isPublic(){return this._isPublic}set isPublic(value){value!==this._isPublic&&(this._isPublic=value,this.isClean&&this.dirty())}asPublic(){return this.isPublic=!0,this}asPrivate(){return this.isPublic=!1,this}duplicate(){const output=new Tag(this.name);return output.id=this.id,output.state=this.state,output.color=this.color,output.space=this.space,output.isPublic=this.isPublic,output}validate(throwError=!1){let output=null;if(!this.isNew&&this.id<=0?output="Tag id must be greater than zero if in non-new state.":!this.name||!/^[a-zA-Z][a-zA-Z0-9 ]*[a-zA-Z0-9]?$/.test(this.name)?output="Tag name is invalid, must only contain letters, numbers, and spaces, starting with a letter":this.color&&!/^#?[A-z0-9]{6}$/.test(this.color)&&(output="Tag color is invalid, must be a 6 character hexadecimal."),throwError&&output!=null)throw Error(output);return output==null}getColorInt(){let hex=this.color;return hex?(hex.startsWith("#")&&(hex=hex.substring(1)),parseInt(hex,16)):null}toJSON(){var _a;return{state:this.state,id:this.id,name:this.name,spaceId:(_a=this.space)==null?void 0:_a.id,color:this.color,isPublic:this.isPublic}}}class Note extends ModelWithState{constructor(text,ownTag){super();__publicField(this,"_id",0);__publicField(this,"_date",new Date);__publicField(this,"_text","");__publicField(this,"_space",null);__publicField(this,"_ownTag",null);__publicField(this,"_tags",[]);__publicField(this,"_attrs",[]);text&&(this.text=text),this._ownTag=ownTag}get id(){return this._id}set id(value){if(!this.isNew)throw Error("Cannot change the id of a Note once it has already been created.");this._id=value,this.ownTag&&this.ownTag.id!=value&&(this.ownTag.id=value)}get date(){return this._date}set date(value){value!==this._date&&(this._date=value,this.isClean&&this.dirty())}at(value){return this.date=value,this}get text(){return this._text}set text(value){value!==this._text&&(this._text=value,this.isClean&&this.dirty())}get space(){return this._space}set space(value){var _a;if(value!==this._space){const idChanged=(value==null?void 0:value.id)!=((_a=this._space)==null?void 0:_a.id);this._space=value,this.isClean&&idChanged&&this.dirty(),this._setOwnTagSpace()}}in(space){return this.space=space,this}get ownTag(){return this._ownTag}setOwnTag(tagName){return this.ownTag==null?(this._ownTag=new Tag(tagName),this.ownTag.id=this.id):this.ownTag.isDeleted&&this.ownTag.dirty(),this.ownTag.name=tagName,this._setOwnTagSpace(),this}removeOwnTag(){return this.ownTag?(this.ownTag.isNew?this._ownTag=null:this.ownTag.delete(),this):this}_setOwnTagSpace(){this.ownTag&&this.space&&(this.ownTag.space=this.space)}get tags(){return this._tags.filter(x=>!x.isDeleted)}get tagsPendingDeletion(){return this._tags.filter(x=>x.isDeleted)}addTag(tag){if(tag.isDeleted)throw Error("Cannot add a tag marked as deleted to a note");if(tag.isNew)throw Error("Cannot add a tag that hasn't yet been saved to a note");if(tag.id==this.id)throw Error("Note cannot add its own tag as a linked tag");if(!tag.isPublic&&tag.space.id!=this.space.id)throw Error("Cannot add a private tag from another space");let nt=this._tags.find(x=>x.tag.id==tag.id);return nt?(nt.isDeleted&&nt.dirty(),nt):(nt=new NoteTag(tag),this._tags.push(nt),nt)}removeTag(tag){const nt=this._tags.find(x=>x.tag.id==tag.id);return nt?(nt.isNew?this._tags=this._tags.filter(x=>x!==nt):nt.delete(),this):this}getTag(tag,space=null){return tag instanceof Tag&&(tag=tag.name),space&&space instanceof Space&&(space=space.id),space!=null?this.tags.find(x=>x.tag.name==tag&&x.tag.space.id==space):this.tags.find(x=>x.tag.name==tag&&x.tag.space.id==this.space.id)}get attrs(){return this._attrs.filter(x=>!x.isDeleted)}get attrsPendingDeletion(){return this._attrs.filter(x=>x.isDeleted)}addAttr(attr,value){if(attr.isDeleted)throw Error("Cannot add an attribute marked as deleted to a note");if(attr.isNew)throw Error("Cannot add an attribute that hasn't yet been saved to a note");const na=new NoteAttr(attr,null,value);return this._attrs.push(na),this}removeAttr(attr){const na=this._attrs.find(x=>x.attr.id==attr.id);return na?(na.isNew?this._attrs=this._attrs.filter(x=>x!==na):na.delete(),this):this}getAttr(attr){return attr instanceof Attr&&(attr=attr.name),this.attrs.find(x=>x.attr.name==attr)}getValue(attr){var _a;return(_a=this.getAttr(attr))==null?void 0:_a.value}duplicate(){var _a;const output=new Note(this.text,(_a=this.ownTag)==null?void 0:_a.duplicate()).at(this.date).in(this.space);return output._attrs=this.attrs.map(x=>x.duplicate()),output._tags=this.tags.map(x=>x.duplicate()),output}toJSON(){return{state:this.state,id:this.id,date:this.date,text:this.text,spaceId:this.space.id,ownTag:this.ownTag,tags:this._tags,attrs:this._attrs}}validate(throwError=!1){function exit(message){if(throwError&&message!=null)throw Error(message);return message==null}if(this.space){if(!this.isNew&&this.id<=0)return exit("Note id must be greater than zero if in non-new state.");if(this.ownTag&&this.ownTag.space.id!=this.space.id)return exit("Note cannot belong to a different space than its own tag")}else return exit("Note must belong to a space.");const survivingAttrs=this.attrs;for(let i=0;i<survivingAttrs.length;i++){const na=survivingAttrs[i];for(let j=i+1;j<survivingAttrs.length;j++){const na2=survivingAttrs[j];if(na.attr.id==na2.attr.id)return exit(`Attr '${na.attr.name}' is duplicated.`)}}if(this.ownTag&&!this.ownTag.validate(throwError))return!1;for(const nt of this._tags)if(!nt.validate(throwError))return!1;for(const na of this._attrs)if(!na.validate(throwError))return!1;return!0}}class NotuCache{constructor(fetcher){__publicField(this,"_fetcher");__publicField(this,"_spaces",null);__publicField(this,"_tags",null);__publicField(this,"_attrs",null);if(!fetcher)throw Error("NotuCache constructor must have a fetcher argument supplied.");this._fetcher=fetcher}async populate(){await this._populateSpaces();const tagsPromise=this._populateTags(),attrsPromise=this._populateAttrs();await Promise.all([tagsPromise,attrsPromise])}async _populateSpaces(){const spacesData=await this._fetcher.getSpacesData();this._spaces=new Map;for(const spaceData of spacesData){const space=this._spaceFromJSON(spaceData);this._spaces.set(space.id,space)}}_spaceFromJSON(spaceData){const space=new Space(spaceData.name);return space.id=spaceData.id,space.version=spaceData.version,space.state=spaceData.state,space}async _populateTags(){const tagsData=await this._fetcher.getTagsData();this._tags=new Map;for(const tagData of tagsData){const tag=this._tagFromJSON(tagData);this._tags.set(tag.id,tag)}}_tagFromJSON(tagData){const tag=new Tag(tagData.name);return tag.id=tagData.id,tag.space=this._spaces.get(tagData.spaceId),tag.color=tagData.color,tag.isPublic=tagData.isPublic,tag.state=tagData.state,tag}async _populateAttrs(){const attrsData=await this._fetcher.getAttrsData();this._attrs=new Map;for(const attrData of attrsData){const attr=this._attrFromJSON(attrData);this._attrs.set(attr.id,attr)}}_attrFromJSON(attrData){const attr=new Attr(attrData.name,attrData.description);return attr.id=attrData.id,attr.type=attrData.type,attr.space=this._spaces.get(attrData.spaceId),attr.state=attrData.state,attr}noteFromJSON(noteData){const note=new Note(noteData.text,this.getTag(noteData.id)).at(new Date(noteData.date)).in(this.getSpace(noteData.spaceId));note.id=noteData.id,note.state=noteData.state;for(const naData of noteData.attrs){const attr=this.getAttr(naData.attrId);note.addAttr(attr,naData.value),note.getAttr(attr).state=naData.state}for(const ntData of noteData.tags){const nt=note.addTag(this.getTag(ntData.tagId));nt.state=ntData.state;for(const ntaData of ntData.attrs){const attr=this.getAttr(ntaData.attrId);nt.addAttr(attr,ntaData.value),nt.getAttr(attr).state=ntaData.state}}return note}getSpaces(){return Array.from(this._spaces.values())}getSpace(id){return this._spaces.get(id)}getSpaceByName(name){for(const space of this._spaces.values())if(space.name==name)return space}spaceSaved(spaceData){const space=this._spaceFromJSON(spaceData);return space.state=="DELETED"?this._spaces.delete(space.id):this._spaces.set(space.id,space),space}getTags(space=null,includeOtherSpacePublics=!1){return space==null?Array.from(this._tags.values()):(space instanceof Space&&(space=space.id),Array.from(this._tags.values()).filter(x=>x.isPublic&&includeOtherSpacePublics||x.space.id==space))}getTag(id){return this._tags.get(id)}getTagByName(name,space){space instanceof Space&&(space=space.id);for(const tag of this._tags.values())if(tag.name==name&&tag.space.id==space)return tag}tagSaved(tagData){const tag=this._tagFromJSON(tagData);return tag.state=="DELETED"?this._tags.delete(tag.id):this._tags.set(tag.id,tag),tag}getAttrs(space){return space instanceof Space&&(space=space.id),Array.from(this._attrs.values()).filter(x=>x.space.id==space)}getAttr(id){return this._attrs.get(id)}getAttrByName(name,space){space instanceof Space&&(space=space.id);for(const attr of this._attrs.values())if(attr.name==name&&attr.space.id==space)return attr}attrSaved(attrData){const attr=this._attrFromJSON(attrData);return attr.state=="DELETED"?this._attrs.delete(attr.id):this._attrs.set(attr.id,attr),attr}}class NotuHttpCacheFetcher{constructor(url,token,fetchMethod=null){__publicField(this,"_url",null);__publicField(this,"_token",null);__publicField(this,"_fetch");if(!url)throw Error("Endpoint URL must be passed into NotuHttpCacheFetcher constructor");if(!token)throw Error("Security token must be passed into NotuHttpCacheFetcher constructor");url.endsWith("/")&&(url=url.substring(0,url.length-1)),this._url=url,this._token=token,this._fetch=fetchMethod??window.fetch.bind(window)}get url(){return this._url}get token(){return this._token}async getSpacesData(){return await this._getX("/spaces")}async getTagsData(){return await this._getX("/tags")}async getAttrsData(){return await this._getX("/attrs")}async _getX(endpoint){return await(await this._fetch(this.url+endpoint,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()}}class ParsedQuery{constructor(){__publicField(this,"where",null);__publicField(this,"order",null);__publicField(this,"tags",[]);__publicField(this,"attrs",[])}}class ParsedTag{constructor(){__publicField(this,"space",null);__publicField(this,"name",null);__publicField(this,"searchDepth",0);__publicField(this,"strictSearchDepth",!0);__publicField(this,"includeOwner",!1)}}class ParsedAttr{constructor(){__publicField(this,"name",null);__publicField(this,"exists",!1);__publicField(this,"tagNameFilters",null)}}function parseQuery(query){const output=splitQuery(query);return output.where=identifyTags(output.where,output),output.order=identifyTags(output.order,output),output.where=identifyAttrs(output.where,output),output.order=identifyAttrs(output.order,output),output}function splitQuery(query){query=" "+query+" ";const output=new ParsedQuery,orderByIndex=query.toUpperCase().indexOf(" ORDER BY ");return orderByIndex<0?output.where=query.trim():(output.where=query.substring(0,orderByIndex).trim(),output.order=query.substring(orderByIndex+10).trim()),output.where==""&&(output.where=null),output}function identifyTags(query,parsedQuery){const regexes=[/(#+\??~?|~)([\w\d]+\.)?([\w\d]+)/,/(#+\??~?|~)\[([\w\d\s]+\.)?([\w\d\s]+)\]/];for(const regex of regexes)for(;;){const match=regex.exec(query);if(!match)break;const hashPrefix=match[1],parsedTag=new ParsedTag;parsedTag.space=match[2]?match[2].substring(0,match[2].length-1):null,parsedTag.name=match[3],parsedTag.includeOwner=hashPrefix.includes("~"),parsedTag.searchDepth=(hashPrefix.match(/#/g)||[]).length,parsedTag.strictSearchDepth=!hashPrefix.includes("?");const fullMatch=match[0],matchStart=query.indexOf(fullMatch),matchEnd=matchStart+fullMatch.length;query=query.substring(0,matchStart)+`{tag${parsedQuery.tags.length}}`+query.substring(matchEnd),parsedQuery.tags.push(parsedTag)}return query}function identifyAttrs(query,parsedQuery){const regexes=[/@([\w\d]+)/,/@\[([\w\d\s]+)\]/];for(const regex of regexes)for(;;){const match=regex.exec(query);if(!match)break;const parsedAttr=new ParsedAttr;parsedAttr.name=match[1];const matchStart=query.indexOf(match[0]);let matchEnd=matchStart+match[0].length;if(query.substring(matchEnd,matchEnd+9)==".Exists()"&&(parsedAttr.exists=!0,matchEnd+=9),query.substring(matchEnd,matchEnd+4)==".On("){let tagFilterStart=matchEnd+4;if(matchEnd=query.indexOf(")",tagFilterStart),matchEnd<0)throw Error("Unclosed bracket detected");let tagNameFilters=query.substring(tagFilterStart,matchEnd).split("|");const dummyParsedQuery=new ParsedQuery;for(let tagNameFilter of tagNameFilters)tagNameFilter.startsWith("~")||(tagNameFilter="~"+tagNameFilter),identifyTags(tagNameFilter,dummyParsedQuery);parsedAttr.tagNameFilters=dummyParsedQuery.tags,matchEnd++}query=query.substring(0,matchStart)+`{attr${parsedQuery.attrs.length}}`+query.substring(matchEnd),parsedQuery.attrs.push(parsedAttr)}return query}exports2.Attr=Attr,exports2.Note=Note,exports2.NoteAttr=NoteAttr,exports2.NoteTag=NoteTag,exports2.Notu=Notu,exports2.NotuCache=NotuCache,exports2.NotuHttpCacheFetcher=NotuHttpCacheFetcher,exports2.NotuHttpClient=NotuHttpClient,exports2.ParsedAttr=ParsedAttr,exports2.ParsedQuery=ParsedQuery,exports2.ParsedTag=ParsedTag,exports2.Space=Space,exports2.Tag=Tag,exports2.parseQuery=parseQuery,Object.defineProperty(exports2,Symbol.toStringTag,{value:"Module"})});
@@ -1,5 +1,7 @@
1
1
  import { Attr, Note, Space, Tag } from ".";
2
+ import { NotuCacheFetcher } from "./services/HttpCacheFetcher";
2
3
  export declare function newNote(text?: string, id?: number): Note;
3
4
  export declare function newSpace(name?: string, id?: number): Space;
4
5
  export declare function newAttr(name?: string, id?: number): Attr;
5
6
  export declare function newTag(name?: string, id?: number): Tag;
7
+ export declare function testCacheFetcher(): NotuCacheFetcher;
@@ -1,10 +1,12 @@
1
1
  import Attr from './models/Attr';
2
- import { CachedClient } from './services/CachedClient';
3
- import HttpClient from './services/HttpClient';
2
+ import { Notu } from './services/Notu';
3
+ import NotuHttpClient from './services/HttpClient';
4
+ import { NotuCache } from './services/NotuCache';
5
+ import { NotuHttpCacheFetcher } from './services/HttpCacheFetcher';
4
6
  import Note from './models/Note';
5
7
  import NoteAttr from './models/NoteAttr';
6
8
  import NoteTag from './models/NoteTag';
7
9
  import parseQuery, { ParsedQuery, ParsedTag, ParsedAttr } from './services/QueryParser';
8
10
  import Space from './models/Space';
9
11
  import Tag from './models/Tag';
10
- export { Attr, CachedClient, HttpClient, Note, NoteAttr, NoteTag, parseQuery, ParsedQuery, ParsedTag, ParsedAttr, Space, Tag };
12
+ export { Attr, Notu, NotuHttpClient, NotuCache, NotuHttpCacheFetcher, Note, NoteAttr, NoteTag, parseQuery, ParsedQuery, ParsedTag, ParsedAttr, Space, Tag };
@@ -27,13 +27,10 @@ export default class Attr extends ModelWithState<Attr> {
27
27
  asNumber(): Attr;
28
28
  asBoolean(): Attr;
29
29
  asDate(): Attr;
30
- private _spaceId;
31
- get spaceId(): number;
32
- set spaceId(value: number);
33
30
  private _space;
34
31
  get space(): Space;
35
32
  set space(value: Space);
36
- in(space: number | Space): Attr;
33
+ in(space: Space): Attr;
37
34
  duplicate(): Attr;
38
35
  validate(throwError?: boolean): boolean;
39
36
  get defaultValue(): any;
@@ -45,6 +42,5 @@ export default class Attr extends ModelWithState<Attr> {
45
42
  type: "TEXT" | "NUMBER" | "BOOLEAN" | "DATE";
46
43
  spaceId: number;
47
44
  };
48
- static fromJSON(json: any): Attr;
49
45
  }
50
46
  export {};
@@ -5,7 +5,7 @@ import Space from './Space';
5
5
  import Tag from './Tag';
6
6
  import Attr from './Attr';
7
7
  export default class Note extends ModelWithState<Note> {
8
- constructor(text?: string);
8
+ constructor(text?: string, ownTag?: Tag);
9
9
  private _id;
10
10
  get id(): number;
11
11
  set id(value: number);
@@ -16,16 +16,13 @@ export default class Note extends ModelWithState<Note> {
16
16
  private _text;
17
17
  get text(): string;
18
18
  set text(value: string);
19
- private _spaceId;
20
- get spaceId(): number;
21
- set spaceId(value: number);
22
19
  private _space;
23
20
  get space(): Space;
24
21
  set space(value: Space);
25
- in(space: number | Space): Note;
22
+ in(space: Space): Note;
26
23
  private _ownTag;
27
24
  get ownTag(): Tag;
28
- setOwnTag(tag: string | Tag): Note;
25
+ setOwnTag(tagName: string): Note;
29
26
  removeOwnTag(): Note;
30
27
  private _setOwnTagSpace;
31
28
  private _tags;
@@ -35,16 +32,12 @@ export default class Note extends ModelWithState<Note> {
35
32
  removeTag(tag: Tag): Note;
36
33
  getTag(tag: string | Tag, space?: number | Space): NoteTag;
37
34
  private _attrs;
38
- /** This contains all attrs that have been added either directly to the note, or to a tag on the note and are not due to be deleted. */
39
- get allAttrs(): Array<NoteAttr>;
40
- /** This contains all attrs that have been added directly to the note and are not due to be deleted. */
41
- get noteAttrs(): Array<NoteAttr>;
42
- /** This contains all attrs that have been added either directly to the note, or to a tag on the note and are due to be deleted. */
43
- get allAttrsPendingDeletion(): Array<NoteAttr>;
44
- addAttr(attr: Attr): NoteAttr;
45
- removeAttr(attr: Attr, tag?: Tag): Note;
46
- getValue(attr: string | Attr): any;
35
+ get attrs(): Array<NoteAttr>;
36
+ get attrsPendingDeletion(): Array<NoteAttr>;
37
+ addAttr(attr: Attr, value?: any): Note;
38
+ removeAttr(attr: Attr): Note;
47
39
  getAttr(attr: string | Attr): NoteAttr;
40
+ getValue(attr: string | Attr): any;
48
41
  duplicate(): Note;
49
42
  toJSON(): {
50
43
  state: "NEW" | "CLEAN" | "DIRTY" | "DELETED";
@@ -56,6 +49,5 @@ export default class Note extends ModelWithState<Note> {
56
49
  tags: NoteTag[];
57
50
  attrs: NoteAttr[];
58
51
  };
59
- static fromJSON(json: any): Note;
60
52
  validate(throwError?: boolean): boolean;
61
53
  }
@@ -1,40 +1,22 @@
1
1
  import ModelWithState from './ModelWithState';
2
- import Note from './Note';
3
2
  import Attr from './Attr';
4
3
  import Tag from './Tag';
5
4
  export default class NoteAttr extends ModelWithState<NoteAttr> {
6
- constructor(note?: number | Note, attr?: number | Attr, value?: any);
7
- private _noteId;
8
- get noteId(): number;
9
- set noteId(value: number);
10
- private _note;
11
- get note(): Note;
12
- set note(value: Note);
13
- private _attrId;
14
- get attrId(): number;
15
- set attrId(value: number);
5
+ constructor(attr: Attr, tag?: Tag, value?: any);
6
+ private _tag;
7
+ get tag(): Tag;
16
8
  private _attr;
17
9
  get attr(): Attr;
18
- set attr(newAttr: Attr);
19
10
  private _value;
20
11
  get value(): any;
21
12
  set value(newVal: any);
22
13
  withValue(value: any): NoteAttr;
23
- private _tagId;
24
- get tagId(): number;
25
- set tagId(value: number);
26
- private _tag;
27
- get tag(): Tag;
28
- set tag(value: Tag);
29
- onTag(tag: number | Tag): NoteAttr;
30
14
  duplicate(): NoteAttr;
31
15
  validate(throwError?: boolean): boolean;
32
16
  toJSON(): {
33
17
  state: "NEW" | "CLEAN" | "DIRTY" | "DELETED";
34
- noteId: number;
35
18
  attrId: number;
36
19
  tagId: number;
37
20
  value: any;
38
21
  };
39
- static fromJSON(json: any): NoteAttr;
40
22
  }
@@ -1,30 +1,23 @@
1
- import Note from './Note';
2
1
  import ModelWithState from './ModelWithState';
3
2
  import Tag from './Tag';
4
3
  import Attr from './Attr';
5
4
  import NoteAttr from './NoteAttr';
6
5
  export default class NoteTag extends ModelWithState<NoteTag> {
7
- constructor(note?: number | Note, tag?: number | Tag);
8
- private _noteId;
9
- get noteId(): number;
10
- set noteId(value: number);
11
- private _note;
12
- get note(): Note;
13
- set note(value: Note);
14
- private _tagId;
15
- get tagId(): number;
16
- set tagId(value: number);
6
+ constructor(tag: Tag);
17
7
  private _tag;
18
8
  get tag(): Tag;
19
- set tag(value: Tag);
9
+ private _attrs;
20
10
  get attrs(): Array<NoteAttr>;
21
- addAttr(attr: Attr): NoteAttr;
11
+ get attrsPendingDeletion(): Array<NoteAttr>;
12
+ addAttr(attr: Attr, value?: any): NoteTag;
13
+ removeAttr(attr: Attr): NoteTag;
14
+ getAttr(attr: string | Attr): NoteAttr;
15
+ getValue(attr: string | Attr): any;
22
16
  duplicate(): NoteTag;
23
17
  validate(throwError?: boolean): boolean;
24
18
  toJSON(): {
25
19
  state: "NEW" | "CLEAN" | "DIRTY" | "DELETED";
26
- noteId: number;
27
20
  tagId: number;
21
+ attrs: NoteAttr[];
28
22
  };
29
- static fromJSON(json: any): NoteTag;
30
23
  }
@@ -1,6 +1,8 @@
1
1
  import ModelWithState from './ModelWithState';
2
2
  export default class Space extends ModelWithState<Space> {
3
- id: number;
3
+ private _id;
4
+ get id(): number;
5
+ set id(value: number);
4
6
  private _name;
5
7
  get name(): string;
6
8
  set name(value: string);
@@ -17,5 +19,4 @@ export default class Space extends ModelWithState<Space> {
17
19
  name: string;
18
20
  version: string;
19
21
  };
20
- static fromJSON(json: any): Space;
21
22
  }
@@ -4,13 +4,10 @@ export default class Tag extends ModelWithState<Tag> {
4
4
  private _id;
5
5
  get id(): number;
6
6
  set id(value: number);
7
- private _spaceId;
8
- get spaceId(): number;
9
- set spaceId(value: number);
10
7
  private _space;
11
8
  get space(): Space;
12
9
  set space(value: Space);
13
- in(space: number | Space): Tag;
10
+ in(space: Space): Tag;
14
11
  private _name;
15
12
  get name(): string;
16
13
  set name(value: string);
@@ -35,5 +32,4 @@ export default class Tag extends ModelWithState<Tag> {
35
32
  color: string;
36
33
  isPublic: boolean;
37
34
  };
38
- static fromJSON(json: any): Tag;
39
35
  }
@@ -0,0 +1,17 @@
1
+ export interface NotuCacheFetcher {
2
+ getSpacesData(): Promise<Array<any>>;
3
+ getTagsData(): Promise<Array<any>>;
4
+ getAttrsData(): Promise<Array<any>>;
5
+ }
6
+ export declare class NotuHttpCacheFetcher implements NotuCacheFetcher {
7
+ private _url;
8
+ get url(): string;
9
+ private _token;
10
+ get token(): string;
11
+ private _fetch;
12
+ constructor(url: string, token: string, fetchMethod?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>);
13
+ getSpacesData(): Promise<Array<any>>;
14
+ getTagsData(): Promise<Array<any>>;
15
+ getAttrsData(): Promise<Array<any>>;
16
+ private _getX;
17
+ }
@@ -1,23 +1,21 @@
1
- import Space from "../models/Space";
2
- import { Note, Attr, Tag } from "..";
1
+ import Attr from '../models/Attr';
2
+ import Note from '../models/Note';
3
+ import Space from '../models/Space';
3
4
  export interface NotuLoginResult {
4
- success: boolean;
5
5
  error: string;
6
6
  token: string;
7
7
  }
8
8
  export interface NotuClient {
9
9
  login(username: string, password: string): Promise<NotuLoginResult>;
10
- getSpaces(): Promise<Array<Space>>;
11
- saveSpace(space: Space): Promise<Space>;
12
- getAttrs(spaceId: number): Promise<Array<Attr>>;
13
- saveAttr(attr: Attr): Promise<Attr>;
14
- getTags(): Promise<Array<Tag>>;
15
- getNotes(query: string, spaceId: number): Promise<Array<Note>>;
16
- getNoteCount(query: string, spaceId: number): Promise<number>;
17
- saveNotes(notes: Array<Note>): Promise<Array<Note>>;
10
+ setup(): Promise<void>;
11
+ saveSpace(space: Space): Promise<any>;
12
+ saveAttr(attr: Attr): Promise<any>;
13
+ getNotes(query: string, space: number | Space): Promise<Array<any>>;
14
+ getNoteCount(query: string, space: number | Space): Promise<number>;
15
+ saveNotes(notes: Array<Note>): Promise<Array<any>>;
18
16
  customJob(name: string, data: any): Promise<any>;
19
17
  }
20
- export default class HttpClient {
18
+ export default class NotuHttpClient implements NotuClient {
21
19
  private _url;
22
20
  get url(): string;
23
21
  private _token;
@@ -26,13 +24,11 @@ export default class HttpClient {
26
24
  private _fetch;
27
25
  constructor(url: string, fetchMethod?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>);
28
26
  login(username: string, password: string): Promise<NotuLoginResult>;
29
- getSpaces(): Promise<Array<Space>>;
30
- saveSpace(space: Space): Promise<Space>;
31
- getAttrs(spaceId?: number): Promise<Array<Attr>>;
32
- saveAttr(attr: Attr): Promise<Attr>;
33
- getTags(): Promise<Array<Tag>>;
34
- getNotes(query: string, spaceId: number): Promise<Array<Note>>;
35
- getNoteCount(query: string, spaceId: number): Promise<number>;
36
- saveNotes(notes: Array<Note>): Promise<Array<Note>>;
27
+ setup(): Promise<void>;
28
+ saveSpace(space: Space): Promise<any>;
29
+ saveAttr(attr: Attr): Promise<any>;
30
+ getNotes(query: string, space: number | Space): Promise<Array<any>>;
31
+ getNoteCount(query: string, space: number | Space): Promise<number>;
32
+ saveNotes(notes: Array<Note>): Promise<Array<any>>;
37
33
  customJob(name: string, data: any): Promise<any>;
38
34
  }
@@ -0,0 +1,26 @@
1
+ import { Attr, Note, Space, Tag } from '..';
2
+ import { NotuClient, NotuLoginResult } from './HttpClient';
3
+ import { NotuCache } from './NotuCache';
4
+ export declare class Notu {
5
+ private _client;
6
+ get client(): NotuClient;
7
+ private _cache;
8
+ get cache(): NotuCache;
9
+ constructor(client: NotuClient, cache: NotuCache);
10
+ login(username: string, password: string): Promise<NotuLoginResult>;
11
+ getSpaces(): Array<Space>;
12
+ getSpace(id: number): Space;
13
+ getSpaceByName(name: string): Space;
14
+ saveSpace(space: Space): Promise<Space>;
15
+ getAttrs(space: number | Space): Array<Attr>;
16
+ getAttr(id: number): Attr;
17
+ getAttrByName(name: string, space: number | Space): Attr;
18
+ saveAttr(attr: Attr): Promise<Attr>;
19
+ getTags(space?: number | Space, includeOtherSpacePublics?: boolean): Array<Tag>;
20
+ getTag(id: number): Tag;
21
+ getTagByName(name: string, space: number | Space): Tag;
22
+ getNotes(query: string, spaceId: number): Promise<Array<Note>>;
23
+ getNoteCount(query: string, spaceId: number): Promise<number>;
24
+ saveNotes(notes: Array<Note>): Promise<Array<Note>>;
25
+ customJob(name: string, data: any): Promise<any>;
26
+ }
@@ -0,0 +1,32 @@
1
+ import Attr from '../models/Attr';
2
+ import Note from '../models/Note';
3
+ import Space from '../models/Space';
4
+ import Tag from '../models/Tag';
5
+ import { NotuCacheFetcher } from './HttpCacheFetcher';
6
+ export declare class NotuCache {
7
+ private _fetcher;
8
+ constructor(fetcher: NotuCacheFetcher);
9
+ private _spaces;
10
+ private _tags;
11
+ private _attrs;
12
+ populate(): Promise<void>;
13
+ private _populateSpaces;
14
+ private _spaceFromJSON;
15
+ private _populateTags;
16
+ private _tagFromJSON;
17
+ private _populateAttrs;
18
+ private _attrFromJSON;
19
+ noteFromJSON(noteData: any): Note;
20
+ getSpaces(): Array<Space>;
21
+ getSpace(id: number): Space;
22
+ getSpaceByName(name: string): Space;
23
+ spaceSaved(spaceData: any): Space;
24
+ getTags(space?: number | Space, includeOtherSpacePublics?: boolean): Array<Tag>;
25
+ getTag(id: number): Tag;
26
+ getTagByName(name: string, space: number | Space): Tag;
27
+ tagSaved(tagData: any): Tag;
28
+ getAttrs(space: number | Space): Array<Attr>;
29
+ getAttr(id: number): Attr;
30
+ getAttrByName(name: string, space: number | Space): Attr;
31
+ attrSaved(attrData: any): Attr;
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notu",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "main": "dist/notu.mjs",
5
5
  "unpkg": "dist/notu.mjs",
6
6
  "types": "dist/types/index.d.ts",
@@ -1,25 +0,0 @@
1
- import { Attr, Note, Space, Tag } from '..';
2
- import { NotuClient, NotuLoginResult } from './HttpClient';
3
- export declare class CachedClient implements NotuClient {
4
- private _internalClient;
5
- constructor(internalClient: NotuClient);
6
- private _spaces;
7
- private _attrs;
8
- private _tags;
9
- private _linkTagsToSpaces;
10
- private _linkAttrsToSpaces;
11
- login(username: string, password: string): Promise<NotuLoginResult>;
12
- getSpaces(): Promise<Array<Space>>;
13
- saveSpace(space: Space): Promise<Space>;
14
- getAttrs(spaceId: number): Promise<Array<Attr>>;
15
- saveAttr(attr: Attr): Promise<Attr>;
16
- getTags(): Promise<Array<Tag>>;
17
- getNotes(query: string, spaceId: number): Promise<Array<Note>>;
18
- getNoteCount(query: string, spaceId: number): Promise<number>;
19
- saveNotes(notes: Array<Note>): Promise<Array<Note>>;
20
- customJob(name: string, data: any): Promise<any>;
21
- cacheAll(spaceId?: number): Promise<void>;
22
- get spaces(): Array<Space>;
23
- get tags(): Array<Tag>;
24
- get attrs(): Array<Attr>;
25
- }