@decaf-ts/for-nano
Version:
decaf-ts persistence adapter for CouchDB via nano
3 lines (2 loc) • 8.64 kB
JavaScript
import{OperationKeys as e,InternalError as t,ConflictError as r,onCreate as s,onCreateUpdate as a}from"@decaf-ts/db-decorators";import{CouchDBRepository as o,CouchDBKeys as i,CouchDBAdapter as n,wrapDocumentScope as c,generateIndexes as h,generateViews as d}from"@decaf-ts/for-couchdb";import l from"nano";import{Model as u}from"@decaf-ts/decorator-validation";import{Dispatch as p,Adapter as w,PersistenceKeys as f,UnsupportedError as g}from"@decaf-ts/core";import{Decoration as y,propMetadata as b,Metadata as m}from"@decaf-ts/decoration";const v="nano";class E extends o{constructor(e,t,r=!1){super(e,t,r)}override(e){return super.override(e).for(e)}}class $ extends p{constructor(e=5e3){super(),this.timeout=e,this.attemptCounter=0,this.active=!1}close(...e){return this.active=!1,this.attemptCounter=0,this.disposeChangeFeed(),super.close(...e)}async changeHandler(t,r,s,a){const o=a||this.adapter.context(e.UPDATE,{},u),n=o.logger.for(this.changeHandler);if(t)return n.error("Error in change request: "+t);try{r="string"==typeof r?r.split("\n").filter(e=>!!e).map(e=>JSON.parse(e)):r}catch(e){return n.error("Error parsing couchdb change feed: "+e)}const c=r.length;if(c>0){n.debug(`Received ${c} changes. processing...`);const t=r.map((t,r)=>{if(r===c-1)return void(this.observerLastUpdate===t.last_seq&&n.error(`Invalid last update check: ${this.observerLastUpdate} !== ${t.last_seq}`));const s=t,[a,o]=s.id.split(i.SEPARATOR);return{table:a,id:o,operation:s.deleted?e.DELETE:"1"===s.changes[s.changes.length-1].rev.split("-")[0]?e.CREATE:e.UPDATE,step:s.changes[s.changes.length-1].rev}}).reduce((e,t)=>{if(!t)return e;const{table:r,id:s,operation:a,step:o}=t;return e[r]||(e[r]={}),e[r][a]||(e[r][a]={ids:new Set,step:o}),e[r][a].ids.add(s),e[r][a].step=o,e},{});for(const e of Object.keys(t))for(const r of Object.keys(t[e]))try{await this.updateObservers(e,r,[...t[e][r].ids.values()],o),this.observerLastUpdate=t[e][r].step,n.verbose(`Observer refresh dispatched by ${r} for ${e}`),n.debug("pks: "+Array.from(t[e][r].ids.values()))}catch(t){n.error(`Failed to dispatch observer refresh for ${e}, op ${r}: ${t}`)}}}async initialize(){const e=this.log.for(this.initialize).for(r);async function r(){if(!this.adapter)throw new t("No adapter/native observed for dispatch");if(!this.active)try{const e=this.adapter.client.changes({feed:"continuous",include_docs:!1,since:this.observerLastUpdate||"now",timeout:this.timeout},this.changeHandler.bind(this));this.changeFeed=e}catch(t){if(++this.attemptCounter>3)return e.error("Failed to subscribe to couchdb changes: "+t);if(e.info(`Failed to subscribe to couchdb changes: ${t}. Retrying in 5 seconds...`),!this.active)return;return await new Promise(e=>setTimeout(e,this.timeout)),r.call(this)}}this.active=!0,r.call(this).then(()=>{this.log.info("Subscribed to couchdb changes")}).catch(e=>{throw new t("Failed to subscribe to couchdb changes: "+e)})}disposeChangeFeed(){this.changeFeed&&(this.changeFeed.cancel?.(),this.changeFeed.close?.(),this.changeFeed.destroy?.(),this.changeFeed.removeAllListeners?.(),this.changeFeed=void 0)}}async function _(e,t,r,s){try{const t=e.get("user");s[r]=t.name||t}catch(e){throw new g("No User found in context. Please provide a user in the context")}}class A extends n{constructor(e,t){super(e,v,t)}async shutdown(...e){const{ctxArgs:t}=(await this.logCtx(e,f.SHUTDOWN,!0)).for(this.shutdown);await super.shutdown(...t),this._client&&(A.closeConnection(this._client[i.NATIVE]),this._client=void 0)}getClient(){const{couchUser:e,couchPassword:t,host:r,dbName:s,protocol:a}=this.config,o=A.connect(e,t,r,a);return c(o,s,e,t)}async flags(e,t,r,...s){return super.flags(e,t,Object.assign({user:{name:r.user?.name||this.config.couchUser}},r,...s))}Dispatch(){return new $}repository(){return E}async index(...e){const t=h(e);for(const e of t){const t=await this.client.createIndex(e),{result:s,id:a,name:o}=t;if("existing"===s)throw new r(`Index for table ${o} with id ${a}`)}const s=d(e);for(const e of s)try{await this.client.insert(e)}catch(t){if(409!==t?.statusCode&&"conflict"!==t?.error)throw t;{const t=await this.client.get(e._id);await this.client.insert({...e,_rev:t._rev})}}}async create(e,r,s,...a){let o;try{o=await this.client.insert(s)}catch(e){throw this.parseError(e)}if(!o.ok)throw new t(`Failed to insert doc id: ${r} in table ${e}`);return this.assignMetadata(s,o.rev)}async createAll(e,r,s,...a){let o;try{o=await this.client.bulk({docs:s})}catch(e){throw this.parseError(e)}if(!o.every(e=>!e.error)){const e=o.reduce((e,t,r)=>(t.error&&e.push(`el ${r}: ${t.error}${t.reason?" - "+t.reason:""}`),e),[]);throw new t(e.join("\n"))}return this.assignMultipleMetadata(s,o.map(e=>e.rev))}async read(e,t,...r){const s=this.generateId(u.tableName(e),t);let a;try{a=await this.client.get(s)}catch(e){throw this.parseError(e)}return this.assignMetadata(a,a._rev)}async readAll(e,r,...s){const a=u.tableName(e);return(await this.client.fetch({keys:r.map(e=>this.generateId(a,e))},{})).rows.map(e=>{if(e.error)throw new t(e.error);if(e.doc){const t=Object.assign({},e.doc);return this.assignMetadata(t,e.doc[i.REV])}throw new t("Should be impossible")})}async update(e,r,s,...a){let o;try{o=await this.client.insert(s)}catch(e){throw this.parseError(e)}if(!o.ok)throw new t(`Failed to update doc id: ${r} in table ${e}`);return this.assignMetadata(s,o.rev)}async updateAll(e,r,s,...a){let o;try{o=await this.client.bulk({docs:s})}catch(e){throw this.parseError(e)}if(!o.every(e=>!e.error)){const e=o.reduce((e,t,r)=>(t.error&&e.push(`el ${r}: ${t.error}${t.reason?" - "+t.reason:""}`),e),[]);throw new t(e.join("\n"))}return this.assignMultipleMetadata(s,o.map(e=>e.rev))}async delete(e,t,...r){const s=this.generateId(u.tableName(e),t);let a;try{a=await this.client.get(s),await this.client.destroy(s,a._rev)}catch(e){throw this.parseError(e)}return this.assignMetadata(a,a._rev)}async deleteAll(e,r,...s){const{log:a}=this.logCtx(s,this.deleteAll),o=u.tableName(e),n=r.map(e=>this.generateId(o,e)),c=(await this.client.fetch({keys:n},{include_docs:!0})).rows.map((e,r)=>{if(e.error)throw new t(e.error);if(!e.doc)throw new t("Unable to load document "+(e.id??n[r]));return Object.assign({},e.doc)});return(await this.client.bulk({docs:c.map(e=>({_id:e[i.ID],_rev:e[i.REV],[i.DELETED]:!0}))})).forEach((e,t)=>{e.error&&a.error(`Failed to delete document ${c[t][i.ID]}: ${e.error} ${e.reason?"- "+e.reason:""}`)}),c.map(e=>this.assignMetadata({...e},e[i.REV]))}async raw(e,t=!0,...r){try{const s=await this.client.find(e);if(s.warning){const{log:t}=this.logCtx(r,this.raw),a={warning:s.warning,selector:e?.selector,sort:e?.sort,use_index:e?.use_index,fields:e?.fields,limit:e?.limit,skip:e?.skip};t.warn(`${s.warning} query=${JSON.stringify(a,null,2)}`)}return t?s.docs:s}catch(e){throw this.parseError(e)}}async view(e,t,r,...s){try{return await this.client.view(e,t,r)}catch(e){throw this.parseError(e)}}static connect(e,t,r="localhost:5984",s="http",a){const o=a?{agent:a}:void 0,i=l({url:`${s}://${e}:${t}@${r}`,...o?{requestDefaults:o}:{}});return i&&a&&(i._decafAgent=a),i}static async createDatabase(e,t){let r;try{r=await e.db.create(t)}catch(e){throw n.parseError(e)}const{ok:s,error:a,reason:o}=r;if(!s)throw n.parseError(a,o)}static async deleteDatabase(e,r){let s;try{s=await e.db.destroy(r)}catch(e){throw n.parseError(e)}const{ok:a}=s;if(!a)throw new t("Failed to delete database with name "+r)}static closeConnection(e){if(!e)return;const t=e?._decafAgent;t&&"function"==typeof t.destroy&&t.destroy()}static async createUser(e,r,s,a,o=["reader","writer"]){const i=e.db.use("_users"),c={_id:"org.couchdb.user:"+s,name:s,password:a,roles:o,type:"user"};try{const a=await i.insert(c),{ok:n}=a;if(!n)throw new t("Failed to create user "+s);if(!(await e.request({db:r,method:"put",path:"_security",body:{admins:{names:[s],roles:[]},members:{names:[s],roles:o}}})).ok)throw new t(`Failed to authorize user ${s} to db ${r}`)}catch(e){throw n.parseError(e)}}static async deleteUser(e,t,r){const s=e.db.use("_users"),a="org.couchdb.user:"+r;try{const e=await s.get(a);await s.destroy(a,e._rev)}catch(e){throw n.parseError(e)}}static decoration(){super.decoration(),y.flavouredAs(v).for(f.CREATED_BY).define(s(_),b(f.CREATED_BY,{})).apply(),y.flavouredAs(v).for(f.UPDATED_BY).define(a(_),b(f.UPDATED_BY,{})).apply()}}w.setCurrent(v),A.decoration();const F="##VERSION##",C="##COMMIT##",D="##FULL_VERSION##",k="##PACKAGE##";m.registerLibrary(k,F);export{C as COMMIT,D as FULL_VERSION,A as NanoAdapter,v as NanoFlavour,E as NanoRepository,k as PACKAGE_NAME,F as VERSION,_ as createdByOnNanoCreateUpdate};
//# sourceMappingURL=for-nano.js.map