Changes
38 changed files (+2162/-55)
-
LICENSES/Apache-2.0.txt (new)
-
@@ -0,0 +1,73 @@Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-
-
-
@@ -12,3 +12,8 @@ [[annotations]]path = ["packages/**/go.sum", "go.work.sum"] SPDX-FileCopyrightText = "2025 Shota FUJI <pockawoooh@gmail.com>" SPDX-License-Identifier = "AGPL-3.0-only" [[annotations]] path = ["vendor/go-sqlite3-js/**/*"] SPDX-FileCopyrightText = "Copyright 2020 The Matrix.org Foundation C.I.C." SPDX-License-Identifier = "Apache-2.0"
-
-
-
@@ -10,6 +10,9 @@ },}, "packages/backend": { "name": "@yamori/backend", "dependencies": { "sql.js": "~1.6.2", }, }, "packages/idb_backend": { "name": "@yamori/idb_backend",
-
@@ -492,7 +495,7 @@ "@vitest/spy": ["@vitest/spy@2.0.5", "", { "dependencies": { "tinyspy": "^3.0.0" } }, "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA=="],"@vitest/utils": ["@vitest/utils@2.1.8", "", { "dependencies": { "@vitest/pretty-format": "2.1.8", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA=="], "@yamori/backend": ["@yamori/backend@workspace:packages/backend", {}], "@yamori/backend": ["@yamori/backend@workspace:packages/backend", { "dependencies": { "sql.js": "~1.6.2" } }], "@yamori/idb_backend": ["@yamori/idb_backend@workspace:packages/idb_backend", { "dependencies": { "@bufbuild/protobuf": "^2.2.2", "@date-fns/tz": "^1.2.0", "@yamori/proto": "packages/proto", "date-fns": "^4.1.0", "idb": "^8.0.0" }, "devDependencies": { "@types/bun": "^1.1.14", "fake-indexeddb": "^6.0.0", "typescript": "^5.7.2" } }],
-
@@ -823,6 +826,8 @@"spdx-ranges": ["spdx-ranges@2.1.1", "", {}, "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA=="], "spdx-satisfies": ["spdx-satisfies@5.0.1", "", { "dependencies": { "spdx-compare": "^1.0.0", "spdx-expression-parse": "^3.0.0", "spdx-ranges": "^2.0.0" } }, "sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw=="], "sql.js": ["sql.js@1.6.2", "", {}, "sha512-9iucI5fXQa+Gspeqf/BNB20PxJIn5LhXDt4mjXoFPqXdR+NqtFs15SdKpSIJ6s529aGL9zFR9p2eSCIEiMsNGA=="], "storybook": ["storybook@8.4.7", "", { "dependencies": { "@storybook/core": "8.4.7" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": { "sb": "./bin/index.cjs", "storybook": "./bin/index.cjs", "getstorybook": "./bin/index.cjs" } }, "sha512-RP/nMJxiWyFc8EVMH5gp20ID032Wvk+Yr3lmKidoegto5Iy+2dVQnUoElZb2zpbVXNHWakGuAkfI0dY1Hfp/vw=="],
-
-
-
@@ -4,6 +4,7 @@ //// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only { "excludes": ["vendor"], "biome": { "lineWidth": 90 },
-
-
-
@@ -6,4 +6,5 @@use ( ./packages/backend ./packages/proto ./vendor/go-sqlite3-js )
-
-
-
@@ -1,11 +1,36 @@github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/tmaxmax/go-sse v0.8.0/go.mod h1:HLoxqxdH+7oSUItjtnpxjzJedfr/+Rrm/dNWBcTxJFM= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-
-
-
@@ -3,8 +3,25 @@ // SPDX-License-Identifier: AGPL-3.0-onlypackage core type Core struct{} import ( "database/sql" "pocka.jp/x/yamori/backend/migrations" ) type Core struct { DB *sql.DB } func Init(db *sql.DB) (*Core, error) { err := migrations.Run(db, []migrations.Migration{ migrations.Migration001{}, }) if err != nil { return nil, err } func Init() (*Core, error) { return &Core{}, nil return &Core{ DB: db, }, nil }
-
-
-
@@ -13,7 +13,18 @@ google.golang.org/protobuf v1.36.5) require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hack-pad/safejs v0.1.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nlepage/go-js-promise v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.14.0 // indirect modernc.org/libc v1.62.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.9.1 // indirect modernc.org/sqlite v1.37.0 // indirect )
-
-
-
@@ -1,15 +1,38 @@connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8= github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ= github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo= github.com/nlepage/go-wasm-http-server/v2 v2.2.1 h1:4tzhSb3HKQ3Ykt2TPfqEnmcPfw8n1E8agv4OzAyckr8= github.com/nlepage/go-wasm-http-server/v2 v2.2.1/go.mod h1:r8j7cEOeUqNp+c+C52sNuWaFTvvT/cNqIwBuEtA36HA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
-
-
-
@@ -0,0 +1,24 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package migrations import ( "database/sql" _ "embed" ) //go:embed 0001.sql var stmts string type Migration001 struct{} func (_ Migration001) Run(tx *sql.Tx) error { _, err := tx.Exec(stmts) return err } func (_ Migration001) Version() uint { return 1 }
-
-
-
-
@@ -0,0 +1,14 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package migrations import ( "database/sql" ) type Migration interface { Run(tx *sql.Tx) error Version() uint }
-
-
-
@@ -0,0 +1,52 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only package migrations import ( "database/sql" "fmt" "log" ) func Run(db *sql.DB, migrations []Migration) error { var currentVersion uint row := db.QueryRow("PRAGMA user_version") if err := row.Scan(¤tVersion); err != nil { return err } log.Printf("Current schema version is %d", currentVersion) for _, m := range migrations { v := m.Version() if v <= currentVersion { continue } log.Printf("Running migration v%d...", v) tx, err := db.Begin() if err != nil { return err } if err := m.Run(tx); err != nil { log.Printf("Failed to run migration v%d: %s", v, err) return tx.Rollback() } if _, err := tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", v)); err != nil { return err } currentVersion = v if err := tx.Commit(); err != nil { return err } log.Printf("Completed migration v%d", v) } return nil }
-
-
-
@@ -3,6 +3,9 @@ "name": "@yamori/backend","private": true, "type": "module", "main": "sw.js", "dependencies": { "sql.js": "~1.6.2" }, "scripts": { "check": "wireit", "make": "wireit",
-
@@ -15,7 +18,13 @@ "env": {"GOOS": "js", "GOARCH": "wasm" }, "files": ["**/*.go", "go.mod", "go.sum"], "files": [ "**/*.go", "go.mod", "go.sum", "../../vendor/**/*.go", "migrations/*.sql" ], "dependencies": ["../proto:go"], "output": ["backend.wasm"], "packageLocks": []
-
@@ -26,7 +35,7 @@ "output": ["backend_prelude.js"]}, "make:bin": { "command": "go build -o backend pocka.jp/x/yamori/backend/server", "files": ["**/*.go", "go.mod", "go.sum"], "files": ["**/*.go", "go.mod", "go.sum", "migrations/*.sql"], "dependencies": ["../proto:go"], "output": ["backend"], "packageLocks": []
-
-
-
@@ -4,6 +4,7 @@package main import ( "database/sql" _ "embed" "log" "net/http"
-
@@ -12,10 +13,17 @@ "golang.org/x/net/http2""golang.org/x/net/http2/h2c" "pocka.jp/x/yamori/backend/core" "pocka.jp/x/yamori/backend/services" _ "modernc.org/sqlite" ) func main() { core, err := core.Init() db, err := sql.Open("sqlite", ":memory:") if err != nil { log.Fatal(err) } core, err := core.Init(db) if err != nil { log.Fatal(err) }
-
-
-
@@ -19,6 +19,10 @@ type Service struct {core *core.Core } func New(core *core.Core) *Service { return &Service{core: core} } func (s *Service) Ping( ctx context.Context, req *connect.Request[metaV1.PingRequest],
-
-
-
@@ -14,11 +14,8 @@func Mux(core *core.Core) *http.ServeMux { mux := http.NewServeMux() meta := meta.Service{} meta.Register(mux) workspace := workspace.Service{} workspace.Register(mux) meta.New(core).Register(mux) workspace.New(core).Register(mux) return mux }
-
-
-
@@ -20,30 +20,21 @@ type Service struct {core *core.Core } func New(core *core.Core) *Service { return &Service{core: core} } func (s *Service) Login( ctx context.Context, req *connect.Request[workspaceV2.LoginRequest], ) (*connect.Response[workspaceV2.LoginResponse], error) { if *req.Msg.Name == "Alice" { ok := workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_Ok{ Ok: &workspaceV2.User{ Name: proto.String("Alice"), }, }, } return connect.NewResponse(&ok), nil } res := workspaceV2.LoginResponse{ return connect.NewResponse(&workspaceV2.LoginResponse{ Result: &workspaceV2.LoginResponse_SystemError{ SystemError: &errorV1.SystemError{ Message: proto.String("Not Implemented"), }, }, } return connect.NewResponse(&res), nil }), nil } func (s *Service) Logout(
-
-
-
@@ -6,16 +6,24 @@package main import ( "database/sql" "log" wasmhttp "github.com/nlepage/go-wasm-http-server/v2" "pocka.jp/x/yamori/backend/core" "pocka.jp/x/yamori/backend/services" _ "github.com/matrix-org/go-sqlite3-js" ) func main() { core, err := core.Init() db, err := sql.Open("sqlite3", "yamori.wasm") if err != nil { log.Fatal(err) } core, err := core.Init(db) if err != nil { log.Fatal(err) }
-
-
-
@@ -5,39 +5,53 @@ // go-wasm-http-server の sw.js を WebWorker で動くようにしたもの。// I/F を揃えているが、 Go の JS シムに触れているため、 Go や // go-wasm-http-server をアップグレードする際には入念に確認すること。 import initSqlJs from "sql.js"; import sqlJsWasmUrl from "sql.js/dist/sql-wasm.wasm?url"; import "./backend_prelude.js"; const wasm = new URL("./backend.wasm", import.meta.url); const wasmUrl = new URL("./backend.wasm", import.meta.url); const handler = new Promise((resolve) => { self.wasmhttp = { path: "/", setHandler: resolve, }; }); async function setup() { const handler = new Promise((resolve) => { self.wasmhttp = { path: "/", setHandler: resolve, }; }); const go = new Go(); go.argv = [wasm]; self.addEventListener("message", async (event) => { const req = new Request(event.data.input, event.data.options); WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(({ instance }) => go.run(instance), ); const resp = await (await handler)(req); self.addEventListener("message", async (event) => { const req = new Request(event.data.input, event.data.options); const body = await resp.arrayBuffer(); const resp = await (await handler)(req); self.postMessage( { id: event.data.id, body, headers: Object.fromEntries(resp.headers.entries()), }, { transfer: [body], }, ); }); const body = await resp.arrayBuffer(); const [wasm, sqlJs] = await Promise.all([ fetch(wasmUrl), initSqlJs({ locateFile: (_file) => sqlJsWasmUrl }), ]); self._go_sqlite = sqlJs; const go = new Go(); go.argv = [wasmUrl]; self.postMessage( { id: event.data.id, body, headers: Object.fromEntries(resp.headers.entries()), }, { transfer: [body], }, WebAssembly.instantiateStreaming(wasm, go.importObject).then(({ instance }) => go.run(instance), ); }); } setup();
-
-
-
@@ -9,3 +9,5 @@ require (connectrpc.com/connect v1.18.1 google.golang.org/protobuf v1.36.5 ) require github.com/google/go-cmp v0.6.0 // indirect
-
-
-
@@ -1,5 +1,5 @@connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
-
-
-
@@ -0,0 +1,1 @@node_modules
-
-
-
@@ -0,0 +1,4 @@.DS_Store main.wasm node_modules yarn.lock
-
-
-
@@ -0,0 +1,21 @@run: timeout: 5m linters: enable: - vet - vetshadow - typecheck - deadcode - gocyclo - golint - varcheck - structcheck - maligned - ineffassign - misspell - unparam - goimports - goconst - unconvert - errcheck - interfacer
-
-
-
@@ -0,0 +1,18 @@FROM golang:1.13-stretch # Install node and yarn RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - RUN apt-get update && apt-get install -y nodejs RUN npm install -g yarn # Download golangci-lint and sql.js WORKDIR /test RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.24.0 ENV GOOS js ENV GOARCH wasm ADD package.json . RUN yarn install # Run the tests ADD . . CMD go test -exec="./go_sqlite_js_wasm_exec" .
-
-
-
@@ -0,0 +1,177 @@Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS
-
-
-
@@ -0,0 +1,18 @@### go-sqlite3-js Experimental SQL driver for sql.js (in-browser sqlite) from Go WASM. Only implements the subset of the SQL API required by Dendrite. To run tests in Docker and Node: ``` $ docker build -t gsj . $ docker run gsj ``` To run tests locally: ```bash $ yarn install $ GOOS=js GOARCH=wasm go test -exec="./go_sqlite_js_wasm_exec" . ```
-
-
-
@@ -0,0 +1,114 @@package sqlite3_js //nolint:golint import ( "context" "database/sql/driver" "fmt" "strings" "sync" "syscall/js" ) // SqliteJsConn implements driver.Conn. type SqliteJsConn struct { JsDb js.Value // sql.js SQL.Database : https://sql-js.github.io/sql.js/documentation/class/Database.html mu *sync.Mutex } // Prepare creates a prepared statement for later queries or executions. Multiple // queries or executions may be run concurrently from the returned statement. The // caller must call the statement's Close method when the statement is no longer // needed. func (conn *SqliteJsConn) Prepare(query string) (stmt driver.Stmt, err error) { defer protect("Prepare", func(e error) { err = e }) return &SqliteJsStmt{ c: conn, js: conn.JsDb.Call("prepare", query), }, nil } // Close returns the connection to the connection pool. All operations after a // Close will return with ErrConnDone. Close is safe to call concurrently with // other operations and will block until all other operations finish. It may be // useful to first cancel any used context and then call close directly after. func (conn SqliteJsConn) Close() error { // TODO return nil } func (conn *SqliteJsConn) Exec(query string, args []driver.Value) (result driver.Result, err error) { defer protect("Exec", func(e error) { err = e }) query = strings.TrimRight(query, ";") if strings.Contains(query, ";") { if len(args) != 0 { return nil, fmt.Errorf("cannot exec multiple statements with placeholders, query: %s nargs=%d", query, len(args)) } jsVal, err := jsTryCatch(func() js.Value { return conn.JsDb.Call("exec", query) }) if err != nil { return nil, err } return &SqliteJsResult{ js: jsVal, changes: 0, id: 0, }, nil } list := make([]namedValue, len(args)) for i, v := range args { list[i] = namedValue{ Ordinal: i + 1, Value: v, } } return conn.exec(context.Background(), query, list) } func (conn *SqliteJsConn) exec(ctx context.Context, query string, args []namedValue) (driver.Result, error) { // FIXME: we removed tbe ability to handle 'tails' - is this a problem? s, err := conn.Prepare(query) if err != nil { return nil, err } var res driver.Result res, err = s.(*SqliteJsStmt).exec(ctx, args) s.Close() return res, err } // Transactions // Begin starts a transaction. The default isolation level is dependent on the driver. func (conn *SqliteJsConn) Begin() (driver.Tx, error) { return conn.begin(context.Background()) } // BeginTx starts and returns a new transaction. // If the context is canceled by the user the sql package will // call Tx.Rollback before discarding and closing the connection. // // This must check opts.Isolation to determine if there is a set // isolation level. If the driver does not support a non-default // level and one is set or if there is a non-default isolation level // that is not supported, an error must be returned. // // This must also check opts.ReadOnly to determine if the read-only // value is true to either set the read-only transaction property if supported // or return an error if it is not supported. func (conn *SqliteJsConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { return conn.begin(ctx) } func (conn *SqliteJsConn) begin(ctx context.Context) (driver.Tx, error) { //nolint:unparam /* if conn.disableTxns { fmt.Println("Ignoring BEGIN, txns disabled") return &SqliteJsTx{c: conn}, nil } if _, err := conn.exec(ctx, "BEGIN", nil); err != nil { return nil, err } */ return &SqliteJsTx{c: conn}, nil }
-
-
-
@@ -0,0 +1,3 @@module github.com/matrix-org/go-sqlite3-js go 1.13
-
-
-
@@ -0,0 +1,2 @@#!/bin/bash exec node "./wasm_exec.js" "$@"
-
-
-
@@ -0,0 +1,56 @@package sqlite3_js //nolint:golint import ( "fmt" "os" "runtime/debug" "syscall/js" ) const ( // The name of the global where sql.js has been loaded. This is the `SQL` var of: // const initSqlJs = require('sql.js'); // const SQL = await initSqlJs({ ...}) globalSQLJS = "_go_sqlite" // The name of the global where sql.js should store its databases. This is purely // for debugging as JS-land doesn't ever read this map, but Go stores databases here. // The value of this global is an empty Map. It is crucial this isn't an empty object // else database names like 'hasOwnProperty' will fail due to it existing but // not being a database object! globalSQLDBs = "_go_sqlite_dbs" ) // jsEnsureGlobal is a helper function to set-if-not-exists and return whether the global existed. func jsEnsureGlobal(globalName string, defaultVal *js.Value) (existed bool) { v := js.Global().Get(globalName) if v.Truthy() { return true } if defaultVal != nil { js.Global().Set(globalName, *defaultVal) v = *defaultVal } return false } // jsTryCatch is a helper function that catches exceptions/panics thrown by fn and returns them as error. // This is useful for calling JS functions which can throw. func jsTryCatch(fn func() js.Value) (val js.Value, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("exception: %s", e) } }() return fn(), nil } // protect is a helper function which guards against panics, setting an error when it happens. func protect(name string, setError func(error)) { err := recover() if err != nil { fmt.Fprintf(os.Stderr, "%s panicked: %s\n", name, err) debug.PrintStack() setError(fmt.Errorf("%s panicked: %s", name, err)) } }
-
-
-
@@ -0,0 +1,10 @@{ "name": "go-sqlite-js", "version": "0.1.0", "description": "go SQL driver for sql.js from go-wasm", "main": "js/index.js", "license": "Apache 2.0", "dependencies": { "sql.js": "^1.6.2" } }
-
-
-
@@ -0,0 +1,23 @@// -*- coding: utf-8 -*- // Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import initSqlJs from 'sql.js' // Load sql.js into a global var which Go will use export function init(config) { return initSqlJs(config).then(SQL => { global._go_sqlite = SQL; }); }
-
-
-
@@ -0,0 +1,245 @@// -*- coding: utf-8 -*- // Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Derived from https://github.com/mattn/go-sqlite3 package sqlite3_js //nolint:golint import ( "context" "database/sql" "database/sql/driver" "fmt" "io" "log" "strconv" "strings" "sync" "syscall/js" ) func init() { sql.Register("sqlite3", &SqliteJsDriver{}) dbMap := js.Global().Get("Map").New() jsEnsureGlobal(globalSQLDBs, &dbMap) exists := jsEnsureGlobal(globalSQLJS, nil) if !exists { panic(globalSQLJS + " must be set a global variable in JS") } } // SqliteJsDriver implements driver.Driver. type SqliteJsDriver struct { ConnectHook func(*SqliteJsConn) error } // SqliteJsTx implements driver.Tx. type SqliteJsTx struct { c *SqliteJsConn } // SqliteJsResult implements sql.Result. type SqliteJsResult struct { js js.Value id int64 changes int64 } // SqliteJsRows implements driver.Rows. type SqliteJsRows struct { s *SqliteJsStmt // nc int // cols []string // decltype []string closed bool cls bool ctx context.Context // no better alternative to pass context into Next() method } // Open a database "connection" to a SQLite database. func (d *SqliteJsDriver) Open(dsn string) (conn driver.Conn, err error) { dsn = strings.TrimPrefix(dsn, "file:") defer protect("Open", func(e error) { err = e }) dbMap := js.Global().Get(globalSQLDBs) jsDb := dbMap.Call("get", dsn) if !jsDb.Truthy() { jsDb = js.Global().Get(globalSQLJS).Get("Database").New(dsn) dbMap.Call("set", dsn, jsDb) } fmt.Println("Open ->", dsn, "err=", err) return &SqliteJsConn{ JsDb: jsDb, mu: &sync.Mutex{}, }, nil } // Commit commits the transaction. func (tx *SqliteJsTx) Commit() error { return nil /* if tx.c.disableTxns { fmt.Println("Ignoring COMMIT, txns disabled") return nil } _, err := tx.c.exec(context.Background(), "COMMIT", nil) if err != nil { // FIXME: ideally should only be called when // && err.(Error).Code == C.SQLITE_BUSY // // sqlite3 will leave the transaction open in this scenario. // However, database/sql considers the transaction complete once we // return from Commit() - we must clean up to honour its semantics. tx.c.exec(context.Background(), "ROLLBACK", nil) } return err */ } // Rollback aborts the transaction. func (tx *SqliteJsTx) Rollback() error { return nil /* if tx.c.disableTxns { fmt.Println("Ignoring ROLLBACK, txns disabled") return nil } _, err := tx.c.exec(context.Background(), "ROLLBACK", nil) return err */ } // Rows // Columns returns the names of the columns. The number of // columns of the result is inferred from the length of the // slice. If a particular column name isn't known, an empty // string should be returned for that entry. func (r *SqliteJsRows) Columns() []string { res := r.s.js.Call("getColumnNames") cols := make([]string, res.Length()) for i := 0; i < res.Length(); i++ { cols[i] = res.Get(strconv.Itoa(i)).String() } return cols } // Next is called to populate the next row of data into // the provided slice. The provided slice will be the same // size as the Columns() are wide. // // Next should return io.EOF when there are no more rows. // // The dest should not be written to outside of Next. Care // should be taken when closing Rows not to modify // a buffer held in dest. func (r *SqliteJsRows) Next(dest []driver.Value) error { r.s.mu.Lock() defer r.s.mu.Unlock() if r.s.closed { return io.EOF } if r.ctx.Done() == nil { return r.nextSyncLocked(dest) } resultCh := make(chan error) go func() { defer func() { if perr := recover(); perr != nil { fmt.Printf("SqliteJsRows.Next panicked! err=%s", perr) resultCh <- fmt.Errorf("SqliteJsRows.Next panicked! err=%s", perr) } }() resultCh <- r.nextSyncLocked(dest) }() select { case err := <-resultCh: return err case <-r.ctx.Done(): select { case <-resultCh: // no need to interrupt default: // this is still racy and can be no-op if executed between sqlite3_* calls in nextSyncLocked. // FIXME: find a way to interrupt // C.sqlite3_interrupt(rc.s.c.db) <-resultCh // ensure goroutine completed } return r.ctx.Err() } } // nextSyncLocked moves cursor to next; must be called with locked mutex. func (r *SqliteJsRows) nextSyncLocked(dest []driver.Value) error { rr := r.s.Next() if rr == nil { return io.EOF } res := *rr for i := 0; i < res.Length(); i++ { jsVal := res.Get(strconv.Itoa(i)) switch t := jsVal.Type(); t { case js.TypeNull: dest[i] = nil case js.TypeBoolean: dest[i] = jsVal.Bool() case js.TypeNumber: dest[i] = jsVal.Int() case js.TypeString: dest[i] = jsVal.String() case js.TypeSymbol: log.Fatal("Don't know how to handle Symbols yet") case js.TypeObject: // check for []byte if jsVal.Get("byteLength").Truthy() { uint8slice := make([]uint8, jsVal.Get("byteLength").Int()) js.CopyBytesToGo(uint8slice, jsVal) dest[i] = uint8slice } else { log.Fatal("Don't know how to handle Objects yet") } case js.TypeFunction: log.Fatal("Don't know how to handle Functions yet") } } return nil } // Close closes the rows iterator. func (r *SqliteJsRows) Close() error { r.s.mu.Lock() defer r.s.mu.Unlock() if r.s.closed || r.closed { return nil } r.closed = true if r.cls { return r.s.Close() } r.s.js.Call("reset") return nil } // Results // LastInsertId return last inserted ID. func (r *SqliteJsResult) LastInsertId() (int64, error) { return r.id, nil } // RowsAffected return how many rows affected. func (r *SqliteJsResult) RowsAffected() (int64, error) { return r.changes, nil }
-
-
-
@@ -0,0 +1,369 @@// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sqlite3_js_test import ( "context" "crypto/sha256" "database/sql" "fmt" "testing" _ "github.com/matrix-org/go-sqlite3-js" ) var i = 1 func newDB(t *testing.T, schema string) *sql.DB { var db *sql.DB var err error i++ if db, err = sql.Open("sqlite3_js", fmt.Sprintf("test-%d.db", i)); err != nil { t.Fatalf("cannot open test.db: %s", err) } _, err = db.Exec(schema) if err != nil { t.Fatalf("cannot create schema: %s", err) } return db } func assertStored(t *testing.T, db *sql.DB, query string, wants []string) { //nolint:unparam rows, err := db.Query(query) if err != nil { t.Fatalf("assertStored: cannot run query: %s", err) } defer rows.Close() var gots []string for rows.Next() { var got string if err := rows.Scan(&got); err != nil { t.Fatalf("assertStored: failed to scan row: %s", err) } gots = append(gots, got) } if len(gots) != len(wants) { t.Fatalf("assertStored: got %d results, want %d", len(gots), len(wants)) } for i := range wants { if gots[i] != wants[i] { t.Errorf("assertStored: result row %d got %s, want %s", i, gots[i], wants[i]) } } } func TestEmptyQuery(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") stmt, err := db.Prepare("SELECT id, name FROM foo") if err != nil { t.Fatal(err) } // querying an empty table shouldn't produce an error rows, err := stmt.Query() if err != nil { t.Fatal(err) } rows.Close() } func TestEmptyStmtQueryWithResults(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") wantIDs := []int{11, 12, 13} wantNames := []string{"Eleven", "Mike", "Dustin"} for i := range wantIDs { _, err := db.Exec("INSERT INTO foo VALUES(?,?)", wantIDs[i], wantNames[i]) if err != nil { t.Fatalf("Insert failed: %s", err) } } stmt, err := db.Prepare("SELECT id, name FROM foo") if err != nil { t.Fatal(err) } rows, err := stmt.Query() if err != nil { t.Fatal(err) } defer rows.Close() i := 0 for rows.Next() { var id int var name string err := rows.Scan(&id, &name) if err != nil { t.Fatalf("Scan failed: %s", err) } if id != wantIDs[i] { t.Errorf("Row %d: got ID %d, want %d", i, id, wantIDs[i]) } if name != wantNames[i] { t.Errorf("Row %d: got name %s, want %s", i, name, wantNames[i]) } i++ } if len(wantIDs) != i { t.Errorf("Mismatched number of returned rows: %d != %d", len(wantIDs), i) } } func TestErrNoRows(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") stmt, err := db.Prepare("SELECT id, name FROM foo") if err != nil { t.Fatal(err) } // query row context should return ErrNoRows var a int64 var b string err = stmt.QueryRowContext(context.Background()).Scan(&a, &b) if err != sql.ErrNoRows { t.Fatalf("Expected sql.ErrNoRows to QueryRowContext, got %s", err) } } func TestMultipleConnSupport(t *testing.T) { // We check this by doing multiple txns at once. If the same conn is used, // we'll error out with: // sql.js: cannot start a transaction within a transaction // Dendrite only does this once, then calls a bunch of stuff db := newDB(t, "CREATE TABLE foo(id INTEGER)") tx, err := db.Begin() if err != nil { t.Fatal(err) } _, err = tx.Exec("CREATE TABLE bar(id INTEGER)") if err != nil { t.Fatalf("tx1 exec failed: %s", err) } // begin a 2nd txn without closing the 1st tx2, err := db.Begin() if err != nil { t.Fatal(err) } _, err = tx2.Exec("CREATE TABLE baz(id INTEGER)") if err != nil { t.Fatalf("tx2 exec failed: %s", err) } if err = tx2.Commit(); err != nil { t.Fatalf("tx2 commit failed: %s", err) } if err = tx.Commit(); err != nil { t.Fatalf("tx1 commit failed: %s", err) } } func TestBlobSupport(t *testing.T) { db := newDB(t, "create table blobs(id INTEGER, thing BLOB)") blobStmt, err := db.Prepare("INSERT INTO blobs(id, thing) values($1, $2)") if err != nil { t.Fatal(err) } rawBytes := sha256.Sum256([]byte("hello world")) _, err = blobStmt.Exec(44, rawBytes[:]) if err != nil { t.Fatal(err) } blobSelectStmt, err := db.Prepare("SELECT thing FROM blobs WHERE id = $1") if err != nil { t.Fatal(err) } var bres []byte if err := blobSelectStmt.QueryRow(44).Scan(&bres); err != nil { t.Fatal(err) } if len(bres) != len(rawBytes) { t.Fatalf("Mismatched lengths: got %d want %d", len(bres), len(rawBytes)) } for i := range bres { if bres[i] != rawBytes[i] { t.Fatalf("Wrong value at pos %d/%d: got %d want %d", i, len(bres), bres[i], rawBytes[i]) } } t.Log("OK: checked ", len(bres), " bytes") } func TestInsertNull(t *testing.T) { db := newDB(t, "create table bar(id INTEGER PRIMARY KEY, name string)") res, err := db.Exec("insert into bar values(9001, NULL)") if err != nil { t.Fatal(err) } if ra, _ := res.RowsAffected(); ra != 1 { t.Fatalf("expected 1 row affected, got %d", ra) } } func TestInsertPrimaryKeyConflict(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") _, err := db.Exec("insert into foo values(42, 'meaning of life')") if err != nil { t.Fatal(err) } _, err = db.Exec("insert into foo values(42, 'meaning of life')") if err == nil { t.Fatal("Expected error, got nil") } } func TestUpdate(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") _, err := db.Exec("insert into foo values(42, 'meaning of life')") if err != nil { t.Fatalf("failed to insert: %s", err) } assertStored(t, db, "SELECT name FROM foo", []string{"meaning of life"}) _, err = db.Exec("UPDATE foo SET name='mol' WHERE name='meaning of life'") if err != nil { t.Fatalf("failed to update: %s", err) } assertStored(t, db, "SELECT name FROM foo", []string{"mol"}) } func TestParameterisedInsert(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") _, err := db.Exec("insert into foo values(?, ?)", 31337, "so leet") if err != nil { t.Fatal(err) } assertStored(t, db, "SELECT name FROM foo", []string{"so leet"}) } func TestParameterisedStmtExec(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") var stmt *sql.Stmt var err error if stmt, err = db.Prepare("insert into foo values(?, ?)"); err != nil { t.Fatal(err) } _, err = stmt.Exec(12345678, "monotonic") if err != nil { t.Fatalf("Failed exec: %s", err) } assertStored(t, db, "SELECT name FROM foo", []string{"monotonic"}) } func TestCommit(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") var txn *sql.Tx var stmt *sql.Stmt var err error if txn, err = db.Begin(); err != nil { t.Fatalf("begin failed: %s", err) } if stmt, err = db.Prepare("insert into foo values(?, ?)"); err != nil { t.Fatalf("prepare failed: %s", err) } stmt = txn.Stmt(stmt) _, err = stmt.Exec(999, "happening") if err != nil { t.Fatalf("exec failed: %s", err) } if err = txn.Commit(); err != nil { t.Fatalf("Commit failed: %s", err) } assertStored(t, db, "SELECT name FROM foo", []string{"happening"}) } func TestRollback(t *testing.T) { t.Skip() // txn support disabled db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") var txn *sql.Tx var stmt *sql.Stmt var err error if txn, err = db.Begin(); err != nil { t.Fatalf("begin failed: %s", err) } if stmt, err = db.Prepare("insert into foo values(?, ?)"); err != nil { t.Fatalf("prepare failed: %s", err) } stmt = txn.Stmt(stmt) _, err = stmt.Exec(666, "not happening") if err != nil { t.Fatalf("exec failed: %s", err) } if err = txn.Rollback(); err != nil { t.Fatalf("rollback failed: %s", err) } assertStored(t, db, "SELECT name FROM foo", []string{}) } func TestStarSelectSingle(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") wantID := 11 wantName := "Eleven" _, err := db.Exec("INSERT INTO foo VALUES(?,?)", wantID, wantName) if err != nil { t.Fatalf("Insert failed: %s", err) } stmt, err := db.Prepare("select * from foo") if err != nil { t.Fatal(err) } var id int var name string if err = stmt.QueryRow().Scan(&id, &name); err != nil { t.Fatalf("Failed to query row: %s", err) } stmt.Close() if id != wantID { t.Errorf("ID: got %d want %d", id, wantID) } if name != wantName { t.Errorf("Name got %s want %s", name, wantName) } } func TestStarSelectMulti(t *testing.T) { db := newDB(t, "create table foo(id INTEGER PRIMARY KEY, name string)") wantIDs := []int{11, 12, 13} wantNames := []string{"Eleven", "Mike", "Dustin"} for i := range wantIDs { _, err := db.Exec("INSERT INTO foo VALUES(?,?)", wantIDs[i], wantNames[i]) if err != nil { t.Fatalf("Insert failed: %s", err) } } rows, err := db.Query("select * from foo") if err != nil { t.Fatal(err) } defer rows.Close() i := 0 for rows.Next() { var id int var name string err := rows.Scan(&id, &name) if err != nil { t.Fatalf("Scan failed: %s", err) } if id != wantIDs[i] { t.Errorf("Row %d: got ID %d, want %d", i, id, wantIDs[i]) } if name != wantNames[i] { t.Errorf("Row %d: got name %s, want %s", i, name, wantNames[i]) } i++ } if len(wantIDs) != i { t.Errorf("Mismatched number of returned rows: %d != %d", len(wantIDs), i) } }
-
-
-
@@ -0,0 +1,224 @@package sqlite3_js //nolint:golint import ( "context" "database/sql/driver" "fmt" "sync" "syscall/js" ) // SqliteJsStmt implements driver.Stmt. type SqliteJsStmt struct { c *SqliteJsConn js js.Value // sql.js Statement: https://sql-js.github.io/sql.js/documentation/class/Statement.html mu sync.Mutex closed bool cls bool // wild guess: connection level statement? hasNext bool } type namedValue struct { Name string Ordinal int Value driver.Value } // Exec executes a prepared statement with the given arguments and returns a // Result summarizing the effect of the statement. func (s *SqliteJsStmt) Exec(args []driver.Value) (driver.Result, error) { list := make([]namedValue, len(args)) for i, v := range args { list[i] = namedValue{ Ordinal: i + 1, Value: v, } } return s.exec(context.Background(), list) } // ExecContext executes a query that doesn't return rows, such // as an INSERT or UPDATE. // // ExecContext must honor the context timeout and return when it is canceled. func (s *SqliteJsStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { list := make([]namedValue, len(args)) for i, nv := range args { list[i] = namedValue(nv) } return s.exec(ctx, list) } // exec executes a query that doesn't return rows. Attempts to honor context timeout. func (s *SqliteJsStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) { if ctx.Done() == nil { return s.execSync(args) } type result struct { r driver.Result err error } resultCh := make(chan result) go func() { defer func() { if perr := recover(); perr != nil { fmt.Printf("SqliteJsStmt.exec panicked! nargs=%d err=%s", len(args), perr) resultCh <- result{nil, fmt.Errorf("SqliteJsStmt.exec panicked! nargs=%d err=%s", len(args), perr)} } }() r, err := s.execSync(args) resultCh <- result{r, err} }() select { case rv := <-resultCh: return rv.r, rv.err case <-ctx.Done(): select { case <-resultCh: // no need to interrupt default: // FIXME: find a way to actually interrupt the connection // this is still racy and can be no-op if executed between sqlite3_* calls in execSync. // C.sqlite3_interrupt(s.c.db) <-resultCh // ensure goroutine completed } return nil, ctx.Err() } } func (s *SqliteJsStmt) execSync(args []namedValue) (driver.Result, error) { // We're going to issue a bunch of JS calls, some of which (last rowid) // are NOT statement-level scoped, but connection-level scoped, so we cannot just // lock the statement mutex we have already, otherwise multiple goroutines may // exec in this function, causing the last insert rowid to be wrong. s.c.mu.Lock() defer s.c.mu.Unlock() jsArgs := make([]interface{}, len(args)) for i, v := range args { if bval, ok := v.Value.([]byte); ok { dst := js.Global().Get("Uint8Array").New(len(bval)) js.CopyBytesToJS(dst, bval) jsArgs[i] = dst } else { jsArgs[i] = js.ValueOf(v.Value) } } result, err := jsTryCatch(func() js.Value { return s.js.Call("run", jsArgs) }) if err != nil { return nil, fmt.Errorf("execSync sql.js: %s", err) } // TODO: Kinda sucks each exec is paired with 2 extra calls but we have to do it ASAP else we risk // getting out of sync with subsequent inserts. rowsModified := s.c.JsDb.Call("getRowsModified") rowidRes, err := jsTryCatch(func() js.Value { rows := s.c.JsDb.Call("exec", "SELECT last_insert_rowid()") if rows.Length() != 1 { // query result // this gets recover()d and turns into an error panic(fmt.Sprintf("last_insert_rowid: expected 1 row to be returned, got %d", rows.Length())) } // 'rows' is of the form: [{columns: ['id'], values:[[1],[2],[3]]}] return rows.Index(0).Get("values").Index(0).Index(0) }) if err != nil { return nil, fmt.Errorf("execSync: error getting rowid: %s", err) } return &SqliteJsResult{ js: result, changes: int64(rowsModified.Int()), id: int64(rowidRes.Int()), }, nil } // Query executes a query that may return rows, such as a // SELECT. // // Deprecated: Drivers should implement StmtQueryContext instead (or additionally). func (s *SqliteJsStmt) Query(args []driver.Value) (driver.Rows, error) { list := make([]namedValue, len(args)) for i, v := range args { list[i] = namedValue{ Ordinal: i + 1, Value: v, } } return s.query(context.Background(), list) } // QueryContext executes a query that may return rows, such as a // SELECT. // // QueryContext must honor the context timeout and return when it is canceled. func (s *SqliteJsStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { list := make([]namedValue, len(args)) for i, nv := range args { list[i] = namedValue(nv) } return s.query(ctx, list) } func (s *SqliteJsStmt) query(ctx context.Context, args []namedValue) (driver.Rows, error) { jsArgs := make([]interface{}, len(args)) for i, v := range args { if bval, ok := v.Value.([]byte); ok { dst := js.Global().Get("Uint8Array").New(len(bval)) js.CopyBytesToJS(dst, bval) jsArgs[i] = dst } else { jsArgs[i] = js.ValueOf(v.Value) } } jsOk := s.js.Call("bind", jsArgs) if !jsOk.Bool() { return nil, fmt.Errorf("failed to bind query") } res := s.js.Call("step") s.hasNext = res.Bool() return &SqliteJsRows{ s: s, cls: s.cls, // FIXME: we never set s.cls, as we haven't implemented conn.Query(), which would set it ctx: ctx, }, nil } func (s *SqliteJsStmt) Next() *js.Value { if !s.hasNext { return nil } row := s.js.Call("get") jsHasNext := s.js.Call("step") s.hasNext = jsHasNext.Bool() return &row } // NumInput returns the number of placeholder parameters. // // If NumInput returns >= 0, the sql package will sanity check // argument counts from callers and return errors to the caller // before the statement's Exec or Query methods are called. // // NumInput may also return -1, if the driver doesn't know // its number of placeholders. In that case, the sql package // will not sanity check Exec or Query argument counts. func (s *SqliteJsStmt) NumInput() int { return -1 } // Close closes the statement. func (s *SqliteJsStmt) Close() error { s.mu.Lock() defer s.mu.Unlock() if s.closed { return nil } s.closed = true res := s.js.Call("free") if !res.Bool() { return fmt.Errorf("couldn't close stmt") } return nil }
-
-
-
@@ -0,0 +1,538 @@// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. const initSqlJs = require('sql.js'); (() => { // Load sql.js initSqlJs().then((SQL) => { global._go_sqlite = SQL; }) // Map multiple JavaScript environments to a single common API, // preferring web standards over Node.js API. // // Environments considered: // - Browsers // - Node.js // - Electron // - Parcel if (typeof global !== "undefined") { // global already exists } else if (typeof window !== "undefined") { window.global = window; } else if (typeof self !== "undefined") { self.global = self; } else { throw new Error("cannot export Go (neither global, window nor self is defined)"); } if (!global.require && typeof require !== "undefined") { global.require = require; } if (!global.fs && global.require) { global.fs = require("fs"); } if (!global.fs) { let outputBuf = ""; global.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); if (nl != -1) { console.log(outputBuf.substr(0, nl)); outputBuf = outputBuf.substr(nl + 1); } return buf.length; }, write(fd, buf, offset, length, position, callback) { if (offset !== 0 || length !== buf.length || position !== null) { throw new Error("not implemented"); } const n = this.writeSync(fd, buf); callback(null, n); }, open(path, flags, mode, callback) { const err = new Error("not implemented"); err.code = "ENOSYS"; callback(err); }, read(fd, buffer, offset, length, position, callback) { const err = new Error("not implemented"); err.code = "ENOSYS"; callback(err); }, fsync(fd, callback) { callback(null); }, }; } if (!global.crypto) { const nodeCrypto = require("crypto"); global.crypto = { getRandomValues(b) { nodeCrypto.randomFillSync(b); }, }; } if (!global.performance) { global.performance = { now() { const [sec, nsec] = process.hrtime(); return sec * 1000 + nsec / 1000000; }, }; } if (!global.TextEncoder) { global.TextEncoder = require("util").TextEncoder; } if (!global.TextDecoder) { global.TextDecoder = require("util").TextDecoder; } // End of polyfills for common API. const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); global.Go = class { constructor() { this.argv = ["js"]; this.env = {}; this.exit = (code) => { if (code !== 0) { console.warn("exit code:", code); } }; this._exitPromise = new Promise((resolve) => { this._resolveExitPromise = resolve; }); this._pendingEvent = null; this._scheduledTimeouts = new Map(); this._nextCallbackTimeoutID = 1; const mem = () => { // The buffer may change when requesting more memory. return new DataView(this._inst.exports.mem.buffer); } const setInt64 = (addr, v) => { mem().setUint32(addr + 0, v, true); mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); } const getInt64 = (addr) => { const low = mem().getUint32(addr + 0, true); const high = mem().getInt32(addr + 4, true); return low + high * 4294967296; } const loadValue = (addr) => { const f = mem().getFloat64(addr, true); if (f === 0) { return undefined; } if (!isNaN(f)) { return f; } const id = mem().getUint32(addr, true); return this._values[id]; } const storeValue = (addr, v) => { const nanHead = 0x7FF80000; if (typeof v === "number") { if (isNaN(v)) { mem().setUint32(addr + 4, nanHead, true); mem().setUint32(addr, 0, true); return; } if (v === 0) { mem().setUint32(addr + 4, nanHead, true); mem().setUint32(addr, 1, true); return; } mem().setFloat64(addr, v, true); return; } switch (v) { case undefined: mem().setFloat64(addr, 0, true); return; case null: mem().setUint32(addr + 4, nanHead, true); mem().setUint32(addr, 2, true); return; case true: mem().setUint32(addr + 4, nanHead, true); mem().setUint32(addr, 3, true); return; case false: mem().setUint32(addr + 4, nanHead, true); mem().setUint32(addr, 4, true); return; } let ref = this._refs.get(v); if (ref === undefined) { ref = this._values.length; this._values.push(v); this._refs.set(v, ref); } let typeFlag = 0; switch (typeof v) { case "string": typeFlag = 1; break; case "symbol": typeFlag = 2; break; case "function": typeFlag = 3; break; } mem().setUint32(addr + 4, nanHead | typeFlag, true); mem().setUint32(addr, ref, true); } const loadSlice = (addr) => { const array = getInt64(addr + 0); const len = getInt64(addr + 8); return new Uint8Array(this._inst.exports.mem.buffer, array, len); } const loadSliceOfValues = (addr) => { const array = getInt64(addr + 0); const len = getInt64(addr + 8); const a = new Array(len); for (let i = 0; i < len; i++) { a[i] = loadValue(array + i * 8); } return a; } const loadString = (addr) => { const saddr = getInt64(addr + 0); const len = getInt64(addr + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); } const timeOrigin = Date.now() - performance.now(); this.importObject = { go: { // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). // This changes the SP, thus we have to update the SP used by the imported function. // func wasmExit(code int32) "runtime.wasmExit": (sp) => { const code = mem().getInt32(sp + 8, true); this.exited = true; delete this._inst; delete this._values; delete this._refs; this.exit(code); }, // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) "runtime.wasmWrite": (sp) => { const fd = getInt64(sp + 8); const p = getInt64(sp + 16); const n = mem().getInt32(sp + 24, true); fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); }, // func nanotime() int64 "runtime.nanotime": (sp) => { setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); }, // func walltime() (sec int64, nsec int32) "runtime.walltime": (sp) => { const msec = (new Date).getTime(); setInt64(sp + 8, msec / 1000); mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); }, // func scheduleTimeoutEvent(delay int64) int32 "runtime.scheduleTimeoutEvent": (sp) => { const id = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++; this._scheduledTimeouts.set(id, setTimeout( () => { this._resume(); while (this._scheduledTimeouts.has(id)) { // for some reason Go failed to register the timeout event, log and try again // (temporary workaround for https://github.com/golang/go/issues/28975) console.warn("scheduleTimeoutEvent: missed timeout event"); this._resume(); } }, getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early )); mem().setInt32(sp + 16, id, true); }, // func clearTimeoutEvent(id int32) "runtime.clearTimeoutEvent": (sp) => { const id = mem().getInt32(sp + 8, true); clearTimeout(this._scheduledTimeouts.get(id)); this._scheduledTimeouts.delete(id); }, // func getRandomData(r []byte) "runtime.getRandomData": (sp) => { crypto.getRandomValues(loadSlice(sp + 8)); }, // func stringVal(value string) ref "syscall/js.stringVal": (sp) => { storeValue(sp + 24, loadString(sp + 8)); }, // func valueGet(v ref, p string) ref "syscall/js.valueGet": (sp) => { const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); sp = this._inst.exports.getsp(); // see comment above storeValue(sp + 32, result); }, // func valueSet(v ref, p string, x ref) "syscall/js.valueSet": (sp) => { Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); }, // func valueIndex(v ref, i int) ref "syscall/js.valueIndex": (sp) => { storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); }, // valueSetIndex(v ref, i int, x ref) "syscall/js.valueSetIndex": (sp) => { Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) "syscall/js.valueCall": (sp) => { try { const v = loadValue(sp + 8); const m = Reflect.get(v, loadString(sp + 16)); const args = loadSliceOfValues(sp + 32); const result = Reflect.apply(m, v, args); sp = this._inst.exports.getsp(); // see comment above storeValue(sp + 56, result); mem().setUint8(sp + 64, 1); } catch (err) { storeValue(sp + 56, err); mem().setUint8(sp + 64, 0); } }, // func valueInvoke(v ref, args []ref) (ref, bool) "syscall/js.valueInvoke": (sp) => { try { const v = loadValue(sp + 8); const args = loadSliceOfValues(sp + 16); const result = Reflect.apply(v, undefined, args); sp = this._inst.exports.getsp(); // see comment above storeValue(sp + 40, result); mem().setUint8(sp + 48, 1); } catch (err) { storeValue(sp + 40, err); mem().setUint8(sp + 48, 0); } }, // func valueNew(v ref, args []ref) (ref, bool) "syscall/js.valueNew": (sp) => { try { const v = loadValue(sp + 8); const args = loadSliceOfValues(sp + 16); const result = Reflect.construct(v, args); sp = this._inst.exports.getsp(); // see comment above storeValue(sp + 40, result); mem().setUint8(sp + 48, 1); } catch (err) { storeValue(sp + 40, err); mem().setUint8(sp + 48, 0); } }, // func valueLength(v ref) int "syscall/js.valueLength": (sp) => { setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); }, // valuePrepareString(v ref) (ref, int) "syscall/js.valuePrepareString": (sp) => { const str = encoder.encode(String(loadValue(sp + 8))); storeValue(sp + 16, str); setInt64(sp + 24, str.length); }, // valueLoadString(v ref, b []byte) "syscall/js.valueLoadString": (sp) => { const str = loadValue(sp + 8); loadSlice(sp + 16).set(str); }, // func valueInstanceOf(v ref, t ref) bool "syscall/js.valueInstanceOf": (sp) => { mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) "syscall/js.copyBytesToGo": (sp) => { const dst = loadSlice(sp + 8); const src = loadValue(sp + 32); if (!(src instanceof Uint8Array)) { mem().setUint8(sp + 48, 0); return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); setInt64(sp + 40, toCopy.length); mem().setUint8(sp + 48, 1); }, // func copyBytesToJS(dst ref, src []byte) (int, bool) "syscall/js.copyBytesToJS": (sp) => { const dst = loadValue(sp + 8); const src = loadSlice(sp + 16); if (!(dst instanceof Uint8Array)) { mem().setUint8(sp + 48, 0); return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); setInt64(sp + 40, toCopy.length); mem().setUint8(sp + 48, 1); }, "debug": (value) => { console.log(value); }, } }; } async run(instance) { this._inst = instance; this._values = [ // TODO: garbage collection NaN, 0, null, true, false, global, this, ]; this._refs = new Map(); this.exited = false; const mem = new DataView(this._inst.exports.mem.buffer) // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. let offset = 4096; const strPtr = (str) => { const ptr = offset; const bytes = encoder.encode(str + "\0"); new Uint8Array(mem.buffer, offset, bytes.length).set(bytes); offset += bytes.length; if (offset % 8 !== 0) { offset += 8 - (offset % 8); } return ptr; }; const argc = this.argv.length; const argvPtrs = []; this.argv.forEach((arg) => { argvPtrs.push(strPtr(arg)); }); const keys = Object.keys(this.env).sort(); argvPtrs.push(keys.length); keys.forEach((key) => { argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); }); const argv = offset; argvPtrs.forEach((ptr) => { mem.setUint32(offset, ptr, true); mem.setUint32(offset + 4, 0, true); offset += 8; }); this._inst.exports.run(argc, argv); if (this.exited) { this._resolveExitPromise(); } await this._exitPromise; } _resume() { if (this.exited) { throw new Error("Go program has already exited"); } this._inst.exports.resume(); if (this.exited) { this._resolveExitPromise(); } } _makeFuncWrapper(id) { const go = this; return function () { const event = { id: id, this: this, args: arguments }; go._pendingEvent = event; go._resume(); return event.result; }; } } if ( global.require && global.require.main === module && global.process && global.process.versions && !global.process.versions.electron ) { if (process.argv.length < 3) { console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); process.exit(1); } const go = new Go(); go.argv = process.argv.slice(2); go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); go.exit = process.exit; WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { process.on("exit", (code) => { // Node.js exits if no event handler is pending if (code === 0 && !go.exited) { // deadlock, make Go print error and stack traces go._pendingEvent = { id: 0 }; go._resume(); } }); return go.run(result.instance); }).catch((err) => { console.error(err); process.exit(1); }); } })();
-