├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── pull_request.yaml │ └── release.yaml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .yarn ├── plugins │ └── @yarnpkg │ │ ├── plugin-constraints.cjs │ │ └── plugin-workspace-tools.cjs └── releases │ └── yarn-3.5.1.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── constraints.pro ├── package.json ├── packages ├── aws-s3 │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── brex │ ├── CHANGELOG.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── types │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ ├── transfer.ts │ │ │ └── vendor.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── gcp-gcs │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── layers.ts │ ├── test │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── gcp-logging │ ├── CHANGELOG.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── github │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── http-client │ ├── CHANGELOG.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── stripe │ ├── CHANGELOG.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── temporal-client │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ ├── batch.ts │ │ ├── client.ts │ │ ├── connection.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ └── types.ts │ ├── test │ │ ├── index.ts │ │ └── lib │ │ │ ├── Trigger.ts │ │ │ └── a-workflow.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── temporal-config │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── turbo.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build-and-test: 14 | name: Build and Test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repo 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Cache 🏎 23 | uses: actions/cache@v3 24 | with: 25 | path: .turbo 26 | key: ${{ runner.os }}-turbo-${{ github.sha }} 27 | restore-keys: | 28 | ${{ runner.os }}-turbo- 29 | 30 | - name: Setup Node.js 18.x 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: 18.x 34 | cache: 'yarn' 35 | 36 | - name: Install 📦 37 | run: yarn --immutable 38 | 39 | - name: Build 🏗 40 | run: yarn build 41 | 42 | - name: Test 🧪 43 | run: yarn test 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/release.yaml' 9 | - '.changeset/**' 10 | - 'yarn.lock' 11 | - 'packages/**' 12 | workflow_dispatch: 13 | 14 | concurrency: ${{ github.workflow }}-${{ github.ref }} 15 | 16 | jobs: 17 | release: 18 | name: Release 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout Repo 22 | uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Cache 🏎 27 | uses: actions/cache@v3 28 | with: 29 | path: .turbo 30 | key: ${{ runner.os }}-turbo-${{ github.sha }} 31 | restore-keys: | 32 | ${{ runner.os }}-turbo- 33 | 34 | - name: Setup Node.js 18.x 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: 18.x 38 | cache: 'yarn' 39 | 40 | - name: Auth with npm registry 41 | run: | 42 | echo 'npmScopes: 43 | effect-use: 44 | npmAlwaysAuth: true 45 | npmRegistryServer: "https://registry.npmjs.org" 46 | npmAuthToken: ${{ secrets.EFFECT_USE_NPM_TOKEN }}' >> ~/.yarnrc.yml 47 | 48 | - name: Install 📦 49 | run: yarn --immutable 50 | 51 | - name: Build 🏗 52 | run: yarn build 53 | 54 | - name: Test 🧪 55 | run: yarn test 56 | 57 | - name: Create Release Pull Request or Publish to npm 58 | id: changesets 59 | uses: changesets/action@v1 60 | with: 61 | version: yarn version 62 | publish: yarn release 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | NPM_TOKEN: ${{ secrets.EFFECT_USE_NPM_TOKEN }} 66 | NODE_AUTH_TOKEN: ${{ secrets.EFFECT_USE_NPM_TOKEN }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 2 | .pnp.* 3 | .yarn/* 4 | !.yarn/patches 5 | !.yarn/plugins 6 | !.yarn/releases 7 | !.yarn/sdks 8 | !.yarn/versions 9 | 10 | .idea 11 | .vscode 12 | node_modules 13 | **/dist/** 14 | .turbo 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .turbo -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-workspace-tools", 5 | factory: function (require) { 6 | var plugin=(()=>{var yr=Object.create;var we=Object.defineProperty;var _r=Object.getOwnPropertyDescriptor;var Er=Object.getOwnPropertyNames;var br=Object.getPrototypeOf,xr=Object.prototype.hasOwnProperty;var W=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,t)=>(typeof require<"u"?require:r)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var q=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),Cr=(e,r)=>{for(var t in r)we(e,t,{get:r[t],enumerable:!0})},Je=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of Er(r))!xr.call(e,s)&&s!==t&&we(e,s,{get:()=>r[s],enumerable:!(n=_r(r,s))||n.enumerable});return e};var Be=(e,r,t)=>(t=e!=null?yr(br(e)):{},Je(r||!e||!e.__esModule?we(t,"default",{value:e,enumerable:!0}):t,e)),wr=e=>Je(we({},"__esModule",{value:!0}),e);var ve=q(ee=>{"use strict";ee.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;ee.find=(e,r)=>e.nodes.find(t=>t.type===r);ee.exceedsLimit=(e,r,t=1,n)=>n===!1||!ee.isInteger(e)||!ee.isInteger(r)?!1:(Number(r)-Number(e))/Number(t)>=n;ee.escapeNode=(e,r=0,t)=>{let n=e.nodes[r];!n||(t&&n.type===t||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};ee.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0===0?(e.invalid=!0,!0):!1;ee.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0===0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;ee.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;ee.reduce=e=>e.reduce((r,t)=>(t.type==="text"&&r.push(t.value),t.type==="range"&&(t.type="text"),r),[]);ee.flatten=(...e)=>{let r=[],t=n=>{for(let s=0;s{"use strict";var tt=ve();rt.exports=(e,r={})=>{let t=(n,s={})=>{let i=r.escapeInvalid&&tt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c="";if(n.value)return(i||a)&&tt.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let p of n.nodes)c+=t(p);return c};return t(e)}});var st=q((Vn,nt)=>{"use strict";nt.exports=function(e){return typeof e=="number"?e-e===0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var ht=q((Jn,pt)=>{"use strict";var at=st(),le=(e,r,t)=>{if(at(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(r===void 0||e===r)return String(e);if(at(r)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n={relaxZeros:!0,...t};typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),i=String(n.shorthand),a=String(n.capture),c=String(n.wrap),p=e+":"+r+"="+s+i+a+c;if(le.cache.hasOwnProperty(p))return le.cache[p].result;let m=Math.min(e,r),h=Math.max(e,r);if(Math.abs(m-h)===1){let y=e+"|"+r;return n.capture?`(${y})`:n.wrap===!1?y:`(?:${y})`}let R=ft(e)||ft(r),f={min:e,max:r,a:m,b:h},$=[],_=[];if(R&&(f.isPadded=R,f.maxLen=String(f.max).length),m<0){let y=h<0?Math.abs(h):1;_=it(y,Math.abs(m),f,n),m=f.a=0}return h>=0&&($=it(m,h,f,n)),f.negatives=_,f.positives=$,f.result=Sr(_,$,n),n.capture===!0?f.result=`(${f.result})`:n.wrap!==!1&&$.length+_.length>1&&(f.result=`(?:${f.result})`),le.cache[p]=f,f.result};function Sr(e,r,t){let n=Pe(e,r,"-",!1,t)||[],s=Pe(r,e,"",!1,t)||[],i=Pe(e,r,"-?",!0,t)||[];return n.concat(i).concat(s).join("|")}function vr(e,r){let t=1,n=1,s=ut(e,t),i=new Set([r]);for(;e<=s&&s<=r;)i.add(s),t+=1,s=ut(e,t);for(s=ct(r+1,n)-1;e1&&c.count.pop(),c.count.push(h.count[0]),c.string=c.pattern+lt(c.count),a=m+1;continue}t.isPadded&&(R=Lr(m,t,n)),h.string=R+h.pattern+lt(h.count),i.push(h),a=m+1,c=h}return i}function Pe(e,r,t,n,s){let i=[];for(let a of e){let{string:c}=a;!n&&!ot(r,"string",c)&&i.push(t+c),n&&ot(r,"string",c)&&i.push(t+c)}return i}function $r(e,r){let t=[];for(let n=0;nr?1:r>e?-1:0}function ot(e,r,t){return e.some(n=>n[r]===t)}function ut(e,r){return Number(String(e).slice(0,-r)+"9".repeat(r))}function ct(e,r){return e-e%Math.pow(10,r)}function lt(e){let[r=0,t=""]=e;return t||r>1?`{${r+(t?","+t:"")}}`:""}function kr(e,r,t){return`[${e}${r-e===1?"":"-"}${r}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Lr(e,r,t){if(!r.isPadded)return e;let n=Math.abs(r.maxLen-String(e).length),s=t.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}le.cache={};le.clearCache=()=>le.cache={};pt.exports=le});var Ue=q((es,Et)=>{"use strict";var Or=W("util"),At=ht(),dt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Nr=e=>r=>e===!0?Number(r):String(r),Me=e=>typeof e=="number"||typeof e=="string"&&e!=="",Ae=e=>Number.isInteger(+e),De=e=>{let r=`${e}`,t=-1;if(r[0]==="-"&&(r=r.slice(1)),r==="0")return!1;for(;r[++t]==="0";);return t>0},Ir=(e,r,t)=>typeof e=="string"||typeof r=="string"?!0:t.stringify===!0,Br=(e,r,t)=>{if(r>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?r-1:r,"0")}return t===!1?String(e):e},gt=(e,r)=>{let t=e[0]==="-"?"-":"";for(t&&(e=e.slice(1),r--);e.length{e.negatives.sort((a,c)=>ac?1:0),e.positives.sort((a,c)=>ac?1:0);let t=r.capture?"":"?:",n="",s="",i;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${t}${e.negatives.join("|")})`),n&&s?i=`${n}|${s}`:i=n||s,r.wrap?`(${t}${i})`:i},mt=(e,r,t,n)=>{if(t)return At(e,r,{wrap:!1,...n});let s=String.fromCharCode(e);if(e===r)return s;let i=String.fromCharCode(r);return`[${s}-${i}]`},Rt=(e,r,t)=>{if(Array.isArray(e)){let n=t.wrap===!0,s=t.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return At(e,r,t)},yt=(...e)=>new RangeError("Invalid range arguments: "+Or.inspect(...e)),_t=(e,r,t)=>{if(t.strictRanges===!0)throw yt([e,r]);return[]},Mr=(e,r)=>{if(r.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Dr=(e,r,t=1,n={})=>{let s=Number(e),i=Number(r);if(!Number.isInteger(s)||!Number.isInteger(i)){if(n.strictRanges===!0)throw yt([e,r]);return[]}s===0&&(s=0),i===0&&(i=0);let a=s>i,c=String(e),p=String(r),m=String(t);t=Math.max(Math.abs(t),1);let h=De(c)||De(p)||De(m),R=h?Math.max(c.length,p.length,m.length):0,f=h===!1&&Ir(e,r,n)===!1,$=n.transform||Nr(f);if(n.toRegex&&t===1)return mt(gt(e,R),gt(r,R),!0,n);let _={negatives:[],positives:[]},y=T=>_[T<0?"negatives":"positives"].push(Math.abs(T)),E=[],S=0;for(;a?s>=i:s<=i;)n.toRegex===!0&&t>1?y(s):E.push(Br($(s,S),R,f)),s=a?s-t:s+t,S++;return n.toRegex===!0?t>1?Pr(_,n):Rt(E,null,{wrap:!1,...n}):E},Ur=(e,r,t=1,n={})=>{if(!Ae(e)&&e.length>1||!Ae(r)&&r.length>1)return _t(e,r,n);let s=n.transform||(f=>String.fromCharCode(f)),i=`${e}`.charCodeAt(0),a=`${r}`.charCodeAt(0),c=i>a,p=Math.min(i,a),m=Math.max(i,a);if(n.toRegex&&t===1)return mt(p,m,!1,n);let h=[],R=0;for(;c?i>=a:i<=a;)h.push(s(i,R)),i=c?i-t:i+t,R++;return n.toRegex===!0?Rt(h,null,{wrap:!1,options:n}):h},$e=(e,r,t,n={})=>{if(r==null&&Me(e))return[e];if(!Me(e)||!Me(r))return _t(e,r,n);if(typeof t=="function")return $e(e,r,1,{transform:t});if(dt(t))return $e(e,r,0,t);let s={...n};return s.capture===!0&&(s.wrap=!0),t=t||s.step||1,Ae(t)?Ae(e)&&Ae(r)?Dr(e,r,t,s):Ur(e,r,Math.max(Math.abs(t),1),s):t!=null&&!dt(t)?Mr(t,s):$e(e,r,1,t)};Et.exports=$e});var Ct=q((ts,xt)=>{"use strict";var Gr=Ue(),bt=ve(),qr=(e,r={})=>{let t=(n,s={})=>{let i=bt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c=i===!0||a===!0,p=r.escapeInvalid===!0?"\\":"",m="";if(n.isOpen===!0||n.isClose===!0)return p+n.value;if(n.type==="open")return c?p+n.value:"(";if(n.type==="close")return c?p+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":c?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let h=bt.reduce(n.nodes),R=Gr(...h,{...r,wrap:!1,toRegex:!0});if(R.length!==0)return h.length>1&&R.length>1?`(${R})`:R}if(n.nodes)for(let h of n.nodes)m+=t(h,n);return m};return t(e)};xt.exports=qr});var vt=q((rs,St)=>{"use strict";var Kr=Ue(),wt=He(),he=ve(),fe=(e="",r="",t=!1)=>{let n=[];if(e=[].concat(e),r=[].concat(r),!r.length)return e;if(!e.length)return t?he.flatten(r).map(s=>`{${s}}`):r;for(let s of e)if(Array.isArray(s))for(let i of s)n.push(fe(i,r,t));else for(let i of r)t===!0&&typeof i=="string"&&(i=`{${i}}`),n.push(Array.isArray(i)?fe(s,i,t):s+i);return he.flatten(n)},Wr=(e,r={})=>{let t=r.rangeLimit===void 0?1e3:r.rangeLimit,n=(s,i={})=>{s.queue=[];let a=i,c=i.queue;for(;a.type!=="brace"&&a.type!=="root"&&a.parent;)a=a.parent,c=a.queue;if(s.invalid||s.dollar){c.push(fe(c.pop(),wt(s,r)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){c.push(fe(c.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let R=he.reduce(s.nodes);if(he.exceedsLimit(...R,r.step,t))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let f=Kr(...R,r);f.length===0&&(f=wt(s,r)),c.push(fe(c.pop(),f)),s.nodes=[];return}let p=he.encloseBrace(s),m=s.queue,h=s;for(;h.type!=="brace"&&h.type!=="root"&&h.parent;)h=h.parent,m=h.queue;for(let R=0;R{"use strict";Ht.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` 7 | `,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Nt=q((ss,Ot)=>{"use strict";var jr=He(),{MAX_LENGTH:Tt,CHAR_BACKSLASH:Ge,CHAR_BACKTICK:Fr,CHAR_COMMA:Qr,CHAR_DOT:Xr,CHAR_LEFT_PARENTHESES:Zr,CHAR_RIGHT_PARENTHESES:Yr,CHAR_LEFT_CURLY_BRACE:zr,CHAR_RIGHT_CURLY_BRACE:Vr,CHAR_LEFT_SQUARE_BRACKET:kt,CHAR_RIGHT_SQUARE_BRACKET:Lt,CHAR_DOUBLE_QUOTE:Jr,CHAR_SINGLE_QUOTE:en,CHAR_NO_BREAK_SPACE:tn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:rn}=$t(),nn=(e,r={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let t=r||{},n=typeof t.maxLength=="number"?Math.min(Tt,t.maxLength):Tt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},i=[s],a=s,c=s,p=0,m=e.length,h=0,R=0,f,$={},_=()=>e[h++],y=E=>{if(E.type==="text"&&c.type==="dot"&&(c.type="text"),c&&c.type==="text"&&E.type==="text"){c.value+=E.value;return}return a.nodes.push(E),E.parent=a,E.prev=c,c=E,E};for(y({type:"bos"});h0){if(a.ranges>0){a.ranges=0;let E=a.nodes.shift();a.nodes=[E,{type:"text",value:jr(a)}]}y({type:"comma",value:f}),a.commas++;continue}if(f===Xr&&R>0&&a.commas===0){let E=a.nodes;if(R===0||E.length===0){y({type:"text",value:f});continue}if(c.type==="dot"){if(a.range=[],c.value+=f,c.type="range",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,c.type="text";continue}a.ranges++,a.args=[];continue}if(c.type==="range"){E.pop();let S=E[E.length-1];S.value+=c.value+f,c=S,a.ranges--;continue}y({type:"dot",value:f});continue}y({type:"text",value:f})}do if(a=i.pop(),a.type!=="root"){a.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let E=i[i.length-1],S=E.nodes.indexOf(a);E.nodes.splice(S,1,...a.nodes)}while(i.length>0);return y({type:"eos"}),s};Ot.exports=nn});var Pt=q((as,Bt)=>{"use strict";var It=He(),sn=Ct(),an=vt(),on=Nt(),Z=(e,r={})=>{let t=[];if(Array.isArray(e))for(let n of e){let s=Z.create(n,r);Array.isArray(s)?t.push(...s):t.push(s)}else t=[].concat(Z.create(e,r));return r&&r.expand===!0&&r.nodupes===!0&&(t=[...new Set(t)]),t};Z.parse=(e,r={})=>on(e,r);Z.stringify=(e,r={})=>It(typeof e=="string"?Z.parse(e,r):e,r);Z.compile=(e,r={})=>(typeof e=="string"&&(e=Z.parse(e,r)),sn(e,r));Z.expand=(e,r={})=>{typeof e=="string"&&(e=Z.parse(e,r));let t=an(e,r);return r.noempty===!0&&(t=t.filter(Boolean)),r.nodupes===!0&&(t=[...new Set(t)]),t};Z.create=(e,r={})=>e===""||e.length<3?[e]:r.expand!==!0?Z.compile(e,r):Z.expand(e,r);Bt.exports=Z});var me=q((is,qt)=>{"use strict";var un=W("path"),se="\\\\/",Mt=`[^${se}]`,ie="\\.",cn="\\+",ln="\\?",Te="\\/",fn="(?=.)",Dt="[^/]",qe=`(?:${Te}|$)`,Ut=`(?:^|${Te})`,Ke=`${ie}{1,2}${qe}`,pn=`(?!${ie})`,hn=`(?!${Ut}${Ke})`,dn=`(?!${ie}{0,1}${qe})`,gn=`(?!${Ke})`,An=`[^.${Te}]`,mn=`${Dt}*?`,Gt={DOT_LITERAL:ie,PLUS_LITERAL:cn,QMARK_LITERAL:ln,SLASH_LITERAL:Te,ONE_CHAR:fn,QMARK:Dt,END_ANCHOR:qe,DOTS_SLASH:Ke,NO_DOT:pn,NO_DOTS:hn,NO_DOT_SLASH:dn,NO_DOTS_SLASH:gn,QMARK_NO_DOT:An,STAR:mn,START_ANCHOR:Ut},Rn={...Gt,SLASH_LITERAL:`[${se}]`,QMARK:Mt,STAR:`${Mt}*?`,DOTS_SLASH:`${ie}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${ie})`,NO_DOTS:`(?!(?:^|[${se}])${ie}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${ie}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${ie}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`},yn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:yn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:un.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?Rn:Gt}}});var Re=q(Q=>{"use strict";var _n=W("path"),En=process.platform==="win32",{REGEX_BACKSLASH:bn,REGEX_REMOVE_BACKSLASH:xn,REGEX_SPECIAL_CHARS:Cn,REGEX_SPECIAL_CHARS_GLOBAL:wn}=me();Q.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);Q.hasRegexChars=e=>Cn.test(e);Q.isRegexChar=e=>e.length===1&&Q.hasRegexChars(e);Q.escapeRegex=e=>e.replace(wn,"\\$1");Q.toPosixSlashes=e=>e.replace(bn,"/");Q.removeBackslashes=e=>e.replace(xn,r=>r==="\\"?"":r);Q.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};Q.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:En===!0||_n.sep==="\\";Q.escapeLast=(e,r,t)=>{let n=e.lastIndexOf(r,t);return n===-1?e:e[n-1]==="\\"?Q.escapeLast(e,r,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};Q.removePrefix=(e,r={})=>{let t=e;return t.startsWith("./")&&(t=t.slice(2),r.prefix="./"),t};Q.wrapOutput=(e,r={},t={})=>{let n=t.contains?"":"^",s=t.contains?"":"$",i=`${n}(?:${e})${s}`;return r.negated===!0&&(i=`(?:^(?!${i}).*$)`),i}});var Yt=q((us,Zt)=>{"use strict";var Kt=Re(),{CHAR_ASTERISK:We,CHAR_AT:Sn,CHAR_BACKWARD_SLASH:ye,CHAR_COMMA:vn,CHAR_DOT:je,CHAR_EXCLAMATION_MARK:Fe,CHAR_FORWARD_SLASH:Xt,CHAR_LEFT_CURLY_BRACE:Qe,CHAR_LEFT_PARENTHESES:Xe,CHAR_LEFT_SQUARE_BRACKET:Hn,CHAR_PLUS:$n,CHAR_QUESTION_MARK:Wt,CHAR_RIGHT_CURLY_BRACE:Tn,CHAR_RIGHT_PARENTHESES:jt,CHAR_RIGHT_SQUARE_BRACKET:kn}=me(),Ft=e=>e===Xt||e===ye,Qt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?1/0:1)},Ln=(e,r)=>{let t=r||{},n=e.length-1,s=t.parts===!0||t.scanToEnd===!0,i=[],a=[],c=[],p=e,m=-1,h=0,R=0,f=!1,$=!1,_=!1,y=!1,E=!1,S=!1,T=!1,L=!1,z=!1,I=!1,re=0,K,g,v={value:"",depth:0,isGlob:!1},k=()=>m>=n,l=()=>p.charCodeAt(m+1),H=()=>(K=g,p.charCodeAt(++m));for(;m0&&(B=p.slice(0,h),p=p.slice(h),R-=h),w&&_===!0&&R>0?(w=p.slice(0,R),o=p.slice(R)):_===!0?(w="",o=p):w=p,w&&w!==""&&w!=="/"&&w!==p&&Ft(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),t.unescape===!0&&(o&&(o=Kt.removeBackslashes(o)),w&&T===!0&&(w=Kt.removeBackslashes(w)));let u={prefix:B,input:e,start:h,base:w,glob:o,isBrace:f,isBracket:$,isGlob:_,isExtglob:y,isGlobstar:E,negated:L,negatedExtglob:z};if(t.tokens===!0&&(u.maxDepth=0,Ft(g)||a.push(v),u.tokens=a),t.parts===!0||t.tokens===!0){let P;for(let b=0;b{"use strict";var ke=me(),Y=Re(),{MAX_LENGTH:Le,POSIX_REGEX_SOURCE:On,REGEX_NON_SPECIAL_CHARS:Nn,REGEX_SPECIAL_CHARS_BACKREF:In,REPLACEMENTS:zt}=ke,Bn=(e,r)=>{if(typeof r.expandRange=="function")return r.expandRange(...e,r);e.sort();let t=`[${e.join("-")}]`;try{new RegExp(t)}catch{return e.map(s=>Y.escapeRegex(s)).join("..")}return t},de=(e,r)=>`Missing ${e}: "${r}" - use "\\\\${r}" to match literal characters`,Vt=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=zt[e]||e;let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let i={type:"bos",value:"",output:t.prepend||""},a=[i],c=t.capture?"":"?:",p=Y.isWindows(r),m=ke.globChars(p),h=ke.extglobChars(m),{DOT_LITERAL:R,PLUS_LITERAL:f,SLASH_LITERAL:$,ONE_CHAR:_,DOTS_SLASH:y,NO_DOT:E,NO_DOT_SLASH:S,NO_DOTS_SLASH:T,QMARK:L,QMARK_NO_DOT:z,STAR:I,START_ANCHOR:re}=m,K=A=>`(${c}(?:(?!${re}${A.dot?y:R}).)*?)`,g=t.dot?"":E,v=t.dot?L:z,k=t.bash===!0?K(t):I;t.capture&&(k=`(${k})`),typeof t.noext=="boolean"&&(t.noextglob=t.noext);let l={input:e,index:-1,start:0,dot:t.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:a};e=Y.removePrefix(e,l),s=e.length;let H=[],w=[],B=[],o=i,u,P=()=>l.index===s-1,b=l.peek=(A=1)=>e[l.index+A],V=l.advance=()=>e[++l.index]||"",J=()=>e.slice(l.index+1),X=(A="",O=0)=>{l.consumed+=A,l.index+=O},Ee=A=>{l.output+=A.output!=null?A.output:A.value,X(A.value)},mr=()=>{let A=1;for(;b()==="!"&&(b(2)!=="("||b(3)==="?");)V(),l.start++,A++;return A%2===0?!1:(l.negated=!0,l.start++,!0)},be=A=>{l[A]++,B.push(A)},oe=A=>{l[A]--,B.pop()},C=A=>{if(o.type==="globstar"){let O=l.braces>0&&(A.type==="comma"||A.type==="brace"),d=A.extglob===!0||H.length&&(A.type==="pipe"||A.type==="paren");A.type!=="slash"&&A.type!=="paren"&&!O&&!d&&(l.output=l.output.slice(0,-o.output.length),o.type="star",o.value="*",o.output=k,l.output+=o.output)}if(H.length&&A.type!=="paren"&&(H[H.length-1].inner+=A.value),(A.value||A.output)&&Ee(A),o&&o.type==="text"&&A.type==="text"){o.value+=A.value,o.output=(o.output||"")+A.value;return}A.prev=o,a.push(A),o=A},xe=(A,O)=>{let d={...h[O],conditions:1,inner:""};d.prev=o,d.parens=l.parens,d.output=l.output;let x=(t.capture?"(":"")+d.open;be("parens"),C({type:A,value:O,output:l.output?"":_}),C({type:"paren",extglob:!0,value:V(),output:x}),H.push(d)},Rr=A=>{let O=A.close+(t.capture?")":""),d;if(A.type==="negate"){let x=k;A.inner&&A.inner.length>1&&A.inner.includes("/")&&(x=K(t)),(x!==k||P()||/^\)+$/.test(J()))&&(O=A.close=`)$))${x}`),A.inner.includes("*")&&(d=J())&&/^\.[^\\/.]+$/.test(d)&&(O=A.close=`)${d})${x})`),A.prev.type==="bos"&&(l.negatedExtglob=!0)}C({type:"paren",extglob:!0,value:u,output:O}),oe("parens")};if(t.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let A=!1,O=e.replace(In,(d,x,M,j,G,Ie)=>j==="\\"?(A=!0,d):j==="?"?x?x+j+(G?L.repeat(G.length):""):Ie===0?v+(G?L.repeat(G.length):""):L.repeat(M.length):j==="."?R.repeat(M.length):j==="*"?x?x+j+(G?k:""):k:x?d:`\\${d}`);return A===!0&&(t.unescape===!0?O=O.replace(/\\/g,""):O=O.replace(/\\+/g,d=>d.length%2===0?"\\\\":d?"\\":"")),O===e&&t.contains===!0?(l.output=e,l):(l.output=Y.wrapOutput(O,l,r),l)}for(;!P();){if(u=V(),u==="\0")continue;if(u==="\\"){let d=b();if(d==="/"&&t.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",C({type:"text",value:u});continue}let x=/^\\+/.exec(J()),M=0;if(x&&x[0].length>2&&(M=x[0].length,l.index+=M,M%2!==0&&(u+="\\")),t.unescape===!0?u=V():u+=V(),l.brackets===0){C({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||o.value==="["||o.value==="[^")){if(t.posix!==!1&&u===":"){let d=o.value.slice(1);if(d.includes("[")&&(o.posix=!0,d.includes(":"))){let x=o.value.lastIndexOf("["),M=o.value.slice(0,x),j=o.value.slice(x+2),G=On[j];if(G){o.value=M+G,l.backtrack=!0,V(),!i.output&&a.indexOf(o)===1&&(i.output=_);continue}}}(u==="["&&b()!==":"||u==="-"&&b()==="]")&&(u=`\\${u}`),u==="]"&&(o.value==="["||o.value==="[^")&&(u=`\\${u}`),t.posix===!0&&u==="!"&&o.value==="["&&(u="^"),o.value+=u,Ee({value:u});continue}if(l.quotes===1&&u!=='"'){u=Y.escapeRegex(u),o.value+=u,Ee({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,t.keepQuotes===!0&&C({type:"text",value:u});continue}if(u==="("){be("parens"),C({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&t.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){Rr(H.pop());continue}C({type:"paren",value:u,output:l.parens?")":"\\)"}),oe("parens");continue}if(u==="["){if(t.nobracket===!0||!J().includes("]")){if(t.nobracket!==!0&&t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else be("brackets");C({type:"bracket",value:u});continue}if(u==="]"){if(t.nobracket===!0||o&&o.type==="bracket"&&o.value.length===1){C({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(t.strictBrackets===!0)throw new SyntaxError(de("opening","["));C({type:"text",value:u,output:`\\${u}`});continue}oe("brackets");let d=o.value.slice(1);if(o.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),o.value+=u,Ee({value:u}),t.literalBrackets===!1||Y.hasRegexChars(d))continue;let x=Y.escapeRegex(o.value);if(l.output=l.output.slice(0,-o.value.length),t.literalBrackets===!0){l.output+=x,o.value=x;continue}o.value=`(${c}${x}|${o.value})`,l.output+=o.value;continue}if(u==="{"&&t.nobrace!==!0){be("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),C(d);continue}if(u==="}"){let d=w[w.length-1];if(t.nobrace===!0||!d){C({type:"text",value:u,output:u});continue}let x=")";if(d.dots===!0){let M=a.slice(),j=[];for(let G=M.length-1;G>=0&&(a.pop(),M[G].type!=="brace");G--)M[G].type!=="dots"&&j.unshift(M[G].value);x=Bn(j,t),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let M=l.output.slice(0,d.outputIndex),j=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=x="\\}",l.output=M;for(let G of j)l.output+=G.output||G.value}C({type:"brace",value:u,output:x}),oe("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,C({type:"text",value:u});continue}if(u===","){let d=u,x=w[w.length-1];x&&B[B.length-1]==="braces"&&(x.comma=!0,d="|"),C({type:"comma",value:u,output:d});continue}if(u==="/"){if(o.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",a.pop(),o=i;continue}C({type:"slash",value:u,output:$});continue}if(u==="."){if(l.braces>0&&o.type==="dot"){o.value==="."&&(o.output=R);let d=w[w.length-1];o.type="dots",o.output+=u,o.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&o.type!=="bos"&&o.type!=="slash"){C({type:"text",value:u,output:R});continue}C({type:"dot",value:u,output:R});continue}if(u==="?"){if(!(o&&o.value==="(")&&t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("qmark",u);continue}if(o&&o.type==="paren"){let x=b(),M=u;if(x==="<"&&!Y.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(o.value==="("&&!/[!=<:]/.test(x)||x==="<"&&!/<([!=]|\w+>)/.test(J()))&&(M=`\\${u}`),C({type:"text",value:u,output:M});continue}if(t.dot!==!0&&(o.type==="slash"||o.type==="bos")){C({type:"qmark",value:u,output:z});continue}C({type:"qmark",value:u,output:L});continue}if(u==="!"){if(t.noextglob!==!0&&b()==="("&&(b(2)!=="?"||!/[!=<:]/.test(b(3)))){xe("negate",u);continue}if(t.nonegate!==!0&&l.index===0){mr();continue}}if(u==="+"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("plus",u);continue}if(o&&o.value==="("||t.regex===!1){C({type:"plus",value:u,output:f});continue}if(o&&(o.type==="bracket"||o.type==="paren"||o.type==="brace")||l.parens>0){C({type:"plus",value:u});continue}C({type:"plus",value:f});continue}if(u==="@"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){C({type:"at",extglob:!0,value:u,output:""});continue}C({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=Nn.exec(J());d&&(u+=d[0],l.index+=d[0].length),C({type:"text",value:u});continue}if(o&&(o.type==="globstar"||o.star===!0)){o.type="star",o.star=!0,o.value+=u,o.output=k,l.backtrack=!0,l.globstar=!0,X(u);continue}let A=J();if(t.noextglob!==!0&&/^\([^?]/.test(A)){xe("star",u);continue}if(o.type==="star"){if(t.noglobstar===!0){X(u);continue}let d=o.prev,x=d.prev,M=d.type==="slash"||d.type==="bos",j=x&&(x.type==="star"||x.type==="globstar");if(t.bash===!0&&(!M||A[0]&&A[0]!=="/")){C({type:"star",value:u,output:""});continue}let G=l.braces>0&&(d.type==="comma"||d.type==="brace"),Ie=H.length&&(d.type==="pipe"||d.type==="paren");if(!M&&d.type!=="paren"&&!G&&!Ie){C({type:"star",value:u,output:""});continue}for(;A.slice(0,3)==="/**";){let Ce=e[l.index+4];if(Ce&&Ce!=="/")break;A=A.slice(3),X("/**",3)}if(d.type==="bos"&&P()){o.type="globstar",o.value+=u,o.output=K(t),l.output=o.output,l.globstar=!0,X(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!j&&P()){l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=K(t)+(t.strictSlashes?")":"|$)"),o.value+=u,l.globstar=!0,l.output+=d.output+o.output,X(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&A[0]==="/"){let Ce=A[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=`${K(t)}${$}|${$}${Ce})`,o.value+=u,l.output+=d.output+o.output,l.globstar=!0,X(u+V()),C({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&A[0]==="/"){o.type="globstar",o.value+=u,o.output=`(?:^|${$}|${K(t)}${$})`,l.output=o.output,l.globstar=!0,X(u+V()),C({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-o.output.length),o.type="globstar",o.output=K(t),o.value+=u,l.output+=o.output,l.globstar=!0,X(u);continue}let O={type:"star",value:u,output:k};if(t.bash===!0){O.output=".*?",(o.type==="bos"||o.type==="slash")&&(O.output=g+O.output),C(O);continue}if(o&&(o.type==="bracket"||o.type==="paren")&&t.regex===!0){O.output=u,C(O);continue}(l.index===l.start||o.type==="slash"||o.type==="dot")&&(o.type==="dot"?(l.output+=S,o.output+=S):t.dot===!0?(l.output+=T,o.output+=T):(l.output+=g,o.output+=g),b()!=="*"&&(l.output+=_,o.output+=_)),C(O)}for(;l.brackets>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=Y.escapeLast(l.output,"["),oe("brackets")}for(;l.parens>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=Y.escapeLast(l.output,"("),oe("parens")}for(;l.braces>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=Y.escapeLast(l.output,"{"),oe("braces")}if(t.strictSlashes!==!0&&(o.type==="star"||o.type==="bracket")&&C({type:"maybe_slash",value:"",output:`${$}?`}),l.backtrack===!0){l.output="";for(let A of l.tokens)l.output+=A.output!=null?A.output:A.value,A.suffix&&(l.output+=A.suffix)}return l};Vt.fastpaths=(e,r)=>{let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=zt[e]||e;let i=Y.isWindows(r),{DOT_LITERAL:a,SLASH_LITERAL:c,ONE_CHAR:p,DOTS_SLASH:m,NO_DOT:h,NO_DOTS:R,NO_DOTS_SLASH:f,STAR:$,START_ANCHOR:_}=ke.globChars(i),y=t.dot?R:h,E=t.dot?f:h,S=t.capture?"":"?:",T={negated:!1,prefix:""},L=t.bash===!0?".*?":$;t.capture&&(L=`(${L})`);let z=g=>g.noglobstar===!0?L:`(${S}(?:(?!${_}${g.dot?m:a}).)*?)`,I=g=>{switch(g){case"*":return`${y}${p}${L}`;case".*":return`${a}${p}${L}`;case"*.*":return`${y}${L}${a}${p}${L}`;case"*/*":return`${y}${L}${c}${p}${E}${L}`;case"**":return y+z(t);case"**/*":return`(?:${y}${z(t)}${c})?${E}${p}${L}`;case"**/*.*":return`(?:${y}${z(t)}${c})?${E}${L}${a}${p}${L}`;case"**/.*":return`(?:${y}${z(t)}${c})?${a}${p}${L}`;default:{let v=/^(.*?)\.(\w+)$/.exec(g);if(!v)return;let k=I(v[1]);return k?k+a+v[2]:void 0}}},re=Y.removePrefix(e,T),K=I(re);return K&&t.strictSlashes!==!0&&(K+=`${c}?`),K};Jt.exports=Vt});var rr=q((ls,tr)=>{"use strict";var Pn=W("path"),Mn=Yt(),Ze=er(),Ye=Re(),Dn=me(),Un=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,r,t=!1)=>{if(Array.isArray(e)){let h=e.map(f=>D(f,r,t));return f=>{for(let $ of h){let _=$(f);if(_)return _}return!1}}let n=Un(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=r||{},i=Ye.isWindows(r),a=n?D.compileRe(e,r):D.makeRe(e,r,!1,!0),c=a.state;delete a.state;let p=()=>!1;if(s.ignore){let h={...r,ignore:null,onMatch:null,onResult:null};p=D(s.ignore,h,t)}let m=(h,R=!1)=>{let{isMatch:f,match:$,output:_}=D.test(h,a,r,{glob:e,posix:i}),y={glob:e,state:c,regex:a,posix:i,input:h,output:_,match:$,isMatch:f};return typeof s.onResult=="function"&&s.onResult(y),f===!1?(y.isMatch=!1,R?y:!1):p(h)?(typeof s.onIgnore=="function"&&s.onIgnore(y),y.isMatch=!1,R?y:!1):(typeof s.onMatch=="function"&&s.onMatch(y),R?y:!0)};return t&&(m.state=c),m};D.test=(e,r,t,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let i=t||{},a=i.format||(s?Ye.toPosixSlashes:null),c=e===n,p=c&&a?a(e):e;return c===!1&&(p=a?a(e):e,c=p===n),(c===!1||i.capture===!0)&&(i.matchBase===!0||i.basename===!0?c=D.matchBase(e,r,t,s):c=r.exec(p)),{isMatch:Boolean(c),match:c,output:p}};D.matchBase=(e,r,t,n=Ye.isWindows(t))=>(r instanceof RegExp?r:D.makeRe(r,t)).test(Pn.basename(e));D.isMatch=(e,r,t)=>D(r,t)(e);D.parse=(e,r)=>Array.isArray(e)?e.map(t=>D.parse(t,r)):Ze(e,{...r,fastpaths:!1});D.scan=(e,r)=>Mn(e,r);D.compileRe=(e,r,t=!1,n=!1)=>{if(t===!0)return e.output;let s=r||{},i=s.contains?"":"^",a=s.contains?"":"$",c=`${i}(?:${e.output})${a}`;e&&e.negated===!0&&(c=`^(?!${c}).*$`);let p=D.toRegex(c,r);return n===!0&&(p.state=e),p};D.makeRe=(e,r={},t=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return r.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ze.fastpaths(e,r)),s.output||(s=Ze(e,r)),D.compileRe(s,r,t,n)};D.toRegex=(e,r)=>{try{let t=r||{};return new RegExp(e,t.flags||(t.nocase?"i":""))}catch(t){if(r&&r.debug===!0)throw t;return/$^/}};D.constants=Dn;tr.exports=D});var sr=q((fs,nr)=>{"use strict";nr.exports=rr()});var cr=q((ps,ur)=>{"use strict";var ir=W("util"),or=Pt(),ae=sr(),ze=Re(),ar=e=>e===""||e==="./",N=(e,r,t)=>{r=[].concat(r),e=[].concat(e);let n=new Set,s=new Set,i=new Set,a=0,c=h=>{i.add(h.output),t&&t.onResult&&t.onResult(h)};for(let h=0;h!n.has(h));if(t&&m.length===0){if(t.failglob===!0)throw new Error(`No matches found for "${r.join(", ")}"`);if(t.nonull===!0||t.nullglob===!0)return t.unescape?r.map(h=>h.replace(/\\/g,"")):r}return m};N.match=N;N.matcher=(e,r)=>ae(e,r);N.isMatch=(e,r,t)=>ae(r,t)(e);N.any=N.isMatch;N.not=(e,r,t={})=>{r=[].concat(r).map(String);let n=new Set,s=[],a=N(e,r,{...t,onResult:c=>{t.onResult&&t.onResult(c),s.push(c.output)}});for(let c of s)a.includes(c)||n.add(c);return[...n]};N.contains=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);if(Array.isArray(r))return r.some(n=>N.contains(e,n,t));if(typeof r=="string"){if(ar(e)||ar(r))return!1;if(e.includes(r)||e.startsWith("./")&&e.slice(2).includes(r))return!0}return N.isMatch(e,r,{...t,contains:!0})};N.matchKeys=(e,r,t)=>{if(!ze.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),r,t),s={};for(let i of n)s[i]=e[i];return s};N.some=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(n.some(a=>i(a)))return!0}return!1};N.every=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(!n.every(a=>i(a)))return!1}return!0};N.all=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);return[].concat(r).every(n=>ae(n,t)(e))};N.capture=(e,r,t)=>{let n=ze.isWindows(t),i=ae.makeRe(String(e),{...t,capture:!0}).exec(n?ze.toPosixSlashes(r):r);if(i)return i.slice(1).map(a=>a===void 0?"":a)};N.makeRe=(...e)=>ae.makeRe(...e);N.scan=(...e)=>ae.scan(...e);N.parse=(e,r)=>{let t=[];for(let n of[].concat(e||[]))for(let s of or(String(n),r))t.push(ae.parse(s,r));return t};N.braces=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return r&&r.nobrace===!0||!/\{.*\}/.test(e)?[e]:or(e,r)};N.braceExpand=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,{...r,expand:!0})};ur.exports=N});var fr=q((hs,lr)=>{"use strict";lr.exports=(e,...r)=>new Promise(t=>{t(e(...r))})});var hr=q((ds,Ve)=>{"use strict";var Gn=fr(),pr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let r=[],t=0,n=()=>{t--,r.length>0&&r.shift()()},s=(c,p,...m)=>{t++;let h=Gn(c,...m);p(h),h.then(n,n)},i=(c,p,...m)=>{tnew Promise(m=>i(c,m,...p));return Object.defineProperties(a,{activeCount:{get:()=>t},pendingCount:{get:()=>r.length}}),a};Ve.exports=pr;Ve.exports.default=pr});var jn={};Cr(jn,{default:()=>Wn});var Se=W("@yarnpkg/cli"),ne=W("@yarnpkg/core"),et=W("@yarnpkg/core"),ue=W("clipanion"),ce=class extends Se.BaseCommand{constructor(){super(...arguments);this.json=ue.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ue.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ue.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ue.Option.Rest()}async execute(){let t=await ne.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ne.Project.find(t,this.context.cwd),i=await ne.Cache.find(t);await n.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(n.workspaces);else if(this.workspaces.length===0){if(!s)throw new Se.WorkspaceRequiredError(n.cwd,this.context.cwd);a=new Set([s])}else a=new Set(this.workspaces.map(p=>n.getWorkspaceByIdent(et.structUtils.parseIdent(p))));for(let p of a)for(let m of this.production?["dependencies"]:ne.Manifest.hardDependencies)for(let h of p.manifest.getForScope(m).values()){let R=n.tryWorkspaceByDescriptor(h);R!==null&&a.add(R)}for(let p of n.workspaces)a.has(p)?this.production&&p.manifest.devDependencies.clear():(p.manifest.installConfig=p.manifest.installConfig||{},p.manifest.installConfig.selfReferences=!1,p.manifest.dependencies.clear(),p.manifest.devDependencies.clear(),p.manifest.peerDependencies.clear(),p.manifest.scripts.clear());return(await ne.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async p=>{await n.install({cache:i,report:p,persistProject:!1})})).exitCode()}};ce.paths=[["workspaces","focus"]],ce.usage=ue.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var Ne=W("@yarnpkg/cli"),ge=W("@yarnpkg/core"),_e=W("@yarnpkg/core"),F=W("@yarnpkg/core"),gr=W("@yarnpkg/plugin-git"),U=W("clipanion"),Oe=Be(cr()),Ar=Be(hr()),te=Be(W("typanion")),pe=class extends Ne.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:te.isOneOf([te.isEnum(["unlimited"]),te.applyCascade(te.isNumber(),[te.isInteger(),te.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!s)throw new Ne.WorkspaceRequiredError(n.cwd,this.context.cwd);await n.restoreInstallState();let i=this.cli.process([this.commandName,...this.args]),a=i.path.length===1&&i.path[0]==="run"&&typeof i.scriptName<"u"?i.scriptName:null;if(i.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let c=this.all?n.topLevelWorkspace:s,p=this.since?Array.from(await gr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:n})):[c,...this.from.length>0?c.getRecursiveWorkspaceChildren():[]],m=g=>Oe.default.isMatch(F.structUtils.stringifyIdent(g.locator),this.from),h=this.from.length>0?p.filter(m):p,R=new Set([...h,...h.map(g=>[...this.recursive?this.since?g.getRecursiveWorkspaceDependents():g.getRecursiveWorkspaceDependencies():g.getRecursiveWorkspaceChildren()]).flat()]),f=[],$=!1;if(a!=null&&a.includes(":")){for(let g of n.workspaces)if(g.manifest.scripts.has(a)&&($=!$,$===!1))break}for(let g of R)a&&!g.manifest.scripts.has(a)&&!$&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(g)).has(a)||a===process.env.npm_lifecycle_event&&g.cwd===s.cwd||this.include.length>0&&!Oe.default.isMatch(F.structUtils.stringifyIdent(g.locator),this.include)||this.exclude.length>0&&Oe.default.isMatch(F.structUtils.stringifyIdent(g.locator),this.exclude)||this.publicOnly&&g.manifest.private===!0||f.push(g);let _=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.ceil(F.nodeUtils.availableParallelism()/2):1,y=_===1?!1:this.parallel,E=y?this.interlaced:!0,S=(0,Ar.default)(_),T=new Map,L=new Set,z=0,I=null,re=!1,K=await _e.StreamReport.start({configuration:t,stdout:this.context.stdout,includePrefix:!1},async g=>{let v=async(k,{commandIndex:l})=>{if(re)return-1;!y&&this.verbose&&l>1&&g.reportSeparator();let H=qn(k,{configuration:t,verbose:this.verbose,commandIndex:l}),[w,B]=dr(g,{prefix:H,interlaced:E}),[o,u]=dr(g,{prefix:H,interlaced:E});try{this.verbose&&g.reportInfo(null,`${H} Process started`);let P=Date.now(),b=await this.cli.run([this.commandName,...this.args],{cwd:k.cwd,stdout:w,stderr:o})||0;w.end(),o.end(),await B,await u;let V=Date.now();if(this.verbose){let J=t.get("enableTimers")?`, completed in ${F.formatUtils.pretty(t,V-P,F.formatUtils.Type.DURATION)}`:"";g.reportInfo(null,`${H} Process exited (exit code ${b})${J}`)}return b===130&&(re=!0,I=b),b}catch(P){throw w.end(),o.end(),await B,await u,P}};for(let k of f)T.set(k.anchoredLocator.locatorHash,k);for(;T.size>0&&!g.hasErrors();){let k=[];for(let[w,B]of T){if(L.has(B.anchoredDescriptor.descriptorHash))continue;let o=!0;if(this.topological||this.topologicalDev){let u=this.topologicalDev?new Map([...B.manifest.dependencies,...B.manifest.devDependencies]):B.manifest.dependencies;for(let P of u.values()){let b=n.tryWorkspaceByDescriptor(P);if(o=b===null||!T.has(b.anchoredLocator.locatorHash),!o)break}}if(!!o&&(L.add(B.anchoredDescriptor.descriptorHash),k.push(S(async()=>{let u=await v(B,{commandIndex:++z});return T.delete(w),L.delete(B.anchoredDescriptor.descriptorHash),u})),!y))break}if(k.length===0){let w=Array.from(T.values()).map(B=>F.structUtils.prettyLocator(t,B.anchoredLocator)).join(", ");g.reportError(_e.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${w})`);return}let H=(await Promise.all(k)).find(w=>w!==0);I===null&&(I=typeof H<"u"?1:I),(this.topological||this.topologicalDev)&&typeof H<"u"&&g.reportError(_e.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return I!==null?I:K.exitCode()}};pe.paths=[["workspaces","foreach"]],pe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});function dr(e,{prefix:r,interlaced:t}){let n=e.createStreamReporter(r),s=new F.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let i=new Promise(c=>{n.on("finish",()=>{c(s.active)})});if(t)return[s,i];let a=new F.miscUtils.BufferStream;return a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()}),[a,i]}function qn(e,{configuration:r,commandIndex:t,verbose:n}){if(!n)return null;let i=`[${F.structUtils.stringifyIdent(e.locator)}]:`,a=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],c=a[t%a.length];return F.formatUtils.pretty(r,i,c)}var Kn={commands:[ce,pe]},Wn=Kn;return wr(jn);})(); 8 | /*! 9 | * fill-range 10 | * 11 | * Copyright (c) 2014-present, Jon Schlinkert. 12 | * Licensed under the MIT License. 13 | */ 14 | /*! 15 | * is-number 16 | * 17 | * Copyright (c) 2014-present, Jon Schlinkert. 18 | * Released under the MIT License. 19 | */ 20 | /*! 21 | * to-regex-range 22 | * 23 | * Copyright (c) 2015-present, Jon Schlinkert. 24 | * Released under the MIT License. 25 | */ 26 | return plugin; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | initScope: effect-use 2 | 3 | nodeLinker: node-modules 4 | 5 | logFilters: 6 | # silence cache misses 7 | - code: YN0013 8 | level: discard 9 | 10 | plugins: 11 | - path: .yarn/plugins/@yarnpkg/plugin-constraints.cjs 12 | spec: "@yarnpkg/plugin-constraints" 13 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 14 | spec: "@yarnpkg/plugin-workspace-tools" 15 | 16 | yarnPath: .yarn/releases/yarn-3.5.1.cjs 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Embedded Insurance® 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 | 5 | 💁 6 | 7 | 8 | effect-use 9 |

