graze-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
3
+ "changelog": [
4
+ "@changesets/changelog-github",
5
+ {
6
+ "repo": "<PLACEHOLDER>"
7
+ }
8
+ ],
9
+ "commit": false,
10
+ "fixed": [],
11
+ "linked": [],
12
+ "access": "restricted",
13
+ "baseBranch": "main",
14
+ "updateInternalDependencies": "patch",
15
+ "ignore": []
16
+ }
@@ -0,0 +1,21 @@
1
+ name: Setup
2
+ description: Perform standard setup and install dependencies using pnpm.
3
+ inputs:
4
+ node-version:
5
+ description: The version of Node.js to install
6
+ required: true
7
+ default: 20.16.0
8
+
9
+ runs:
10
+ using: composite
11
+ steps:
12
+ - name: Install pnpm
13
+ uses: pnpm/action-setup@v3
14
+ - name: Install node
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ cache: pnpm
18
+ node-version: ${{ inputs.node-version }}
19
+ - name: Install dependencies
20
+ shell: bash
21
+ run: pnpm install
@@ -0,0 +1,57 @@
1
+ name: Check
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ branches: [main]
7
+ push:
8
+ branches: [main]
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ permissions: {}
15
+
16
+ jobs:
17
+ build:
18
+ name: Build
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 10
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - name: Install dependencies
24
+ uses: ./.github/actions/setup
25
+ - run: pnpm codegen
26
+ - name: Check source state
27
+ run: git add packages/*/src && git diff-index --cached HEAD --exit-code packages/*/src
28
+
29
+ types:
30
+ name: Types
31
+ runs-on: ubuntu-latest
32
+ timeout-minutes: 10
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - name: Install dependencies
36
+ uses: ./.github/actions/setup
37
+ - run: pnpm check
38
+
39
+ lint:
40
+ name: Lint
41
+ runs-on: ubuntu-latest
42
+ timeout-minutes: 10
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - name: Install dependencies
46
+ uses: ./.github/actions/setup
47
+ - run: pnpm lint
48
+
49
+ test:
50
+ name: Test
51
+ runs-on: ubuntu-latest
52
+ timeout-minutes: 10
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - name: Install dependencies
56
+ uses: ./.github/actions/setup
57
+ - run: pnpm test
@@ -0,0 +1,34 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ release:
14
+ if: github.repository_owner == 'Effect-Ts'
15
+ name: Release
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 10
18
+ permissions:
19
+ contents: write
20
+ id-token: write
21
+ pull-requests: write
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - name: Install dependencies
25
+ uses: ./.github/actions/setup
26
+ - name: Create Release Pull Request or Publish
27
+ id: changesets
28
+ uses: changesets/action@v1
29
+ with:
30
+ version: pnpm changeset-version
31
+ publish: pnpm changeset-publish
32
+ env:
33
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,24 @@
1
+ name: Snapshot
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main, next-minor, next-major]
6
+ workflow_dispatch:
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ snapshot:
12
+ name: Snapshot
13
+ if: github.repository_owner == 'Effect-Ts'
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 10
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Install dependencies
19
+ uses: ./.github/actions/setup
20
+ - name: Build package
21
+ run: pnpm build
22
+ - name: Create snapshot
23
+ id: snapshot
24
+ run: pnpx pkg-pr-new@0.0.24 publish --pnpm --comment=off
@@ -0,0 +1,72 @@
1
+ {
2
+ // Place your cadence workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
3
+ // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
4
+ // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
5
+ // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
6
+ // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
7
+ // Placeholders with the same ids are connected.
8
+ // Example:
9
+ // "Print to console": {
10
+ // "scope": "javascript,typescript",
11
+ // "prefix": "log",
12
+ // "body": [
13
+ // "console.log('$1');",
14
+ // "$2"
15
+ // ],
16
+ // "description": "Log output to console"
17
+ // }
18
+ "Gen Function $": {
19
+ "prefix": "gg",
20
+ "body": [
21
+ "function* () {\n\t$0\n}"
22
+ ],
23
+ "description": "Generator function"
24
+ },
25
+ "Gen Function $ (wrapped)": {
26
+ "prefix": "egg",
27
+ "body": [
28
+ "Effect.gen(function* () {\n\t$0\n})"
29
+ ],
30
+ "description": "Generator function"
31
+ },
32
+ "Gen Yield $": {
33
+ "prefix": "yy",
34
+ "body": [
35
+ "yield* $0"
36
+ ],
37
+ "description": "Yield generator"
38
+ },
39
+ "Gen Yield $ (const)": {
40
+ "prefix": "cyy",
41
+ "body": [
42
+ "const $1 = yield* $0"
43
+ ],
44
+ "description": "Yield generator constant"
45
+ },
46
+ "Tagged Class $": {
47
+ "prefix": "etc",
48
+ "body": [
49
+ "export class $1 extends Context.Tag(\"$1\")<$1,I$1> (){",
50
+ "}"
51
+ ]
52
+ },
53
+ "Tagged Error $": {
54
+ "prefix": "ete",
55
+ "body": [
56
+ "export class $1 extends Data.TaggedError(\"$1\")<{",
57
+ "\tmessage: string",
58
+ "\tcause?: unknown",
59
+ "}> {}"
60
+ ]
61
+ },
62
+ "Effect Service $": {
63
+ "prefix": "effs",
64
+ "body": [
65
+ "export class $1 extends Effect.Service<$1>()(\"$1\", {",
66
+ "\teffect: Effect.gen(function* () {",
67
+ "\t\t$0",
68
+ "\t}),",
69
+ "}) {}"
70
+ ]
71
+ },
72
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "recommendations": [
3
+ "effectful-tech.effect-vscode",
4
+ "dbaeumer.vscode-eslint"
5
+ ]
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "typescript.tsdk": "node_modules/typescript/lib",
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
4
+ "editor.formatOnSave": true,
5
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # graze-client
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initialize GrazeClient
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present <PLACEHOLDER>
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.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Graze Client
2
+
3
+ This is a simple Typescript package for interacting with the [Graze](https://graze.social/) API. The API is undocumented and not officially supported, but this package was made with their blessing.
4
+
5
+ It is a bit hastily put together and under development (not all APIs are implemented), but it works.
6
+
7
+ There are two exports:
8
+
9
+ ### 1. A promise-based `GrazeService`
10
+
11
+ ```ts
12
+ import { GrazeService } from "graze-client";
13
+
14
+ const graze = new GrazeService({
15
+ apiUrl: "https://api.graze.social",
16
+ cookie: "my-graze-cookie",
17
+ userId: 15,
18
+ });
19
+
20
+ const myFeeds = await graze.getFeeds();
21
+ const feed = await graze.getFeed({ path: { feedId: 1234 } });
22
+ // hide a post
23
+ await graze.hidePost({
24
+ payload: { algo_id: 1234, at_uri: "at://<did>/app.bsky.feed.post/<rkey>" },
25
+ });
26
+ // update an algorithm
27
+ const hasVideo: Form.Filter = {
28
+ embed_type: ["==", "video"],
29
+ };
30
+ const hasKeyword: Form.Filter = {
31
+ regex_matches: ["text", /Hello World!/.source, true],
32
+ };
33
+ const res = await graze.updateAlgorithm({
34
+ record_name: "my-feed",
35
+ display_name: "My Feed",
36
+ user_id: 1,
37
+ order: "new",
38
+ id: 1234,
39
+ algorithm_manifest: {
40
+ filter: { and: [hasVideo, hasKeyword] },
41
+ },
42
+ });
43
+
44
+ // publish the algorithm
45
+ const res = await graze.publishAlgorithm({ path: { feedId: 1234 } });
46
+ ```
47
+
48
+ ### 2. An [Effect](https://effect.website/) based `GrazeClient`
49
+
50
+ This package is build with [Effect](https://effect.website/). One feature of Effect is the ability to generate typesafe API clients with input/output validation. The `GrazeClient` is an Effect service for performing API requests. A benefit of using Effect is that errors will be typed in addition to success cases.
51
+
52
+ In the below example, the `updateAlgo` effect has the type: `Effect.Effect<void, HttpApiDecodeError | HttpClientError | ParseError, GrazeClient>`, meaning the success value is `void`, the possible errors are `HttpApiDecodeError | HttpClientError | ParseError`, and the effect requires a `GrazeClient` to run.
53
+
54
+ ```ts
55
+ const updateAlgo = Effect.gen(function* () {
56
+ const client = yield* GrazeClient;
57
+
58
+ const res = yield* client.updateAlgorithm({
59
+ record_name: "my-feed",
60
+ display_name: "My Feed",
61
+ user_id: 1,
62
+ order: "new",
63
+ id: 1234,
64
+ algorithm_manifest: {
65
+ filter: {
66
+ and: [
67
+ {
68
+ regex_matches: ["text", /Hello World!/.source, true],
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ });
74
+ });
75
+ ```
76
+
77
+ If you're interested, I recommend reading more in the [Effect](https://effect.website/) docs.
78
+
79
+ # To do
80
+
81
+ - [ ] Organize schema to provide more useful type exports
82
+ - [ ] Add additional filter blocks to `UpdateAlgorithmPayload` schema.
83
+ - [ ] Add API to create a feed.
@@ -0,0 +1,120 @@
1
+ import { fixupPluginRules } from "@eslint/compat"
2
+ import { FlatCompat } from "@eslint/eslintrc"
3
+ import js from "@eslint/js"
4
+ import tsParser from "@typescript-eslint/parser"
5
+ import codegen from "eslint-plugin-codegen"
6
+ import _import from "eslint-plugin-import"
7
+ import simpleImportSort from "eslint-plugin-simple-import-sort"
8
+ import sortDestructureKeys from "eslint-plugin-sort-destructure-keys"
9
+ import path from "node:path"
10
+ import { fileURLToPath } from "node:url"
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = path.dirname(__filename)
14
+ const compat = new FlatCompat({
15
+ baseDirectory: __dirname,
16
+ recommendedConfig: js.configs.recommended,
17
+ allConfig: js.configs.all
18
+ })
19
+
20
+ export default [
21
+ {
22
+ ignores: ["**/dist", "**/build", "**/docs", "**/*.md"]
23
+ },
24
+ ...compat.extends(
25
+ "eslint:recommended",
26
+ "plugin:@typescript-eslint/eslint-recommended",
27
+ "plugin:@typescript-eslint/recommended",
28
+ "plugin:@effect/recommended"
29
+ ),
30
+ {
31
+ plugins: {
32
+ import: fixupPluginRules(_import),
33
+ "sort-destructure-keys": sortDestructureKeys,
34
+ "simple-import-sort": simpleImportSort,
35
+ codegen
36
+ },
37
+
38
+ languageOptions: {
39
+ parser: tsParser,
40
+ ecmaVersion: 2018,
41
+ sourceType: "module"
42
+ },
43
+
44
+ settings: {
45
+ "import/parsers": {
46
+ "@typescript-eslint/parser": [".ts", ".tsx"]
47
+ },
48
+
49
+ "import/resolver": {
50
+ typescript: {
51
+ alwaysTryTypes: true
52
+ }
53
+ }
54
+ },
55
+
56
+ rules: {
57
+ "codegen/codegen": "error",
58
+ "no-fallthrough": "off",
59
+ "no-irregular-whitespace": "off",
60
+ "object-shorthand": "error",
61
+ "prefer-destructuring": "off",
62
+ "sort-imports": "off",
63
+
64
+ "no-restricted-syntax": ["error", {
65
+ selector: "CallExpression[callee.property.name='push'] > SpreadElement.arguments",
66
+ message: "Do not use spread arguments in Array.push"
67
+ }],
68
+
69
+ "no-unused-vars": "off",
70
+ "prefer-rest-params": "off",
71
+ "prefer-spread": "off",
72
+ "import/first": "error",
73
+ "import/newline-after-import": "error",
74
+ "import/no-duplicates": "error",
75
+ "import/no-unresolved": "off",
76
+ "import/order": "off",
77
+ "simple-import-sort/imports": "off",
78
+ "sort-destructure-keys/sort-destructure-keys": "error",
79
+ "deprecation/deprecation": "off",
80
+
81
+ "@typescript-eslint/array-type": ["warn", {
82
+ default: "generic",
83
+ readonly: "generic"
84
+ }],
85
+
86
+ "@typescript-eslint/member-delimiter-style": 0,
87
+ "@typescript-eslint/no-non-null-assertion": "off",
88
+ "@typescript-eslint/ban-types": "off",
89
+ "@typescript-eslint/no-explicit-any": "off",
90
+ "@typescript-eslint/no-empty-interface": "off",
91
+ "@typescript-eslint/consistent-type-imports": "warn",
92
+
93
+ "@typescript-eslint/no-unused-vars": ["error", {
94
+ argsIgnorePattern: "^_",
95
+ varsIgnorePattern: "^_"
96
+ }],
97
+
98
+ "@typescript-eslint/ban-ts-comment": "off",
99
+ "@typescript-eslint/camelcase": "off",
100
+ "@typescript-eslint/explicit-function-return-type": "off",
101
+ "@typescript-eslint/explicit-module-boundary-types": "off",
102
+ "@typescript-eslint/interface-name-prefix": "off",
103
+ "@typescript-eslint/no-array-constructor": "off",
104
+ "@typescript-eslint/no-use-before-define": "off",
105
+ "@typescript-eslint/no-namespace": "off",
106
+
107
+ "@effect/dprint": ["error", {
108
+ config: {
109
+ indentWidth: 2,
110
+ lineWidth: 120,
111
+ semiColons: "asi",
112
+ quoteStyle: "alwaysDouble",
113
+ trailingCommas: "never",
114
+ operatorPosition: "maintain",
115
+ "arrowFunction.useParentheses": "force"
116
+ }
117
+ }]
118
+ }
119
+ }
120
+ ]
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "graze-client",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "A basic Effect package",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/kristojorg/graze-client"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "directory": "."
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/dts/index.d.ts",
18
+ "import": "./dist/esm/index.js",
19
+ "default": "./dist/cjs/index.js"
20
+ },
21
+ "./effect": {
22
+ "types": "./dist/dts/GrazeClient.d.ts",
23
+ "import": "./dist/esm/GrazeClient.js",
24
+ "default": "./dist/cjs/GrazeClient.js"
25
+ },
26
+ "./schema": {
27
+ "types": "./dist/dts/schema/index.d.ts",
28
+ "import": "./dist/esm/schema/index.js",
29
+ "default": "./dist/cjs/schema/index.js"
30
+ }
31
+ },
32
+ "scripts": {
33
+ "codegen": "build-utils prepare-v2",
34
+ "build": "bun build-esm && bun build-annotate && bun build-cjs && build-utils pack-v2",
35
+ "build-esm": "tsc -b tsconfig.build.json",
36
+ "build-cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps",
37
+ "build-annotate": "babel build/esm --plugins annotate-pure-calls --out-dir build/esm --source-maps",
38
+ "check": "tsc -b tsconfig.json",
39
+ "lint": "eslint \"**/{src,test,examples,scripts,dtslint}/**/*.{ts,mjs}\"",
40
+ "lint-fix": "bun lint --fix",
41
+ "test": "vitest",
42
+ "coverage": "vitest --coverage",
43
+ "changeset-version": "changeset version",
44
+ "changeset-publish": "bun build && TEST_DIST= bun vitest && changeset publish"
45
+ },
46
+ "dependencies": {
47
+ "@effect/platform": "^0.75.1",
48
+ "effect": "latest"
49
+ },
50
+ "devDependencies": {
51
+ "@babel/cli": "^7.24.8",
52
+ "@babel/core": "^7.25.2",
53
+ "@babel/plugin-transform-export-namespace-from": "^7.24.7",
54
+ "@babel/plugin-transform-modules-commonjs": "^7.24.8",
55
+ "@changesets/changelog-github": "^0.5.0",
56
+ "@changesets/cli": "^2.27.8",
57
+ "@effect/build-utils": "^0.7.7",
58
+ "@effect/eslint-plugin": "^0.2.0",
59
+ "@effect/language-service": "^0.1.0",
60
+ "@effect/vitest": "latest",
61
+ "@eslint/compat": "1.1.1",
62
+ "@eslint/eslintrc": "3.1.0",
63
+ "@eslint/js": "9.10.0",
64
+ "@types/node": "^22.5.2",
65
+ "@typescript-eslint/eslint-plugin": "^8.4.0",
66
+ "@typescript-eslint/parser": "^8.4.0",
67
+ "babel-plugin-annotate-pure-calls": "^0.4.0",
68
+ "eslint": "^9.10.0",
69
+ "eslint-import-resolver-typescript": "^3.6.3",
70
+ "eslint-plugin-codegen": "^0.28.0",
71
+ "eslint-plugin-import": "^2.30.0",
72
+ "eslint-plugin-simple-import-sort": "^12.1.1",
73
+ "eslint-plugin-sort-destructure-keys": "^2.0.0",
74
+ "tsx": "^4.17.0",
75
+ "typescript": "^5.6.2",
76
+ "vitest": "^2.0.5"
77
+ },
78
+ "effect": {
79
+ "generateExports": {
80
+ "include": [
81
+ "**/*.ts"
82
+ ]
83
+ },
84
+ "generateIndex": {
85
+ "include": [
86
+ "**/*.ts"
87
+ ]
88
+ }
89
+ },
90
+ "patchedDependencies": {
91
+ "@changesets/get-github-info@0.6.0": "patches/@changesets__get-github-info@0.6.0.patch",
92
+ "babel-plugin-annotate-pure-calls@0.4.0": "patches/babel-plugin-annotate-pure-calls@0.4.0.patch"
93
+ }
94
+ }
@@ -0,0 +1,48 @@
1
+ diff --git a/dist/changesets-get-github-info.cjs.js b/dist/changesets-get-github-info.cjs.js
2
+ index a74df59f8a5988f458a3476087399f5e6dfe4818..ce5e60ef9916eb0cb76ab1e9dd422abcad752bf6 100644
3
+ --- a/dist/changesets-get-github-info.cjs.js
4
+ +++ b/dist/changesets-get-github-info.cjs.js
5
+ @@ -251,18 +251,13 @@ async function getInfo(request) {
6
+ b = new Date(b.mergedAt);
7
+ return a > b ? 1 : a < b ? -1 : 0;
8
+ })[0] : null;
9
+ -
10
+ - if (associatedPullRequest) {
11
+ - user = associatedPullRequest.author;
12
+ - }
13
+ -
14
+ return {
15
+ user: user ? user.login : null,
16
+ pull: associatedPullRequest ? associatedPullRequest.number : null,
17
+ links: {
18
+ commit: `[\`${request.commit.slice(0, 7)}\`](${data.commitUrl})`,
19
+ pull: associatedPullRequest ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` : null,
20
+ - user: user ? `[@${user.login}](${user.url})` : null
21
+ + user: user ? `@${user.login}` : null
22
+ }
23
+ };
24
+ }
25
+ diff --git a/dist/changesets-get-github-info.esm.js b/dist/changesets-get-github-info.esm.js
26
+ index 27e5c972ab1202ff16f5124b471f4bbcc46be2b5..3940a8fe86e10cb46d8ff6436dea1103b1839927 100644
27
+ --- a/dist/changesets-get-github-info.esm.js
28
+ +++ b/dist/changesets-get-github-info.esm.js
29
+ @@ -242,18 +242,13 @@ async function getInfo(request) {
30
+ b = new Date(b.mergedAt);
31
+ return a > b ? 1 : a < b ? -1 : 0;
32
+ })[0] : null;
33
+ -
34
+ - if (associatedPullRequest) {
35
+ - user = associatedPullRequest.author;
36
+ - }
37
+ -
38
+ return {
39
+ user: user ? user.login : null,
40
+ pull: associatedPullRequest ? associatedPullRequest.number : null,
41
+ links: {
42
+ commit: `[\`${request.commit.slice(0, 7)}\`](${data.commitUrl})`,
43
+ pull: associatedPullRequest ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` : null,
44
+ - user: user ? `[@${user.login}](${user.url})` : null
45
+ + user: user ? `@${user.login}` : null
46
+ }
47
+ };
48
+ }
@@ -0,0 +1,13 @@
1
+ diff --git a/lib/index.js b/lib/index.js
2
+ index 2182884e21874ebb37261e2375eec08ad956fc9a..ef5630199121c2830756e00c7cc48cf1078c8207 100644
3
+ --- a/lib/index.js
4
+ +++ b/lib/index.js
5
+ @@ -78,7 +78,7 @@ const isInAssignmentContext = path => {
6
+
7
+ parentPath = _ref.parentPath;
8
+
9
+ - if (parentPath.isVariableDeclaration() || parentPath.isAssignmentExpression()) {
10
+ + if (parentPath.isVariableDeclaration() || parentPath.isAssignmentExpression() || parentPath.isClassDeclaration()) {
11
+ return true;
12
+ }
13
+ } while (parentPath !== statement);
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "declaration": false,
6
+ "declarationMap": false,
7
+ "composite": false,
8
+ "incremental": false
9
+ }
10
+ }
package/setupTests.ts ADDED
@@ -0,0 +1,3 @@
1
+ import * as it from "@effect/vitest"
2
+
3
+ it.addEqualityTesters()
@@ -0,0 +1,121 @@
1
+ import {
2
+ AlgoUpdateResponse,
3
+ GetAlgoResponse,
4
+ GetAlgorithmsResponse,
5
+ HidePostBody,
6
+ } from "./schema/shared.js";
7
+ import {
8
+ FetchHttpClient,
9
+ HttpApi,
10
+ HttpApiClient,
11
+ HttpApiEndpoint,
12
+ HttpApiGroup,
13
+ HttpApiSchema,
14
+ HttpClient,
15
+ HttpClientRequest,
16
+ } from "@effect/platform";
17
+ import { Config, Data, Effect, Redacted, Schema } from "effect";
18
+
19
+ import * as Form from "./schema/UpdateAlgorithmPayload.js";
20
+
21
+ function isStringOrBlob(val: unknown): val is string | Blob {
22
+ return typeof val === "string" || val instanceof Blob;
23
+ }
24
+
25
+ const encodeForm = (obj: Form.FullForm) =>
26
+ Effect.gen(function* () {
27
+ const encoded = yield* Schema.encode(Form.FullForm)(obj);
28
+ const formData = new FormData();
29
+ for (const key in encoded) {
30
+ if (encoded.hasOwnProperty(key)) {
31
+ const val = encoded[key as keyof Form.FullForm];
32
+ if (isStringOrBlob(val)) {
33
+ formData.append(key, val);
34
+ }
35
+ }
36
+ }
37
+ return formData;
38
+ });
39
+
40
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString);
41
+ const FeedIdParam = HttpApiSchema.param("feedId", Schema.NumberFromString);
42
+
43
+ const GrazeApiGroup = HttpApiGroup.make("graze", { topLevel: true })
44
+ .add(
45
+ HttpApiEndpoint.get("getFeeds")`/app/my_feeds`
46
+ .addSuccess(GetAlgorithmsResponse)
47
+ .setPayload(
48
+ Schema.Struct({
49
+ user_id: UserIdParam,
50
+ })
51
+ )
52
+ )
53
+ .add(
54
+ HttpApiEndpoint.get("getFeed")`/app/my_feeds/${FeedIdParam}`.addSuccess(
55
+ GetAlgoResponse
56
+ )
57
+ )
58
+ .add(
59
+ HttpApiEndpoint.post("updateAlgorithm")`/app/edit_algo_image`
60
+ .setPayload(HttpApiSchema.Multipart(Form.FullForm))
61
+ .addSuccess(AlgoUpdateResponse)
62
+ )
63
+ .add(
64
+ HttpApiEndpoint.get(
65
+ "publishAlgorithm"
66
+ )`/app/publish_algo/${FeedIdParam}`.addSuccess(Schema.Unknown)
67
+ )
68
+ .add(
69
+ HttpApiEndpoint.post("hidePost")`/app/hide_post`
70
+ .setPayload(HidePostBody)
71
+ .addSuccess(Schema.Unknown)
72
+ );
73
+
74
+ class GrazeApiDef extends HttpApi.make("grazeApi").add(GrazeApiGroup) {}
75
+
76
+ const grazeLive = Effect.gen(function* () {
77
+ const baseUrl = yield* Config.url("GRAZE_API_URL");
78
+ const cookie = yield* Config.redacted("GRAZE_COOKIE");
79
+
80
+ const client = yield* HttpApiClient.make(GrazeApiDef, {
81
+ baseUrl: baseUrl.toString(),
82
+ transformClient: (client) =>
83
+ client.pipe(
84
+ HttpClient.filterStatusOk,
85
+ HttpClient.mapRequest(
86
+ HttpClientRequest.setHeaders({
87
+ cookie: Redacted.value(cookie),
88
+ })
89
+ )
90
+ ),
91
+ });
92
+
93
+ const updateAlgorithm = (data: Form.FullForm) =>
94
+ Effect.gen(function* () {
95
+ const formData = yield* encodeForm(data);
96
+ return yield* client.updateAlgorithm({ payload: formData });
97
+ });
98
+
99
+ const userId = yield* Config.integer("GRAZE_USER_ID");
100
+ const getFeeds = () => client.getFeeds({ payload: { user_id: userId } });
101
+
102
+ return {
103
+ ...client,
104
+ updateAlgorithm,
105
+ getFeeds,
106
+ };
107
+ });
108
+
109
+ export class GrazeClient extends Effect.Service<GrazeClient>()("GrazeClient", {
110
+ effect: grazeLive,
111
+ dependencies: [FetchHttpClient.layer],
112
+ }) {
113
+ // TODO: test service
114
+ }
115
+
116
+ export class GrazeError extends Data.TaggedError("GrazeError")<{
117
+ message: string;
118
+ status: number;
119
+ statusText: string;
120
+ json: unknown;
121
+ }> {}
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { ConfigProvider, Effect, Layer } from "effect";
2
+ import { GrazeClient } from "src/GrazeClient.js";
3
+
4
+ type Promisify<T> = T extends (
5
+ ...args: any[]
6
+ ) => Effect.Effect<infer A, infer E, never>
7
+ ? (...args: Parameters<T>) => Promise<A>
8
+ : never;
9
+
10
+ export class GrazeService {
11
+ getFeed: Promisify<typeof GrazeClient.Service.getFeed>;
12
+ getFeeds: Promisify<typeof GrazeClient.Service.getFeeds>;
13
+ hidePost: Promisify<typeof GrazeClient.Service.hidePost>;
14
+ updateAlgorithm: Promisify<typeof GrazeClient.Service.updateAlgorithm>;
15
+ publishAlgorithm: Promisify<typeof GrazeClient.Service.publishAlgorithm>;
16
+
17
+ constructor({
18
+ apiUrl,
19
+ cookie,
20
+ userId,
21
+ }: {
22
+ apiUrl: string;
23
+ cookie: string;
24
+ userId: number;
25
+ }) {
26
+ const envMap = new Map([
27
+ ["GRAZE_API_URL", apiUrl],
28
+ ["GRAZE_COOKIE", cookie],
29
+ ["GRAZE_USER_ID", userId.toString()],
30
+ ]);
31
+ const provider = ConfigProvider.fromMap(envMap);
32
+ const layer = Layer.setConfigProvider(provider);
33
+ const make = setup.pipe(
34
+ Effect.provide(layer),
35
+ Effect.provide(GrazeClient.Default)
36
+ );
37
+ const api = Effect.runSync(make);
38
+ this.getFeed = api.getFeed;
39
+ this.getFeeds = api.getFeeds;
40
+ this.hidePost = api.hidePost;
41
+ this.updateAlgorithm = api.updateAlgorithm;
42
+ this.publishAlgorithm = api.publishAlgorithm;
43
+ }
44
+ }
45
+
46
+ const setup = Effect.gen(function* () {
47
+ const client = yield* GrazeClient;
48
+ const getFeed = (...args: Parameters<typeof client.getFeed>) =>
49
+ client.getFeed(...args).pipe(Effect.runPromise);
50
+ const getFeeds = (...args: Parameters<typeof client.getFeeds>) =>
51
+ client.getFeeds(...args).pipe(Effect.runPromise);
52
+ const hidePost = (...args: Parameters<typeof client.hidePost>) =>
53
+ client.hidePost(...args).pipe(Effect.runPromise);
54
+ const updateAlgorithm = (
55
+ ...args: Parameters<typeof client.updateAlgorithm>
56
+ ) => client.updateAlgorithm(...args).pipe(Effect.runPromise);
57
+ const publishAlgorithm = (
58
+ ...args: Parameters<typeof client.publishAlgorithm>
59
+ ) => client.publishAlgorithm(...args).pipe(Effect.runPromise);
60
+
61
+ return {
62
+ getFeed,
63
+ getFeeds,
64
+ hidePost,
65
+ updateAlgorithm,
66
+ publishAlgorithm,
67
+ };
68
+ });
@@ -0,0 +1,220 @@
1
+ import { Schema as S } from "effect";
2
+
3
+ // ------------------- Primitives ---------------------------------
4
+
5
+ export const ListUri = S.String;
6
+ export type ListUri = typeof ListUri.Type;
7
+
8
+ export const AttributeName = S.Union(
9
+ S.Literal("text"),
10
+ S.Literal("reply"),
11
+ S.Literal("embed.images[*].alt"),
12
+ S.Literal("embed.alt"),
13
+ S.Literal("embed"),
14
+ S.Literal("embed.external.uri"),
15
+ S.Literal("embed.external.title"),
16
+ S.Literal("embed.external.description")
17
+ );
18
+ export type AttributeName = typeof AttributeName.Type;
19
+ export const AttributeComparison = S.Literal("==");
20
+ export type AttributeComparison = typeof AttributeComparison.Type;
21
+ export const AttributeValue = S.Null;
22
+ export type AttributeValue = typeof AttributeValue.Type;
23
+
24
+ // ------------------- Filters ------------------------------------
25
+
26
+ export type Filter =
27
+ | And
28
+ | Or
29
+ | ListMemberFilter
30
+ | AttributeCompare
31
+ | RegexMatches
32
+ | RegexNegationMatches
33
+ | ContentModerationBlock
34
+ | EntityExcludesLabels
35
+ | EmbedType;
36
+
37
+ export type And = Readonly<{ and: ReadonlyArray<Filter> }>;
38
+ export type Or = Readonly<{ or: ReadonlyArray<Filter> }>;
39
+ export type ListMemberFilter = Readonly<{
40
+ list_member: readonly [ListUri, "in" | "not_in"];
41
+ }>;
42
+ export type AttributeCompare = Readonly<{
43
+ attribute_compare: readonly [
44
+ AttributeName,
45
+ AttributeComparison,
46
+ AttributeValue
47
+ ];
48
+ }>;
49
+ export type RegexMatches = Readonly<{
50
+ regex_matches: readonly [AttributeName, string, boolean];
51
+ }>;
52
+ export type RegexNegationMatches = Readonly<{
53
+ regex_negation_matches: readonly [AttributeName, string, boolean];
54
+ }>;
55
+ export type ContentModerationBlock = Readonly<{
56
+ content_moderation: readonly ["OK", ">=", number];
57
+ }>;
58
+ export type EntityExcludesLabels = Readonly<{
59
+ entity_excludes: readonly ["labels", readonly ["porn", "sexual", "nudity"]];
60
+ }>;
61
+ export type EmbedType = Readonly<{
62
+ embed_type: readonly [
63
+ "==" | "!=",
64
+ "video" | "image" | "link" | "post" | "gif" | "image_group"
65
+ ];
66
+ }>;
67
+
68
+ export const Filter = S.Union(
69
+ S.suspend((): S.Schema<And> => And),
70
+ S.suspend((): S.Schema<Or> => Or),
71
+ S.suspend((): S.Schema<ListMemberFilter> => ListMemberFilter),
72
+ S.suspend((): S.Schema<AttributeCompare> => AttributeCompare),
73
+ S.suspend((): S.Schema<RegexMatches> => RegexMatches),
74
+ S.suspend((): S.Schema<RegexNegationMatches> => RegexNegationMatches),
75
+ S.suspend((): S.Schema<ContentModerationBlock> => ContentModerationBlock),
76
+ S.suspend((): S.Schema<EntityExcludesLabels> => EntityExcludesLabels),
77
+ S.suspend((): S.Schema<EmbedType> => EmbedType)
78
+ );
79
+
80
+ export const Or = S.Struct({ or: S.Array(Filter) });
81
+ export const And = S.Struct({ and: S.Array(Filter) });
82
+ export const ListMemberFilter = S.Struct({
83
+ list_member: S.Tuple(ListUri, S.Literal("in", "not_in")),
84
+ });
85
+ export const AttributeCompare = S.Struct({
86
+ attribute_compare: S.Tuple(
87
+ AttributeName,
88
+ AttributeComparison,
89
+ AttributeValue
90
+ ),
91
+ });
92
+ const RegexMatchBase = S.Tuple(
93
+ AttributeName,
94
+ S.String.annotations({
95
+ title: "Regex Pattern",
96
+ description: "The source of the regex pattern to match against.",
97
+ }),
98
+ S.Boolean.annotations({
99
+ title: "Case Insensitive?",
100
+ description: "True if the regex should be case insensitive.",
101
+ })
102
+ );
103
+ export const RegexMatches = S.Struct({
104
+ regex_matches: RegexMatchBase,
105
+ });
106
+ export const RegexNegationMatches = S.Struct({
107
+ regex_negation_matches: RegexMatchBase,
108
+ });
109
+ export const ContentModerationBlock = S.Struct({
110
+ content_moderation: S.Tuple(
111
+ S.Literal("OK"),
112
+ S.Literal(">="),
113
+ S.Number.annotations({
114
+ title: "Threshold",
115
+ description: "The threshold for content moderation.",
116
+ })
117
+ ),
118
+ });
119
+ export const EntityExcludesLabels = S.Struct({
120
+ entity_excludes: S.Tuple(
121
+ S.Literal("labels"),
122
+ S.Tuple(S.Literal("porn"), S.Literal("sexual"), S.Literal("nudity"))
123
+ ),
124
+ });
125
+ export const EmbedType = S.Struct({
126
+ embed_type: S.Tuple(
127
+ S.Literal("==", "!="),
128
+ S.Literal("video", "image", "link", "post", "gif", "image_group")
129
+ ),
130
+ });
131
+
132
+ // ---------------------- Sort Settings -----------------------------
133
+
134
+ export const SortSettings = S.Struct({
135
+ time_window: S.Number,
136
+ decay_penalty: S.Number,
137
+ like_count_multiplier: S.Number,
138
+ reply_count_multiplier: S.Number,
139
+ repost_count_multiplier: S.Number,
140
+ reader_like_count_multiplier: S.Number,
141
+ reader_reply_count_multiplier: S.Number,
142
+ reader_repost_count_multiplier: S.Number,
143
+ });
144
+ export type SortSettings = typeof SortSettings.Type;
145
+
146
+ // ---------------------- Form Data ---------------------------------
147
+
148
+ export const DisplayName = S.String.annotations({
149
+ description: "The public name of the feed.",
150
+ });
151
+ export type DisplayName = typeof DisplayName.Type;
152
+
153
+ export const RecordName = S.String.annotations({
154
+ description: "The slug of the feed url.",
155
+ });
156
+ export type RecordName = typeof RecordName.Type;
157
+
158
+ export const Description = S.String.annotations({
159
+ description: "The description to be shown to users of the feed.",
160
+ });
161
+ export type Description = typeof Description.Type;
162
+
163
+ export const Order = S.Union(
164
+ S.Literal("new"),
165
+ S.Literal("trending"),
166
+ S.Literal("blend")
167
+ );
168
+ export type Order = typeof Order.Type;
169
+
170
+ export const FeedId = S.NumberFromString;
171
+ export type FeedId = typeof FeedId.Type;
172
+
173
+ export const UserId = S.NumberFromString;
174
+ export type UserId = typeof UserId.Type;
175
+
176
+ export const AlgorithmManifest = S.Struct({
177
+ filter: Filter,
178
+ sort_settings: S.optional(SortSettings),
179
+ });
180
+ export type AlgorithmManifest = typeof AlgorithmManifest.Type;
181
+
182
+ export const Metadata = S.Struct({
183
+ document: S.optional(S.Unknown),
184
+ schema: S.optional(
185
+ S.Struct({
186
+ schemaVersion: S.Literal(2),
187
+ sequences: S.Record({ key: S.String, value: S.Number }),
188
+ })
189
+ ),
190
+ session: S.optional(S.Unknown),
191
+ });
192
+ export type Metadata = typeof Metadata.Type;
193
+
194
+ export const FullForm = S.Struct({
195
+ id: FeedId,
196
+ user_id: UserId,
197
+ file: S.optional(S.Unknown),
198
+ display_name: DisplayName,
199
+ record_name: RecordName,
200
+ description: S.optional(Description),
201
+ order: Order,
202
+ algorithm_manifest: S.parseJson(AlgorithmManifest),
203
+ metadata: S.optional(S.parseJson(Metadata)),
204
+ });
205
+ export type FullForm = typeof FullForm.Type;
206
+ export type EncodedFullForm = typeof FullForm.Encoded;
207
+
208
+ /**
209
+ * Actual structure of FormData
210
+ *
211
+ * display_name
212
+ * record_name
213
+ * description
214
+ * order
215
+ * algorithm_manifest
216
+ * metadata
217
+ * id
218
+ * user_id
219
+ * session_cookie
220
+ */
@@ -0,0 +1,2 @@
1
+ export * from "./shared.js";
2
+ export * from "./UpdateAlgorithmPayload.js";
@@ -0,0 +1,76 @@
1
+ import { Schema as S } from "effect";
2
+
3
+ export const PartialAlgorithm = S.Struct({
4
+ algorithm_uri: S.String,
5
+ algorithm_manifest_id: S.Union(S.Null, S.Number),
6
+ draft_algorithm_manifest_id: S.Number,
7
+ record_name: S.String,
8
+ display_name: S.String,
9
+ description: S.String,
10
+ feed_did: S.String,
11
+ avatar_url: S.Union(S.String, S.Null),
12
+ cpm_cents: S.Union(S.Number, S.Null),
13
+ deleted: S.Boolean,
14
+ active: S.Boolean,
15
+ public: S.Boolean,
16
+ monetization_available: S.Boolean,
17
+ status: S.Union(S.String, S.Null),
18
+ unique_operator_ids: S.Array(S.Number),
19
+ user_id: S.Number,
20
+ id: S.Number,
21
+ created_at: S.DateFromString,
22
+ updated_at: S.DateFromString,
23
+ deleted_at: S.Union(S.DateFromString, S.Null),
24
+ });
25
+ export type PartialAlgorithm = typeof PartialAlgorithm.Type;
26
+
27
+ const Stats = S.Unknown;
28
+ const LatestError = S.Unknown;
29
+
30
+ const FullAlgorithm = S.Struct({
31
+ ...PartialAlgorithm.fields,
32
+ stats: Stats,
33
+ latest_error: LatestError,
34
+ latest_compute_cycle: S.Unknown,
35
+ subscriber_lists: S.Unknown,
36
+ });
37
+ const FullAlgorithmWithManifest = S.Struct({
38
+ ...FullAlgorithm.fields,
39
+ draft_algorithm_manifest: S.Unknown,
40
+ algorithm_manifest: S.Unknown,
41
+ });
42
+
43
+ export const AlgoUpdateResponse = S.Struct({
44
+ message: S.String,
45
+ algorithm: PartialAlgorithm,
46
+ });
47
+ export type AlgoUpdateResponse = typeof AlgoUpdateResponse.Type;
48
+
49
+ export const GetAlgoResponse = S.parseJson(FullAlgorithmWithManifest);
50
+ export type GetAlgoResponse = typeof GetAlgoResponse.Type;
51
+
52
+ export const HidePostBody = S.Struct({
53
+ algo_id: S.Number,
54
+ at_uri: S.String,
55
+ });
56
+ export type HidePostBody = typeof HidePostBody.Type;
57
+
58
+ // ----------------------- Get Algorithms Response -----------------------
59
+
60
+ export const AlgorithWithStats = S.Struct({
61
+ ...PartialAlgorithm.fields,
62
+ stats: S.Struct({
63
+ page_renders: S.Number,
64
+ unique_users: S.Number,
65
+ post_renders: S.Number,
66
+ }),
67
+ });
68
+ export type AlgorithmWithStats = typeof AlgorithWithStats.Type;
69
+ export const GetAlgorithmsResponse = S.Struct({
70
+ page: S.Number,
71
+ page_size: S.Number,
72
+ total_items: S.Number,
73
+ total_pages: S.Number,
74
+ user_algos: S.Array(AlgorithWithStats),
75
+ });
76
+ export type GetAlgorithmsResponse = typeof GetAlgorithmsResponse.Type;
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+
3
+ describe("Dummy", () => {
4
+ it("should pass", () => {
5
+ expect(true).toBe(true)
6
+ })
7
+ })
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "exactOptionalPropertyTypes": true,
5
+ "moduleDetection": "force",
6
+ "composite": true,
7
+ "downlevelIteration": true,
8
+ "resolveJsonModule": true,
9
+ "esModuleInterop": false,
10
+ "declaration": true,
11
+ "skipLibCheck": true,
12
+ "emitDecoratorMetadata": true,
13
+ "experimentalDecorators": true,
14
+ "moduleResolution": "NodeNext",
15
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
16
+ "types": [],
17
+ "isolatedModules": true,
18
+ "sourceMap": true,
19
+ "declarationMap": true,
20
+ "noImplicitReturns": false,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": false,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "noEmitOnError": false,
25
+ "noErrorTruncation": false,
26
+ "allowJs": false,
27
+ "checkJs": false,
28
+ "forceConsistentCasingInFileNames": true,
29
+ "noImplicitAny": true,
30
+ "noImplicitThis": true,
31
+ "noUncheckedIndexedAccess": false,
32
+ "strictNullChecks": true,
33
+ "baseUrl": ".",
34
+ "target": "ES2022",
35
+ "module": "NodeNext",
36
+ "incremental": true,
37
+ "removeComments": false,
38
+ "plugins": [{ "name": "@effect/language-service" }]
39
+ }
40
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.src.json",
3
+ "compilerOptions": {
4
+ "types": ["node"],
5
+ "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo",
6
+ "outDir": "build/esm",
7
+ "declarationDir": "build/dts",
8
+ "stripInternal": true
9
+ }
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "include": [],
4
+ "references": [
5
+ { "path": "tsconfig.src.json" },
6
+ { "path": "tsconfig.test.json" }
7
+ ]
8
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "include": ["src"],
4
+ "compilerOptions": {
5
+ "types": ["node"],
6
+ "outDir": "build/src",
7
+ "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo",
8
+ "rootDir": "src"
9
+ }
10
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "include": ["test"],
4
+ "references": [
5
+ { "path": "tsconfig.src.json" }
6
+ ],
7
+ "compilerOptions": {
8
+ "types": ["node"],
9
+ "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo",
10
+ "rootDir": "test",
11
+ "noEmit": true
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import path from "path"
2
+ import { defineConfig } from "vitest/config"
3
+
4
+ export default defineConfig({
5
+ plugins: [],
6
+ test: {
7
+ setupFiles: [path.join(__dirname, "setupTests.ts")],
8
+ include: ["./test/**/*.test.ts"],
9
+ globals: true
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ "@template/basic/test": path.join(__dirname, "test"),
14
+ "@template/basic": path.join(__dirname, "src")
15
+ }
16
+ }
17
+ })