|
| 1 | +import{DataSourcePlugin,PluginContext}from"lowcoder-sdk/dataSource"; |
| 2 | +import_from"lodash"; |
| 3 | +import{ServiceError}from"../../common/error"; |
| 4 | +import{ConfigToType}from"lowcoder-sdk/dataSource"; |
| 5 | + |
| 6 | +constdataSourceConfig={ |
| 7 | +type:"dataSource", |
| 8 | +params:[ |
| 9 | +{ |
| 10 | +key:"url", |
| 11 | +type:"textInput", |
| 12 | +label:"URL", |
| 13 | +tooltip:"e.g. https://db-company.turso.io", |
| 14 | +placeholder:"<Your Turso URL>", |
| 15 | +rules:[{required:true,message:"Please add the URL to your database"}] |
| 16 | +}, |
| 17 | +{ |
| 18 | +key:"token", |
| 19 | +label:"Token", |
| 20 | +type:"password", |
| 21 | +placeholder:"<Your Token>", |
| 22 | +rules:[{required:true,message:"Please input the token"}], |
| 23 | +} |
| 24 | +], |
| 25 | +}asconst; |
| 26 | +typeDataSourceDataType=ConfigToType<typeofdataSourceConfig>; |
| 27 | + |
| 28 | +constqueryConfig={ |
| 29 | +type:"query", |
| 30 | +label:"Action", |
| 31 | +actions:[ |
| 32 | +{ |
| 33 | +actionName:"Query", |
| 34 | +label:"Query", |
| 35 | +params:[ |
| 36 | +{ |
| 37 | +label:"Query String", |
| 38 | +key:"queryString", |
| 39 | +type:"sqlInput", |
| 40 | +}, |
| 41 | +{ |
| 42 | +label:"Include raw", |
| 43 | +key:"includeRaw", |
| 44 | +tooltip:"Include raw information in the response", |
| 45 | +type:"switch" |
| 46 | +} |
| 47 | +], |
| 48 | +}, |
| 49 | +], |
| 50 | +}asconst; |
| 51 | +typeActionDataType=ConfigToType<typeofqueryConfig>; |
| 52 | + |
| 53 | +// from https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md#hrana-over-http |
| 54 | +typeRow={ |
| 55 | +type:"integer"|"text"|"float"|"blob"|"null"; |
| 56 | +value?:string; |
| 57 | +}; |
| 58 | +typeCol={ |
| 59 | +name:string; |
| 60 | +decltype:string; |
| 61 | +} |
| 62 | +typeResultSet={ |
| 63 | +cols:Col[]; |
| 64 | +rows:Row[][]; |
| 65 | +affected_row_count:number; |
| 66 | +last_insert_rowid:number|null; |
| 67 | +replication_index:string; |
| 68 | +rows_read:number; |
| 69 | +rows_written:number; |
| 70 | +query_duration_ms:number; |
| 71 | +} |
| 72 | +typeResult={ |
| 73 | +type:"ok" |
| 74 | +response:{ |
| 75 | +type:"execute"|"close"|string; |
| 76 | +result:ResultSet |
| 77 | +} |
| 78 | +}|{ |
| 79 | +type:"error"; |
| 80 | +error:any |
| 81 | +}; |
| 82 | + |
| 83 | +typeResponse={ |
| 84 | +baton:string|null; |
| 85 | +base_url:string|null; |
| 86 | +results:Result[]; |
| 87 | +} |
| 88 | + |
| 89 | +consttursoPlugin:DataSourcePlugin<ActionDataType,DataSourceDataType>={ |
| 90 | +id:"turso", |
| 91 | +name:"Turso", |
| 92 | +category:"database", |
| 93 | +icon:"turso.svg", |
| 94 | + dataSourceConfig, |
| 95 | + queryConfig, |
| 96 | +run:asyncfunction(actionData,dataSourceConfig,ctx:PluginContext):Promise<any>{ |
| 97 | +const{url:_url, token}=dataSourceConfig; |
| 98 | +consturl=_url.replace("libsql://","https://"); |
| 99 | +const{ queryString, includeRaw}=actionData; |
| 100 | + |
| 101 | +constresult=awaitfetch(`${url}/v2/pipeline`,{ |
| 102 | +method:"POST", |
| 103 | +headers:{ |
| 104 | +"Content-Type":"application/json", |
| 105 | +"Authorization":`Bearer${token}` |
| 106 | +}, |
| 107 | +body:JSON.stringify({ |
| 108 | +requests:[ |
| 109 | +{type:"execute",stmt:{sql:queryString}}, |
| 110 | +{type:"close"} |
| 111 | +] |
| 112 | +}) |
| 113 | +}) |
| 114 | + |
| 115 | +if(!result.ok){ |
| 116 | +thrownewServiceError(`Failed to execute query. Endpoint returned${result.status}:${result.statusText}.`); |
| 117 | +} |
| 118 | + |
| 119 | +constdata=awaitresult.json()asResponse; |
| 120 | +constparsed=parseResult(data.results[0]); |
| 121 | + |
| 122 | +returnincludeRaw ?parsed :parsed?.values; |
| 123 | +}, |
| 124 | +}; |
| 125 | + |
| 126 | + |
| 127 | +functionparseValue(val:Col&Row):{[key:string]:any}{ |
| 128 | +constname=val.name; |
| 129 | +letvalue:any=val.value; |
| 130 | + |
| 131 | +switch(true){ |
| 132 | +caseval.type==="integer"&&val.decltype==="boolean": |
| 133 | +value=Boolean(Number.parseInt(value)); |
| 134 | +break; |
| 135 | +caseval.type==="integer": |
| 136 | +value=Number.parseInt(value); |
| 137 | +break; |
| 138 | +caseval.type==="float": |
| 139 | +value=Number.parseFloat(value); |
| 140 | +break; |
| 141 | +case["datetime","date"].includes(val.decltype)&&val.type==="text"&&!!value: |
| 142 | +value=newDate(value); |
| 143 | +break; |
| 144 | +caseval.type==="null": |
| 145 | +value=null; |
| 146 | +break; |
| 147 | +} |
| 148 | + |
| 149 | +return{ |
| 150 | +[name]:value, |
| 151 | +}; |
| 152 | +} |
| 153 | + |
| 154 | +functionparseResult( |
| 155 | +result:Result |
| 156 | +):{raw:ResultSet;values:Record<string,any>}|undefined{ |
| 157 | +if(result.type==="error"){ |
| 158 | +thrownewServiceError(`Cannot parse result, received error:${result.error}`); |
| 159 | +} |
| 160 | + |
| 161 | +constres=result.response.result; |
| 162 | +if(!res)return; |
| 163 | + |
| 164 | +constcombined:(Col&Row)[][]=res.rows.map((row)=> |
| 165 | +row.map((col,i)=>({ |
| 166 | + ...col, |
| 167 | + ...res.cols[i], |
| 168 | +})) |
| 169 | +); |
| 170 | + |
| 171 | +constvalues:any[]=[]; |
| 172 | +for(letrowofcombined){ |
| 173 | +values.push( |
| 174 | +row.reduce((acc,curr)=>({ ...acc, ...parseValue(curr)}),{}) |
| 175 | +); |
| 176 | +} |
| 177 | + |
| 178 | +return{ |
| 179 | + values, |
| 180 | +raw:res, |
| 181 | +}; |
| 182 | +} |
| 183 | + |
| 184 | +exportdefaulttursoPlugin; |