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.
- package/.changeset/config.json +16 -0
- package/.github/actions/setup/action.yml +21 -0
- package/.github/workflows/check.yml +57 -0
- package/.github/workflows/release.yml +34 -0
- package/.github/workflows/snapshot.yml +24 -0
- package/.vscode/effect.code-snippets +72 -0
- package/.vscode/extensions.json +6 -0
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/eslint.config.mjs +120 -0
- package/package.json +94 -0
- package/patches/@changesets__get-github-info@0.6.0.patch +48 -0
- package/patches/babel-plugin-annotate-pure-calls@0.4.0.patch +13 -0
- package/scratchpad/tsconfig.json +10 -0
- package/setupTests.ts +3 -0
- package/src/GrazeClient.ts +121 -0
- package/src/index.ts +68 -0
- package/src/schema/UpdateAlgorithmPayload.ts +220 -0
- package/src/schema/index.ts +2 -0
- package/src/schema/shared.ts +76 -0
- package/test/Dummy.test.ts +7 -0
- package/tsconfig.base.json +40 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +8 -0
- package/tsconfig.src.json +10 -0
- package/tsconfig.test.json +13 -0
- package/vitest.config.ts +17 -0
|
@@ -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
|
+
}
|
package/CHANGELOG.md
ADDED
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);
|
package/setupTests.ts
ADDED
|
@@ -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,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,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
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -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
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -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
|
+
})
|