From 57d55fe58f7442f2c719d259ef1410feaf5eb664 Mon Sep 17 00:00:00 2001 From: MapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Wed, 8 Dec 2021 14:12:26 -0600 Subject: [PATCH] beginnings: rendering text to message --- .gitignore | 1 + package.json | 17 +- pnpm-lock.yaml | 288 +++++++++++++++++++++++++-- src/container.ts | 28 +++ src/helpers/raise.ts | 5 + src/helpers/reject-after.ts | 6 + src/helpers/to-error.ts | 3 + src/helpers/types.ts | 1 + src/helpers/wait-for-with-timeout.ts | 10 + src/helpers/wait-for.ts | 8 + src/instance.ts | 25 +++ src/main.test.ts | 6 - src/main.ts | 5 +- src/reconciler.ts | 62 ++++++ src/render.test.ts | 52 +++++ src/render.ts | 16 ++ test/env.ts | 10 + 17 files changed, 516 insertions(+), 27 deletions(-) create mode 100644 src/container.ts create mode 100644 src/helpers/raise.ts create mode 100644 src/helpers/reject-after.ts create mode 100644 src/helpers/to-error.ts create mode 100644 src/helpers/types.ts create mode 100644 src/helpers/wait-for-with-timeout.ts create mode 100644 src/helpers/wait-for.ts create mode 100644 src/instance.ts delete mode 100644 src/main.test.ts create mode 100644 src/reconciler.ts create mode 100644 src/render.test.ts create mode 100644 src/render.ts create mode 100644 test/env.ts diff --git a/.gitignore b/.gitignore index d7274cc..ce5c3cb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ dist node_modules .vscode coverage +.env diff --git a/package.json b/package.json index d7fb95a..1991ef6 100644 --- a/package.json +++ b/package.json @@ -23,14 +23,26 @@ "author": "itsMapleLeaf", "license": "MIT", "dependencies": { - "match-sorter": "^6.3.1" + "@types/node": "*", + "@types/react": "*", + "@types/react-reconciler": "^0.26.4", + "react-reconciler": "^0.26.2" + }, + "peerDependencies": { + "@types/scheduler": "^0.16.2", + "discord.js": "^13.3", + "react": "^17.0.2", + "scheduler": "^0.20.2" }, "devDependencies": { "@itsmapleleaf/configs": "^1.0.4", + "@types/scheduler": "^0.16.2", "@typescript-eslint/eslint-plugin": "^5.6.0", "@typescript-eslint/parser": "^5.6.0", "ava": "^4.0.0-rc.1", "c8": "^7.10.0", + "discord.js": "^13.3.1", + "dotenv": "^10.0.0", "esbuild-node-loader": "^0.6.3", "eslint": "^8.4.1", "eslint-config-prettier": "^8.3.0", @@ -39,8 +51,11 @@ "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", + "nanoid": "^3.1.30", "npm-run-all": "^4.1.5", "prettier": "^2.5.1", + "react": "^17.0.2", + "scheduler": "^0.20.2", "tsup": "^5.10.3", "typescript": "^4.5.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14801d6..c19e113 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,10 +2,16 @@ lockfileVersion: 5.3 specifiers: '@itsmapleleaf/configs': ^1.0.4 + '@types/node': '*' + '@types/react': '*' + '@types/react-reconciler': ^0.26.4 + '@types/scheduler': ^0.16.2 '@typescript-eslint/eslint-plugin': ^5.6.0 '@typescript-eslint/parser': ^5.6.0 ava: ^4.0.0-rc.1 c8: ^7.10.0 + discord.js: ^13.3.1 + dotenv: ^10.0.0 esbuild-node-loader: ^0.6.3 eslint: ^8.4.1 eslint-config-prettier: ^8.3.0 @@ -14,21 +20,30 @@ specifiers: eslint-plugin-jsx-a11y: ^6.5.1 eslint-plugin-react: ^7.27.1 eslint-plugin-react-hooks: ^4.3.0 - match-sorter: ^6.3.1 + nanoid: ^3.1.30 npm-run-all: ^4.1.5 prettier: ^2.5.1 + react: ^17.0.2 + react-reconciler: ^0.26.2 + scheduler: ^0.20.2 tsup: ^5.10.3 typescript: ^4.5.2 dependencies: - match-sorter: 6.3.1 + '@types/node': 16.11.12 + '@types/react': 17.0.37 + '@types/react-reconciler': 0.26.4 + react-reconciler: 0.26.2_react@17.0.2 devDependencies: '@itsmapleleaf/configs': 1.0.4 + '@types/scheduler': 0.16.2 '@typescript-eslint/eslint-plugin': 5.6.0_16d83f5c41c3abb1061a82b07c18e4f3 '@typescript-eslint/parser': 5.6.0_eslint@8.4.1+typescript@4.5.2 ava: 4.0.0-rc.1 c8: 7.10.0 + discord.js: 13.3.1 + dotenv: 10.0.0 esbuild-node-loader: 0.6.3_typescript@4.5.2 eslint: 8.4.1 eslint-config-prettier: 8.3.0_eslint@8.4.1 @@ -37,8 +52,11 @@ devDependencies: eslint-plugin-jsx-a11y: 6.5.1_eslint@8.4.1 eslint-plugin-react: 7.27.1_eslint@8.4.1 eslint-plugin-react-hooks: 4.3.0_eslint@8.4.1 + nanoid: 3.1.30 npm-run-all: 4.1.5 prettier: 2.5.1 + react: 17.0.2 + scheduler: 0.20.2 tsup: 5.10.3_typescript@4.5.2 typescript: 4.5.2 @@ -57,11 +75,37 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.9 + dev: true /@bcoe/v8-coverage/0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@discordjs/builders/0.8.2: + resolution: {integrity: sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==} + engines: {node: '>=16.0.0', npm: '>=7.0.0'} + dependencies: + '@sindresorhus/is': 4.2.0 + discord-api-types: 0.24.0 + ow: 0.27.0 + ts-mixer: 6.0.0 + tslib: 2.3.1 + dev: true + + /@discordjs/collection/0.3.2: + resolution: {integrity: sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==} + engines: {node: '>=16.0.0', npm: '>=7.0.0'} + dev: true + + /@discordjs/form-data/3.0.1: + resolution: {integrity: sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.34 + dev: true + /@eslint/eslintrc/1.0.5: resolution: {integrity: sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -145,6 +189,16 @@ packages: fastq: 1.13.0 dev: true + /@sapphire/async-queue/1.1.9: + resolution: {integrity: sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: true + + /@sindresorhus/is/4.2.0: + resolution: {integrity: sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==} + engines: {node: '>=10'} + dev: true + /@types/eslint/8.2.1: resolution: {integrity: sha512-UP9rzNn/XyGwb5RQ2fok+DzcIRIYwc16qTXse5+Smsy8MOIccCChT15KAwnsgQx4PzJkaMq4myFyZ4CL5TjhIQ==} dependencies: @@ -179,8 +233,41 @@ packages: resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} dev: true + /@types/node-fetch/2.5.12: + resolution: {integrity: sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==} + dependencies: + '@types/node': 16.11.12 + form-data: 3.0.1 + dev: true + /@types/node/16.11.12: resolution: {integrity: sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==} + + /@types/prop-types/15.7.4: + resolution: {integrity: sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==} + dev: false + + /@types/react-reconciler/0.26.4: + resolution: {integrity: sha512-bdx4aIBkQRDAnzc23JBFeZmVpmfLJHfHikmQukEt9qs4bQtq9f+PDbNwhR9u74FkIUyIDz1I1qJ8OF6RwadKpw==} + dependencies: + '@types/react': 17.0.37 + dev: false + + /@types/react/17.0.37: + resolution: {integrity: sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==} + dependencies: + '@types/prop-types': 15.7.4 + '@types/scheduler': 0.16.2 + csstype: 3.0.10 + dev: false + + /@types/scheduler/0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + + /@types/ws/8.2.2: + resolution: {integrity: sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==} + dependencies: + '@types/node': 16.11.12 dev: true /@typescript-eslint/eslint-plugin/5.6.0_16d83f5c41c3abb1061a82b07c18e4f3: @@ -497,6 +584,10 @@ packages: resolution: {integrity: sha1-9wtzXGvKGlycItmCw+Oef+ujva0=} dev: true + /asynckit/0.4.0: + resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} + dev: true + /atob/2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} engines: {node: '>= 4.5.0'} @@ -867,6 +958,13 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -964,6 +1062,10 @@ packages: which: 2.0.2 dev: true + /csstype/3.0.10: + resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==} + dev: false + /currently-unhandled/0.4.1: resolution: {integrity: sha1-mI3zP+qxke95mmE2nddsF635V+o=} engines: {node: '>=0.10.0'} @@ -1064,6 +1166,11 @@ packages: slash: 3.0.0 dev: true + /delayed-stream/1.0.0: + resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} + engines: {node: '>=0.4.0'} + dev: true + /dir-glob/2.2.2: resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==} engines: {node: '>=4'} @@ -1078,6 +1185,29 @@ packages: path-type: 4.0.0 dev: true + /discord-api-types/0.24.0: + resolution: {integrity: sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==} + engines: {node: '>=12'} + dev: true + + /discord.js/13.3.1: + resolution: {integrity: sha512-zn4G8tL5+tMV00+0aSsVYNYcIfMSdT2g0nudKny+Ikd+XKv7m6bqI7n3Vji0GIRqXDr5ArPaw+iYFM2I1Iw3vg==} + engines: {node: '>=16.6.0', npm: '>=7.0.0'} + dependencies: + '@discordjs/builders': 0.8.2 + '@discordjs/collection': 0.3.2 + '@discordjs/form-data': 3.0.1 + '@sapphire/async-queue': 1.1.9 + '@types/node-fetch': 2.5.12 + '@types/ws': 8.2.2 + discord-api-types: 0.24.0 + node-fetch: 2.6.6 + ws: 8.3.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /doctrine/2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -1092,6 +1222,18 @@ packages: esutils: 2.0.3 dev: true + /dot-prop/6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + dependencies: + is-obj: 2.0.0 + dev: true + + /dotenv/10.0.0: + resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} + engines: {node: '>=10'} + dev: true + /emittery/0.10.0: resolution: {integrity: sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==} engines: {node: '>=12'} @@ -1794,6 +1936,15 @@ packages: signal-exit: 3.0.6 dev: true + /form-data/3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.34 + dev: true + /fragment-cache/0.2.1: resolution: {integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=} engines: {node: '>=0.10.0'} @@ -2285,6 +2436,11 @@ packages: engines: {node: '>=0.12.0'} dev: true + /is-obj/2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + dev: true + /is-path-cwd/2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} engines: {node: '>=6'} @@ -2421,7 +2577,6 @@ packages: /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /js-yaml/3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} @@ -2558,6 +2713,10 @@ packages: p-locate: 6.0.0 dev: true + /lodash.isequal/4.5.0: + resolution: {integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA=} + dev: true + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -2579,7 +2738,6 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 - dev: true /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} @@ -2614,13 +2772,6 @@ packages: object-visit: 1.0.1 dev: true - /match-sorter/6.3.1: - resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==} - dependencies: - '@babel/runtime': 7.16.3 - remove-accents: 0.4.2 - dev: false - /matcher/5.0.0: resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2684,6 +2835,18 @@ packages: picomatch: 2.3.0 dev: true + /mime-db/1.51.0: + resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types/2.1.34: + resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.51.0 + dev: true + /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2732,6 +2895,12 @@ packages: thenify-all: 1.6.0 dev: true + /nanoid/3.1.30: + resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + /nanomatch/1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} engines: {node: '>=0.10.0'} @@ -2761,6 +2930,13 @@ packages: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} dev: true + /node-fetch/2.6.6: + resolution: {integrity: sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==} + engines: {node: 4.x || >=6.0.0} + dependencies: + whatwg-url: 5.0.0 + dev: true + /nofilter/3.1.0: resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} engines: {node: '>=12.19'} @@ -2813,7 +2989,6 @@ packages: /object-assign/4.1.1: resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} engines: {node: '>=0.10.0'} - dev: true /object-copy/0.1.0: resolution: {integrity: sha1-fn2Fi3gb18mRpBupde04EnVOmYw=} @@ -2938,6 +3113,18 @@ packages: wcwidth: 1.0.1 dev: true + /ow/0.27.0: + resolution: {integrity: sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==} + engines: {node: '>=12'} + dependencies: + '@sindresorhus/is': 4.2.0 + callsites: 3.1.0 + dot-prop: 6.0.1 + lodash.isequal: 4.5.0 + type-fest: 1.4.0 + vali-date: 1.0.0 + dev: true + /p-all/2.1.0: resolution: {integrity: sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA==} engines: {node: '>=6'} @@ -3240,6 +3427,26 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true + /react-reconciler/0.26.2_react@17.0.2: + resolution: {integrity: sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^17.0.2 + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react: 17.0.2 + scheduler: 0.20.2 + dev: false + + /react/17.0.2: + resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: true + /read-pkg/3.0.0: resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=} engines: {node: '>=4'} @@ -3267,6 +3474,7 @@ packages: /regenerator-runtime/0.13.9: resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + dev: true /regex-not/1.0.2: resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} @@ -3289,10 +3497,6 @@ packages: engines: {node: '>=8'} dev: true - /remove-accents/0.4.2: - resolution: {integrity: sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=} - dev: false - /repeat-element/1.1.4: resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} engines: {node: '>=0.10.0'} @@ -3397,6 +3601,12 @@ packages: ret: 0.1.15 dev: true + /scheduler/0.20.2: + resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + /semver/5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -3797,6 +4007,10 @@ packages: safe-regex: 1.1.0 dev: true + /tr46/0.0.3: + resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} + dev: true + /tree-kill/1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3806,6 +4020,10 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-mixer/6.0.0: + resolution: {integrity: sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==} + dev: true + /tsconfig-paths/3.12.0: resolution: {integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==} dependencies: @@ -3819,6 +4037,10 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true + /tslib/2.3.1: + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} + dev: true + /tsup/5.10.3_typescript@4.5.2: resolution: {integrity: sha512-yjbup830+Ym6ahEkIrwIiy0DlY3xkfmGBcvxQLXOa2cUIwRIP8HdrEheg9E1Fthqrzf9Gus9SO1UFiz+Dz+/tQ==} hasBin: true @@ -3874,6 +4096,11 @@ packages: engines: {node: '>=10'} dev: true + /type-fest/1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true + /type-fest/2.6.0: resolution: {integrity: sha512-XN1FDGGtaSDA6CFsCW5iolTQqFsnJ+ZF6JqSz0SqXoh4F8GY0xqUv5RYnTilpmL+sOH8OH4FX8tf9YyAPM2LDA==} engines: {node: '>=12.20'} @@ -3951,6 +4178,11 @@ packages: source-map: 0.7.3 dev: true + /vali-date/1.0.0: + resolution: {integrity: sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=} + engines: {node: '>=0.10.0'} + dev: true + /validate-npm-package-license/3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -3964,11 +4196,22 @@ packages: defaults: 1.0.3 dev: true + /webidl-conversions/3.0.1: + resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} + dev: true + /well-known-symbols/2.0.0: resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} engines: {node: '>=6'} dev: true + /whatwg-url/5.0.0: + resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + /which-boxed-primitive/1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -4021,6 +4264,19 @@ packages: typedarray-to-buffer: 3.1.5 dev: true + /ws/8.3.0: + resolution: {integrity: sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /y18n/5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} diff --git a/src/container.ts b/src/container.ts new file mode 100644 index 0000000..50e35a4 --- /dev/null +++ b/src/container.ts @@ -0,0 +1,28 @@ +import type { TextBasedChannels } from "discord.js" +import type { ReacordInstance } from "./instance.js" + +export class ReacordContainer { + channel: TextBasedChannels + instances = new Set() + + constructor(channel: TextBasedChannels) { + this.channel = channel + } + + add(instance: ReacordInstance) { + this.instances.add(instance) + instance.render(this.channel) + } + + remove(instance: ReacordInstance) { + this.instances.delete(instance) + instance.destroy() + } + + clear() { + this.instances.forEach((instance) => { + instance.destroy() + }) + this.instances.clear() + } +} diff --git a/src/helpers/raise.ts b/src/helpers/raise.ts new file mode 100644 index 0000000..6472f9b --- /dev/null +++ b/src/helpers/raise.ts @@ -0,0 +1,5 @@ +import { toError } from "./to-error.js" + +export function raise(error: unknown): never { + throw toError(error) +} diff --git a/src/helpers/reject-after.ts b/src/helpers/reject-after.ts new file mode 100644 index 0000000..26cebc0 --- /dev/null +++ b/src/helpers/reject-after.ts @@ -0,0 +1,6 @@ +import { setTimeout } from "node:timers/promises" + +export async function rejectAfter(timeMs: number) { + await setTimeout(timeMs) + return Promise.reject(`rejected after ${timeMs}ms`) +} diff --git a/src/helpers/to-error.ts b/src/helpers/to-error.ts new file mode 100644 index 0000000..0a18003 --- /dev/null +++ b/src/helpers/to-error.ts @@ -0,0 +1,3 @@ +export function toError(value: unknown) { + return value instanceof Error ? value : new Error(String(value)) +} diff --git a/src/helpers/types.ts b/src/helpers/types.ts new file mode 100644 index 0000000..2f782c1 --- /dev/null +++ b/src/helpers/types.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise diff --git a/src/helpers/wait-for-with-timeout.ts b/src/helpers/wait-for-with-timeout.ts new file mode 100644 index 0000000..66f5319 --- /dev/null +++ b/src/helpers/wait-for-with-timeout.ts @@ -0,0 +1,10 @@ +import { rejectAfter } from "./reject-after.js" +import type { MaybePromise } from "./types.js" +import { waitFor } from "./wait-for.js" + +export function waitForWithTimeout( + condition: () => MaybePromise, + timeout = 1000, +) { + return Promise.race([waitFor(condition), rejectAfter(timeout)]) +} diff --git a/src/helpers/wait-for.ts b/src/helpers/wait-for.ts new file mode 100644 index 0000000..ae6739a --- /dev/null +++ b/src/helpers/wait-for.ts @@ -0,0 +1,8 @@ +import { setTimeout } from "node:timers/promises" +import type { MaybePromise } from "./types.js" + +export async function waitFor(condition: () => MaybePromise) { + while (!(await condition())) { + await setTimeout() + } +} diff --git a/src/instance.ts b/src/instance.ts new file mode 100644 index 0000000..175f97b --- /dev/null +++ b/src/instance.ts @@ -0,0 +1,25 @@ +import type { Message, TextBasedChannels } from "discord.js" + +export class ReacordInstance { + message?: Message + content: string + + constructor(content: string) { + this.content = content + } + + render(channel: TextBasedChannels) { + if (this.message) { + this.message.edit(this.content).catch(console.error) + } else { + channel.send(this.content).then((message) => { + this.message = message + }, console.error) + } + } + + destroy() { + this.message?.delete().catch(console.error) + this.message?.channel.messages.cache.delete(this.message?.id) + } +} diff --git a/src/main.test.ts b/src/main.test.ts deleted file mode 100644 index 28a0f6c..0000000 --- a/src/main.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import test from "ava" -import { result } from "./main.js" - -test("it is b", (t) => { - t.deepEqual(result, ["b"]) -}) diff --git a/src/main.ts b/src/main.ts index 507d3a5..517e751 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1 @@ -/* eslint-disable import/no-unused-modules */ -import { matchSorter } from "match-sorter" - -export const result = matchSorter(["a", "b", "c"], "b") +export * from "./render.js" diff --git a/src/reconciler.ts b/src/reconciler.ts new file mode 100644 index 0000000..3ab41ee --- /dev/null +++ b/src/reconciler.ts @@ -0,0 +1,62 @@ +import ReactReconciler from "react-reconciler" +import type { ReacordContainer } from "./container.js" +import { ReacordInstance } from "./instance.js" + +export const reconciler = ReactReconciler< + unknown, + Record, + ReacordContainer, + ReacordInstance, + ReacordInstance, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown, + unknown +>({ + now: Date.now, + supportsMutation: true, + isPrimaryRenderer: true, + noTimeout: -1, + supportsHydration: false, + supportsPersistence: false, + + getRootHostContext: () => ({}), + getChildHostContext: () => ({}), + shouldSetTextContent: () => false, + + createInstance: ( + type, + props, + rootContainerInstance, + hostContext, + internalInstanceHandle, + ) => { + throw new Error("Not implemented") + }, + + createTextInstance: ( + text, + rootContainerInstance, + hostContext, + internalInstanceHandle, + ) => { + return new ReacordInstance(text) + }, + + prepareForCommit: () => null, + resetAfterCommit: () => null, + + clearContainer: (container) => { + container.clear() + }, + appendChildToContainer: (container, child) => { + container.add(child) + }, + removeChildFromContainer: (container, child) => { + container.remove(child) + }, +}) diff --git a/src/render.test.ts b/src/render.test.ts new file mode 100644 index 0000000..5af03d3 --- /dev/null +++ b/src/render.test.ts @@ -0,0 +1,52 @@ +import test from "ava" +import { Client, TextChannel } from "discord.js" +import { nanoid } from "nanoid" +import { testBotToken, testChannelId } from "../test/env.js" +import { raise } from "./helpers/raise.js" +import { waitForWithTimeout } from "./helpers/wait-for-with-timeout.js" +import { render } from "./render.js" + +const client = new Client({ + intents: ["GUILDS"], +}) + +let channel: TextChannel + +test.before(async () => { + await client.login(testBotToken) + + const result = + client.channels.cache.get(testChannelId) ?? + (await client.channels.fetch(testChannelId)) ?? + raise("Channel not found") + + if (!(result instanceof TextChannel)) { + throw new Error("Channel must be a text channel") + } + + channel = result +}) + +test.after(() => { + client.destroy() +}) + +test("rendering text", async (t) => { + const content = nanoid() + const root = render(content, channel) + + await waitForWithTimeout( + () => channel.messages.cache.some((m) => m.content === content), + 4000, + ) + + root.destroy() + + await waitForWithTimeout(() => { + return channel.messages.cache + .filter((m) => !m.deleted) + .every((m) => m.content !== content) + }, 4000) + + t.pass() +}) diff --git a/src/render.ts b/src/render.ts new file mode 100644 index 0000000..dad87a8 --- /dev/null +++ b/src/render.ts @@ -0,0 +1,16 @@ +import type { TextBasedChannels } from "discord.js" +import { ReacordContainer } from "./container" +import { reconciler } from "./reconciler" + +export type ReacordRenderTarget = TextBasedChannels + +export function render(content: string, target: ReacordRenderTarget) { + const container = new ReacordContainer(target) + const containerId = reconciler.createContainer(container, 0, false, null) + reconciler.updateContainer(content, containerId) + return { + destroy: () => { + reconciler.updateContainer(null, containerId) + }, + } +} diff --git a/test/env.ts b/test/env.ts new file mode 100644 index 0000000..7d0319f --- /dev/null +++ b/test/env.ts @@ -0,0 +1,10 @@ +import "dotenv/config.js" +import { raise } from "../src/helpers/raise.js" + +function getEnv(name: string) { + return process.env[name] ?? raise(`Missing environment variable: ${name}`) +} + +export const testBotToken = getEnv("TEST_BOT_TOKEN") +export const testGuildId = getEnv("TEST_GUILD_ID") +export const testChannelId = getEnv("TEST_CHANNEL_ID")