10 | 11 | 12 | effect-use is a library that collects common [Effect](https://github.com/Effect-TS/effect) wrappers in one place for developer convenience. 13 | 14 | 15 | ## Project Status 16 | Alpha software, subject to change. 17 | 18 | 19 | 20 | ## Packages 21 | - [`@effect-use/aws-s3`](./packages/aws-s3) - Interact with AWS S3 buckets (via `@aws/client-s3`) 22 | - [`@effect-use/gcp-gcs`](./packages/gcp-gcs) - Interact with Google Cloud Storage (GCS) (via `@google-cloud/storage`) 23 | - [`@effect-use/gcp-logging`](./packages/gcp-logging) - Log traces and spans with Google Cloud Stackdriver 24 | - [`@effect-use/temporal-client`](./packages/temporal-client) - Signal or start workflows (via `@temporalio/client`) 25 | - [`@effect-use/temporal-config`](./packages/temporal-config) - Define, require Temporal connection configuration 26 | - [`@effect-use/github`](./packages/github) - Interact with the GitHub API (via `@octokit/rest`) 27 | - [`@effect-use/stripe`](./packages/stripe) - Process payments with [Stripe](https://stripe.com/docs/api) 28 | - [`@effect-use/brex`](./packages/brex) - Move money with the [Brex API](https://developer.brex.com/) 29 | - [`@effect-use/kubernetes`](./packages/kubernetes) - WIP 30 | 31 | ## Usage 32 | ```typescript 33 | import { GitHub } from '@effect-use/github' 34 | import { Effect, Layer, pipe } from 'effect' 35 | 36 | const getLatestCommit = pipe( 37 | Effect.flatMap(GitHub, github => github.getRepo({owner: 'embedded-insurance', repo: 'effect-use'})), 38 | Effect.map(result => result.repo.latestCommit), 39 | Effect.mapError(e => ({ _tag: "Whoops" })) 40 | ) 41 | 42 | // Let's provide our dependencies 43 | // And instead of the real GitHub, let's just make something up that looks exactly like it. 44 | // a.k.a., "it satisfies the interface" 45 | const GitHubLayerTest = Layer.succeed(GitHub, { 46 | getRepo: (args: any)=> ({ latestCommit: '125' }) 47 | } as GitHub) 48 | 49 | const result = pipe( 50 | getLatestCommit, 51 | Effect.provide(GitHubLayerTest), 52 | Effect.runPromise 53 | ) 54 | 55 | expect(result).toEqual({latestCommit: '125'}) 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /constraints.pro: -------------------------------------------------------------------------------- 1 | requires_specific_version(DependencyIdent, Version) :- 2 | (DependencyIdent, Version) = ('@temporalio/activity', '1.8.6'); 3 | (DependencyIdent, Version) = ('@temporalio/worker', '1.8.6'); 4 | (DependencyIdent, Version) = ('@temporalio/workflow', '1.8.6'); 5 | (DependencyIdent, Version) = ('@temporalio/client', '1.8.6'); 6 | 7 | (DependencyIdent, Version) = ('effect', '3.12.1'); 8 | 9 | (DependencyIdent, Version) = ('typescript', '5.7.2'); 10 | (DependencyIdent, Version) = ('prettier', '2.8.8'); 11 | 12 | (DependencyIdent, Version) = ('jest', '29.5.0'); 13 | (DependencyIdent, Version) = ('ts-jest', '29.1.0'). 14 | 15 | 16 | gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange2, DependencyType) :- 17 | workspace_has_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType), 18 | requires_specific_version(DependencyIdent,DependencyRange2). 19 | 20 | 21 | gen_enforced_field(WorkspaceCwd, 'main', 'dist/index.js'). 22 | gen_enforced_field(WorkspaceCwd, 'engines.node', '>=18'). 23 | gen_enforced_field(WorkspaceCwd, 'license', 'MIT'). 24 | gen_enforced_field(WorkspaceCwd, 'repository.type', 'git'). 25 | gen_enforced_field(WorkspaceCwd, 'repository.url', 'https://github.com/embedded-insurance/effect-use.git'). 26 | gen_enforced_field(WorkspaceCwd, 'repository.directory', WorkspaceCwd). 27 | gen_enforced_field(WorkspaceCwd, 'publishConfig.access', 'public'). 28 | gen_enforced_field(WorkspaceCwd, 'scripts.format', 'prettier --write .') :- WorkspaceCwd \= '.'. 29 | gen_enforced_field(WorkspaceCwd, 'scripts.clean', 'rimraf node_modules & rimraf dist & rimraf .turbo') :- WorkspaceCwd \= '.'. 30 | gen_enforced_field(WorkspaceCwd, 'scripts.build', 'rimraf dist && tsc -p tsconfig.build.json') :- WorkspaceCwd \= '.'. 31 | gen_enforced_field(WorkspaceCwd, 'scripts.typecheck', 'tsc --noEmit') :- WorkspaceCwd \= '.'. 32 | 33 | % workspace_cwd_dirname(WorkspaceCwd, Dirname) :- 34 | % split_atom(WorkspaceCwd, '/', '', XS), 35 | % [_, Dirname]. 36 | 37 | % in 'packages/' 38 | workspace_cwd_dirname(WorkspaceCwd, Dirname) :- 39 | atomic_list_concat(['packages', Dirname], '/', WorkspaceCwd). 40 | 41 | % Folders in "packages" have a package name "@effect-use/" 42 | gen_enforced_field(WorkspaceCwd, 'name', PackageName) :- 43 | % not the monorepo root 44 | WorkspaceCwd \= '.', 45 | % package name and folder name must match 46 | workspace_cwd_dirname(WorkspaceCwd, Dirname), 47 | atom_concat('@effect-use/', Dirname, PackageName). 48 | 49 | 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "effect-use", 3 | "private": true, 4 | "packageManager": "yarn@3.5.1", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "test": "yarn turbo test --continue", 10 | "build": "yarn turbo build", 11 | "clean": "yarn turbo clean && yarn cache clean && rimraf node_modules", 12 | "format": "yarn turbo format", 13 | "typecheck": "yarn turbo typecheck", 14 | "version": "yarn changeset version", 15 | "release": "yarn workspaces foreach --from '@effect-use/*' --no-private npm publish --tolerate-republish && yarn changeset tag" 16 | }, 17 | "devDependencies": { 18 | "@changesets/cli": "2.26.1", 19 | "@temporalio/testing": "1.8.6", 20 | "@temporalio/worker": "1.8.6", 21 | "@types/jest": "^29.5.1", 22 | "@types/node": "18", 23 | "jest": "29.5.0", 24 | "prettier": "2.8.8", 25 | "rimraf": "^5.0.1", 26 | "ts-jest": "29.1.0", 27 | "ts-node": "10.9.1", 28 | "turbo": "1.10.13", 29 | "typescript": "5.7.2" 30 | }, 31 | "engines": { 32 | "node": ">=18" 33 | }, 34 | "license": "MIT", 35 | "repository": { 36 | "directory": ".", 37 | "type": "git", 38 | "url": "https://github.com/embedded-insurance/effect-use.git" 39 | }, 40 | "resolutions": { 41 | "effect": "3.12.1", 42 | "@temporalio/activity": "1.8.6", 43 | "@temporalio/worker": "1.8.6", 44 | "@temporalio/workflow": "1.8.6", 45 | "@temporalio/client": "1.8.6" 46 | }, 47 | "publishConfig": { 48 | "access": "public" 49 | }, 50 | "main": "dist/index.js", 51 | "dependencies": { 52 | "effect": "3.12.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/aws-s3/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/aws-s3 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - 62aa9e7: upgrade to effect next.61, schema 0.55 26 | 27 | ## 1.0.0 28 | 29 | ### Major Changes 30 | 31 | - 38f0e5b: use effect package, upgrade schema 32 | 33 | ## 0.1.0 34 | 35 | ### Minor Changes 36 | 37 | - 8fe9f3e: latest effect packages 38 | 39 | ## 0.0.1 40 | 41 | ### Patch Changes 42 | 43 | - 73bbd53: make publishable to npm public 44 | -------------------------------------------------------------------------------- /packages/aws-s3/README.md: -------------------------------------------------------------------------------- 1 | # @effect-use/aws-s3 2 | -------------------------------------------------------------------------------- /packages/aws-s3/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest' 2 | 3 | const config: Config = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | testMatch: ['/test/**/*.ts'], 7 | } 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /packages/aws-s3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/aws-s3", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "dependencies": { 6 | "@aws-sdk/client-s3": "^3.335.0", 7 | "effect": "3.12.1" 8 | }, 9 | "main": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "files": [ 12 | "dist" 13 | ], 14 | "devDependencies": { 15 | "@types/jest": "^29.5.1", 16 | "jest": "29.5.0", 17 | "ts-jest": "29.1.0", 18 | "typescript": "5.7.2" 19 | }, 20 | "scripts": { 21 | "build": "rimraf dist && tsc -p tsconfig.build.json", 22 | "test": "jest", 23 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 24 | "format": "prettier --write .", 25 | "typecheck": "tsc --noEmit" 26 | }, 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "license": "MIT", 31 | "repository": { 32 | "directory": "packages/aws-s3", 33 | "type": "git", 34 | "url": "https://github.com/embedded-insurance/effect-use.git" 35 | }, 36 | "publishConfig": { 37 | "access": "public" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/aws-s3/src/index.ts: -------------------------------------------------------------------------------- 1 | import { S3 as S3Client, S3ClientConfig } from '@aws-sdk/client-s3' 2 | import * as Effect from 'effect/Effect' 3 | import * as Layer from 'effect/Layer' 4 | import * as Context from 'effect/Context' 5 | 6 | /** 7 | * Tag for the S3 client 8 | */ 9 | export const S3 = Context.GenericTag('@aws-sdk/client-s3') 10 | 11 | export type S3 = S3Client 12 | 13 | /** 14 | * Create a layer for the S3 client 15 | * @param options 16 | */ 17 | export const makeS3Layer = (options: S3ClientConfig = {}) => 18 | Layer.effect( 19 | S3, 20 | Effect.try(() => new S3Client(options)) 21 | ) 22 | -------------------------------------------------------------------------------- /packages/aws-s3/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as Effect from 'effect/Effect' 2 | import * as Layer from 'effect/Layer' 3 | import { pipe } from 'effect/Function' 4 | import { PutObjectCommand } from '@aws-sdk/client-s3' 5 | import { S3 } from '../src' 6 | 7 | const s3CollectWrites = (args: { writes: unknown[] }): Partial => { 8 | return { 9 | send: async (sent: PutObjectCommand) => { 10 | args.writes.push(sent) 11 | }, 12 | } 13 | } 14 | 15 | test('Fake S3 implementation', () => { 16 | let writes: unknown[] = [] 17 | pipe( 18 | Effect.flatMap(S3, (s3) => 19 | Effect.promise(() => 20 | s3.send( 21 | new PutObjectCommand({ 22 | Bucket: 'test-bucket', 23 | Key: 'test-key', 24 | Body: 'test-body', 25 | }) 26 | ) 27 | ) 28 | ), 29 | Effect.provide( 30 | Layer.succeed(S3, s3CollectWrites({ writes }) as unknown as S3) 31 | ), 32 | Effect.runPromise 33 | ).then(() => { 34 | expect( 35 | writes.map( 36 | (x) => 37 | // @ts-expect-error 38 | x.input 39 | ) 40 | ).toEqual([ 41 | { 42 | Bucket: 'test-bucket', 43 | Key: 'test-key', 44 | Body: 'test-body', 45 | }, 46 | ]) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /packages/aws-s3/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/aws-s3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": true, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/brex/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/brex 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ### Patch Changes 10 | 11 | - Updated dependencies [91dec26] 12 | - @effect-use/http-client@5.0.0 13 | 14 | ## 4.0.0 15 | 16 | ### Major Changes 17 | 18 | - aa3f556: use effect 3 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [aa3f556] 23 | - @effect-use/http-client@4.0.0 24 | 25 | ## 3.0.0 26 | 27 | ### Major Changes 28 | 29 | - 513f048: upgrade effect packages 30 | 31 | ### Patch Changes 32 | 33 | - Updated dependencies [513f048] 34 | - @effect-use/http-client@3.0.0 35 | 36 | ## 2.0.0 37 | 38 | ### Major Changes 39 | 40 | - 62aa9e7: upgrade to effect next.61, schema 0.55 41 | 42 | ### Patch Changes 43 | 44 | - Updated dependencies [62aa9e7] 45 | - @effect-use/http-client@2.0.0 46 | 47 | ## 1.0.1 48 | 49 | ### Patch Changes 50 | 51 | - Updated dependencies [fba1a70] 52 | - @effect-use/http-client@1.0.1 53 | 54 | ## 1.0.0 55 | 56 | ### Major Changes 57 | 58 | - 38f0e5b: use effect package, upgrade schema 59 | 60 | ### Patch Changes 61 | 62 | - Updated dependencies [38f0e5b] 63 | - @effect-use/http-client@1.0.0 64 | 65 | ## 0.1.1 66 | 67 | ### Patch Changes 68 | 69 | - Updated dependencies [645ba6d] 70 | - @effect-use/http-client@0.1.1 71 | 72 | ## 0.1.0 73 | 74 | ### Minor Changes 75 | 76 | - 8fe9f3e: latest effect packages 77 | 78 | ### Patch Changes 79 | 80 | - Updated dependencies [8fe9f3e] 81 | - @effect-use/http-client@0.1.0 82 | 83 | ## 0.0.2 84 | 85 | ### Patch Changes 86 | 87 | - e719486: add brex package 88 | -------------------------------------------------------------------------------- /packages/brex/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['/test/**/*.ts'], 5 | passWithNoTests: true, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /packages/brex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/brex", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "devDependencies": { 6 | "@types/jest": "^29.5.2", 7 | "@types/node": "20", 8 | "jest": "29.5.0", 9 | "prettier": "2.8.8", 10 | "ts-jest": "29.1.0", 11 | "ts-node": "^10.9.1", 12 | "typescript": "5.7.2" 13 | }, 14 | "scripts": { 15 | "build": "rimraf dist && tsc -p tsconfig.build.json", 16 | "format": "prettier --write .", 17 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 18 | "test": "jest", 19 | "typecheck": "tsc --noEmit" 20 | }, 21 | "exports": { 22 | ".": "./dist/index.js" 23 | }, 24 | "engines": { 25 | "node": ">=18" 26 | }, 27 | "license": "MIT", 28 | "repository": { 29 | "directory": "packages/brex", 30 | "type": "git", 31 | "url": "https://github.com/embedded-insurance/effect-use.git" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "dependencies": { 37 | "@effect-use/http-client": "workspace:^", 38 | "effect": "3.12.1" 39 | }, 40 | "main": "dist/index.js" 41 | } 42 | -------------------------------------------------------------------------------- /packages/brex/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as HTTP from '@effect-use/http-client' 2 | import * as Context from 'effect/Context' 3 | import { flow, pipe } from 'effect/Function' 4 | import * as Effect from 'effect/Effect' 5 | import * as Layer from 'effect/Layer' 6 | import * as S from 'effect/Schema' 7 | import { 8 | BrexCreateTransferPayload, 9 | CreateTransferResponse, 10 | GetTransferResponse, 11 | GetVendorResponse, 12 | ListTransfersResponse, 13 | ListVendorsResponse, 14 | } from './types' 15 | 16 | export const BrexConfig = S.extend( 17 | S.Struct({ BREX_API_KEY: S.String }), 18 | S.partial(S.Struct({ BREX_BASE_URL: S.String })) 19 | ) 20 | export type BrexConfig = S.Schema.Type 21 | 22 | const defaultBrexAPIURL = 'https://platform.brexapis.com' 23 | 24 | const BrexHTTPClient = Context.GenericTag('brex') 25 | 26 | export type BrexClient = { 27 | Transfer: { 28 | createTransfer: ( 29 | input: CreateTransferArgs 30 | ) => Effect.Effect 31 | getTransfer: (id: string) => Effect.Effect 32 | listTransfers: ( 33 | cursor?: string, 34 | limit?: number 35 | ) => Effect.Effect 36 | } 37 | Vendor: { 38 | getVendor: (id: string) => Effect.Effect 39 | listVendors: () => Effect.Effect 40 | } 41 | } 42 | 43 | export const Brex = Context.GenericTag('brex') 44 | 45 | export const makeBrexClientLayer = ( 46 | config: BrexConfig 47 | ): Layer.Layer => 48 | Layer.sync(Brex, () => { 49 | const http = pipe( 50 | HTTP.make({ 51 | baseURL: config.BREX_BASE_URL || defaultBrexAPIURL, 52 | headers: { 53 | Authorization: `Bearer ${config.BREX_API_KEY}`, 54 | }, 55 | }), 56 | Effect.provideService(HTTP.Fetch, fetch), 57 | Effect.runSync 58 | ) 59 | return { 60 | Transfer: { 61 | createTransfer: flow( 62 | createTransfer, 63 | Effect.provideService(BrexHTTPClient, http), 64 | Effect.withLogSpan('@effect-use/brex-client.createTransfer') 65 | ), 66 | getTransfer: flow( 67 | getTransfer, 68 | Effect.provideService(BrexHTTPClient, http), 69 | Effect.withLogSpan('@effect-use/brex-client.getTransfer') 70 | ), 71 | listTransfers: flow( 72 | listTransfers, 73 | Effect.provideService(BrexHTTPClient, http), 74 | Effect.withLogSpan('@effect-use/brex-client.listTransfers') 75 | ), 76 | }, 77 | Vendor: { 78 | getVendor: flow( 79 | getVendor, 80 | Effect.provideService(BrexHTTPClient, http), 81 | Effect.withLogSpan('@effect-use/brex-client.getVendor') 82 | ), 83 | listVendors: flow( 84 | listVendors, 85 | Effect.provideService(BrexHTTPClient, http), 86 | Effect.withLogSpan('@effect-use/brex-client.listVendors') 87 | ), 88 | }, 89 | } 90 | }) 91 | 92 | /** 93 | * Transfers 94 | * 95 | * https://developer.brex.com/openapi/payments_api/#tag/Transfers 96 | * 97 | */ 98 | const CreateTransferArgs = S.Struct({ 99 | input: BrexCreateTransferPayload, 100 | idempotencyKey: S.String, 101 | }) 102 | type CreateTransferArgs = S.Schema.Type 103 | const createTransfer = ( 104 | args: CreateTransferArgs 105 | ): Effect.Effect => 106 | pipe( 107 | Effect.flatMap(BrexHTTPClient, (client) => 108 | pipe( 109 | Effect.logDebug('Creating Brex transfer'), 110 | Effect.flatMap(() => 111 | client.post({ 112 | path: '/v1/transfers', 113 | headers: { 114 | 'Content-Type': 'application/json', 115 | 'Idempotency-Key': args.idempotencyKey, 116 | }, 117 | body: JSON.stringify(args.input), 118 | }) 119 | ) 120 | ) 121 | ), 122 | Effect.flatMap((response) => 123 | Effect.tryPromise({ try: () => response.json(), catch: (e) => e }) 124 | ), 125 | Effect.tapErrorCause((e) => 126 | Effect.logError('Failed to create Brex transfer', e) 127 | ) 128 | ) 129 | 130 | const getTransfer = ( 131 | id: string 132 | ): Effect.Effect => 133 | Effect.flatMap(BrexHTTPClient, (client) => 134 | pipe( 135 | client.get({ 136 | path: `/v1/transfers?id=${id}`, 137 | }), 138 | Effect.flatMap((response) => 139 | Effect.tryPromise({ try: () => response.json(), catch: (e) => e }) 140 | ), 141 | Effect.tapErrorCause((e) => 142 | Effect.logError(`Failed to retrieve Brex transfer`, e) 143 | ) 144 | ) 145 | ) 146 | 147 | const listTransfers = ( 148 | cursor?: string, 149 | limit?: number 150 | ): Effect.Effect => 151 | Effect.flatMap(BrexHTTPClient, (client) => 152 | pipe( 153 | Effect.Do, 154 | Effect.let( 155 | 'queryParams', 156 | () => 157 | new URLSearchParams( 158 | removeUndefined({ 159 | cursor, 160 | limit, 161 | }) 162 | ) 163 | ), 164 | Effect.let('path', ({ queryParams }) => '/v1/transfers?' + queryParams), 165 | Effect.bind('response', ({ path }) => client.get({ path })), 166 | Effect.flatMap(({ response }) => 167 | Effect.tryPromise({ try: () => response.json(), catch: (e) => e }) 168 | ), 169 | Effect.tapErrorCause((e) => 170 | Effect.logError(`Failed to list Brex transfers`, e) 171 | ) 172 | ) 173 | ) 174 | 175 | /** 176 | * Vendors 177 | * 178 | * https://developer.brex.com/openapi/payments_api/#tag/Vendors 179 | */ 180 | const getVendor = ( 181 | id: string 182 | ): Effect.Effect => 183 | pipe( 184 | Effect.flatMap(BrexHTTPClient, (client) => 185 | client.get({ path: `/v1/vendors?id=${id}` }) 186 | ), 187 | Effect.flatMap((response) => 188 | Effect.tryPromise({ try: () => response.json(), catch: (e) => e }) 189 | ), 190 | Effect.tapErrorCause((e) => 191 | Effect.logError(`Failed to retrieve Brex vendor`, e) 192 | ) 193 | ) 194 | 195 | const listVendors = (): Effect.Effect< 196 | ListVendorsResponse, 197 | unknown, 198 | HTTP.Client 199 | > => 200 | pipe( 201 | BrexHTTPClient, 202 | Effect.tap(() => Effect.logDebug('Listing Brex vendors')), 203 | Effect.flatMap((client) => client.get({ path: `/v1/vendors` })), 204 | Effect.flatMap((response) => 205 | Effect.tryPromise({ try: () => response.json(), catch: (e) => e }) 206 | ), 207 | Effect.tapErrorCause((e) => 208 | Effect.logError(`Failed to retrieve Brex vendors`, e) 209 | ) 210 | ) 211 | 212 | const removeUndefined = (obj: Record): Record => { 213 | Object.keys(obj).forEach((key) => obj[key] === undefined && delete obj[key]) 214 | return obj 215 | } 216 | -------------------------------------------------------------------------------- /packages/brex/src/types/common.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from 'effect/Function' 2 | import * as S from 'effect/Schema' 3 | 4 | /** 5 | * PaymentType 6 | */ 7 | export const PaymentType = S.Literal( 8 | 'ACH', 9 | 'BOOK_TRANSFER', 10 | 'CHEQUE', 11 | 'DOMESTIC_WIRE', 12 | 'INTERNATIONAL_WIRE' 13 | ) 14 | export type PaymentType = S.Schema.Type 15 | 16 | /** 17 | * CurrencyAmount 18 | */ 19 | export const CurrencyAmount = pipe( 20 | S.Number, 21 | S.int(), 22 | S.nonNegative(), 23 | S.annotations({ 24 | description: 'Integer amount in lowest currency denomination', 25 | }) 26 | ) 27 | export type CurrencyAmount = S.Schema.Type 28 | 29 | /** 30 | * Money 31 | */ 32 | export const Money = S.Struct({ 33 | amount: CurrencyAmount, 34 | currency: S.NullOr(S.Literal('USD')), // ISO 4217 format. default: USD 35 | }) 36 | export type Money = S.Schema.Type 37 | -------------------------------------------------------------------------------- /packages/brex/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './transfer' 2 | export * from './vendor' 3 | export * from './common' 4 | -------------------------------------------------------------------------------- /packages/brex/src/types/transfer.ts: -------------------------------------------------------------------------------- 1 | import * as S from 'effect/Schema' 2 | import { Money, PaymentType } from './common' 3 | 4 | /** 5 | * OriginatingAccount 6 | */ 7 | export const OriginatingAccount = S.Struct({ 8 | type: S.Literal('BREX_CASH'), 9 | id: S.String, 10 | }) 11 | export type OriginatingAccount = S.Schema.Type 12 | 13 | /** 14 | * Recipient 15 | */ 16 | export const Recipient = S.Struct({ 17 | type: S.Literal('ACCOUNT_ID', 'PAYMENT_INSTRUMENT_ID'), 18 | id: S.String, 19 | }) 20 | export type Recipient = S.Schema.Type 21 | 22 | /** 23 | * Counterparty 24 | */ 25 | export const CounterpartyType = S.Literal('VENDOR', 'BOOK_TRANSFER') 26 | export type CounterpartyType = S.Schema.Type 27 | 28 | export const Counterparty = S.Struct({ 29 | type: CounterpartyType, 30 | recipient: Recipient, 31 | }) 32 | export type Counterparty = S.Schema.Type 33 | 34 | /** 35 | * TransferStatus 36 | */ 37 | export const TransferStatus = S.Literal( 38 | 'SCHEDULED', // The transaction is scheduled to enter the PROCESSING status. 39 | 'PROCESSING', // Brex has started to process the sending or receiving of this transaction. 40 | 'PENDING_APPROVAL', // The transaction requires approval before it can enter the SCHEDULED or PROCESSING status. 41 | 'PROCESSED', // The money movement has been fully completed, which could mean money sent has arrived. 42 | 'FAILED' // A grouping of multiple terminal states that prevented the transaction from completing. This includes 43 | // a user-cancellation, approval being denied, insufficient funds, failed verifications, etc. 44 | ) 45 | export type TransferStatus = S.Schema.Type 46 | 47 | /** 48 | * CancellationReason 49 | */ 50 | export const CancellationReason = S.Literal( 51 | 'USER_CANCELLED', // The transfer was canceled. 52 | 'INSUFFICIENT_FUNDS', // The transfer could not be sent due to insufficient funds. 53 | 'APPROVAL_DENIED', // The transfer was not sent because it was denied. 54 | 'BLOCKED_BY_POSITIVE_PAY' // The transfer was blocked because of the ACH debit settings. 55 | ) 56 | export type CancellationReason = S.Schema.Type 57 | 58 | /** 59 | * Transfer 60 | */ 61 | const TransferRequired = S.Struct({ 62 | id: S.String, 63 | payment_type: PaymentType, 64 | amount: Money, 65 | originating_account: OriginatingAccount, 66 | status: TransferStatus, 67 | }) 68 | // TODO: they call all these "| null", should these all be wrapped in S.nullable? 69 | const TransferOptional = S.partial( 70 | S.Struct({ 71 | counterparty: S.NullOr( 72 | S.Struct({ 73 | type: CounterpartyType, 74 | payment_instrument_id: S.String, 75 | id: S.String, 76 | routing_number: S.NullOr(S.String), 77 | account_number: S.NullOr(S.String), 78 | }) 79 | ), 80 | description: S.NullOr(S.String), 81 | process_date: S.NullOr(S.String), 82 | cancellation_reason: S.NullOr(S.String), 83 | estimated_delivery_date: S.NullOr(S.String), 84 | creator_user_id: S.NullOr(S.String), 85 | created_at: S.NullOr(S.String), 86 | display_name: S.NullOr(S.String), 87 | external_memo: S.NullOr(S.String), 88 | }) 89 | ) 90 | export const Transfer = S.extend(TransferRequired, TransferOptional) 91 | export type Transfer = S.Schema.Type 92 | 93 | /** 94 | * Create Transfer Payload and response 95 | */ 96 | export const BrexCreateTransferPayload = S.Struct({ 97 | counterparty: Counterparty, 98 | amount: Money, 99 | description: S.String, // internal use 100 | external_memo: S.String, // - 101 | originating_account: OriginatingAccount, 102 | approval_type: S.NullOr(S.Literal('MANUAL')), // MANUAL requires a cash admin to approve the transaction before disbursing funds 103 | }) 104 | export type BrexCreateTransferPayload = S.Schema.Type< 105 | typeof BrexCreateTransferPayload 106 | > 107 | 108 | export const CreateTransferResponse = Transfer 109 | export type CreateTransferResponse = S.Schema.Type< 110 | typeof CreateTransferResponse 111 | > 112 | 113 | /** 114 | * Get Transfer response 115 | */ 116 | export const GetTransferResponse = Transfer 117 | export type GetTransferResponse = S.Schema.Type 118 | 119 | /** 120 | * List Transfers response 121 | */ 122 | const ListTransfersResponseRequired = S.Struct({ 123 | items: S.Array(Transfer), 124 | }) 125 | const ListTransfersResponseOptional = S.partial( 126 | S.Struct({ 127 | next_cursor: S.String, 128 | }) 129 | ) 130 | export const ListTransfersResponse = S.extend( 131 | ListTransfersResponseRequired, 132 | ListTransfersResponseOptional 133 | ) 134 | export type ListTransfersResponse = S.Schema.Type 135 | -------------------------------------------------------------------------------- /packages/brex/src/types/vendor.ts: -------------------------------------------------------------------------------- 1 | import * as S from 'effect/Schema' 2 | 3 | /** 4 | * Payment Account Type 5 | */ 6 | export const PaymentAccountType = S.Literal( 7 | 'ACH', 8 | 'CHEQUE', 9 | 'DOMESTIC_WIRE', 10 | 'INTERNATIONAL_WIRE' 11 | ) 12 | export type PaymentAccountType = S.Schema.Type 13 | 14 | /** 15 | * Address 16 | */ 17 | export const Address = S.partial( 18 | S.Struct({ 19 | line1: S.NullOr(S.String), 20 | line2: S.NullOr(S.String), 21 | city: S.NullOr(S.String), 22 | state: S.NullOr(S.String), 23 | country: S.NullOr(S.String), 24 | postal_code: S.NullOr(S.String), 25 | phone_number: S.NullOr(S.String), 26 | }) 27 | ) 28 | export type Address = S.Schema.Type 29 | 30 | /** 31 | * Payment Account Details 32 | */ 33 | export const PaymentAccountDetails = S.Struct({ 34 | type: PaymentAccountType, 35 | payment_instrument_id: S.String, 36 | routing_number: S.String, 37 | account_number: S.String, 38 | address: Address, 39 | }) 40 | export type PaymentAccountDetails = S.Schema.Type 41 | 42 | /** 43 | * Vendor 44 | */ 45 | const VendorRequired = S.Struct({ 46 | id: S.String, 47 | }) 48 | const VendorOptional = S.partial( 49 | S.Struct({ 50 | company_name: S.String, 51 | email: S.NullOr(S.String), 52 | phone: S.NullOr(S.String), 53 | payment_accounts: S.NullOr(S.Array(PaymentAccountDetails)), 54 | }) 55 | ) 56 | export const Vendor = S.extend(VendorRequired, VendorOptional) 57 | export type Vendor = S.Schema.Type 58 | 59 | /** 60 | * Get Vendor Response 61 | */ 62 | export const GetVendorResponse = Vendor 63 | export type GetVendorResponse = S.Schema.Type 64 | 65 | /** 66 | * List Vendors Response 67 | */ 68 | const ListVendorsResponseOptional = S.partial( 69 | S.Struct({ 70 | next_cursor: S.String, 71 | }) 72 | ) 73 | const ListVendorsResponseRequired = S.Struct({ 74 | items: S.Array(Vendor), 75 | }) 76 | export const ListVendorsResponse = S.extend( 77 | ListVendorsResponseRequired, 78 | ListVendorsResponseOptional 79 | ) 80 | export type ListVendorsResponse = S.Schema.Type 81 | -------------------------------------------------------------------------------- /packages/brex/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/brex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": false, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/gcp-gcs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/gcp-gcs 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 2.0.1 22 | 23 | ### Patch Changes 24 | 25 | - 3941b18: Update import for NodeStreamP 26 | 27 | ## 2.0.0 28 | 29 | ### Major Changes 30 | 31 | - 62aa9e7: upgrade to effect next.61, schema 0.55 32 | 33 | ## 1.1.4 34 | 35 | ### Patch Changes 36 | 37 | - 03d4004: Support cases when errors are not of Error type 38 | 39 | ## 1.1.3 40 | 41 | ### Patch Changes 42 | 43 | - 803942e: Add dependency 44 | 45 | ## 1.1.2 46 | 47 | ### Patch Changes 48 | 49 | - f7fb0ee: Export layers 50 | 51 | ## 1.1.1 52 | 53 | ### Patch Changes 54 | 55 | - d4be62f: Export types 56 | 57 | ## 1.1.0 58 | 59 | ### Minor Changes 60 | 61 | - e27159f: Add download function to GCS 62 | 63 | ## 1.0.0 64 | 65 | ### Major Changes 66 | 67 | - 38f0e5b: use effect package, upgrade schema 68 | 69 | ## 0.2.0 70 | 71 | ### Minor Changes 72 | 73 | - d6138f3: add getPresignedUrl method 74 | 75 | ## 0.1.0 76 | 77 | ### Minor Changes 78 | 79 | - 8fe9f3e: latest effect packages 80 | 81 | ## 0.0.1 82 | 83 | ### Patch Changes 84 | 85 | - 73bbd53: make publishable to npm public 86 | -------------------------------------------------------------------------------- /packages/gcp-gcs/README.md: -------------------------------------------------------------------------------- 1 | # @effect-use/gcp-gcs 2 | -------------------------------------------------------------------------------- /packages/gcp-gcs/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest' 2 | 3 | const config: Config = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | testMatch: ['/test/**/*.ts'], 7 | } 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /packages/gcp-gcs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/gcp-gcs", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "dependencies": { 6 | "@google-cloud/storage": "^6.10.1", 7 | "effect": "3.12.1", 8 | "tmp": "^0.2.1" 9 | }, 10 | "main": "dist/index.js", 11 | "types": "dist/index.d.ts", 12 | "files": [ 13 | "dist" 14 | ], 15 | "devDependencies": { 16 | "jest": "29.5.0", 17 | "typescript": "5.7.2" 18 | }, 19 | "scripts": { 20 | "build": "rimraf dist && tsc -p tsconfig.build.json", 21 | "test": "jest", 22 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 23 | "format": "prettier --write .", 24 | "typecheck": "tsc --noEmit" 25 | }, 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "license": "MIT", 30 | "repository": { 31 | "directory": "packages/gcp-gcs", 32 | "type": "git", 33 | "url": "https://github.com/embedded-insurance/effect-use.git" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/gcp-gcs/src/index.ts: -------------------------------------------------------------------------------- 1 | import { GetSignedUrlResponse, Storage } from '@google-cloud/storage' 2 | import * as Effect from 'effect/Effect' 3 | import * as Context from 'effect/Context' 4 | import fs from 'fs' 5 | import * as NodeStreamP from 'stream/promises' 6 | 7 | export * from './layers' 8 | export type GCS = Storage 9 | export const GCS = Context.GenericTag('@google-cloud/storage') 10 | 11 | export type GCSWriteError = { 12 | _tag: 'GCSWriteError' 13 | message: string 14 | stack: unknown 15 | } 16 | 17 | export type GCSDownloadError = { 18 | _tag: 'GCSDownloadError' 19 | message: string 20 | stack: unknown 21 | } 22 | 23 | export type GCSUrlSigningError = { 24 | _tag: 'GCSUrlSigningError' 25 | message: string 26 | stack: unknown 27 | } 28 | 29 | type Errors = GCSWriteError | GCSDownloadError | GCSUrlSigningError 30 | type GenericMessage= { 31 | message: string 32 | } 33 | const createError = (e: Error |GenericMessage, type: T['_tag']): T => { 34 | if (e instanceof Error) { 35 | return { 36 | _tag: type, 37 | message: e.message, 38 | stack: e.stack, 39 | } as T 40 | } 41 | 42 | return { 43 | _tag: type, 44 | message: `${e.message}`, 45 | stack: 'No stack available', 46 | } as T 47 | } 48 | 49 | /** 50 | * Writes data to key in bucket 51 | * @param bucket 52 | * @param key 53 | * @param data 54 | */ 55 | export const write = ( 56 | bucket: string, 57 | key: string, 58 | data: string | Buffer 59 | ): Effect.Effect => 60 | Effect.flatMap(GCS, (gcs) => 61 | Effect.tryPromise({ 62 | try: () => gcs.bucket(bucket).file(key).save(data), 63 | catch: (e) => createError(e as any, 'GCSWriteError'), 64 | }) 65 | ) 66 | 67 | /** 68 | * Download data from a bucket to tmp local file 69 | * @param bucket 70 | * @param key 71 | */ 72 | export const download = ( 73 | bucket: string, 74 | key: string 75 | ): Effect.Effect => { 76 | const tmp = require('tmp') 77 | const fileName: string = tmp.tmpNameSync() 78 | 79 | return Effect.flatMap(GCS, (gcs) => 80 | Effect.as( 81 | Effect.tryPromise({ 82 | try: (signal) => { 83 | const readStream = gcs.bucket(bucket).file(key).createReadStream() 84 | 85 | return NodeStreamP.pipeline( 86 | readStream, 87 | fs.createWriteStream(fileName), 88 | { signal } 89 | ) 90 | }, 91 | catch: (e) => createError(e as any, 'GCSDownloadError') 92 | }), 93 | fileName 94 | ) 95 | ) 96 | } 97 | 98 | /** 99 | * Returns the presigned URL for a file in a bucket 100 | * @param bucket 101 | * @param key 102 | * @param lifetime as time to live in milliseconds (relative to when URL is created) 103 | */ 104 | export const getPresignedUrl = ( 105 | bucket: string, 106 | key: string, 107 | lifetime: number 108 | ): Effect.Effect => 109 | Effect.flatMap(GCS, (gcs) => 110 | Effect.tryPromise({ 111 | try: () => 112 | gcs 113 | .bucket(bucket) 114 | .file(key) 115 | .getSignedUrl({ 116 | version: 'v4', 117 | action: 'read', 118 | expires: Date.now() + lifetime, 119 | }), 120 | catch: (e) => createError(e as any, 'GCSUrlSigningError') 121 | }) 122 | ) 123 | -------------------------------------------------------------------------------- /packages/gcp-gcs/src/layers.ts: -------------------------------------------------------------------------------- 1 | import { Storage, StorageOptions } from '@google-cloud/storage' 2 | import { Readable } from 'stream' 3 | import * as Layer from 'effect/Layer' 4 | import { GCS } from './index' 5 | import * as Effect from 'effect/Effect' 6 | 7 | export const makeGCSTestLayer = ({ 8 | output = 'ciao!', 9 | throws = false, 10 | writer = [], 11 | }: { 12 | output?: string 13 | throws?: boolean 14 | writer?: unknown[] 15 | }) => 16 | Layer.succeed(GCS, { 17 | bucket: (bucketName: string) => ({ 18 | file: (key: string) => ({ 19 | getSignedUrl: ({ 20 | version, 21 | action, 22 | expires, 23 | }: { 24 | version: string 25 | action: string 26 | expires: string 27 | }) => { 28 | if (throws) { 29 | throw new Error('getSignedUrl: test error') 30 | } 31 | return Promise.resolve([ 32 | `https://gcp.com/${bucketName}/${key}/${version}/${action}/${expires}`, 33 | ]) 34 | }, 35 | save: (body: string) => { 36 | if (throws) { 37 | throw new Error('save: test error') 38 | } 39 | writer.push({ bucketName, key, body }) 40 | return Promise.resolve() 41 | }, 42 | createReadStream: () => 43 | new Readable({ 44 | read() { 45 | if (throws) { 46 | throw new Error('createReadStream: test error') 47 | } 48 | this.push(output) 49 | this.push(null) 50 | }, 51 | }), 52 | }), 53 | }), 54 | } as Storage) 55 | 56 | 57 | /** 58 | * Create a layer for the GCS client 59 | * @param options 60 | */ 61 | export const makeGCSLiveLayer = ( 62 | options?: StorageOptions, 63 | ): Layer.Layer => 64 | Layer.effect( 65 | GCS, 66 | Effect.try(() => new Storage(options)) 67 | ) 68 | -------------------------------------------------------------------------------- /packages/gcp-gcs/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as Effect from 'effect/Effect' 2 | import * as Either from 'effect/Either' 3 | import { pipe } from 'effect/Function' 4 | import { download, getPresignedUrl, write } from '../src' 5 | import fs from 'fs' 6 | import { makeGCSLiveLayer, makeGCSTestLayer } from '../src/layers' 7 | 8 | describe('write', () => { 9 | it('writes a file to a bucket', async () => { 10 | let writer: unknown[] = [] 11 | 12 | const program = Effect.provide( 13 | write('test-bucket', 'test-key', 'test-body'), 14 | makeGCSTestLayer({ writer }) 15 | ) 16 | await Effect.runPromise( 17 | pipe( 18 | program, 19 | Effect.flatMap(() => { 20 | expect(writer).toEqual([ 21 | { 22 | bucketName: 'test-bucket', 23 | key: 'test-key', 24 | body: 'test-body', 25 | }, 26 | ]) 27 | return Effect.void; 28 | }) 29 | ) 30 | ) 31 | }) 32 | 33 | describe('when an error occurs', () => { 34 | it('returns a typed error', async () => { 35 | const program = Effect.provide( 36 | write('test-bucket', 'test-key', 'test-body'), 37 | makeGCSTestLayer({ throws: true }) 38 | ) 39 | await Effect.runPromise( 40 | pipe( 41 | program, 42 | Effect.flatMap((r) => { 43 | expect(r).not.toBeDefined() 44 | return Effect.void; 45 | }), 46 | Effect.catchTag('GCSWriteError', (e) => { 47 | expect(e).toEqual({ 48 | _tag: 'GCSWriteError', 49 | message: 'save: test error', 50 | stack: expect.any(String), 51 | }) 52 | return Effect.void; 53 | }) 54 | ) 55 | ) 56 | }) 57 | }) 58 | 59 | describe('e2e implementation', () => { 60 | it.skip('calls to test that failures are handled', async () => { 61 | const result = await pipe( 62 | write('ei-tech-dev-broker-reportings', 'test-key2', 'test-body'), 63 | Effect.either, 64 | Effect.provide(makeGCSLiveLayer()), 65 | Effect.runPromise 66 | ) 67 | console.log(result) 68 | expect(Either.isLeft(result)).toBe(true) 69 | 70 | }, 40000) 71 | }) 72 | }) 73 | 74 | describe('presigned URL', () => { 75 | it('returns a presigned URL for a file in a bucket', async () => { 76 | const program = Effect.provide( 77 | getPresignedUrl('a-bucket-name', 'file.txt', 1000), 78 | makeGCSTestLayer({}) 79 | ) 80 | const url = await Effect.runPromise(program) 81 | expect(url[0]).toContain('https://gcp.com/a-bucket-name/file.txt/v4/read/') 82 | }) 83 | 84 | describe('when an error occurs', () => { 85 | it('returns a typed error', async () => { 86 | const program = Effect.provide( 87 | getPresignedUrl('a-bucket-name', 'file.txt', 100), 88 | makeGCSTestLayer({ throws: true }) 89 | ) 90 | await Effect.runPromise( 91 | pipe( 92 | program, 93 | Effect.flatMap((r) => { 94 | expect(r).not.toBeDefined() 95 | return Effect.void; 96 | }), 97 | Effect.catchTag('GCSUrlSigningError', (e) => { 98 | expect(e).toEqual({ 99 | _tag: 'GCSUrlSigningError', 100 | message: 'getSignedUrl: test error', 101 | stack: expect.any(String), 102 | }) 103 | return Effect.void; 104 | }) 105 | ) 106 | ) 107 | }) 108 | }) 109 | }) 110 | 111 | describe('download', () => { 112 | it('downloads a file from google cloud storage', async () => { 113 | const output = 'ciao!' 114 | const program = Effect.provide( 115 | download('a-bucket-name', 'file.txt'), 116 | makeGCSTestLayer({ output }) 117 | ) 118 | const filePath = await Effect.runPromise(program) 119 | fs.readFile(filePath, 'utf8', (err, data) => { 120 | expect(data).toEqual(output) 121 | }) 122 | }) 123 | 124 | describe('when an error occurs', () => { 125 | it('returns a typed error', async () => { 126 | const program = Effect.provide( 127 | download('a-bucket-name', 'file.txt'), 128 | makeGCSTestLayer({ throws: true }) 129 | ) 130 | await Effect.runPromise( 131 | pipe( 132 | program, 133 | Effect.flatMap((r) => { 134 | expect(r).not.toBeDefined() 135 | return Effect.void; 136 | }), 137 | Effect.catchTag('GCSDownloadError', (e) => { 138 | expect(e).toEqual({ 139 | _tag: 'GCSDownloadError', 140 | message: 'createReadStream: test error', 141 | stack: expect.any(String), 142 | }) 143 | return Effect.void; 144 | }) 145 | ) 146 | ) 147 | }) 148 | }) 149 | }) 150 | -------------------------------------------------------------------------------- /packages/gcp-gcs/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/gcp-gcs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": true, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/gcp-logging/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/gcp-logging 2 | 3 | ## 6.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 5.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 4.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 3.0.0 22 | 23 | ### Major Changes 24 | 25 | - 62aa9e7: upgrade to effect next.61, schema 0.55 26 | 27 | ## 2.0.1 28 | 29 | ### Patch Changes 30 | 31 | - ff53ed2: Log format 32 | 33 | ## 2.0.0 34 | 35 | ### Major Changes 36 | 37 | - 677e5a0: log to stderr when error, format cause 38 | 39 | ## 1.0.0 40 | 41 | ### Major Changes 42 | 43 | - 38f0e5b: use effect package, upgrade schema 44 | 45 | ### Minor Changes 46 | 47 | - 470ce70: Add LogLayer to replace the default logger 48 | 49 | ## 0.2.0 50 | 51 | ### Minor Changes 52 | 53 | - 8fe9f3e: latest effect packages 54 | 55 | ## 0.1.0 56 | 57 | ### Minor Changes 58 | 59 | - bb6fe94: The default formatter will stringify the messages so the log output is on a single line 60 | 61 | ## 0.0.1 62 | 63 | ### Patch Changes 64 | 65 | - 73bbd53: make publishable to npm public 66 | -------------------------------------------------------------------------------- /packages/gcp-logging/jest.config.ts: -------------------------------------------------------------------------------- 1 | // import type { Config } from 'jest' 2 | 3 | const config = 4 | //: Config 5 | { 6 | preset: 'ts-jest', 7 | testEnvironment: 'node', 8 | testMatch: ['/test/**/*.ts'], 9 | } 10 | 11 | export default config 12 | -------------------------------------------------------------------------------- /packages/gcp-logging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/gcp-logging", 3 | "version": "6.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "devDependencies": { 6 | "@types/jest": "^29.5.2", 7 | "@types/node": "18", 8 | "@types/uuid": "^9.0.1", 9 | "jest": "29.5.0", 10 | "ts-jest": "29.1.0", 11 | "ts-node": "^10.9.1", 12 | "typescript": "5.7.2" 13 | }, 14 | "scripts": { 15 | "build": "rimraf dist && tsc -p tsconfig.build.json", 16 | "format": "prettier --write .", 17 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 18 | "test": "jest", 19 | "typecheck": "tsc --noEmit" 20 | }, 21 | "main": "dist/index.js", 22 | "exports": { 23 | ".": "./dist/index.js", 24 | "./*": "./dist/*.js" 25 | }, 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "license": "MIT", 30 | "repository": { 31 | "directory": "packages/gcp-logging", 32 | "type": "git", 33 | "url": "https://github.com/embedded-insurance/effect-use.git" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | }, 38 | "dependencies": { 39 | "effect": "3.12.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/gcp-logging/src/index.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from 'effect/Function' 2 | import * as Effect from 'effect/Effect' 3 | import * as FiberRef from 'effect/FiberRef' 4 | import * as FiberRefs from 'effect/FiberRefs' 5 | import * as Logger from 'effect/Logger' 6 | import * as Option from 'effect/Option' 7 | import * as List from 'effect/List' 8 | import * as HashMap from 'effect/HashMap' 9 | import * as Cause from 'effect/Cause' 10 | import * as LogLevel from 'effect/LogLevel' 11 | import * as S from 'effect/Schema' 12 | import * as Layer from 'effect/Layer' 13 | import * as LogSpan from 'effect/LogSpan' 14 | 15 | type LogMeta = Record 16 | 17 | const logMeta = FiberRef.unsafeMake({}) 18 | 19 | export const logTrace = (message: string, data: LogMeta) => 20 | Effect.locally(logMeta, data)(Effect.logTrace(message)) 21 | 22 | export const logDebug = (message: string, data: LogMeta) => 23 | Effect.locally(logMeta, data)(Effect.logDebug(message)) 24 | 25 | export const logInfo = (message: string, data: LogMeta) => 26 | Effect.locally(logMeta, data)(Effect.logInfo(message)) 27 | 28 | export const logWarning = (message: string, data: LogMeta) => 29 | Effect.locally(logMeta, data)(Effect.logWarning(message)) 30 | 31 | export const logError = (message: string, data: LogMeta) => 32 | Effect.locally(logMeta, data)(Effect.logError(message)) 33 | 34 | export const logFatal = (message: string, data: LogMeta) => 35 | Effect.locally(logMeta, data)(Effect.logFatal(message)) 36 | 37 | export const defaultLogFunction = (a: LogEntry) => { 38 | if (a.level === 'ERROR' || a.level === 'FATAL') { 39 | console.error(JSON.stringify(a)) 40 | } else { 41 | console.log(JSON.stringify(a)) 42 | } 43 | } 44 | 45 | export type LogEntry = { 46 | annotations: Record 47 | timestamp: string 48 | level: LogLevel.LogLevel['label'] 49 | message: unknown 50 | meta: Record 51 | span?: LogSpan.LogSpan | undefined 52 | parent?: LogSpan.LogSpan | undefined 53 | exception?: string 54 | [GCP_LOG_SPAN_KEY]?: string | undefined 55 | [GCP_LOG_TRACE_KEY]?: string | undefined 56 | } 57 | 58 | export const customLogger = ( 59 | logFn: (a: LogEntry) => void = defaultLogFunction 60 | ) => 61 | Logger.make( 62 | ({ 63 | fiberId, 64 | logLevel, 65 | message, 66 | cause, 67 | context, 68 | spans, 69 | annotations, 70 | date, 71 | }) => { 72 | const meta = FiberRefs.getOrDefault(context, logMeta) 73 | const sp = List.head(spans) 74 | const span = Option.isSome(sp) ? sp.value : undefined 75 | const parent = pipe(List.tail(spans), Option.flatMap(List.head)) 76 | const parentSpan = Option.isSome(parent) ? parent.value : undefined 77 | 78 | const anno = HashMap.reduce(annotations, {}, (a, v, k) => ({ 79 | ...a, 80 | [k]: v, 81 | })) 82 | 83 | let messageOrCause = message 84 | let exception: any = undefined 85 | 86 | if (cause && cause._tag != 'Interrupt') { 87 | messageOrCause = message ?? Cause.pretty(cause) 88 | // TODO: this code could go away when https://github.com/Effect-TS/effect/pull/1756 is merged 89 | switch (cause._tag) { 90 | case 'Fail': 91 | exception = Cause.pretty(cause) 92 | break 93 | case 'Die': 94 | exception = Option.getOrElse(Cause.dieOption(cause), () => ({ 95 | stack: 'Exception came with no defect!', 96 | })) 97 | exception = exception.stack 98 | break 99 | case 'Parallel': 100 | case 'Sequential': 101 | exception = Cause.pretty(cause) 102 | break 103 | } 104 | } 105 | if ( 106 | message && 107 | Array.isArray(message) && 108 | message.length === 1 && 109 | typeof message[0] === 'string' 110 | ) { 111 | message = message[0] 112 | } 113 | 114 | // TODO: should we use logAnnotations for span & trace? 115 | logFn({ 116 | annotations: { 117 | ...anno, 118 | }, 119 | timestamp: date.toISOString(), 120 | level: logLevel.label, 121 | exception, 122 | message, 123 | meta, 124 | span, 125 | parent: parentSpan, 126 | [GCP_LOG_SPAN_KEY]: span?.label, 127 | [GCP_LOG_TRACE_KEY]: parentSpan?.label, 128 | }) 129 | } 130 | ) 131 | 132 | // https://github.com/googleapis/nodejs-logging/blob/main/src/entry.ts 133 | export const GCP_LOG_TRACE_KEY = 'logging.googleapis.com/trace' as const 134 | export const GCP_LOG_SPAN_KEY = 'logging.googleapis.com/spanId' as const 135 | 136 | export const withTrace = 137 | (args: { trace: string; span: string }) => 138 | (effect: Effect.Effect) => 139 | pipe( 140 | effect, 141 | Effect.withLogSpan(args.span), 142 | Effect.annotateLogs(GCP_LOG_TRACE_KEY, args.trace), 143 | Effect.annotateLogs(GCP_LOG_SPAN_KEY, args.span) 144 | ) 145 | 146 | export const LogLevelSchema = S.Literal< 147 | [ 148 | LogLevel.All['_tag'], 149 | LogLevel.Fatal['_tag'], 150 | LogLevel.Error['_tag'], 151 | LogLevel.Warning['_tag'], 152 | LogLevel.Info['_tag'], 153 | LogLevel.Debug['_tag'], 154 | LogLevel.Trace['_tag'], 155 | LogLevel.None['_tag'] 156 | ] 157 | >('All', 'Fatal', 'Error', 'Warning', 'Info', 'Debug', 'Trace', 'None') 158 | 159 | export const LogLayer = (level: LogLevel.Literal) => 160 | Layer.provideMerge( 161 | Logger.replace(Logger.defaultLogger, customLogger()), 162 | Logger.minimumLogLevel(LogLevel.fromLiteral(level)) 163 | ) 164 | -------------------------------------------------------------------------------- /packages/gcp-logging/test/index.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from 'effect/Function' 2 | import * as Effect from 'effect/Effect' 3 | import * as Logger from 'effect/Logger' 4 | import * as Duration from 'effect/Duration' 5 | import * as Clock from 'effect/Clock' 6 | import { ClockTypeId } from 'effect/Clock' 7 | import * as Layer from 'effect/Layer' 8 | import { customLogger, withTrace, logInfo } from '../src' 9 | import { Cause } from 'effect' 10 | 11 | const testClock: Clock.Clock = { 12 | [Clock.ClockTypeId]: ClockTypeId, 13 | get currentTimeMillis(): Effect.Effect { 14 | return Effect.succeed(0) 15 | }, 16 | sleep(duration: Duration.Duration): Effect.Effect { 17 | return Effect.void 18 | }, 19 | unsafeCurrentTimeMillis(): number { 20 | return 0 21 | }, 22 | get currentTimeNanos(): Effect.Effect { 23 | return Effect.succeed(0n) 24 | }, 25 | unsafeCurrentTimeNanos(): bigint { 26 | return 0n 27 | }, 28 | } 29 | 30 | const child = pipe( 31 | Effect.Do, 32 | Effect.tap(() => Effect.log('enter')), 33 | Effect.flatMap(() => Effect.succeed(42)), 34 | Effect.withLogSpan('child') 35 | ) 36 | const parent = pipe(child, Effect.withLogSpan('parent')) 37 | 38 | const makeTestLayer = (onLog: (x: any) => void) => 39 | Layer.mergeAll( 40 | Layer.setClock(testClock), 41 | Logger.replace( 42 | Logger.defaultLogger, 43 | customLogger((a) => { 44 | onLog(a) 45 | }) 46 | ) 47 | ) 48 | 49 | describe('logError', () => { 50 | describe('when logging error with a message and a cause from a handled error', () => { 51 | it('reports the failure', () => { 52 | let log: unknown[] = [] 53 | 54 | pipe( 55 | pipe( 56 | Effect.logError( 57 | 'message in the log', 58 | Cause.fail('cause of the error') 59 | ) 60 | ), 61 | 62 | Effect.provide( 63 | pipe( 64 | Layer.provideMerge( 65 | Layer.succeed(Clock.Clock, testClock), 66 | Logger.replace( 67 | Logger.defaultLogger, 68 | customLogger((a) => { 69 | log.push(a) 70 | }) 71 | ) 72 | ) 73 | ) 74 | ), 75 | Effect.runSync 76 | ) 77 | 78 | expect(log).toEqual([ 79 | { 80 | annotations: {}, 81 | level: 'ERROR', 82 | 'logging.googleapis.com/spanId': undefined, 83 | 'logging.googleapis.com/trace': undefined, 84 | message: 'message in the log', 85 | exception: 'Error: cause of the error', 86 | meta: {}, 87 | parent: undefined, 88 | span: undefined, 89 | timestamp: expect.any(String), 90 | }, 91 | ]) 92 | }) 93 | }) 94 | 95 | describe('when logging error with a message and a cause from an unhandled error', () => { 96 | const functionThatThrowError = () => { 97 | throw Error('an error message') 98 | } 99 | 100 | it('reports the exception', () => { 101 | let log: unknown[] = [] 102 | 103 | pipe( 104 | pipe( 105 | Effect.Do, 106 | Effect.flatMap(() => { 107 | functionThatThrowError() 108 | return Effect.void 109 | }), 110 | Effect.catchAllCause((cause) => 111 | Effect.logError('message in the log', cause) 112 | ) 113 | ), 114 | 115 | Effect.provide( 116 | pipe( 117 | Layer.provideMerge( 118 | Layer.succeed(Clock.Clock, testClock), 119 | Logger.replace( 120 | Logger.defaultLogger, 121 | customLogger((a) => { 122 | log.push(a) 123 | }) 124 | ) 125 | ) 126 | ) 127 | ), 128 | Effect.runSync 129 | ) 130 | expect(log).toEqual([ 131 | { 132 | annotations: {}, 133 | level: 'ERROR', 134 | 'logging.googleapis.com/spanId': undefined, 135 | 'logging.googleapis.com/trace': undefined, 136 | message: 'message in the log', 137 | meta: {}, 138 | exception: expect.stringContaining('functionThatThrowError'), 139 | parent: undefined, 140 | span: undefined, 141 | timestamp: expect.any(String), 142 | }, 143 | ]) 144 | }) 145 | }) 146 | }) 147 | 148 | test('effect versions after span log changes', () => { 149 | let logs: any[] = [] 150 | pipe( 151 | parent, 152 | Effect.provide( 153 | makeTestLayer((x) => { 154 | logs.push(x) 155 | }) 156 | ), 157 | Effect.runSync 158 | ) 159 | expect(logs).toEqual([ 160 | { 161 | annotations: {}, 162 | level: 'INFO', 163 | 'logging.googleapis.com/spanId': 'child', 164 | 'logging.googleapis.com/trace': 'parent', 165 | message: 'enter', 166 | meta: {}, 167 | parent: { 168 | label: 'parent', 169 | startTime: 0, 170 | }, 171 | span: { 172 | label: 'child', 173 | startTime: 0, 174 | }, 175 | timestamp: '1970-01-01T00:00:00.000Z', 176 | }, 177 | ]) 178 | }) 179 | 180 | test('logging - should be ok', () => { 181 | let log: unknown[] = [] 182 | pipe( 183 | pipe( 184 | logInfo('test', { test: 'test' }), 185 | Effect.flatMap(() => 186 | withTrace({ 187 | trace: 'test-trace', 188 | span: 'test-span-child', 189 | })(logInfo('test-child', { test: 'test-child' })) 190 | ), 191 | withTrace({ trace: 'test-trace2', span: 'test-span2' }) 192 | ), 193 | 194 | Effect.provide( 195 | pipe( 196 | Layer.provideMerge( 197 | Layer.succeed(Clock.Clock, testClock), 198 | Logger.replace( 199 | Logger.defaultLogger, 200 | customLogger((a) => { 201 | log.push(a) 202 | }) 203 | ) 204 | ) 205 | ) 206 | ), 207 | Effect.runSync 208 | ) 209 | expect(log).toEqual([ 210 | { 211 | annotations: { 212 | 'logging.googleapis.com/spanId': 'test-span2', 213 | 'logging.googleapis.com/trace': 'test-trace2', 214 | }, 215 | level: 'INFO', 216 | message: 'test', 217 | 'logging.googleapis.com/spanId': 'test-span2', 218 | 'logging.googleapis.com/trace': undefined, 219 | meta: { 220 | test: 'test', 221 | }, 222 | parent: undefined, 223 | span: { 224 | label: 'test-span2', 225 | startTime: expect.any(Number), 226 | }, 227 | timestamp: expect.any(String), 228 | }, 229 | { 230 | annotations: { 231 | 'logging.googleapis.com/spanId': 'test-span-child', 232 | 'logging.googleapis.com/trace': 'test-trace', 233 | }, 234 | 'logging.googleapis.com/spanId': 'test-span-child', 235 | 'logging.googleapis.com/trace': 'test-span2', 236 | level: 'INFO', 237 | message: 'test-child', 238 | meta: { 239 | test: 'test-child', 240 | }, 241 | parent: { 242 | label: 'test-span2', 243 | startTime: expect.any(Number), 244 | }, 245 | span: { 246 | label: 'test-span-child', 247 | startTime: expect.any(Number), 248 | }, 249 | timestamp: expect.any(String), 250 | }, 251 | ]) 252 | }) 253 | -------------------------------------------------------------------------------- /packages/gcp-logging/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/gcp-logging/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": true, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/github/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/github 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - 62aa9e7: upgrade to effect next.61, schema 0.55 26 | 27 | ## 1.0.0 28 | 29 | ### Major Changes 30 | 31 | - 38f0e5b: use effect package, upgrade schema 32 | 33 | ## 0.1.0 34 | 35 | ### Minor Changes 36 | 37 | - 8fe9f3e: latest effect packages 38 | 39 | ## 0.0.1 40 | 41 | ### Patch Changes 42 | 43 | - 73bbd53: make publishable to npm public 44 | -------------------------------------------------------------------------------- /packages/github/README.md: -------------------------------------------------------------------------------- 1 | # @effect-use/github 2 | 3 | > GitHub API for [Effect](https://github.com/Effect-TS/effect) 4 | 5 | This library provides an easy way to make GitHub API calls. 6 | 7 | Usage: 8 | 9 | ```typescript 10 | import { GitHub, getFileContents, makeGitHubLayer } from '@effect-use/github' 11 | 12 | pipe( 13 | { 14 | owner: 'embedded-insurance', 15 | repo: 'effect-use', 16 | path: 'packages/github/README.md', 17 | }, 18 | getFileContents, 19 | Effect.provideLayer(makeGitHubLayer('my-github-token')), 20 | Effect.runPromise 21 | ) 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/github/jest.config.ts: -------------------------------------------------------------------------------- 1 | // import type { Config } from 'jest' 2 | 3 | const config = 4 | //: Config 5 | { 6 | preset: 'ts-jest', 7 | testEnvironment: 'node', 8 | testMatch: ['/test/**/*.ts'], 9 | passWithNoTests: true, 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /packages/github/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/github", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "devDependencies": { 6 | "@types/jest": "^29.5.2", 7 | "@types/node": "20", 8 | "@types/uuid": "^9.0.1", 9 | "jest": "29.5.0", 10 | "ts-jest": "29.1.0", 11 | "ts-node": "^10.9.1", 12 | "typescript": "5.7.2" 13 | }, 14 | "scripts": { 15 | "build": "rimraf dist && tsc -p tsconfig.build.json", 16 | "format": "prettier --write .", 17 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 18 | "test": "jest", 19 | "typecheck": "tsc --noEmit" 20 | }, 21 | "exports": { 22 | ".": "./dist/index.js" 23 | }, 24 | "engines": { 25 | "node": ">=18" 26 | }, 27 | "license": "MIT", 28 | "repository": { 29 | "directory": "packages/github", 30 | "type": "git", 31 | "url": "https://github.com/embedded-insurance/effect-use.git" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "dependencies": { 37 | "@octokit/rest": "^20.0.1", 38 | "effect": "3.12.1" 39 | }, 40 | "main": "dist/index.js" 41 | } 42 | -------------------------------------------------------------------------------- /packages/github/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Effect from 'effect/Effect' 2 | import * as Context from 'effect/Context' 3 | import { Octokit } from '@octokit/rest' 4 | import { pipe } from 'effect/Function' 5 | import * as Layer from 'effect/Layer' 6 | 7 | export const GitHub = Context.GenericTag('GitHub') 8 | 9 | /** 10 | * Returns the contents of a file from a GitHub repository 11 | * 12 | * @example 13 | * ```typescript 14 | * import { GitHub, getFileContents, makeGitHubLayer } from '@effect-use/github' 15 | * 16 | * pipe( 17 | * { 18 | * owner: 'embedded-insurance', 19 | * repo: 'umbrella', 20 | * path: 'packages/effect-github/README.md', 21 | * }, 22 | * getFileContents, 23 | * Effect.provide( makeGitHubLayer('my-github-token')), 24 | * Effect.runPromise 25 | * ) 26 | * ``` 27 | */ 28 | export const getFileContents = (args: { 29 | owner: string 30 | repo: string 31 | path: string 32 | }) => 33 | Effect.flatMap(GitHub, (api) => 34 | pipe( 35 | Effect.tryPromise({ 36 | try: () => 37 | api.repos.getContent({ 38 | headers: { 39 | 'X-GitHub-Api-Version': '2022-11-28', 40 | }, 41 | ...args, 42 | }), 43 | catch: (e) => e, 44 | }), 45 | Effect.flatMap((x) => 46 | Array.isArray(x) ? Effect.fail(x) : Effect.succeed(x) 47 | ) 48 | ) 49 | ) 50 | 51 | /** 52 | * Creates or updates a file in a GitHub repository 53 | * @example 54 | * ```typescript 55 | * import { GitHub, createOrUpdateFileContents, makeGitHubLayer } from '@effect-use/github' 56 | * 57 | * pipe( 58 | * { 59 | * owner: 'embedded-insurance', 60 | * repo: 'effect-use', 61 | * path: 'packages/github/README.md', 62 | * content: 'Hello World!', 63 | * message: 'Update README.md', 64 | * sha: '1234567890', 65 | * committer: { 66 | * name: 'ei-bot', 67 | * email: 'hello@example.com' 68 | * } 69 | * }, 70 | * createOrUpdateFileContents, 71 | * Effect.provideLayer(makeGitHubLayer('my-github-token')), 72 | * Effect.runPromise 73 | * ) 74 | * 75 | * @param args 76 | */ 77 | export const createOrUpdateFileContents = (args: { 78 | owner: string 79 | repo: string 80 | path: string 81 | content: string 82 | message: string 83 | sha: string 84 | committer: { 85 | name: string 86 | email: string 87 | } 88 | }) => 89 | Effect.flatMap(GitHub, (api) => 90 | Effect.tryPromise({ 91 | try: () => 92 | api.request('PUT /repos/{owner}/{repo}/contents/{path}', { 93 | headers: { 'X-GitHub-Api-Version': '2022-11-28' }, 94 | ...args, 95 | }), 96 | catch: (e) => e, 97 | }) 98 | ) 99 | 100 | export const makeGitHubLayer = (token?: string) => 101 | Layer.succeed(GitHub, GitHub.of(new Octokit({ auth: token }))) 102 | -------------------------------------------------------------------------------- /packages/github/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/github/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": true, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/http-client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/http-client 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - 62aa9e7: upgrade to effect next.61, schema 0.55 26 | 27 | ## 1.0.1 28 | 29 | ### Patch Changes 30 | 31 | - fba1a70: fix: patch method typo, missing type def for patch, head methods 32 | 33 | ## 1.0.0 34 | 35 | ### Major Changes 36 | 37 | - 38f0e5b: use effect package, upgrade schema 38 | 39 | ## 0.1.1 40 | 41 | ### Patch Changes 42 | 43 | - 645ba6d: Use tagged errors 44 | 45 | ## 0.1.0 46 | 47 | ### Minor Changes 48 | 49 | - 8fe9f3e: latest effect packages 50 | 51 | ## 0.0.1 52 | 53 | ### Patch Changes 54 | 55 | - 73bbd53: make publishable to npm public 56 | -------------------------------------------------------------------------------- /packages/http-client/jest.config.ts: -------------------------------------------------------------------------------- 1 | // import type { Config } from 'jest' 2 | 3 | const config = 4 | //: Config 5 | { 6 | preset: 'ts-jest', 7 | testEnvironment: 'node', 8 | testMatch: ['/test/**/*.ts'], 9 | passWithNoTests: true, 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /packages/http-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/http-client", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "devDependencies": { 6 | "@types/jest": "^29.5.2", 7 | "@types/node": "20", 8 | "@types/uuid": "^9.0.1", 9 | "jest": "29.5.0", 10 | "ts-jest": "29.1.0", 11 | "ts-node": "^10.9.1", 12 | "typescript": "5.7.2" 13 | }, 14 | "scripts": { 15 | "build": "rimraf dist && tsc -p tsconfig.build.json", 16 | "format": "prettier --write .", 17 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 18 | "test": "jest", 19 | "typecheck": "tsc --noEmit" 20 | }, 21 | "exports": { 22 | ".": "./dist/index.js" 23 | }, 24 | "engines": { 25 | "node": ">=18" 26 | }, 27 | "license": "MIT", 28 | "repository": { 29 | "directory": "packages/http-client", 30 | "type": "git", 31 | "url": "https://github.com/embedded-insurance/effect-use.git" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "dependencies": { 37 | "effect": "3.12.1" 38 | }, 39 | "main": "dist/index.js" 40 | } 41 | -------------------------------------------------------------------------------- /packages/http-client/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Context from 'effect/Context' 2 | import * as Effect from 'effect/Effect' 3 | import { pipe } from 'effect/Function' 4 | import { isRecord } from 'effect/Predicate' 5 | import { TaggedError } from 'effect/Data' 6 | 7 | export type Config = { 8 | /** 9 | * Base URL for all requests 10 | */ 11 | baseURL?: string 12 | /** 13 | * Default headers to be sent with every request 14 | */ 15 | headers?: Record<'Content-Type' | 'Authorization' | string, string> 16 | } 17 | 18 | export class FetchError extends TaggedError('FetchError')<{ 19 | readonly input: { 20 | readonly baseURL?: string 21 | readonly path?: string 22 | readonly method?: string 23 | } 24 | readonly stack?: string 25 | readonly message: unknown 26 | }> {} 27 | 28 | export class InvalidURL extends TaggedError('InvalidURL')<{ 29 | readonly input: string 30 | readonly stack?: string 31 | readonly message: unknown 32 | }> {} 33 | 34 | export class ErrorResponse extends TaggedError('ErrorResponse')<{ 35 | readonly statusCode: number 36 | readonly response: Response 37 | readonly message: unknown 38 | }> {} 39 | 40 | export type Method = ( 41 | args: RequestInit & { 42 | path?: string 43 | headers?: Record 44 | } 45 | ) => Effect.Effect 46 | 47 | export type Client = { 48 | baseUrl: string 49 | headers: Record 50 | get: Method 51 | post: Method 52 | put: Method 53 | delete: Method 54 | patch: Method 55 | head: Method 56 | } 57 | 58 | export type Fetch = (input: string, init: RequestInit) => Promise 59 | export const Fetch = Context.GenericTag('fetch') 60 | 61 | export const makeURL = (input: string): Effect.Effect => 62 | pipe( 63 | Effect.try(() => new URL(input)), 64 | Effect.mapError( 65 | (e) => 66 | new InvalidURL({ 67 | input: input, 68 | stack: (e as Error).stack, 69 | message: (e as Error).message, 70 | }) 71 | ) 72 | ) 73 | 74 | /** 75 | * Creates an HTTP client 76 | * 77 | * @example 78 | * import { Effect, pipe } from 'effect' 79 | * import * as HTTP from '@effect-use/http' 80 | * 81 | * pipe( 82 | * HTTP.make({ 83 | * baseURL: 'https://google.com', 84 | * headers: { 85 | * 'Content-Type': 'application/json', 86 | * 'Authorization': 'Bearer 1234' 87 | * }, 88 | * }), 89 | * Effect.map((http) => http.get({ path: '/hello' })), 90 | * Effect.provideService(HTTP.Fetch, fetch), 91 | * Effect.runPromise 92 | * ) 93 | * @param args 94 | */ 95 | export const make = (args: Config): Effect.Effect => 96 | Effect.map(Fetch, (fetch) => { 97 | const f = ( 98 | method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' 99 | ) => { 100 | const fn: Method = (req) => 101 | pipe( 102 | makeURL(`${args.baseURL || ''}${req.path || ''}`), 103 | Effect.tap((url) => Effect.logDebug(`${method} ${url.toString()}`)), 104 | Effect.flatMap((url) => 105 | Effect.tryPromise({ 106 | try: (signal) => { 107 | return fetch(url.toString(), { 108 | ...req, 109 | method, 110 | signal: req.signal || signal, 111 | headers: { 112 | ...args.headers, 113 | ...req.headers, 114 | }, 115 | }) 116 | }, 117 | catch: (e) => e, 118 | }) 119 | ), 120 | Effect.mapError((e) => { 121 | if (isRecord(e) && e._tag === 'InvalidURL') { 122 | return e as unknown as InvalidURL 123 | } 124 | return new FetchError({ 125 | input: { 126 | baseURL: args.baseURL, 127 | path: req.path, 128 | method, 129 | }, 130 | stack: (e as Error).stack, 131 | message: (e as Error).message, 132 | }) 133 | }), 134 | Effect.flatMap((response) => 135 | response.status >= 400 136 | ? Effect.fail( 137 | new ErrorResponse({ 138 | response: response, 139 | statusCode: response.status, 140 | message: response.statusText, 141 | }) 142 | ) 143 | : Effect.succeed(response) 144 | ), 145 | Effect.tapErrorCause(Effect.logError), 146 | Effect.withLogSpan('@effect-use/http-client') 147 | ) 148 | Object.defineProperty(fn, 'name', { value: method }) 149 | return fn 150 | } 151 | 152 | return { 153 | baseUrl: args.baseURL || '', 154 | headers: args.headers || {}, 155 | get: f('GET'), 156 | post: f('POST'), 157 | put: f('PUT'), 158 | delete: f('DELETE'), 159 | patch: f('PATCH'), 160 | head: f('HEAD'), 161 | } 162 | }) 163 | -------------------------------------------------------------------------------- /packages/http-client/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as HTTP from '../src' 2 | import * as Effect from 'effect/Effect' 3 | import { pipe } from 'effect/Function' 4 | import * as Cause from 'effect/Cause' 5 | 6 | it('returns OK when response is 200', async () => { 7 | const { ok } = await pipe( 8 | Effect.Do, 9 | Effect.bind('http', () => 10 | HTTP.make({ 11 | baseURL: 'https://httpbin.org', 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | Authorization: 'Bearer 1234', 15 | }, 16 | }) 17 | ), 18 | Effect.bind('ok', ({ http }) => 19 | Effect.all( 20 | { 21 | get: pipe( 22 | http.get({ path: '/get' }), 23 | Effect.map((a) => a.ok) 24 | ), 25 | post: pipe( 26 | http.post({ path: '/post' }), 27 | Effect.map((a) => a.ok) 28 | ), 29 | put: pipe( 30 | http.put({ path: '/put' }), 31 | Effect.map((a) => a.ok) 32 | ), 33 | delete: pipe( 34 | http.delete({ path: '/delete' }), 35 | Effect.map((a) => a.ok) 36 | ), 37 | }, 38 | { concurrency: 'unbounded' } 39 | ) 40 | ), 41 | Effect.provideService(HTTP.Fetch, fetch), 42 | Effect.tapError((e) => Effect.logError(Cause.fail(e))), 43 | Effect.runPromise 44 | ) 45 | 46 | expect(ok.get).toEqual(true) 47 | expect(ok.post).toEqual(true) 48 | expect(ok.put).toEqual(true) 49 | expect(ok.delete).toEqual(true) 50 | }) 51 | 52 | it('returns the error message returned by the server with the status code', async () => { 53 | const failure = await pipe( 54 | Effect.Do, 55 | Effect.bind('http', () => 56 | HTTP.make({ 57 | baseURL: 'https://httpbin.org/', 58 | headers: { 59 | 'Content-Type': 'application/json', 60 | Authorization: 'Bearer 1234', 61 | }, 62 | }) 63 | ), 64 | Effect.flatMap(({ http }) => http.get({ path: '/status/404/random' })), 65 | Effect.provideService(HTTP.Fetch, fetch), 66 | Effect.runPromiseExit 67 | ) 68 | // @ts-expect-error 69 | const cause = failure.cause 70 | expect(cause._tag).toEqual('Fail') 71 | 72 | const error = cause.error as HTTP.ErrorResponse 73 | 74 | expect(error.statusCode).toEqual(404) 75 | expect(error.message).toEqual('NOT FOUND') 76 | expect(error.response).toBeInstanceOf(Response) 77 | }) 78 | 79 | it('returns a FetchError response in case of network error', async () => { 80 | const baseURL = 'https://httpbin.org' 81 | const path = '/status/400' 82 | const headers = { 83 | 'Content-Type': 'application/json', 84 | Authorization: 'Bearer 1234', 85 | } 86 | const failure = await pipe( 87 | Effect.Do, 88 | Effect.bind('http', () => 89 | HTTP.make({ 90 | baseURL, 91 | headers, 92 | }) 93 | ), 94 | Effect.bind('failure', ({ http }) => http.get({ path })), 95 | Effect.provideService(HTTP.Fetch, () => 96 | Promise.reject(new TypeError('Network error')) 97 | ), 98 | Effect.runPromiseExit 99 | ) 100 | 101 | // @ts-expect-error 102 | const cause = failure.cause 103 | expect(cause._tag).toEqual('Fail') 104 | expect(cause.error).toBeInstanceOf(HTTP.FetchError) 105 | expect(cause.error.input.baseURL).toEqual(baseURL) 106 | expect(cause.error.input.path).toEqual(path) 107 | expect(cause.error.input.method).toEqual('GET') 108 | expect(cause.error.message).toEqual('Network error') 109 | expect(cause.error.stack).toBeDefined() 110 | }) 111 | -------------------------------------------------------------------------------- /packages/http-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/http-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": false, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/stripe/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/stripe 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - 62aa9e7: upgrade to effect next.61, schema 0.55 26 | 27 | ## 1.0.0 28 | 29 | ### Major Changes 30 | 31 | - 38f0e5b: use effect package, upgrade schema 32 | 33 | ## 0.1.0 34 | 35 | ### Minor Changes 36 | 37 | - 8fe9f3e: latest effect packages 38 | 39 | ## 0.0.2 40 | 41 | ### Patch Changes 42 | 43 | - 088271d: add stripe package 44 | -------------------------------------------------------------------------------- /packages/stripe/jest.config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['/test/**/*.ts'], 5 | passWithNoTests: true, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /packages/stripe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/stripe", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "devDependencies": { 6 | "@types/jest": "^29.5.2", 7 | "@types/node": "20", 8 | "jest": "29.5.0", 9 | "prettier": "2.8.8", 10 | "ts-jest": "29.1.0", 11 | "ts-node": "^10.9.1", 12 | "typescript": "5.7.2" 13 | }, 14 | "scripts": { 15 | "build": "rimraf dist && tsc -p tsconfig.build.json", 16 | "format": "prettier --write .", 17 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 18 | "test": "jest", 19 | "typecheck": "tsc --noEmit" 20 | }, 21 | "exports": { 22 | ".": "./dist/index.js" 23 | }, 24 | "engines": { 25 | "node": ">=18" 26 | }, 27 | "license": "MIT", 28 | "repository": { 29 | "directory": "packages/stripe", 30 | "type": "git", 31 | "url": "https://github.com/embedded-insurance/effect-use.git" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "dependencies": { 37 | "effect": "3.12.1", 38 | "stripe": "^12.17.0" 39 | }, 40 | "main": "dist/index.js" 41 | } 42 | -------------------------------------------------------------------------------- /packages/stripe/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Context from 'effect/Context' 2 | import { flow } from 'effect/Function' 3 | import * as Effect from 'effect/Effect' 4 | import * as Layer from 'effect/Layer' 5 | import * as StripeAPI from 'stripe' 6 | 7 | // https://stripe.com/docs/reports/api 8 | // https://stripe.com/docs/reports/report-types 9 | 10 | const Stripe = Context.GenericTag('stripe') 11 | 12 | export type StripeClient = { 13 | BalanceTransactions: { 14 | getBalanceTransaction: ( 15 | balanceTransactionId: string 16 | ) => Effect.Effect< 17 | StripeAPI.Stripe.Response, 18 | unknown 19 | > 20 | listBalanceTransactions: ( 21 | args: StripeAPI.Stripe.BalanceTransactionListParams 22 | ) => Effect.Effect< 23 | { 24 | data: Array 25 | has_more: boolean 26 | }, 27 | unknown 28 | > 29 | } 30 | Payouts: { 31 | getPayoutById: ( 32 | payoutId: string 33 | ) => Effect.Effect< 34 | StripeAPI.Stripe.Response, 35 | unknown 36 | > 37 | listPayouts: ( 38 | args: StripeAPI.Stripe.PayoutListParams 39 | ) => Effect.Effect< 40 | StripeAPI.Stripe.Response< 41 | StripeAPI.Stripe.ApiList 42 | >, 43 | unknown 44 | > 45 | getPayoutReconciliationReport: () => Effect.Effect< 46 | StripeAPI.Stripe.Response, 47 | unknown 48 | > 49 | } 50 | Invoices: { 51 | getInvoice: ( 52 | invoiceId: string 53 | ) => Effect.Effect< 54 | StripeAPI.Stripe.Response, 55 | unknown 56 | > 57 | } 58 | } 59 | 60 | export const StripeClient = Context.GenericTag('stripe-client') 61 | 62 | export const makeStripeClientLayer = ( 63 | stripeAPIKey: string 64 | ): Layer.Layer => 65 | Layer.sync(StripeClient, () => { 66 | const stripeClient = new StripeAPI.Stripe(stripeAPIKey, { 67 | apiVersion: '2022-11-15', 68 | typescript: true, 69 | }) 70 | return { 71 | Payouts: { 72 | getPayoutById: flow( 73 | getPayoutById, 74 | Effect.provideService(Stripe, stripeClient), 75 | Effect.withLogSpan('@effect-use/stripe.getPayoutById') 76 | ), 77 | listPayouts: flow( 78 | listPayouts, 79 | Effect.provideService(Stripe, stripeClient), 80 | Effect.withLogSpan('@effect-use/stripe.listPayouts') 81 | ), 82 | getPayoutReconciliationReport: flow( 83 | getItemizedPayoutReconciliationReport, 84 | Effect.provideService(Stripe, stripeClient), 85 | Effect.withLogSpan('@effect-use/stripe.getPayoutReconciliationReport') 86 | ), 87 | }, 88 | BalanceTransactions: { 89 | getBalanceTransaction: flow( 90 | getBalanceTransaction, 91 | Effect.provideService(Stripe, stripeClient), 92 | Effect.withLogSpan('@effect-use/stripe.getBalanceTransaction') 93 | ), 94 | listBalanceTransactions: flow( 95 | listBalanceTransactions, 96 | Effect.provideService(Stripe, stripeClient), 97 | Effect.withLogSpan('@effect-use/stripe.listBalanceTransactions') 98 | ), 99 | }, 100 | Invoices: { 101 | getInvoice: flow( 102 | getInvoice, 103 | Effect.provideService(Stripe, stripeClient), 104 | Effect.withLogSpan('@effect-use/stripe.getInvoice') 105 | ), 106 | }, 107 | } 108 | }) 109 | 110 | /** 111 | * Balance Transactions 112 | * 113 | * https://stripe.com/docs/reports/balance 114 | * https://stripe.com/docs/reports/report-types#balance 115 | */ 116 | const getBalanceTransaction = ( 117 | balanceTransactionId: string 118 | ): Effect.Effect< 119 | StripeAPI.Stripe.Response, 120 | unknown, 121 | StripeAPI.Stripe 122 | > => 123 | Effect.flatMap(Stripe, (stripe) => 124 | Effect.tryPromise({ 125 | try: () => stripe.balanceTransactions.retrieve(balanceTransactionId), 126 | catch: (e) => e, 127 | }) 128 | ) 129 | 130 | const listBalanceTransactions = ( 131 | args: StripeAPI.Stripe.BalanceTransactionListParams 132 | ): Effect.Effect< 133 | { 134 | data: Array 135 | has_more: boolean 136 | }, 137 | unknown, 138 | StripeAPI.Stripe 139 | > => 140 | Effect.flatMap(Stripe, (stripe) => 141 | Effect.tryPromise({ 142 | try: () => stripe.balanceTransactions.list(args), 143 | catch: (e) => e, 144 | }) 145 | ) 146 | 147 | /** 148 | * Payouts 149 | * 150 | * https://stripe.com/docs/reports/payout-reconciliation 151 | */ 152 | const getPayoutById = ( 153 | payoutId: string 154 | ): Effect.Effect< 155 | StripeAPI.Stripe.Response, 156 | unknown, 157 | StripeAPI.Stripe 158 | > => 159 | Effect.flatMap(Stripe, (stripe) => 160 | Effect.tryPromise({ 161 | try: () => stripe.payouts.retrieve(payoutId), 162 | catch: (e) => e, 163 | }) 164 | ) 165 | 166 | const listPayouts = ( 167 | args: StripeAPI.Stripe.PayoutListParams 168 | ): Effect.Effect< 169 | StripeAPI.Stripe.Response>, 170 | unknown, 171 | StripeAPI.Stripe 172 | > => 173 | Effect.flatMap(Stripe, (stripe) => 174 | Effect.tryPromise({ try: () => stripe.payouts.list(args), catch: (e) => e }) 175 | ) 176 | 177 | // https://stripe.com/docs/reports/report-types#payout-reconciliation 178 | const getItemizedPayoutReconciliationReport = ( 179 | reportTypeId: '1' | '2' | '3' | '4' | '5' = '5' 180 | ): Effect.Effect< 181 | StripeAPI.Stripe.Response, 182 | unknown, 183 | StripeAPI.Stripe 184 | > => 185 | Effect.flatMap(Stripe, (stripe) => 186 | Effect.tryPromise({ 187 | try: () => 188 | stripe.reporting.reportTypes.retrieve( 189 | `payout_reconciliation.itemized.${reportTypeId}` 190 | ), 191 | catch: (e) => e, 192 | }) 193 | ) 194 | 195 | /** 196 | * Invoices 197 | */ 198 | const getInvoice = ( 199 | invoiceId: string 200 | ): Effect.Effect< 201 | StripeAPI.Stripe.Response, 202 | unknown, 203 | StripeAPI.Stripe 204 | > => 205 | Effect.flatMap(Stripe, (stripe) => 206 | Effect.tryPromise({ 207 | try: () => stripe.invoices.retrieve(invoiceId), 208 | catch: (e) => e, 209 | }) 210 | ) 211 | -------------------------------------------------------------------------------- /packages/stripe/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/stripe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": false, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/temporal-client/.gitignore: -------------------------------------------------------------------------------- 1 | test/test-certs 2 | -------------------------------------------------------------------------------- /packages/temporal-client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/temporal-client 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ### Patch Changes 10 | 11 | - Updated dependencies [91dec26] 12 | - @effect-use/temporal-config@5.0.0 13 | 14 | ## 4.0.0 15 | 16 | ### Major Changes 17 | 18 | - aa3f556: use effect 3 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [aa3f556] 23 | - @effect-use/temporal-config@4.0.0 24 | 25 | ## 3.0.0 26 | 27 | ### Major Changes 28 | 29 | - 513f048: upgrade effect packages 30 | 31 | ### Patch Changes 32 | 33 | - Updated dependencies [513f048] 34 | - @effect-use/temporal-config@3.0.0 35 | 36 | ## 2.0.2 37 | 38 | ### Patch Changes 39 | 40 | - 7b8dbe7: prevent from/to issues with S.Date 41 | 42 | ## 2.0.1 43 | 44 | ### Patch Changes 45 | 46 | - b2a60d7: add client types, use tagged error, remove createSignalLayer 47 | 48 | ## 2.0.0 49 | 50 | ### Major Changes 51 | 52 | - 62aa9e7: upgrade to effect next.61, schema 0.55 53 | 54 | ### Patch Changes 55 | 56 | - Updated dependencies [62aa9e7] 57 | - @effect-use/temporal-config@2.0.0 58 | 59 | ## 1.0.1 60 | 61 | ### Patch Changes 62 | 63 | - 64fccac: latest temporal sdks, add signal with start test 64 | 65 | ## 1.0.0 66 | 67 | ### Major Changes 68 | 69 | - 38f0e5b: use effect package, upgrade schema 70 | 71 | ### Patch Changes 72 | 73 | - Updated dependencies [38f0e5b] 74 | - @effect-use/temporal-config@1.0.0 75 | 76 | ## 0.1.0 77 | 78 | ### Minor Changes 79 | 80 | - 8fe9f3e: latest effect packages 81 | 82 | ### Patch Changes 83 | 84 | - Updated dependencies [8fe9f3e] 85 | - @effect-use/temporal-config@0.1.0 86 | 87 | ## 0.0.1 88 | 89 | ### Patch Changes 90 | 91 | - Updated dependencies [73bbd53] 92 | - @effect-use/temporal-config@0.0.1 93 | -------------------------------------------------------------------------------- /packages/temporal-client/README.md: -------------------------------------------------------------------------------- 1 | # @effect-use/temoral-client 2 | -------------------------------------------------------------------------------- /packages/temporal-client/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest' 2 | 3 | const config: Config = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | testMatch: ['/test/**/*.ts'], 7 | testPathIgnorePatterns: ['test/lib'], 8 | transform: { '^.+\\.ts$': ['ts-jest', { diagnostics: false }] }, 9 | // Trying to avoid writing files to disk. 10 | // FIXME. This means we don't need to build dependent packages 11 | // Problem is all ei-tech packages would need to be at this location... 12 | // not clear why they can't be resolved using yarn workspaces >< 13 | moduleNameMapper: { 14 | '^@effect-use/([^/].*)/(.*)$': '/../$1/src/$2', 15 | '^@effect-use/(.*)$': '/../$1/src', 16 | }, 17 | } 18 | 19 | export default config 20 | -------------------------------------------------------------------------------- /packages/temporal-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/temporal-client", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "main": "dist/index.js", 13 | "types": "dist/index.d.ts", 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "build": "rimraf dist && tsc -p tsconfig.build.json", 19 | "test": "jest --forceExit", 20 | "format": "prettier --write .", 21 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "repository": { 25 | "directory": "packages/temporal-client", 26 | "type": "git", 27 | "url": "https://github.com/embedded-insurance/effect-use.git" 28 | }, 29 | "dependencies": { 30 | "@effect-use/temporal-config": "workspace:^", 31 | "@temporalio/client": "1.8.6", 32 | "@temporalio/workflow": "1.8.6", 33 | "effect": "3.12.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/temporal-client/src/batch.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Payload, 3 | PayloadConverter as TemporalPayloadConverter, 4 | ValueError, 5 | } from '@temporalio/workflow' 6 | import * as Effect from 'effect/Effect' 7 | import * as Context from 'effect/Context' 8 | import * as S from 'effect/Schema' 9 | import { identity, pipe } from 'effect/Function' 10 | import { 11 | startWorkflow, 12 | signal, 13 | signalWithStart, 14 | SignalWithStartInput, 15 | SignalWithStartError, 16 | SignalWithStartOutput, 17 | SignalWorkflowInput, 18 | SignalWorkflowError, 19 | SignalWorkflowOutput, 20 | StartWorkflowInput, 21 | StartWorkflowError, 22 | StartWorkflowOutput, 23 | TemporalClient, 24 | } from './client' 25 | 26 | export const StartWorkflowBatchInput = S.Array(StartWorkflowInput) 27 | export type StartWorkflowBatchInput = S.Schema.Type< 28 | typeof StartWorkflowBatchInput 29 | > 30 | 31 | export const StartWorkflowBatchOutput = S.Struct({ 32 | successes: S.Array(StartWorkflowOutput), 33 | failures: S.Array(StartWorkflowError), 34 | }) 35 | export type StartWorkflowBatchOutput = S.Schema.Type< 36 | typeof StartWorkflowBatchOutput 37 | > 38 | 39 | /** 40 | * Starts workflows in parallel. 41 | * Returns successes and failures as output. 42 | * @param args 43 | */ 44 | export const startWorkflowBatch = ( 45 | args: StartWorkflowBatchInput 46 | ): Effect.Effect => 47 | pipe( 48 | Effect.partition(args.map(startWorkflow), identity, { 49 | batching: true, 50 | concurrency: 'unbounded', 51 | }), 52 | Effect.map(([failures, successes]) => ({ successes, failures })) 53 | // We can do more for a stronger batch API 54 | // https://effect.website/docs/guide/batching-caching 55 | ) 56 | 57 | export const SignalBatchInput = S.Array(SignalWorkflowInput) 58 | export type SignalBatchInput = S.Schema.Type 59 | 60 | export const SignalBatchOutput = S.Struct({ 61 | successes: S.Array(SignalWorkflowOutput), 62 | failures: S.Array(SignalWorkflowError), 63 | }) 64 | export type SignalBatchOutput = S.Schema.Type 65 | 66 | /** 67 | * Runs signals in parallel. 68 | * Returns successes and failures. 69 | * @param args 70 | */ 71 | export const signalBatch = ( 72 | args: SignalBatchInput 73 | ): Effect.Effect => 74 | pipe( 75 | Effect.partition(args.map(signal), identity, { 76 | batching: true, 77 | concurrency: 'unbounded', 78 | }), 79 | Effect.map(([failures, successes]) => ({ successes, failures })) 80 | // We can do more for a stronger batch API 81 | // https://effect.website/docs/guide/batching-caching 82 | ) 83 | 84 | export const SignalWithStartBatchInput = S.Array(SignalWithStartInput) 85 | export type SignalWithStartBatchInput = S.Schema.Type< 86 | typeof SignalWithStartBatchInput 87 | > 88 | 89 | export const SignalWithStartBatchOutput = S.Struct({ 90 | successes: S.Array(SignalWithStartOutput), 91 | failures: S.Array(SignalWithStartError), 92 | }) 93 | export type SignalWithStartBatchOutput = S.Schema.Type< 94 | typeof SignalWithStartBatchOutput 95 | > 96 | 97 | /** 98 | * Signals with starts in parallel. 99 | * Returns successes and failures. 100 | * @param args 101 | */ 102 | export const signalWithStartBatch = ( 103 | args: SignalWithStartBatchInput 104 | ): Effect.Effect => 105 | pipe( 106 | Effect.partition(args.map(signalWithStart), identity, { 107 | batching: true, 108 | concurrency: 'unbounded', 109 | }), 110 | Effect.map(([failures, successes]) => ({ successes, failures })) 111 | // We can do more for a stronger batch API 112 | // https://effect.website/docs/guide/batching-caching 113 | ) 114 | 115 | export type PayloadConverter = TemporalPayloadConverter 116 | export const PayloadConverter = Context.GenericTag( 117 | '@temporalio/workflow.PayloadConverter' 118 | ) 119 | 120 | export const getPayloadConverter = Effect.flatMap(TemporalClient, (client) => 121 | Effect.try(() => client.options.loadedDataConverter.payloadConverter) 122 | ) 123 | 124 | export const convertPayload = ( 125 | payload: any 126 | ): Effect.Effect => 127 | Effect.flatMap(PayloadConverter, (c) => 128 | Effect.try({ 129 | try: () => c.toPayload(payload), 130 | catch: (e) => { 131 | if (e instanceof Error && e.name === 'ValueError') { 132 | return e as ValueError 133 | } 134 | return e 135 | }, 136 | }) 137 | ) 138 | 139 | /** 140 | * Note: You may prefer `signalBatch`. 141 | * 142 | * Wraps Temporal's batch signal API. 143 | * Creates a job with the specified arguments. 144 | * 145 | * You should probably use `signalBatch` because this API appears to lack: 146 | * - Feedback on whether signals were successful 147 | * - Support for a mix of signal types 148 | * - One signal type, different payloads 149 | * @param args 150 | */ 151 | export const batchSignal = (args: { 152 | namespace: string 153 | jobId: string 154 | signal: string 155 | args: Array<{ payload: any[]; workflowId: string; runId?: string }> 156 | }) => 157 | Effect.flatMap(TemporalClient, (client) => { 158 | const conversions = args.args.map((x) => 159 | pipe( 160 | Effect.map(convertPayload(x.payload), (encoded) => ({ 161 | payload: encoded, 162 | workflowId: x.workflowId, 163 | runId: x.runId, 164 | })), 165 | Effect.mapError((e) => ({ 166 | _tag: 'BatchSignalItemDataEncodingError', 167 | error: e, 168 | workflowId: x.workflowId, 169 | runId: x.runId, 170 | })), 171 | Effect.tapError((e) => Effect.logError('Whooops')) 172 | ) 173 | ) 174 | 175 | return pipe( 176 | Effect.partition(conversions, identity, { 177 | batching: true, 178 | concurrency: 'unbounded', 179 | }), 180 | Effect.flatMap(([failedEncodings, signals]) => { 181 | const identifiers = signals.map((x) => ({ 182 | workflowId: x.workflowId, 183 | runId: x.runId || null, 184 | })) 185 | return pipe( 186 | Effect.tryPromise({ 187 | try: () => 188 | client.workflowService.startBatchOperation({ 189 | reason: 'testing', 190 | namespace: args.namespace, 191 | jobId: args.jobId, 192 | executions: identifiers, 193 | signalOperation: { 194 | signal: args.signal, 195 | identity: 'waas-operator@v0.0.0', 196 | input: { 197 | payloads: signals.map((x) => x.payload), 198 | }, 199 | }, 200 | }), 201 | catch: (e) => e, 202 | }), 203 | Effect.map((x) => ({ 204 | failures: failedEncodings, 205 | successes: identifiers, 206 | result: x, 207 | })) 208 | ) 209 | }), 210 | // uses the payload converter from this client 211 | Effect.provideServiceEffect(PayloadConverter, getPayloadConverter) 212 | ) 213 | }) 214 | 215 | export const GoogleProtobufTimestamp = S.Struct({ 216 | seconds: S.String, 217 | nanos: S.Number, 218 | }) 219 | 220 | export const DateFromGoogleProtobufTimestamp = pipe( 221 | GoogleProtobufTimestamp, 222 | S.transform(S.ValidDateFromSelf, { 223 | decode: (x) => new Date(Number(x.seconds) * 1000 + x.nanos / 1000000), 224 | 225 | encode: (x) => ({ 226 | seconds: String(x.getTime() / 1000), 227 | nanos: x.getMilliseconds() * 1000000, 228 | }), 229 | }) 230 | ) 231 | 232 | export const BatchOperationResult = S.Struct({ 233 | operationType: S.Union(S.Literal('BATCH_OPERATION_TYPE_SIGNAL'), S.String), 234 | jobId: S.String, 235 | state: S.Literal( 236 | 'BATCH_OPERATION_STATE_UNSPECIFIED', 237 | 'BATCH_OPERATION_STATE_RUNNING', 238 | 'BATCH_OPERATION_STATE_COMPLETED', 239 | 'BATCH_OPERATION_STATE_FAILED' 240 | ), 241 | startTime: DateFromGoogleProtobufTimestamp, 242 | closeTime: DateFromGoogleProtobufTimestamp, 243 | totalOperationCount: S.NumberFromString, 244 | completeOperationCount: S.NumberFromString, 245 | identity: S.String, 246 | reason: S.String, 247 | }) 248 | 249 | export const describeBatchOperation = (args: { 250 | namespace: string 251 | jobId: string 252 | }) => 253 | Effect.flatMap(TemporalClient, (client) => 254 | pipe( 255 | Effect.tryPromise({ 256 | try: () => 257 | client.workflowService.describeBatchOperation({ 258 | namespace: args.namespace, 259 | jobId: args.jobId, 260 | }), 261 | catch: (e) => e, 262 | }), 263 | Effect.flatMap((x) => 264 | pipe(x.toJSON(), S.decodeUnknown(BatchOperationResult)) 265 | ) 266 | ) 267 | ) 268 | -------------------------------------------------------------------------------- /packages/temporal-client/src/client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Workflow, 3 | WorkflowExecutionDescription, 4 | WorkflowHandle, 5 | WorkflowNotFoundError as TemporalWorkflowNotFoundError, 6 | WorkflowExecutionAlreadyStartedError as TemporalWorkflowExecutionAlreadyStartedError, 7 | WorkflowSignalWithStartOptions, 8 | Client, 9 | Connection, 10 | QueryNotRegisteredError, 11 | QueryRejectedError, 12 | WorkflowStartOptions, 13 | } from '@temporalio/client' 14 | import { Effect, Context, Layer, pipe, Scope } from 'effect' 15 | import * as S from 'effect/Schema' 16 | import { TemporalConfig, TemporalConfigTag } from '@effect-use/temporal-config' 17 | import { 18 | WorkflowExecutionAlreadyStartedError, 19 | WorkflowNotFoundError, 20 | } from './errors' 21 | import { ConnectionLive, TemporalConnection } from './connection' 22 | import { CommonWorkflowOptions } from './types' 23 | 24 | export type TemporalClient = Client 25 | export const TemporalClient = Context.GenericTag( 26 | '@effect-use.temporal-client/TemporalClient' 27 | ) 28 | 29 | export const buildClient = Effect.flatMap( 30 | Effect.all([TemporalConnection, TemporalConfigTag] as const), 31 | ([connection, config]) => 32 | Effect.provide( 33 | TemporalClient, 34 | Layer.effect( 35 | TemporalClient, 36 | Effect.try({ 37 | try: () => new Client({ connection, namespace: config.namespace }), 38 | catch: (e) => e, 39 | }) 40 | ) 41 | ) 42 | ) 43 | 44 | export const makeClient = (args: { 45 | connection: TemporalConnection 46 | config: TemporalConfig 47 | }) => 48 | Effect.try({ 49 | try: () => 50 | new Client({ 51 | connection: args.connection, 52 | namespace: args.config.namespace, 53 | }), 54 | catch: (e) => e, 55 | }) 56 | 57 | export const SignalWithStartInput = S.extend( 58 | S.Struct({ 59 | workflowType: S.String, 60 | workflowId: S.String, 61 | taskQueue: S.String, 62 | signal: S.String, 63 | signalArgs: S.Array(S.Unknown), 64 | }), 65 | CommonWorkflowOptions 66 | ) 67 | 68 | export type SignalWithStartInput = S.Schema.Type 69 | 70 | export const SignalWithStartOutput = pipe( 71 | S.Struct({ 72 | workflowId: S.String, 73 | signaledRunId: S.String, 74 | }), 75 | S.annotations({ 76 | description: 77 | "Signals a running workflow or starts it if it doesn't exist. Echoes the workflowId used to make the request and the run ID of the workflow that was signaled.", 78 | }) 79 | ) 80 | export type SignalWithStartOutput = S.Schema.Type 81 | 82 | export const SignalWithStartError = S.Unknown 83 | export type SignalWithStartError = S.Schema.Type 84 | 85 | export const signalWithStart = ( 86 | args: SignalWithStartInput 87 | ): Effect.Effect => 88 | Effect.flatMap(TemporalClient, (client) => 89 | pipe( 90 | Effect.tryPromise({ 91 | try: () => 92 | client.workflow.signalWithStart( 93 | args.workflowType, 94 | args as unknown as WorkflowSignalWithStartOptions 95 | ), 96 | catch: (e) => e, 97 | }), 98 | Effect.map((x) => ({ 99 | workflowId: x.workflowId, 100 | signaledRunId: x.signaledRunId, 101 | })) 102 | ) 103 | ) 104 | 105 | /** 106 | * Returns a handle to a running workflow or fails if the workflow is not found 107 | * @param args 108 | */ 109 | export const getWorkflowHandle = (args: { 110 | workflowId: string 111 | runId?: string 112 | }): Effect.Effect< 113 | WorkflowHandle, 114 | TemporalWorkflowNotFoundError | unknown, 115 | TemporalClient 116 | > => 117 | Effect.flatMap(TemporalClient, (client) => 118 | pipe( 119 | Effect.sync(() => client.workflow.getHandle(args.workflowId, args.runId)), 120 | Effect.bindTo('handle'), 121 | Effect.flatMap(({ handle }) => 122 | pipe( 123 | Effect.tryPromise< 124 | WorkflowExecutionDescription, 125 | TemporalWorkflowNotFoundError | unknown 126 | >({ 127 | try: () => handle.describe(), 128 | catch: (e) => e, 129 | }), 130 | Effect.map(() => handle) 131 | ) 132 | ) 133 | ) 134 | ) 135 | 136 | export const SignalWorkflowInput = S.extend( 137 | S.Struct({ 138 | workflowId: S.String, 139 | runId: S.optional(S.String), 140 | signal: S.String, 141 | signalArgs: S.Array(S.Unknown), 142 | }), 143 | S.partial(S.Struct({ correlationId: S.String })) 144 | ) 145 | export type SignalWorkflowInput = S.Schema.Type 146 | export const SignalWorkflowOutput = S.extend( 147 | S.Struct({ 148 | workflowId: S.String, 149 | runId: S.String, 150 | taskQueue: S.String, 151 | }), 152 | S.partial( 153 | S.Struct({ 154 | /** 155 | * The correlationId that was provided to `signal`, if any 156 | * Can be used to correlate inputs and outputs when broadcasting signals with signalBatch 157 | */ 158 | correlationId: S.optional(S.String), 159 | }) 160 | ) 161 | ) 162 | export type SignalWorkflowOutput = S.Schema.Type 163 | 164 | export const SignalWorkflowError = S.Union(WorkflowNotFoundError, S.Unknown) 165 | export type SignalWorkflowError = S.Schema.Type 166 | 167 | /** 168 | * Sends a message to a running workflow 169 | * Fails if the workflow is not found 170 | * Returns the runId of the workflow that was signaled 171 | * @param args 172 | */ 173 | export const signal = ( 174 | args: SignalWorkflowInput 175 | ): Effect.Effect => 176 | Effect.flatMap(TemporalClient, (client) => 177 | pipe( 178 | Effect.Do, 179 | Effect.bind('handle', () => 180 | Effect.try(() => client.workflow.getHandle(args.workflowId, args.runId)) 181 | ), 182 | Effect.bind('executionDescription', ({ handle }) => 183 | Effect.tryPromise({ try: () => handle.describe(), catch: (e) => e }) 184 | ), 185 | Effect.bind('result', ({ handle }) => 186 | Effect.tryPromise(() => handle.signal(args.signal, ...args.signalArgs)) 187 | ), 188 | Effect.map(({ result, executionDescription }) => ({ 189 | workflowId: executionDescription.workflowId, 190 | runId: executionDescription.runId, 191 | taskQueue: executionDescription.taskQueue, 192 | correlationId: args.correlationId, 193 | })), 194 | Effect.mapError((e) => { 195 | if (e instanceof TemporalWorkflowNotFoundError) { 196 | return new WorkflowNotFoundError({ 197 | workflowId: args.workflowId, 198 | runId: args.runId, 199 | // TODO. Verify this is the correct source of truth for the namespace that was used for this signal 200 | namespace: client.options.namespace, 201 | correlationId: args.correlationId, 202 | }) 203 | } 204 | return e 205 | }) 206 | ) 207 | ) 208 | 209 | export const StartWorkflowInput = S.extend( 210 | S.Struct({ 211 | workflowId: S.String, 212 | workflowType: S.String, 213 | args: S.optional(S.Array(S.Unknown)), 214 | taskQueue: S.String, 215 | }), 216 | CommonWorkflowOptions 217 | ) 218 | export type StartWorkflowInput = S.Schema.Type 219 | 220 | export const StartWorkflowOutput = S.Struct({ 221 | runId: S.String, 222 | }) 223 | export type StartWorkflowOutput = S.Schema.Type 224 | 225 | export const StartWorkflowError = S.Union( 226 | WorkflowExecutionAlreadyStartedError, 227 | S.Unknown 228 | ) 229 | export type StartWorkflowError = S.Schema.Type 230 | 231 | export const startWorkflow = ( 232 | args: StartWorkflowInput 233 | ): Effect.Effect => 234 | Effect.flatMap(TemporalClient, (client) => 235 | pipe( 236 | Effect.tryPromise({ 237 | try: () => 238 | client.workflow.start( 239 | args.workflowType, 240 | // https://github.com/Effect-TS/schema/issues/305 241 | args as unknown as WorkflowStartOptions 242 | ), 243 | catch: (e: TemporalWorkflowExecutionAlreadyStartedError | unknown) => { 244 | if (e instanceof TemporalWorkflowExecutionAlreadyStartedError) { 245 | return new WorkflowExecutionAlreadyStartedError(e) 246 | } 247 | return e 248 | }, 249 | }), 250 | Effect.map((x) => ({ runId: x.firstExecutionRunId })) 251 | ) 252 | ) 253 | 254 | export const query = (args: { 255 | workflowId: string 256 | queryName: string 257 | queryArgs: unknown[] 258 | }) => 259 | pipe( 260 | getWorkflowHandle({ workflowId: args.workflowId }), 261 | Effect.flatMap((workflow) => 262 | Effect.tryPromise({ 263 | try: () => workflow.query(args.queryName, ...args.queryArgs), 264 | catch: (e: QueryNotRegisteredError | QueryRejectedError | unknown) => e, 265 | }) 266 | ) 267 | ) 268 | 269 | export const createTemporalClientLayer = ( 270 | config: TemporalConfig 271 | ): Layer.Layer => 272 | pipe( 273 | Layer.effect(TemporalClient, buildClient), 274 | Layer.provideMerge(ConnectionLive), 275 | Layer.provideMerge(Layer.effect(TemporalConfigTag, Effect.succeed(config))) 276 | ) 277 | -------------------------------------------------------------------------------- /packages/temporal-client/src/connection.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@temporalio/client' 2 | import { Effect, Context, Scope, Layer, pipe } from 'effect' 3 | import { TemporalConfig, TemporalConfigTag } from '@effect-use/temporal-config' 4 | 5 | export type TemporalConnection = Connection 6 | export const TemporalConnection = Context.GenericTag( 7 | '@effect-use.temporal-client/Connection' 8 | ) 9 | 10 | const optionalConnectionConfig = (config: TemporalConfig) => 11 | config.clientKey && config.clientCert 12 | ? { 13 | tls: { 14 | clientCertPair: { 15 | crt: Buffer.from(config.clientCert), 16 | key: Buffer.from(config.clientKey), 17 | }, 18 | }, 19 | } 20 | : undefined 21 | 22 | export const acquireConnection: Effect.Effect< 23 | Connection, 24 | unknown, 25 | TemporalConfig 26 | > = Effect.flatMap(TemporalConfigTag, (config) => 27 | pipe( 28 | Effect.logDebug('Acquiring Temporal connection...'), 29 | Effect.flatMap(() => 30 | Effect.tryPromise({ 31 | try: () => 32 | Connection.connect({ 33 | address: config.address, 34 | ...optionalConnectionConfig(config), 35 | }), 36 | catch: (e) => e, 37 | }) 38 | ), 39 | Effect.tap(() => Effect.logDebug('Temporal connection acquired.')) 40 | ) 41 | ) 42 | 43 | export const connectionResource: Effect.Effect< 44 | Connection, 45 | unknown, 46 | Scope.Scope | TemporalConfig 47 | > = Effect.acquireRelease(acquireConnection, (conn) => 48 | pipe( 49 | Effect.logDebug('Closing Temporal connection...'), 50 | Effect.flatMap(() => Effect.promise(() => conn.close())), 51 | Effect.tap(() => Effect.logDebug('Temporal connection closed.')) 52 | ) 53 | ) 54 | /** 55 | * @category dependency layer 56 | */ 57 | export const ConnectionLive: Layer.Layer< 58 | Connection, 59 | unknown, 60 | TemporalConfig | Scope.Scope 61 | > = Layer.effect(TemporalConnection, connectionResource) 62 | -------------------------------------------------------------------------------- /packages/temporal-client/src/errors.ts: -------------------------------------------------------------------------------- 1 | import * as S from 'effect/Schema' 2 | 3 | export class WorkflowNotFoundError extends S.TaggedError()( 4 | 'WorkflowNotFoundError', 5 | { 6 | /** 7 | * The workflowId that wasn't found 8 | */ 9 | workflowId: S.String, 10 | /** 11 | * The namespace workflow wasn't found in 12 | */ 13 | namespace: S.String, 14 | /** 15 | * The runId that was provided to `signal`, if any 16 | */ 17 | runId: S.optional(S.String), 18 | /** 19 | * The correlationId that was provided to `signal`, if any 20 | */ 21 | correlationId: S.optional(S.String), 22 | } 23 | ) {} 24 | 25 | export class WorkflowExecutionAlreadyStartedError extends S.TaggedError()( 26 | 'WorkflowExecutionAlreadyStartedError', 27 | { 28 | workflowId: S.String, 29 | workflowType: S.String, 30 | } 31 | ) {} 32 | -------------------------------------------------------------------------------- /packages/temporal-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './batch' 2 | export * from './client' 3 | export * from './connection' 4 | export * from './errors' 5 | export * from './types' 6 | -------------------------------------------------------------------------------- /packages/temporal-client/src/types.ts: -------------------------------------------------------------------------------- 1 | import * as S from 'effect/Schema' 2 | import { pipe } from 'effect' 3 | 4 | export const MsStr = S.TemplateLiteral( 5 | S.Number, 6 | S.Literal('y', 'w', 'd', 'h', 'm', 's', 'ms') 7 | ) 8 | 9 | export const SearchAttributes = S.Record({ 10 | key: S.String, 11 | value: S.Union( 12 | S.Array(S.String), 13 | S.Array(S.Number), 14 | S.Array(S.Boolean) 15 | // S.array(S.Date) 16 | ), 17 | }) 18 | 19 | // Avoid importing the proto implementation to reduce workflow bundle size 20 | // Copied from temporal.api.enums.v1.WorkflowIdReusePolicy 21 | /** 22 | * Concept: {@link https://docs.temporal.io/concepts/what-is-a-workflow-id-reuse-policy/ | Workflow Id Reuse Policy} 23 | * 24 | * Whether a Workflow can be started with a Workflow Id of a Closed Workflow. 25 | * 26 | * *Note: A Workflow can never be started with a Workflow Id of a Running Workflow.* 27 | */ 28 | export enum WorkflowIdReusePolicy { 29 | /** 30 | * No need to use this. 31 | * 32 | * (If a `WorkflowIdReusePolicy` is set to this, or is not set at all, the default value will be used.) 33 | */ 34 | WORKFLOW_ID_REUSE_POLICY_UNSPECIFIED = 0, 35 | 36 | /** 37 | * The Workflow can be started if the previous Workflow is in a Closed state. 38 | * @default 39 | */ 40 | WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE = 1, 41 | 42 | /** 43 | * The Workflow can be started if the previous Workflow is in a Closed state that is not Completed. 44 | */ 45 | WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY = 2, 46 | 47 | /** 48 | * The Workflow cannot be started. 49 | */ 50 | WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE = 3, 51 | 52 | /** 53 | * Terminate the current workflow if one is already running. 54 | */ 55 | WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING = 4, 56 | } 57 | 58 | export const WorkflowDurationOptions = S.Struct({ 59 | /** 60 | * The time after which workflow run is automatically terminated by Temporal service. Do not 61 | * rely on run timeout for business level timeouts. It is preferred to use in workflow timers 62 | * for this purpose. 63 | * 64 | * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} 65 | */ 66 | workflowRunTimeout: S.optional(S.Union(MsStr, S.Number)), 67 | 68 | /** 69 | * 70 | * The time after which workflow execution (which includes run retries and continue as new) is 71 | * automatically terminated by Temporal service. Do not rely on execution timeout for business 72 | * level timeouts. It is preferred to use in workflow timers for this purpose. 73 | * 74 | * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} 75 | */ 76 | workflowExecutionTimeout: S.optional(S.Union(MsStr, S.Number)), 77 | 78 | /** 79 | * Maximum execution time of a single workflow task. Default is 10 seconds. 80 | * 81 | * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} 82 | */ 83 | workflowTaskTimeout: S.optional(S.Union(MsStr, S.Number)), 84 | }) 85 | 86 | export const BaseWorkflowOptions = S.Struct({ 87 | workflowIdReusePolicy: S.optional(S.Enums(WorkflowIdReusePolicy)), 88 | /** 89 | * Optional cron schedule for Workflow. If a cron schedule is specified, the Workflow will run as a cron based on the 90 | * schedule. The scheduling will be based on UTC time. The schedule for the next run only happens after the current 91 | * run is completed/failed/timeout. If a RetryPolicy is also supplied, and the Workflow failed or timed out, the 92 | * Workflow will be retried based on the retry policy. While the Workflow is retrying, it won't schedule its next run. 93 | * If the next schedule is due while the Workflow is running (or retrying), then it will skip that schedule. Cron 94 | * Workflow will not stop until it is terminated or cancelled (by returning temporal.CanceledError). 95 | * https://crontab.guru/ is useful for testing your cron expressions. 96 | */ 97 | chronSchedule: S.optional(S.String), 98 | 99 | /** 100 | * Specifies additional non-indexed information to attach to the Workflow Execution. The values can be anything that 101 | * is serializable by {@link DataConverter}. 102 | */ 103 | 104 | memo: S.optional(S.Record({ key: S.String, value: S.Unknown })), 105 | /** 106 | * Specifies additional indexed information to attach to the Workflow Execution. More info: 107 | * https://docs.temporal.io/docs/typescript/search-attributes 108 | * 109 | * Values are always converted using {@link JsonPayloadConverter}, even when a custom data converter is provided. 110 | */ 111 | searchAttributes: S.optional(SearchAttributes), 112 | 113 | followRuns: S.optional(S.Boolean), 114 | 115 | retry: S.optional( 116 | S.Struct({ 117 | /** 118 | * Coefficient used to calculate the next retry interval. 119 | * The next retry interval is previous interval multiplied by this coefficient. 120 | * @minimum 1 121 | * @default 2 122 | */ 123 | backoffCoefficient: S.optionalWith( 124 | pipe(S.Number, S.greaterThanOrEqualTo(1)), 125 | { default: () => 2 } 126 | ), 127 | 128 | /** 129 | * Interval of the first retry. 130 | * If coefficient is 1 then it is used for all retries 131 | * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} 132 | * @default 1 second 133 | */ 134 | initialInterval: S.optional(S.Union(MsStr, S.Number)), 135 | 136 | /** 137 | * Maximum number of attempts. When exceeded, retries stop (even if {@link ActivityOptions.scheduleToCloseTimeout} 138 | * hasn't been reached). 139 | * 140 | * @default Infinity 141 | */ 142 | maximumAttempts: S.optional(S.Number), 143 | 144 | /** 145 | * Maximum interval between retries. 146 | * Exponential backoff leads to interval increase. 147 | * This value is the cap of the increase. 148 | * 149 | * @default 100x of {@link initialInterval} 150 | * @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string} 151 | */ 152 | maximumInterval: S.optional(S.Union(MsStr, S.Number)), 153 | 154 | /** 155 | * List of application failures types to not retry. 156 | */ 157 | nonRetryableErrorTypes: S.optional(S.Array(S.String)), 158 | }) 159 | ), 160 | }) 161 | 162 | export const CommonWorkflowOptions = S.extend( 163 | BaseWorkflowOptions, 164 | WorkflowDurationOptions 165 | ) 166 | -------------------------------------------------------------------------------- /packages/temporal-client/test/index.ts: -------------------------------------------------------------------------------- 1 | import { TestWorkflowEnvironment } from '@temporalio/testing' 2 | import * as Layer from 'effect/Layer' 3 | import { TemporalClient } from '../src' 4 | import { pipe } from 'effect/Function' 5 | import * as Effect from 'effect/Effect' 6 | import { signalWithStart } from '../src' 7 | import { Worker } from '@temporalio/worker' 8 | import { Trigger } from './lib/Trigger' 9 | 10 | let testEnv: TestWorkflowEnvironment 11 | beforeAll(async () => { 12 | testEnv = await TestWorkflowEnvironment.createLocal() 13 | }) 14 | afterAll(async () => { 15 | await testEnv?.teardown() 16 | }) 17 | test('signalWithStart - should be idempotent', async () => { 18 | const worker = await Worker.create({ 19 | connection: testEnv.nativeConnection, 20 | namespace: 'default', 21 | taskQueue: 'test', 22 | workflowsPath: require.resolve('./lib/a-workflow'), 23 | }) 24 | 25 | const t1 = new Trigger() 26 | let seqId = -1 27 | const signal = () => { 28 | seqId += 1 29 | return pipe( 30 | pipe( 31 | signalWithStart({ 32 | workflowId: 'test', 33 | workflowType: 'aWorkflow', 34 | signal: 'hello', 35 | signalArgs: [{ seqId }], 36 | taskQueue: 'test', 37 | workflowExecutionTimeout: '1m', 38 | }), 39 | Effect.tap((a) => 40 | pipe( 41 | Effect.logInfo('signalWithStart'), 42 | Effect.annotateLogs({ 43 | workflowId: a.workflowId, 44 | signaledRunId: a.signaledRunId, 45 | seqId, 46 | }) 47 | ) 48 | ) 49 | ), 50 | Effect.provide(Layer.succeed(TemporalClient, testEnv.client)), 51 | Effect.either, 52 | Effect.runPromise 53 | ) 54 | } 55 | 56 | await signal() 57 | 58 | const p = worker.runUntil( 59 | // @ts-ignore 60 | t1 61 | ) 62 | 63 | expect(worker.getState() === 'RUNNING').toEqual(true) 64 | 65 | await signal() 66 | 67 | await signal() 68 | 69 | t1.resolve() 70 | 71 | await p 72 | }) 73 | -------------------------------------------------------------------------------- /packages/temporal-client/test/lib/Trigger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A `PromiseLike` helper which exposes its `resolve` and `reject` methods. 3 | * 4 | * Vanilla version, copied from @temporalio/workflow 5 | */ 6 | export class Trigger implements PromiseLike { 7 | // Typescript does not realize that the promise executor is run synchronously in the constructor 8 | // @ts-expect-error 9 | public resolve: (value?: T | PromiseLike) => void 10 | // @ts-expect-error 11 | public reject: (reason?: any) => void 12 | protected readonly promise: Promise 13 | 14 | constructor() { 15 | this.promise = new Promise((resolve, reject) => { 16 | this.resolve = resolve as any 17 | this.reject = reject 18 | }) 19 | // Avoid unhandled rejections(...?) 20 | this.promise.catch(() => undefined) 21 | } 22 | 23 | then( 24 | onfulfilled?: 25 | | ((value: T) => TResult1 | PromiseLike) 26 | | undefined 27 | | null, 28 | onrejected?: 29 | | ((reason: any) => TResult2 | PromiseLike) 30 | | undefined 31 | | null 32 | ): PromiseLike { 33 | return this.promise.then(onfulfilled, onrejected) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/temporal-client/test/lib/a-workflow.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineQuery, 3 | defineSignal, 4 | setHandler, 5 | Trigger, 6 | } from '@temporalio/workflow' 7 | 8 | export const aWorkflow = async () => { 9 | let state = { signals: [] } 10 | const t = new Trigger() 11 | console.log('aWorkflow') 12 | const signal = defineSignal('hello') 13 | // @ts-ignore 14 | setHandler(signal, (args) => { 15 | console.log('signal received', args) 16 | // @ts-ignore 17 | state.signals.push(args) 18 | }) 19 | const query = defineQuery('state') 20 | // @ts-ignore 21 | setHandler(query, () => { 22 | console.log('query received') 23 | return state 24 | }) 25 | 26 | await t 27 | } 28 | -------------------------------------------------------------------------------- /packages/temporal-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/temporal-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": false, 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/temporal-config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-use/temporal-config 2 | 3 | ## 5.0.0 4 | 5 | ### Major Changes 6 | 7 | - 91dec26: Use effect 3.12 8 | 9 | ## 4.0.0 10 | 11 | ### Major Changes 12 | 13 | - aa3f556: use effect 3 14 | 15 | ## 3.0.0 16 | 17 | ### Major Changes 18 | 19 | - 513f048: upgrade effect packages 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - 62aa9e7: upgrade to effect next.61, schema 0.55 26 | 27 | ## 1.0.0 28 | 29 | ### Major Changes 30 | 31 | - 38f0e5b: use effect package, upgrade schema 32 | 33 | ## 0.1.0 34 | 35 | ### Minor Changes 36 | 37 | - 8fe9f3e: latest effect packages 38 | 39 | ## 0.0.1 40 | 41 | ### Patch Changes 42 | 43 | - 73bbd53: make publishable to npm public 44 | -------------------------------------------------------------------------------- /packages/temporal-config/README.md: -------------------------------------------------------------------------------- 1 | # @effect-use/temporal-config 2 | -------------------------------------------------------------------------------- /packages/temporal-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-use/temporal-config", 3 | "version": "5.0.0", 4 | "packageManager": "yarn@3.5.1", 5 | "dependencies": { 6 | "effect": "3.12.1" 7 | }, 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "files": [ 11 | "dist" 12 | ], 13 | "devDependencies": { 14 | "typescript": "5.7.2" 15 | }, 16 | "scripts": { 17 | "build": "rimraf dist && tsc -p tsconfig.build.json", 18 | "format": "prettier --write .", 19 | "clean": "rimraf node_modules & rimraf dist & rimraf .turbo", 20 | "typecheck": "tsc --noEmit" 21 | }, 22 | "engines": { 23 | "node": ">=18" 24 | }, 25 | "license": "MIT", 26 | "repository": { 27 | "directory": "packages/temporal-config", 28 | "type": "git", 29 | "url": "https://github.com/embedded-insurance/effect-use.git" 30 | }, 31 | "publishConfig": { 32 | "access": "public" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/temporal-config/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'effect' 2 | import * as S from 'effect/Schema' 3 | 4 | const TemporalConfigRequired = S.Struct({ 5 | address: S.String, 6 | namespace: S.String, 7 | }) 8 | const TemporalConfigOptional = S.partial( 9 | S.Struct({ 10 | clientCert: S.String, 11 | clientKey: S.String, 12 | }) 13 | ) 14 | /** 15 | * Data required to establish a connection to Temporal. 16 | */ 17 | export const TemporalConfig = S.extend( 18 | TemporalConfigRequired, 19 | TemporalConfigOptional 20 | ) 21 | 22 | /** 23 | * Data required to establish a connection to Temporal. 24 | */ 25 | export type TemporalConfig = S.Schema.Type 26 | 27 | /** 28 | * Data required to establish a connection to Temporal. 29 | */ 30 | export const TemporalConfigTag = Context.GenericTag( 31 | '@effect-use.temporal-config/TemporalConfig' 32 | ) 33 | 34 | const TemporalEnvRequired = S.Struct({ 35 | TEMPORAL_ADDRESS: S.String, 36 | TEMPORAL_NAMESPACE: S.String, 37 | }) 38 | const TemporalEnvOptional = S.partial( 39 | S.Struct({ 40 | TEMPORAL_CLIENT_CERT: S.String, 41 | TEMPORAL_CLIENT_KEY: S.String, 42 | }) 43 | ) 44 | 45 | // const TemporalEnvOptional = S.union( 46 | // S.struct({}), 47 | // S.struct({ 48 | // TEMPORAL_CLIENT_CERT: S.string, 49 | // TEMPORAL_CLIENT_KEY: S.string, 50 | // }) 51 | // ) 52 | 53 | /** 54 | * Data required to establish a connection to Temporal. 55 | */ 56 | export const TemporalEnv = S.extend(TemporalEnvRequired, TemporalEnvOptional) 57 | 58 | /** 59 | * Data required to establish a connection to Temporal. 60 | */ 61 | export type TemporalEnv = S.Schema.Type 62 | 63 | /** 64 | * Data required to establish a connection to Temporal. 65 | */ 66 | export const TemporalEnvTag = Context.GenericTag( 67 | '@effect-use.temporal-config/TemporalEnv' 68 | ) 69 | 70 | /** 71 | * Data required to establish a connection to Temporal. 72 | * @param env 73 | * @category natural transformation 74 | */ 75 | export const configFromEnv = (env: TemporalEnv): TemporalConfig => ({ 76 | address: env.TEMPORAL_ADDRESS, 77 | namespace: env.TEMPORAL_NAMESPACE, 78 | clientCert: env.TEMPORAL_CLIENT_CERT, 79 | clientKey: env.TEMPORAL_CLIENT_KEY, 80 | }) 81 | // 82 | // /** 83 | // * Data required to establish a connection to Temporal. 84 | // * @param env 85 | // * @category natural transformation 86 | // */ 87 | // export const configFromEnv = (env: TemporalEnv): TemporalConfig => ({ 88 | // address: env.TEMPORAL_ADDRESS, 89 | // namespace: env.TEMPORAL_NAMESPACE, 90 | // ...('TEMPORAL_CLIENT_CERT' in env 91 | // ? { 92 | // clientCert: env.TEMPORAL_CLIENT_CERT, 93 | // clientKey: env.TEMPORAL_CLIENT_KEY, 94 | // } 95 | // : undefined), 96 | // }) 97 | -------------------------------------------------------------------------------- /packages/temporal-config/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/temporal-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "moduleResolution": "nodenext", 11 | "exactOptionalPropertyTypes": false, // TODO. this seems mostly not helpful...we use it other places 12 | "skipLibCheck": true, 13 | "outDir": "dist", 14 | "plugins": [ 15 | { 16 | "name": "@effect/language-service" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": [ 6 | "^build" 7 | ], 8 | "outputs": [ 9 | "dist/**" 10 | ], 11 | "cache": false 12 | }, 13 | "test": {}, 14 | "clean": { 15 | "dependsOn": [ 16 | "^clean" 17 | ], 18 | "cache": false 19 | }, 20 | "format": {}, 21 | "typecheck": {} 22 | } 23 | } 24 | --------------------------------------------------------------------------------