notu 0.7.11 → 0.8.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.mjs CHANGED
@@ -33,96 +33,6 @@ class ModelWithState {
33
33
  return !0;
34
34
  }
35
35
  }
36
- class Tag extends ModelWithState {
37
- constructor(name = "") {
38
- super();
39
- __publicField(this, "_id", 0);
40
- __publicField(this, "_space", null);
41
- __publicField(this, "_name", "");
42
- __publicField(this, "_color", null);
43
- __publicField(this, "_isPublic", !1);
44
- this._name = name;
45
- }
46
- get id() {
47
- return this._id;
48
- }
49
- set id(value) {
50
- if (!this.isNew)
51
- throw Error("Cannot change the id of a Tag once it has already been created.");
52
- this._id = value;
53
- }
54
- get space() {
55
- return this._space;
56
- }
57
- set space(value) {
58
- var _a;
59
- if (value !== this._space) {
60
- const idChanged = (value == null ? void 0 : value.id) != ((_a = this._space) == null ? void 0 : _a.id);
61
- this._space = value, this.isClean && idChanged && this.dirty();
62
- }
63
- }
64
- in(space) {
65
- return this.space = space, this;
66
- }
67
- get name() {
68
- return this._name;
69
- }
70
- set name(value) {
71
- value !== this._name && (this._name = value, this.isClean && this.dirty());
72
- }
73
- getQualifiedName(contextSpaceId) {
74
- var _a;
75
- return contextSpaceId == ((_a = this.space) == null ? void 0 : _a.id) ? this.name : `${this.space.name}.${this.name}`;
76
- }
77
- get color() {
78
- return this._color;
79
- }
80
- set color(value) {
81
- value !== this._color && (this._color = value, this.isClean && this.dirty());
82
- }
83
- get isPublic() {
84
- return this._isPublic;
85
- }
86
- set isPublic(value) {
87
- if (!this.isNew && this.isPublic && !value)
88
- throw Error("Cannot change a tag from public to private once its already been saved.");
89
- value !== this._isPublic && (this._isPublic = value, this.isClean && this.dirty());
90
- }
91
- asPublic() {
92
- return this.isPublic = !0, this;
93
- }
94
- asPrivate() {
95
- return this.isPublic = !1, this;
96
- }
97
- duplicate() {
98
- const output = new Tag(this.name);
99
- return output.id = this.id, output.state = this.state, output.color = this.color, output.space = this.space, output.isPublic = this.isPublic, output;
100
- }
101
- validate(throwError = !1) {
102
- let output = null;
103
- 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)
104
- throw Error(output);
105
- return output == null;
106
- }
107
- getColorInt() {
108
- let hex = this.color;
109
- return hex ? (hex.startsWith("#") && (hex = hex.substring(1)), parseInt(hex, 16)) : null;
110
- }
111
- static getColorFromInt(color) {
112
- return color == null ? null : "#" + color.toString(16).toUpperCase().padStart(6, "0");
113
- }
114
- toJSON() {
115
- var _a;
116
- return {
117
- state: this.state,
118
- id: this.id,
119
- name: this.name,
120
- spaceId: (_a = this.space) == null ? void 0 : _a.id,
121
- color: this.color,
122
- isPublic: this.isPublic
123
- };
124
- }
125
- }
126
36
  class Attr extends ModelWithState {
127
37
  constructor(name, description) {
128
38
  super();
@@ -219,13 +129,6 @@ class Attr extends ModelWithState {
219
129
  return /* @__PURE__ */ new Date();
220
130
  }
221
131
  }
222
- getColorInt() {
223
- let hex = this.color;
224
- return hex ? (hex.startsWith("#") && (hex = hex.substring(1)), parseInt(hex, 16)) : null;
225
- }
226
- static getColorFromInt(color) {
227
- return Tag.getColorFromInt(color);
228
- }
229
132
  toJSON() {
230
133
  var _a;
231
134
  return {
@@ -482,6 +385,89 @@ class Space extends ModelWithState {
482
385
  };
483
386
  }
484
387
  }
388
+ class Tag extends ModelWithState {
389
+ constructor(name = "") {
390
+ super();
391
+ __publicField(this, "_id", 0);
392
+ __publicField(this, "_space", null);
393
+ __publicField(this, "_name", "");
394
+ __publicField(this, "_color", null);
395
+ __publicField(this, "_isPublic", !1);
396
+ this._name = name;
397
+ }
398
+ get id() {
399
+ return this._id;
400
+ }
401
+ set id(value) {
402
+ if (!this.isNew)
403
+ throw Error("Cannot change the id of a Tag once it has already been created.");
404
+ this._id = value;
405
+ }
406
+ get space() {
407
+ return this._space;
408
+ }
409
+ set space(value) {
410
+ var _a;
411
+ if (value !== this._space) {
412
+ const idChanged = (value == null ? void 0 : value.id) != ((_a = this._space) == null ? void 0 : _a.id);
413
+ this._space = value, this.isClean && idChanged && this.dirty();
414
+ }
415
+ }
416
+ in(space) {
417
+ return this.space = space, this;
418
+ }
419
+ get name() {
420
+ return this._name;
421
+ }
422
+ set name(value) {
423
+ value !== this._name && (this._name = value, this.isClean && this.dirty());
424
+ }
425
+ getQualifiedName(contextSpaceId) {
426
+ var _a;
427
+ return contextSpaceId == ((_a = this.space) == null ? void 0 : _a.id) ? this.name : `${this.space.name}.${this.name}`;
428
+ }
429
+ get color() {
430
+ return this._color;
431
+ }
432
+ set color(value) {
433
+ value !== this._color && (this._color = value, this.isClean && this.dirty());
434
+ }
435
+ get isPublic() {
436
+ return this._isPublic;
437
+ }
438
+ set isPublic(value) {
439
+ if (!this.isNew && this.isPublic && !value)
440
+ throw Error("Cannot change a tag from public to private once its already been saved.");
441
+ value !== this._isPublic && (this._isPublic = value, this.isClean && this.dirty());
442
+ }
443
+ asPublic() {
444
+ return this.isPublic = !0, this;
445
+ }
446
+ asPrivate() {
447
+ return this.isPublic = !1, this;
448
+ }
449
+ duplicate() {
450
+ const output = new Tag(this.name);
451
+ return output.id = this.id, output.state = this.state, output.color = this.color, output.space = this.space, output.isPublic = this.isPublic, output;
452
+ }
453
+ validate(throwError = !1) {
454
+ let output = null;
455
+ 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)
456
+ throw Error(output);
457
+ return output == null;
458
+ }
459
+ toJSON() {
460
+ var _a;
461
+ return {
462
+ state: this.state,
463
+ id: this.id,
464
+ name: this.name,
465
+ spaceId: (_a = this.space) == null ? void 0 : _a.id,
466
+ color: this.color,
467
+ isPublic: this.isPublic
468
+ };
469
+ }
470
+ }
485
471
  class Note extends ModelWithState {
486
472
  constructor(text, ownTag) {
487
473
  super();
@@ -578,8 +564,8 @@ class Note extends ModelWithState {
578
564
  throw Error("Cannot add an attribute marked as deleted to a note");
579
565
  if (attr.isNew)
580
566
  throw Error("Cannot add an attribute that hasn't yet been saved to a note");
581
- const na = new NoteAttr(attr, null, value);
582
- return this._attrs.push(na), this;
567
+ let na = this.attrs.find((x) => x.attr.id == attr.id);
568
+ return na ? (na.isDeleted && na.dirty(), value != null && (na.value = value), this) : (na = new NoteAttr(attr, null, value), this._attrs.push(na), this);
583
569
  }
584
570
  removeAttr(attr) {
585
571
  const na = this._attrs.find((x) => x.attr.id == attr.id);
@@ -662,6 +648,10 @@ class NotuHttpClient {
662
648
  set token(value) {
663
649
  this._token = value;
664
650
  }
651
+ _validateResponseStatus(response) {
652
+ if (response.status >= 400 && response.status < 600)
653
+ throw Error(response.statusText);
654
+ }
665
655
  async login(username, password) {
666
656
  const response = await this._fetch(
667
657
  this.url + "/login",
@@ -670,87 +660,98 @@ class NotuHttpClient {
670
660
  body: JSON.stringify({ username, password })
671
661
  }
672
662
  );
673
- if (response.body != null) {
663
+ if (this._validateResponseStatus(response), response.body != null) {
674
664
  const result = await response.json();
675
- return result.token && (this._token = result.token), result;
665
+ return result && (this._token = result), result;
676
666
  }
677
- return { token: null, error: "Unknown error occurred on the server" };
667
+ throw Error("Unknown error occurred on the server");
678
668
  }
679
669
  async setup() {
680
- await (await this._fetch(
670
+ const response = await this._fetch(
681
671
  this.url + "/setup",
682
672
  {
683
673
  method: "POST",
684
674
  headers: { Authorization: "Bearer " + this.token }
685
675
  }
686
- )).json();
676
+ );
677
+ this._validateResponseStatus(response), await response.json();
687
678
  }
688
679
  async saveSpace(space) {
689
- return await (await this._fetch(
680
+ const response = await this._fetch(
690
681
  this.url + "/spaces",
691
682
  {
692
683
  method: "POST",
693
684
  body: JSON.stringify(space),
694
685
  headers: { Authorization: "Bearer " + this.token }
695
686
  }
696
- )).json();
687
+ );
688
+ return this._validateResponseStatus(response), await response.json();
697
689
  }
698
690
  async saveAttr(attr) {
699
- return await (await this._fetch(
691
+ const response = await this._fetch(
700
692
  this.url + "/attrs",
701
693
  {
702
694
  method: "POST",
703
695
  body: JSON.stringify(attr),
704
696
  headers: { Authorization: "Bearer " + this.token }
705
697
  }
706
- )).json();
698
+ );
699
+ return this._validateResponseStatus(response), await response.json();
707
700
  }
708
701
  async getNotes(query, space) {
709
- return space instanceof Space && (space = space.id), await (await this._fetch(
702
+ space instanceof Space && (space = space.id);
703
+ const response = await this._fetch(
710
704
  this.url + `/notes?space=${space}&query=${encodeURIComponent(query)}`,
711
705
  {
712
706
  method: "GET",
713
707
  headers: { Authorization: "Bearer " + this.token }
714
708
  }
715
- )).json();
709
+ );
710
+ return this._validateResponseStatus(response), await response.json();
716
711
  }
717
712
  async getNoteCount(query, space) {
718
- return space instanceof Space && (space = space.id), (await (await this._fetch(
713
+ space instanceof Space && (space = space.id);
714
+ const response = await this._fetch(
719
715
  this.url + `/notes?count=true&space=${space}&query=${encodeURIComponent(query)}`,
720
716
  {
721
717
  method: "GET",
722
718
  headers: { Authorization: "Bearer " + this.token }
723
719
  }
724
- )).json()).count;
720
+ );
721
+ return this._validateResponseStatus(response), (await response.json()).count;
725
722
  }
726
723
  async getRelatedNotes(tag) {
727
- return tag instanceof Tag && (tag = tag.id), tag instanceof Note && (tag = tag.id), await (await this._fetch(
724
+ tag instanceof Tag && (tag = tag.id), tag instanceof Note && (tag = tag.id);
725
+ const response = await this._fetch(
728
726
  this.url + `/notes?tag=${tag}`,
729
727
  {
730
728
  method: "GET",
731
729
  headers: { Authorization: "Bearer " + this.token }
732
730
  }
733
- )).json();
731
+ );
732
+ return this._validateResponseStatus(response), await response.json();
734
733
  }
735
734
  async saveNotes(notes) {
736
- return await (await this._fetch(
735
+ const response = await this._fetch(
737
736
  this.url + "/notes",
738
737
  {
739
738
  method: "POST",
740
739
  body: JSON.stringify(notes),
741
740
  headers: { Authorization: "Bearer " + this.token }
742
741
  }
743
- )).json();
742
+ );
743
+ return this._validateResponseStatus(response), await response.json();
744
744
  }
745
745
  async customJob(name, data) {
746
- return await (await this._fetch(
746
+ const response = await this._fetch(
747
747
  this.url + "/customjob",
748
748
  {
749
749
  method: "POST",
750
750
  body: JSON.stringify({ name, data }),
751
751
  headers: { Authorization: "Bearer " + this.token }
752
752
  }
753
- )).json();
753
+ );
754
+ return this._validateResponseStatus(response), await response.json();
754
755
  }
755
756
  }
756
757
  class NotuCache {
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 Tag extends ModelWithState{constructor(name=""){super();__publicField(this,"_id",0);__publicField(this,"_space",null);__publicField(this,"_name","");__publicField(this,"_color",null);__publicField(this,"_isPublic",!1);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){if(!this.isNew&&this.isPublic&&!value)throw Error("Cannot change a tag from public to private once its already been saved.");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}static getColorFromInt(color){return color==null?null:"#"+color.toString(16).toUpperCase().padStart(6,"0")}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 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);__publicField(this,"_color",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}get color(){return this._color}set color(value){value!==this._color&&(this._color=value,this.isClean&&this.dirty())}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.color=this.color,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.":this.color&&!/^#?[A-z0-9]{6}$/.test(this.color)&&(output="Tag color is invalid, must be a 6 character hexadecimal."):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}}getColorInt(){let hex=this.color;return hex?(hex.startsWith("#")&&(hex=hex.substring(1)),parseInt(hex,16)):null}static getColorFromInt(color){return Tag.getColorFromInt(color)}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,color:this.color}}}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)}async setup(){return await this.client.setup()}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=null){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 getRelatedNotes(tag){return(await this.client.getRelatedNotes(tag)).map(n=>this.cache.noteFromJSON(n))}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 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.isText&&typeof newVal!="string"?newVal=String(newVal):this.attr.isDate&&!(newVal instanceof Date)?newVal=new Date(newVal):this.attr.isNumber&&typeof newVal!="number"?newVal=Number(newVal):this.attr.isBoolean&&typeof newVal!="boolean"&&(newVal=!!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(){var _a;return{state:this.state,attrId:this.attr.id,tagId:(_a=this.tag)==null?void 0:_a.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.map(x=>x.toJSON())}}}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 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?this.tags.find(x=>x.tag===tag):(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(){var _a;return{state:this.state,id:this.id,date:this.date,text:this.text,spaceId:this.space.id,ownTag:(_a=this.ownTag)==null?void 0:_a.toJSON(),tags:this._tags.map(x=>x.toJSON()),attrs:this._attrs.map(x=>x.toJSON())}}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 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 getRelatedNotes(tag){return tag instanceof Tag&&(tag=tag.id),tag instanceof Note&&(tag=tag.id),await(await this._fetch(this.url+`/notes?tag=${tag}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}})).json()}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 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 ownTag=!noteData.ownTag||noteData.ownTag.state=="CLEAN"?this.getTag(noteData.id):this.tagFromJSON(noteData.ownTag),note=new Note(noteData.text,ownTag).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=null){return space==null?Array.from(this._attrs.values()):(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
+ (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);__publicField(this,"_color",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}get color(){return this._color}set color(value){value!==this._color&&(this._color=value,this.isClean&&this.dirty())}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.color=this.color,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.":this.color&&!/^#?[A-z0-9]{6}$/.test(this.color)&&(output="Tag color is invalid, must be a 6 character hexadecimal."):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,color:this.color}}}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)}async setup(){return await this.client.setup()}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=null){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 getRelatedNotes(tag){return(await this.client.getRelatedNotes(tag)).map(n=>this.cache.noteFromJSON(n))}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 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.isText&&typeof newVal!="string"?newVal=String(newVal):this.attr.isDate&&!(newVal instanceof Date)?newVal=new Date(newVal):this.attr.isNumber&&typeof newVal!="number"?newVal=Number(newVal):this.attr.isBoolean&&typeof newVal!="boolean"&&(newVal=!!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(){var _a;return{state:this.state,attrId:this.attr.id,tagId:(_a=this.tag)==null?void 0:_a.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.map(x=>x.toJSON())}}}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 Tag extends ModelWithState{constructor(name=""){super();__publicField(this,"_id",0);__publicField(this,"_space",null);__publicField(this,"_name","");__publicField(this,"_color",null);__publicField(this,"_isPublic",!1);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){if(!this.isNew&&this.isPublic&&!value)throw Error("Cannot change a tag from public to private once its already been saved.");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}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?this.tags.find(x=>x.tag===tag):(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");let na=this.attrs.find(x=>x.attr.id==attr.id);return na?(na.isDeleted&&na.dirty(),value!=null&&(na.value=value),this):(na=new NoteAttr(attr,null,value),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(){var _a;return{state:this.state,id:this.id,date:this.date,text:this.text,spaceId:this.space.id,ownTag:(_a=this.ownTag)==null?void 0:_a.toJSON(),tags:this._tags.map(x=>x.toJSON()),attrs:this._attrs.map(x=>x.toJSON())}}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 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}_validateResponseStatus(response){if(response.status>=400&&response.status<600)throw Error(response.statusText)}async login(username,password){const response=await this._fetch(this.url+"/login",{method:"POST",body:JSON.stringify({username,password})});if(this._validateResponseStatus(response),response.body!=null){const result=await response.json();return result&&(this._token=result),result}throw Error("Unknown error occurred on the server")}async setup(){const response=await this._fetch(this.url+"/setup",{method:"POST",headers:{Authorization:"Bearer "+this.token}});this._validateResponseStatus(response),await response.json()}async saveSpace(space){const response=await this._fetch(this.url+"/spaces",{method:"POST",body:JSON.stringify(space),headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),await response.json()}async saveAttr(attr){const response=await this._fetch(this.url+"/attrs",{method:"POST",body:JSON.stringify(attr),headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),await response.json()}async getNotes(query,space){space instanceof Space&&(space=space.id);const response=await this._fetch(this.url+`/notes?space=${space}&query=${encodeURIComponent(query)}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),await response.json()}async getNoteCount(query,space){space instanceof Space&&(space=space.id);const response=await this._fetch(this.url+`/notes?count=true&space=${space}&query=${encodeURIComponent(query)}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),(await response.json()).count}async getRelatedNotes(tag){tag instanceof Tag&&(tag=tag.id),tag instanceof Note&&(tag=tag.id);const response=await this._fetch(this.url+`/notes?tag=${tag}`,{method:"GET",headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),await response.json()}async saveNotes(notes){const response=await this._fetch(this.url+"/notes",{method:"POST",body:JSON.stringify(notes),headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),await response.json()}async customJob(name,data){const response=await this._fetch(this.url+"/customjob",{method:"POST",body:JSON.stringify({name,data}),headers:{Authorization:"Bearer "+this.token}});return this._validateResponseStatus(response),await response.json()}}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 ownTag=!noteData.ownTag||noteData.ownTag.state=="CLEAN"?this.getTag(noteData.id):this.tagFromJSON(noteData.ownTag),note=new Note(noteData.text,ownTag).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=null){return space==null?Array.from(this._attrs.values()):(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"})});
@@ -37,8 +37,6 @@ export default class Attr extends ModelWithState<Attr> {
37
37
  duplicate(): Attr;
38
38
  validate(throwError?: boolean): boolean;
39
39
  get defaultValue(): any;
40
- getColorInt(): number;
41
- static getColorFromInt(color: number): string;
42
40
  toJSON(): {
43
41
  state: "NEW" | "CLEAN" | "DIRTY" | "DELETED";
44
42
  id: number;
@@ -23,8 +23,6 @@ export default class Tag extends ModelWithState<Tag> {
23
23
  constructor(name?: string);
24
24
  duplicate(): Tag;
25
25
  validate(throwError?: boolean): boolean;
26
- getColorInt(): number;
27
- static getColorFromInt(color: number): string;
28
26
  toJSON(): {
29
27
  state: "NEW" | "CLEAN" | "DIRTY" | "DELETED";
30
28
  id: number;
@@ -2,12 +2,8 @@ import Attr from '../models/Attr';
2
2
  import Note from '../models/Note';
3
3
  import Space from '../models/Space';
4
4
  import Tag from '../models/Tag';
5
- export interface NotuLoginResult {
6
- error: string;
7
- token: string;
8
- }
9
5
  export interface NotuClient {
10
- login(username: string, password: string): Promise<NotuLoginResult>;
6
+ login(username: string, password: string): Promise<string>;
11
7
  setup(): Promise<void>;
12
8
  saveSpace(space: Space): Promise<any>;
13
9
  saveAttr(attr: Attr): Promise<any>;
@@ -24,8 +20,9 @@ export default class NotuHttpClient implements NotuClient {
24
20
  get token(): string;
25
21
  set token(value: string);
26
22
  private _fetch;
23
+ private _validateResponseStatus;
27
24
  constructor(url: string, fetchMethod?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>);
28
- login(username: string, password: string): Promise<NotuLoginResult>;
25
+ login(username: string, password: string): Promise<string>;
29
26
  setup(): Promise<void>;
30
27
  saveSpace(space: Space): Promise<any>;
31
28
  saveAttr(attr: Attr): Promise<any>;
@@ -1,5 +1,5 @@
1
1
  import { Attr, Note, Space, Tag } from '..';
2
- import { NotuClient, NotuLoginResult } from './HttpClient';
2
+ import { NotuClient } from './HttpClient';
3
3
  import { NotuCache } from './NotuCache';
4
4
  export declare class Notu {
5
5
  private _client;
@@ -7,7 +7,7 @@ export declare class Notu {
7
7
  private _cache;
8
8
  get cache(): NotuCache;
9
9
  constructor(client: NotuClient, cache: NotuCache);
10
- login(username: string, password: string): Promise<NotuLoginResult>;
10
+ login(username: string, password: string): Promise<string>;
11
11
  setup(): Promise<void>;
12
12
  getSpaces(): Array<Space>;
13
13
  getSpace(id: number): Space;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notu",
3
- "version": "0.7.11",
3
+ "version": "0.8.0",
4
4
  "main": "dist/notu.mjs",
5
5
  "unpkg": "dist/notu.mjs",
6
6
  "types": "dist/types/index.d.ts",