@sovereignbase/convergent-replicated-struct 0.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +271 -271
- package/dist/index.cjs.map +1 -1
- package/package.json +89 -89
package/LICENSE
CHANGED
|
@@ -1,201 +1,201 @@
|
|
|
1
|
-
Apache License
|
|
2
|
-
Version 2.0, January 2004
|
|
3
|
-
http://www.apache.org/licenses/
|
|
4
|
-
|
|
5
|
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
-
|
|
7
|
-
1. Definitions.
|
|
8
|
-
|
|
9
|
-
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
-
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
-
|
|
12
|
-
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
-
the copyright owner that is granting the License.
|
|
14
|
-
|
|
15
|
-
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
-
other entities that control, are controlled by, or are under common
|
|
17
|
-
control with that entity. For the purposes of this definition,
|
|
18
|
-
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
-
direction or management of such entity, whether by contract or
|
|
20
|
-
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
-
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
-
|
|
23
|
-
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
-
exercising permissions granted by this License.
|
|
25
|
-
|
|
26
|
-
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
-
including but not limited to software source code, documentation
|
|
28
|
-
source, and configuration files.
|
|
29
|
-
|
|
30
|
-
"Object" form shall mean any form resulting from mechanical
|
|
31
|
-
transformation or translation of a Source form, including but
|
|
32
|
-
not limited to compiled object code, generated documentation,
|
|
33
|
-
and conversions to other media types.
|
|
34
|
-
|
|
35
|
-
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
-
Object form, made available under the License, as indicated by a
|
|
37
|
-
copyright notice that is included in or attached to the work
|
|
38
|
-
(an example is provided in the Appendix below).
|
|
39
|
-
|
|
40
|
-
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
-
form, that is based on (or derived from) the Work and for which the
|
|
42
|
-
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
-
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
-
of this License, Derivative Works shall not include works that remain
|
|
45
|
-
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
-
the Work and Derivative Works thereof.
|
|
47
|
-
|
|
48
|
-
"Contribution" shall mean any work of authorship, including
|
|
49
|
-
the original version of the Work and any modifications or additions
|
|
50
|
-
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
-
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
-
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
-
means any form of electronic, verbal, or written communication sent
|
|
55
|
-
to the Licensor or its representatives, including but not limited to
|
|
56
|
-
communication on electronic mailing lists, source code control systems,
|
|
57
|
-
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
-
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
-
excluding communication that is conspicuously marked or otherwise
|
|
60
|
-
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
-
|
|
62
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
-
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
-
subsequently incorporated within the Work.
|
|
65
|
-
|
|
66
|
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
-
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
-
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
-
Work and such Derivative Works in Source or Object form.
|
|
72
|
-
|
|
73
|
-
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
-
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
-
(except as stated in this section) patent license to make, have made,
|
|
77
|
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
-
where such license applies only to those patent claims licensable
|
|
79
|
-
by such Contributor that are necessarily infringed by their
|
|
80
|
-
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
-
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
-
institute patent litigation against any entity (including a
|
|
83
|
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
-
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
-
or contributory patent infringement, then any patent licenses
|
|
86
|
-
granted to You under this License for that Work shall terminate
|
|
87
|
-
as of the date such litigation is filed.
|
|
88
|
-
|
|
89
|
-
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
-
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
-
modifications, and in Source or Object form, provided that You
|
|
92
|
-
meet the following conditions:
|
|
93
|
-
|
|
94
|
-
(a) You must give any other recipients of the Work or
|
|
95
|
-
Derivative Works a copy of this License; and
|
|
96
|
-
|
|
97
|
-
(b) You must cause any modified files to carry prominent notices
|
|
98
|
-
stating that You changed the files; and
|
|
99
|
-
|
|
100
|
-
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
-
that You distribute, all copyright, patent, trademark, and
|
|
102
|
-
attribution notices from the Source form of the Work,
|
|
103
|
-
excluding those notices that do not pertain to any part of
|
|
104
|
-
the Derivative Works; and
|
|
105
|
-
|
|
106
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
-
distribution, then any Derivative Works that You distribute must
|
|
108
|
-
include a readable copy of the attribution notices contained
|
|
109
|
-
within such NOTICE file, excluding those notices that do not
|
|
110
|
-
pertain to any part of the Derivative Works, in at least one
|
|
111
|
-
of the following places: within a NOTICE text file distributed
|
|
112
|
-
as part of the Derivative Works; within the Source form or
|
|
113
|
-
documentation, if provided along with the Derivative Works; or,
|
|
114
|
-
within a display generated by the Derivative Works, if and
|
|
115
|
-
wherever such third-party notices normally appear. The contents
|
|
116
|
-
of the NOTICE file are for informational purposes only and
|
|
117
|
-
do not modify the License. You may add Your own attribution
|
|
118
|
-
notices within Derivative Works that You distribute, alongside
|
|
119
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
-
that such additional attribution notices cannot be construed
|
|
121
|
-
as modifying the License.
|
|
122
|
-
|
|
123
|
-
You may add Your own copyright statement to Your modifications and
|
|
124
|
-
may provide additional or different license terms and conditions
|
|
125
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
-
the conditions stated in this License.
|
|
129
|
-
|
|
130
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
-
this License, without any additional terms or conditions.
|
|
134
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
-
the terms of any separate license agreement you may have executed
|
|
136
|
-
with Licensor regarding such Contributions.
|
|
137
|
-
|
|
138
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for reasonable and customary use in describing the
|
|
141
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
-
|
|
143
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
-
implied, including, without limitation, any warranties or conditions
|
|
148
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
-
appropriateness of using or redistributing the Work and assume any
|
|
151
|
-
risks associated with Your exercise of permissions under this License.
|
|
152
|
-
|
|
153
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
-
unless required by applicable law (such as deliberate and grossly
|
|
156
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
-
liable to You for damages, including any direct, indirect, special,
|
|
158
|
-
incidental, or consequential damages of any character arising as a
|
|
159
|
-
result of this License or out of the use or inability to use the
|
|
160
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
-
other commercial damages or losses), even if such Contributor
|
|
163
|
-
has been advised of the possibility of such damages.
|
|
164
|
-
|
|
165
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
-
or other liability obligations and/or rights consistent with this
|
|
169
|
-
License. However, in accepting such obligations, You may act only
|
|
170
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
-
defend, and hold each Contributor harmless for any liability
|
|
173
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
-
of your accepting any such warranty or additional liability.
|
|
175
|
-
|
|
176
|
-
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright [yyyy] [name of copyright owner]
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,271 +1,271 @@
|
|
|
1
|
-
[](https://www.npmjs.com/package/@sovereignbase/convergent-replicated-struct)
|
|
2
|
-
[](https://github.com/sovereignbase/convergent-replicated-struct/actions/workflows/ci.yaml)
|
|
3
|
-
[](https://codecov.io/gh/sovereignbase/convergent-replicated-struct)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
|
|
6
|
-
# convergent-replicated-struct
|
|
7
|
-
|
|
8
|
-
State-based CRDT for fixed-key object structs with per-field overwrite tracking.
|
|
9
|
-
|
|
10
|
-
## Compatibility
|
|
11
|
-
|
|
12
|
-
- Runtimes: Node >= 20; modern browsers; Bun; Deno; Cloudflare Workers; Edge Runtime.
|
|
13
|
-
- Module format: ESM + CommonJS.
|
|
14
|
-
- Required globals / APIs: `EventTarget`, `CustomEvent`, `structuredClone`.
|
|
15
|
-
- TypeScript: bundled types.
|
|
16
|
-
|
|
17
|
-
## Goals
|
|
18
|
-
|
|
19
|
-
- Fixed-key replica shape defined by a default struct.
|
|
20
|
-
- One visible value per field at any time.
|
|
21
|
-
- Malformed ingress is ignored during hydration and merge instead of crashing the replica.
|
|
22
|
-
- `read()`, `values()`, `entries()`, snapshots, deltas, and change payloads are detached with `structuredClone`.
|
|
23
|
-
- Explicit `acknowledge()` and `garbageCollect()` APIs for overwrite-history compaction.
|
|
24
|
-
- Consistent behavior across Node, browsers, and worker/edge runtimes.
|
|
25
|
-
|
|
26
|
-
## Installation
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
npm install @sovereignbase/convergent-replicated-struct
|
|
30
|
-
# or
|
|
31
|
-
pnpm add @sovereignbase/convergent-replicated-struct
|
|
32
|
-
# or
|
|
33
|
-
yarn add @sovereignbase/convergent-replicated-struct
|
|
34
|
-
# or
|
|
35
|
-
bun add @sovereignbase/convergent-replicated-struct
|
|
36
|
-
# or
|
|
37
|
-
deno add jsr:@sovereignbase/convergent-replicated-struct
|
|
38
|
-
# or
|
|
39
|
-
vlt install jsr:@sovereignbase/convergent-replicated-struct
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Usage
|
|
43
|
-
|
|
44
|
-
### Copy-paste example
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { OOStruct } from '@sovereignbase/convergent-replicated-struct'
|
|
48
|
-
|
|
49
|
-
type TodoStruct = {
|
|
50
|
-
title: string
|
|
51
|
-
count: number
|
|
52
|
-
meta: { done: boolean }
|
|
53
|
-
tags: string[]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const alice = OOStruct.create<TodoStruct>({
|
|
57
|
-
title: '',
|
|
58
|
-
count: 0,
|
|
59
|
-
meta: { done: false },
|
|
60
|
-
tags: [],
|
|
61
|
-
})
|
|
62
|
-
const bob = OOStruct.create<TodoStruct>({
|
|
63
|
-
title: '',
|
|
64
|
-
count: 0,
|
|
65
|
-
meta: { done: false },
|
|
66
|
-
tags: [],
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
alice.addEventListener('delta', (event) => bob.merge(event.detail))
|
|
70
|
-
alice.update('title', 'hello world')
|
|
71
|
-
alice.update('meta', { done: true })
|
|
72
|
-
|
|
73
|
-
console.log(bob.read('title')) // "hello world"
|
|
74
|
-
console.log(bob.read('meta')) // { done: true }
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Hydrating from a snapshot
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
import {
|
|
81
|
-
OOStruct,
|
|
82
|
-
type OOStructSnapshot,
|
|
83
|
-
} from '@sovereignbase/convergent-replicated-struct'
|
|
84
|
-
|
|
85
|
-
type DraftStruct = {
|
|
86
|
-
title: string
|
|
87
|
-
count: number
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const source = new OOStruct<DraftStruct>({
|
|
91
|
-
title: '',
|
|
92
|
-
count: 0,
|
|
93
|
-
})
|
|
94
|
-
let snapshot!: OOStructSnapshot<DraftStruct>
|
|
95
|
-
|
|
96
|
-
source.addEventListener(
|
|
97
|
-
'snapshot',
|
|
98
|
-
(event) => {
|
|
99
|
-
snapshot = event.detail
|
|
100
|
-
},
|
|
101
|
-
{ once: true }
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
source.update('title', 'draft')
|
|
105
|
-
source.snapshot()
|
|
106
|
-
|
|
107
|
-
const restored = OOStruct.create<DraftStruct>(
|
|
108
|
-
{
|
|
109
|
-
title: '',
|
|
110
|
-
count: 0,
|
|
111
|
-
},
|
|
112
|
-
snapshot
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
console.log(restored.entries()) // [['title', 'draft'], ['count', 0]]
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Event channels
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
import { OOStruct } from '@sovereignbase/convergent-replicated-struct'
|
|
122
|
-
|
|
123
|
-
const replica = new OOStruct({
|
|
124
|
-
name: '',
|
|
125
|
-
count: 0,
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
replica.addEventListener('delta', (event) => {
|
|
129
|
-
console.log('delta', event.detail)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
replica.addEventListener('change', (event) => {
|
|
133
|
-
console.log('change', event.detail)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
replica.addEventListener('ack', (event) => {
|
|
137
|
-
console.log('ack', event.detail)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
replica.addEventListener('snapshot', (event) => {
|
|
141
|
-
console.log('snapshot', event.detail)
|
|
142
|
-
})
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Acknowledgements and garbage collection
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import {
|
|
149
|
-
OOStruct,
|
|
150
|
-
type OOStructAck,
|
|
151
|
-
} from '@sovereignbase/convergent-replicated-struct'
|
|
152
|
-
|
|
153
|
-
type CounterStruct = {
|
|
154
|
-
title: string
|
|
155
|
-
count: number
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const left = new OOStruct<CounterStruct>({
|
|
159
|
-
title: '',
|
|
160
|
-
count: 0,
|
|
161
|
-
})
|
|
162
|
-
const right = new OOStruct<CounterStruct>({
|
|
163
|
-
title: '',
|
|
164
|
-
count: 0,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const frontiers: Array<OOStructAck<CounterStruct>> = []
|
|
168
|
-
|
|
169
|
-
left.addEventListener(
|
|
170
|
-
'ack',
|
|
171
|
-
(event) => {
|
|
172
|
-
frontiers.push(event.detail)
|
|
173
|
-
},
|
|
174
|
-
{ once: true }
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
right.addEventListener(
|
|
178
|
-
'ack',
|
|
179
|
-
(event) => {
|
|
180
|
-
frontiers.push(event.detail)
|
|
181
|
-
},
|
|
182
|
-
{ once: true }
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
left.acknowledge()
|
|
186
|
-
right.acknowledge()
|
|
187
|
-
|
|
188
|
-
left.garbageCollect(frontiers)
|
|
189
|
-
right.garbageCollect(frontiers)
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## Runtime behavior
|
|
193
|
-
|
|
194
|
-
### Validation and errors
|
|
195
|
-
|
|
196
|
-
Local API misuse throws `OOStructError` with stable error codes:
|
|
197
|
-
|
|
198
|
-
- `DEFAULTS_NOT_CLONEABLE`
|
|
199
|
-
- `VALUE_NOT_CLONEABLE`
|
|
200
|
-
- `VALUE_TYPE_MISMATCH`
|
|
201
|
-
|
|
202
|
-
Hydration and merge are ingress-tolerant: malformed top-level payloads, unknown keys, malformed field entries, invalid UUIDs, invalid overwrite members, and mismatched runtime kinds are ignored instead of throwing.
|
|
203
|
-
|
|
204
|
-
### Safety and copying semantics
|
|
205
|
-
|
|
206
|
-
- Constructor defaults must be `structuredClone`-compatible.
|
|
207
|
-
- `read()`, `values()`, and `entries()` return detached clones.
|
|
208
|
-
- `delta`, `change`, and `snapshot` event payloads are detached from live state.
|
|
209
|
-
- `update()` stores a cloned value, so later caller-side mutation does not mutate replica state through shared references.
|
|
210
|
-
|
|
211
|
-
### Convergence and compaction
|
|
212
|
-
|
|
213
|
-
- The convergence guarantee is the resolved live struct state.
|
|
214
|
-
- Internal overwrite history may differ between replicas after acknowledgement-based garbage collection while the resolved live state still converges.
|
|
215
|
-
- `garbageCollect()` compacts overwritten identifiers that are below the smallest acknowledgement frontier for a key while preserving the active predecessor link.
|
|
216
|
-
|
|
217
|
-
## Tests
|
|
218
|
-
|
|
219
|
-
- Suite: unit, integration, and end-to-end runtime tests.
|
|
220
|
-
- Node test runner: `node --test` for unit and integration suites.
|
|
221
|
-
- Coverage: `c8` with 100% statements / branches / functions / lines on built `dist/**/*.js`.
|
|
222
|
-
- E2E runtimes: Node ESM, Node CJS, Bun ESM, Bun CJS, Deno ESM, Cloudflare Workers ESM, Edge Runtime ESM.
|
|
223
|
-
- Browser E2E: Chromium, Firefox, WebKit, mobile Chrome, mobile Safari via Playwright.
|
|
224
|
-
- Current status: `npm run test` passes on Node 22.14.0 (`win32 x64`).
|
|
225
|
-
|
|
226
|
-
## Benchmarks
|
|
227
|
-
|
|
228
|
-
How it was run:
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
node benchmark/bench.js
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Environment: Node 22.14.0 (`win32 x64`)
|
|
235
|
-
|
|
236
|
-
| Benchmark | Result |
|
|
237
|
-
| ----------------------------- | ------------------------- |
|
|
238
|
-
| constructor empty | 44,359 ops/s (2254.3 ms) |
|
|
239
|
-
| constructor hydrate x64 | 19,610 ops/s (255.0 ms) |
|
|
240
|
-
| constructor hydrate x256 | 8,088 ops/s (247.3 ms) |
|
|
241
|
-
| constructor hydrate x1024 | 1,724 ops/s (290.0 ms) |
|
|
242
|
-
| create() empty | 49,874 ops/s (2005.1 ms) |
|
|
243
|
-
| create() hydrate x256 | 6,886 ops/s (290.4 ms) |
|
|
244
|
-
| read primitive | 846,289 ops/s (236.3 ms) |
|
|
245
|
-
| read object | 298,983 ops/s (668.9 ms) |
|
|
246
|
-
| read array | 278,710 ops/s (717.6 ms) |
|
|
247
|
-
| keys() | 32,349,896 ops/s (6.2 ms) |
|
|
248
|
-
| values() | 103,489 ops/s (966.3 ms) |
|
|
249
|
-
| entries() | 110,300 ops/s (906.6 ms) |
|
|
250
|
-
| snapshot() | 65,513 ops/s (305.3 ms) |
|
|
251
|
-
| acknowledge() | 536,890 ops/s (93.1 ms) |
|
|
252
|
-
| update string | 29,547 ops/s (1692.2 ms) |
|
|
253
|
-
| update number | 30,591 ops/s (1634.5 ms) |
|
|
254
|
-
| update object | 22,114 ops/s (2261.0 ms) |
|
|
255
|
-
| update array | 24,763 ops/s (2019.1 ms) |
|
|
256
|
-
| delete(key) | 8,352 ops/s (5986.8 ms) |
|
|
257
|
-
| delete() reset all | 6,836 ops/s (2925.5 ms) |
|
|
258
|
-
| merge direct successor | 32,541 ops/s (1536.5 ms) |
|
|
259
|
-
| merge stale conflict | 30,995 ops/s (645.3 ms) |
|
|
260
|
-
| merge hydrate snapshot x256 | 5,748 ops/s (869.9 ms) |
|
|
261
|
-
| merge noop duplicate | 7,576 ops/s (6600.1 ms) |
|
|
262
|
-
| garbageCollect() x512 history | 3,111 ops/s (1607.0 ms) |
|
|
263
|
-
| add/remove listener roundtrip | 49,005 ops/s (4081.2 ms) |
|
|
264
|
-
| update with listeners | 25,120 ops/s (1194.3 ms) |
|
|
265
|
-
| merge with listeners | 31,649 ops/s (631.9 ms) |
|
|
266
|
-
|
|
267
|
-
Results vary by machine, runtime version, and payload shape.
|
|
268
|
-
|
|
269
|
-
## License
|
|
270
|
-
|
|
271
|
-
Apache-2.0
|
|
1
|
+
[](https://www.npmjs.com/package/@sovereignbase/convergent-replicated-struct)
|
|
2
|
+
[](https://github.com/sovereignbase/convergent-replicated-struct/actions/workflows/ci.yaml)
|
|
3
|
+
[](https://codecov.io/gh/sovereignbase/convergent-replicated-struct)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
# convergent-replicated-struct
|
|
7
|
+
|
|
8
|
+
State-based CRDT for fixed-key object structs with per-field overwrite tracking.
|
|
9
|
+
|
|
10
|
+
## Compatibility
|
|
11
|
+
|
|
12
|
+
- Runtimes: Node >= 20; modern browsers; Bun; Deno; Cloudflare Workers; Edge Runtime.
|
|
13
|
+
- Module format: ESM + CommonJS.
|
|
14
|
+
- Required globals / APIs: `EventTarget`, `CustomEvent`, `structuredClone`.
|
|
15
|
+
- TypeScript: bundled types.
|
|
16
|
+
|
|
17
|
+
## Goals
|
|
18
|
+
|
|
19
|
+
- Fixed-key replica shape defined by a default struct.
|
|
20
|
+
- One visible value per field at any time.
|
|
21
|
+
- Malformed ingress is ignored during hydration and merge instead of crashing the replica.
|
|
22
|
+
- `read()`, `values()`, `entries()`, snapshots, deltas, and change payloads are detached with `structuredClone`.
|
|
23
|
+
- Explicit `acknowledge()` and `garbageCollect()` APIs for overwrite-history compaction.
|
|
24
|
+
- Consistent behavior across Node, browsers, and worker/edge runtimes.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @sovereignbase/convergent-replicated-struct
|
|
30
|
+
# or
|
|
31
|
+
pnpm add @sovereignbase/convergent-replicated-struct
|
|
32
|
+
# or
|
|
33
|
+
yarn add @sovereignbase/convergent-replicated-struct
|
|
34
|
+
# or
|
|
35
|
+
bun add @sovereignbase/convergent-replicated-struct
|
|
36
|
+
# or
|
|
37
|
+
deno add jsr:@sovereignbase/convergent-replicated-struct
|
|
38
|
+
# or
|
|
39
|
+
vlt install jsr:@sovereignbase/convergent-replicated-struct
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Copy-paste example
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { OOStruct } from '@sovereignbase/convergent-replicated-struct'
|
|
48
|
+
|
|
49
|
+
type TodoStruct = {
|
|
50
|
+
title: string
|
|
51
|
+
count: number
|
|
52
|
+
meta: { done: boolean }
|
|
53
|
+
tags: string[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const alice = OOStruct.create<TodoStruct>({
|
|
57
|
+
title: '',
|
|
58
|
+
count: 0,
|
|
59
|
+
meta: { done: false },
|
|
60
|
+
tags: [],
|
|
61
|
+
})
|
|
62
|
+
const bob = OOStruct.create<TodoStruct>({
|
|
63
|
+
title: '',
|
|
64
|
+
count: 0,
|
|
65
|
+
meta: { done: false },
|
|
66
|
+
tags: [],
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
alice.addEventListener('delta', (event) => bob.merge(event.detail))
|
|
70
|
+
alice.update('title', 'hello world')
|
|
71
|
+
alice.update('meta', { done: true })
|
|
72
|
+
|
|
73
|
+
console.log(bob.read('title')) // "hello world"
|
|
74
|
+
console.log(bob.read('meta')) // { done: true }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Hydrating from a snapshot
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {
|
|
81
|
+
OOStruct,
|
|
82
|
+
type OOStructSnapshot,
|
|
83
|
+
} from '@sovereignbase/convergent-replicated-struct'
|
|
84
|
+
|
|
85
|
+
type DraftStruct = {
|
|
86
|
+
title: string
|
|
87
|
+
count: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const source = new OOStruct<DraftStruct>({
|
|
91
|
+
title: '',
|
|
92
|
+
count: 0,
|
|
93
|
+
})
|
|
94
|
+
let snapshot!: OOStructSnapshot<DraftStruct>
|
|
95
|
+
|
|
96
|
+
source.addEventListener(
|
|
97
|
+
'snapshot',
|
|
98
|
+
(event) => {
|
|
99
|
+
snapshot = event.detail
|
|
100
|
+
},
|
|
101
|
+
{ once: true }
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
source.update('title', 'draft')
|
|
105
|
+
source.snapshot()
|
|
106
|
+
|
|
107
|
+
const restored = OOStruct.create<DraftStruct>(
|
|
108
|
+
{
|
|
109
|
+
title: '',
|
|
110
|
+
count: 0,
|
|
111
|
+
},
|
|
112
|
+
snapshot
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
console.log(restored.entries()) // [['title', 'draft'], ['count', 0]]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Event channels
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { OOStruct } from '@sovereignbase/convergent-replicated-struct'
|
|
122
|
+
|
|
123
|
+
const replica = new OOStruct({
|
|
124
|
+
name: '',
|
|
125
|
+
count: 0,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
replica.addEventListener('delta', (event) => {
|
|
129
|
+
console.log('delta', event.detail)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
replica.addEventListener('change', (event) => {
|
|
133
|
+
console.log('change', event.detail)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
replica.addEventListener('ack', (event) => {
|
|
137
|
+
console.log('ack', event.detail)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
replica.addEventListener('snapshot', (event) => {
|
|
141
|
+
console.log('snapshot', event.detail)
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Acknowledgements and garbage collection
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import {
|
|
149
|
+
OOStruct,
|
|
150
|
+
type OOStructAck,
|
|
151
|
+
} from '@sovereignbase/convergent-replicated-struct'
|
|
152
|
+
|
|
153
|
+
type CounterStruct = {
|
|
154
|
+
title: string
|
|
155
|
+
count: number
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const left = new OOStruct<CounterStruct>({
|
|
159
|
+
title: '',
|
|
160
|
+
count: 0,
|
|
161
|
+
})
|
|
162
|
+
const right = new OOStruct<CounterStruct>({
|
|
163
|
+
title: '',
|
|
164
|
+
count: 0,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const frontiers: Array<OOStructAck<CounterStruct>> = []
|
|
168
|
+
|
|
169
|
+
left.addEventListener(
|
|
170
|
+
'ack',
|
|
171
|
+
(event) => {
|
|
172
|
+
frontiers.push(event.detail)
|
|
173
|
+
},
|
|
174
|
+
{ once: true }
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
right.addEventListener(
|
|
178
|
+
'ack',
|
|
179
|
+
(event) => {
|
|
180
|
+
frontiers.push(event.detail)
|
|
181
|
+
},
|
|
182
|
+
{ once: true }
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
left.acknowledge()
|
|
186
|
+
right.acknowledge()
|
|
187
|
+
|
|
188
|
+
left.garbageCollect(frontiers)
|
|
189
|
+
right.garbageCollect(frontiers)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Runtime behavior
|
|
193
|
+
|
|
194
|
+
### Validation and errors
|
|
195
|
+
|
|
196
|
+
Local API misuse throws `OOStructError` with stable error codes:
|
|
197
|
+
|
|
198
|
+
- `DEFAULTS_NOT_CLONEABLE`
|
|
199
|
+
- `VALUE_NOT_CLONEABLE`
|
|
200
|
+
- `VALUE_TYPE_MISMATCH`
|
|
201
|
+
|
|
202
|
+
Hydration and merge are ingress-tolerant: malformed top-level payloads, unknown keys, malformed field entries, invalid UUIDs, invalid overwrite members, and mismatched runtime kinds are ignored instead of throwing.
|
|
203
|
+
|
|
204
|
+
### Safety and copying semantics
|
|
205
|
+
|
|
206
|
+
- Constructor defaults must be `structuredClone`-compatible.
|
|
207
|
+
- `read()`, `values()`, and `entries()` return detached clones.
|
|
208
|
+
- `delta`, `change`, and `snapshot` event payloads are detached from live state.
|
|
209
|
+
- `update()` stores a cloned value, so later caller-side mutation does not mutate replica state through shared references.
|
|
210
|
+
|
|
211
|
+
### Convergence and compaction
|
|
212
|
+
|
|
213
|
+
- The convergence guarantee is the resolved live struct state.
|
|
214
|
+
- Internal overwrite history may differ between replicas after acknowledgement-based garbage collection while the resolved live state still converges.
|
|
215
|
+
- `garbageCollect()` compacts overwritten identifiers that are below the smallest acknowledgement frontier for a key while preserving the active predecessor link.
|
|
216
|
+
|
|
217
|
+
## Tests
|
|
218
|
+
|
|
219
|
+
- Suite: unit, integration, and end-to-end runtime tests.
|
|
220
|
+
- Node test runner: `node --test` for unit and integration suites.
|
|
221
|
+
- Coverage: `c8` with 100% statements / branches / functions / lines on built `dist/**/*.js`.
|
|
222
|
+
- E2E runtimes: Node ESM, Node CJS, Bun ESM, Bun CJS, Deno ESM, Cloudflare Workers ESM, Edge Runtime ESM.
|
|
223
|
+
- Browser E2E: Chromium, Firefox, WebKit, mobile Chrome, mobile Safari via Playwright.
|
|
224
|
+
- Current status: `npm run test` passes on Node 22.14.0 (`win32 x64`).
|
|
225
|
+
|
|
226
|
+
## Benchmarks
|
|
227
|
+
|
|
228
|
+
How it was run:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
node benchmark/bench.js
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Environment: Node 22.14.0 (`win32 x64`)
|
|
235
|
+
|
|
236
|
+
| Benchmark | Result |
|
|
237
|
+
| ----------------------------- | ------------------------- |
|
|
238
|
+
| constructor empty | 44,359 ops/s (2254.3 ms) |
|
|
239
|
+
| constructor hydrate x64 | 19,610 ops/s (255.0 ms) |
|
|
240
|
+
| constructor hydrate x256 | 8,088 ops/s (247.3 ms) |
|
|
241
|
+
| constructor hydrate x1024 | 1,724 ops/s (290.0 ms) |
|
|
242
|
+
| create() empty | 49,874 ops/s (2005.1 ms) |
|
|
243
|
+
| create() hydrate x256 | 6,886 ops/s (290.4 ms) |
|
|
244
|
+
| read primitive | 846,289 ops/s (236.3 ms) |
|
|
245
|
+
| read object | 298,983 ops/s (668.9 ms) |
|
|
246
|
+
| read array | 278,710 ops/s (717.6 ms) |
|
|
247
|
+
| keys() | 32,349,896 ops/s (6.2 ms) |
|
|
248
|
+
| values() | 103,489 ops/s (966.3 ms) |
|
|
249
|
+
| entries() | 110,300 ops/s (906.6 ms) |
|
|
250
|
+
| snapshot() | 65,513 ops/s (305.3 ms) |
|
|
251
|
+
| acknowledge() | 536,890 ops/s (93.1 ms) |
|
|
252
|
+
| update string | 29,547 ops/s (1692.2 ms) |
|
|
253
|
+
| update number | 30,591 ops/s (1634.5 ms) |
|
|
254
|
+
| update object | 22,114 ops/s (2261.0 ms) |
|
|
255
|
+
| update array | 24,763 ops/s (2019.1 ms) |
|
|
256
|
+
| delete(key) | 8,352 ops/s (5986.8 ms) |
|
|
257
|
+
| delete() reset all | 6,836 ops/s (2925.5 ms) |
|
|
258
|
+
| merge direct successor | 32,541 ops/s (1536.5 ms) |
|
|
259
|
+
| merge stale conflict | 30,995 ops/s (645.3 ms) |
|
|
260
|
+
| merge hydrate snapshot x256 | 5,748 ops/s (869.9 ms) |
|
|
261
|
+
| merge noop duplicate | 7,576 ops/s (6600.1 ms) |
|
|
262
|
+
| garbageCollect() x512 history | 3,111 ops/s (1607.0 ms) |
|
|
263
|
+
| add/remove listener roundtrip | 49,005 ops/s (4081.2 ms) |
|
|
264
|
+
| update with listeners | 25,120 ops/s (1194.3 ms) |
|
|
265
|
+
| merge with listeners | 31,649 ops/s (631.9 ms) |
|
|
266
|
+
|
|
267
|
+
Results vary by machine, runtime version, and payload shape.
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
Apache-2.0
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["c:\\Users\\jorts\\convergent-replicated-struct\\dist\\index.cjs"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAAmC;AACnC;AACA;AACA,IAAI,cAAc,EAAE,MAAM,QAAQ,MAAM;AACxC;AACA;AACA;AACA,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA,EAAE,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE;AAC7B,IAAI,MAAM,OAAO,mBAAE,OAAQ,UAAG,MAAI;AAClC,IAAI,KAAK,CAAC,CAAC,2CAA2C,EAAE,MAAM,CAAC,CAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA","file":"C:\\Users\\jorts\\convergent-replicated-struct\\dist\\index.cjs","sourcesContent":[null]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/convergent-replicated-struct/convergent-replicated-struct/dist/index.cjs","../src/OOStruct/class.ts","../src/.errors/class.ts","../src/OOStruct/parseSnapshotEntryToStateEntry/index.ts","../src/OOStruct/parseStateEntryToSnapshotEntry/index.ts"],"names":["key"],"mappings":"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","file":"/home/runner/work/convergent-replicated-struct/convergent-replicated-struct/dist/index.cjs","sourcesContent":[null,"import { v7 as uuidv7 } from 'uuid'\nimport type {\n OOStructChange,\n OOStructDelta,\n OOStructEventListenerFor,\n OOStructEventMap,\n OOStructSnapshot,\n OOStructSnapshotEntry,\n OOStructState,\n OOStructStateEntry,\n OOStructAck,\n} from '../.types/index.js'\nimport { OOStructError } from '../.errors/class.js'\nimport { parseSnapshotEntryToStateEntry } from './parseSnapshotEntryToStateEntry/index.js'\nimport { parseStateEntryToSnapshotEntry } from './parseStateEntryToSnapshotEntry/index.js'\nimport { isUuidV7, prototype, safeStructuredClone } from '@sovereignbase/utils'\n\n/**\n * Represents an observed-overwrite struct replica.\n *\n * The struct shape is fixed by the provided default values.\n */\nexport class OOStruct<T extends Record<string, unknown>> {\n private readonly __eventTarget = new EventTarget()\n private readonly __defaults: T\n private readonly __state: OOStructState<T>\n private __live: T\n\n /**\n * Creates a replica from default values and an optional snapshot.\n *\n * @param defaults - The default field values that define the struct shape.\n * @param snapshot - An optional serialized snapshot used for hydration.\n * @throws {OOStructError} Thrown when the default values are not supported by `structuredClone`.\n */\n constructor(\n defaults: { [K in keyof T]: T[K] },\n snapshot?: OOStructSnapshot<T>\n ) {\n const [cloned, copiedDefaults] = safeStructuredClone(defaults)\n if (!cloned)\n throw new OOStructError(\n 'DEFAULTS_NOT_CLONEABLE',\n 'Default values must be supported by structuredClone.'\n )\n this.__defaults = copiedDefaults\n this.__state = {} as OOStructState<T>\n this.__live = {} as T\n\n const snapshotIsObject = snapshot && prototype(snapshot) === 'record'\n\n for (const key of Object.keys(this.__defaults)) {\n const defaultValue = this.__defaults[key as keyof T]\n if (snapshotIsObject && Object.hasOwn(snapshot, key)) {\n const valid = parseSnapshotEntryToStateEntry(\n defaultValue,\n snapshot[key as keyof T]\n )\n if (valid) {\n this.__live[key as keyof T] = valid.__value\n this.__state[key as keyof T] = valid\n continue\n }\n }\n this.__live[key as keyof T] = defaultValue\n const root = uuidv7()\n this.__state[key as keyof T] = {\n __uuidv7: uuidv7(),\n __after: root,\n __value: defaultValue,\n __overwrites: new Set([root]),\n }\n }\n }\n\n /**CRUD*/\n /**\n * Creates a new replica.\n *\n * @param defaults - The default field values that define the struct shape.\n * @param snapshot - An optional serialized snapshot used for hydration.\n * @returns A new OO-Struct replica.\n */\n static create<T extends Record<string, unknown>>(\n defaults: { [K in keyof T]: T[K] },\n snapshot?: OOStructSnapshot<T>\n ): OOStruct<T> {\n return new OOStruct(defaults, snapshot)\n }\n\n /**\n * Reads the current value of a field.\n *\n * @param key - The field key to read.\n * @returns A cloned copy of the field's current value.\n */\n read<K extends keyof T>(key: K): T[K] {\n return structuredClone(this.__live[key])\n }\n\n /**\n * Overwrites a field with a new value.\n *\n * @param key - The field key to overwrite.\n * @param value - The next value for the field.\n * @throws {OOStructError} Thrown when the value is not supported by `structuredClone`.\n * @throws {OOStructError} Thrown when the value runtime type does not match the default value runtime type.\n */\n update<K extends keyof T>(key: K, value: T[K]): void {\n const [cloned, copiedValue] = safeStructuredClone(value)\n if (!cloned)\n throw new OOStructError(\n 'VALUE_NOT_CLONEABLE',\n 'Updated values must be supported by structuredClone.'\n )\n\n if (prototype(copiedValue) !== prototype(this.__defaults[key]))\n throw new OOStructError(\n 'VALUE_TYPE_MISMATCH',\n 'Updated value must match the default value runtime type.'\n )\n const delta: OOStructDelta<T> = {}\n const change: OOStructChange<T> = {}\n delta[key] = this.overwriteAndReturnSnapshotEntry(key, copiedValue)\n change[key] = structuredClone(copiedValue)\n this.__eventTarget.dispatchEvent(\n new CustomEvent('delta', { detail: delta })\n )\n this.__eventTarget.dispatchEvent(\n new CustomEvent('change', { detail: change })\n )\n }\n\n /**\n * Resets one field or the entire struct back to default values.\n *\n * @param key - The optional field key to reset. When omitted, every field is reset.\n */\n delete<K extends keyof T>(key?: K): void {\n const delta: OOStructDelta<T> = {}\n const change: OOStructChange<T> = {}\n\n if (key !== undefined) {\n if (!Object.hasOwn(this.__defaults, key)) return\n const value = this.__defaults[key]\n delta[key] = this.overwriteAndReturnSnapshotEntry(key, value)\n change[key] = structuredClone(value)\n } else {\n for (const [key, value] of Object.entries(this.__defaults)) {\n delta[key as K] = this.overwriteAndReturnSnapshotEntry(\n key as K,\n value as T[K]\n )\n change[key as K] = structuredClone(value as T[K])\n }\n }\n this.__eventTarget.dispatchEvent(\n new CustomEvent('delta', { detail: delta })\n )\n this.__eventTarget.dispatchEvent(\n new CustomEvent('change', { detail: change })\n )\n }\n\n /**MAGS*/\n /**\n * Merges an incoming delta into the current replica.\n *\n * @param replica - The incoming partial snapshot projection to merge.\n */\n merge<K extends keyof T>(replica: OOStructDelta<T>): void {\n if (!replica || typeof replica !== 'object' || Array.isArray(replica))\n return\n\n const delta: OOStructDelta<T> = {}\n const change: OOStructChange<T> = {}\n let hasDelta = false\n let hasChange = false\n\n for (const [key, value] of Object.entries(replica)) {\n if (!Object.hasOwn(this.__state, key)) continue\n\n const candidate = parseSnapshotEntryToStateEntry(\n this.__defaults[key as K],\n value as OOStructSnapshotEntry<T[K]>\n )\n if (!candidate) continue\n\n const target = this.__state[key as K]\n const current = { ...target }\n let frontier = ''\n for (const overwrite of target.__overwrites) {\n if (frontier < overwrite) frontier = overwrite\n }\n\n for (const overwrite of candidate.__overwrites) {\n if (overwrite <= frontier || target.__overwrites.has(overwrite))\n continue\n target.__overwrites.add(overwrite)\n }\n\n if (target.__overwrites.has(candidate.__uuidv7)) continue\n\n if (current.__uuidv7 === candidate.__uuidv7) {\n if (current.__after < candidate.__after) {\n target.__value = candidate.__value\n target.__after = candidate.__after\n target.__overwrites.add(candidate.__after)\n this.__live[key as K] = candidate.__value\n change[key as K] = structuredClone(candidate.__value)\n hasChange = true\n } else {\n delta[key as K] = this.overwriteAndReturnSnapshotEntry(\n key as K,\n current.__value\n )\n hasDelta = true\n }\n continue\n }\n\n if (\n current.__uuidv7 === candidate.__after ||\n target.__overwrites.has(current.__uuidv7) ||\n candidate.__uuidv7 > current.__uuidv7\n ) {\n target.__uuidv7 = candidate.__uuidv7\n target.__value = candidate.__value\n target.__after = candidate.__after\n target.__overwrites.add(candidate.__after)\n target.__overwrites.add(current.__uuidv7)\n this.__live[key as K] = candidate.__value\n change[key as K] = structuredClone(candidate.__value)\n hasChange = true\n continue\n }\n\n target.__overwrites.add(candidate.__uuidv7)\n delta[key as K] = parseStateEntryToSnapshotEntry(target)\n hasDelta = true\n }\n if (hasDelta)\n this.__eventTarget.dispatchEvent(\n new CustomEvent('delta', { detail: delta })\n )\n if (hasChange)\n this.__eventTarget.dispatchEvent(\n new CustomEvent('change', { detail: change })\n )\n }\n\n /**\n * Emits the current acknowledgement frontier for each field.\n */\n acknowledge<K extends Extract<keyof T, string>>(): void {\n const ack: OOStructAck<T> = {}\n for (const [key, value] of Object.entries(this.__state)) {\n let max = ''\n for (const overwrite of (value as OOStructStateEntry<T[K]>)\n .__overwrites) {\n if (max < overwrite) max = overwrite\n }\n ack[key as K] = max\n }\n this.__eventTarget.dispatchEvent(new CustomEvent('ack', { detail: ack }))\n }\n\n /**\n * Removes overwritten identifiers that every provided frontier has acknowledged.\n *\n * @param frontiers - A collection of acknowledgement frontiers to compact against.\n */\n garbageCollect<K extends Extract<keyof T, string>>(\n frontiers: Array<OOStructAck<T>>\n ): void {\n if (!Array.isArray(frontiers) || frontiers.length < 1) return\n const smallestAcknowledgementsPerKey: OOStructAck<T> = {}\n\n for (const frontier of frontiers) {\n for (const [key, value] of Object.entries(frontier)) {\n if (!Object.hasOwn(this.__state, key) || !isUuidV7(value)) continue\n\n const current = smallestAcknowledgementsPerKey[key as K]\n if (typeof current === 'string' && current <= value) continue\n smallestAcknowledgementsPerKey[key as K] = value\n }\n }\n\n for (const [key, value] of Object.entries(smallestAcknowledgementsPerKey)) {\n const target = this.__state[key]\n const smallest = value as string\n for (const uuidv7 of target.__overwrites) {\n if (uuidv7 === target.__after || uuidv7 > smallest) continue\n target.__overwrites.delete(uuidv7)\n }\n }\n }\n\n /**\n * Emits a serialized snapshot of the current replica state.\n */\n snapshot(): void {\n const snapshot = {} as OOStructSnapshot<T>\n\n for (const [key, value] of Object.entries(this.__state)) {\n snapshot[key as keyof T] = parseStateEntryToSnapshotEntry(\n value as OOStructStateEntry<T[keyof T]>\n )\n }\n\n this.__eventTarget.dispatchEvent(\n new CustomEvent('snapshot', { detail: snapshot })\n )\n }\n\n /**ADDITIONAL*/\n\n /**\n * Returns the struct field keys.\n *\n * @returns The field keys in the current replica.\n */\n keys<K extends keyof T>(): Array<K> {\n return Object.keys(this.__live) as Array<K>\n }\n\n /**\n * Returns cloned copies of the current field values.\n *\n * @returns The current field values.\n */\n values<K extends keyof T>(): Array<T[K]> {\n return Object.values(this.__live).map((value) =>\n structuredClone(value)\n ) as Array<T[K]>\n }\n\n /**\n * Returns cloned key-value pairs for the current replica state.\n *\n * @returns The current field entries.\n */\n entries<K extends keyof T>(): Array<[K, T[K]]> {\n return Object.entries(this.__live).map(([key, value]) => [\n key as K,\n structuredClone(value as T[K]),\n ])\n }\n\n /**EVENTS*/\n\n /**\n * Registers an event listener.\n *\n * @param type - The event type to listen for.\n * @param listener - The listener to register.\n * @param options - Listener registration options.\n */\n addEventListener<K extends keyof OOStructEventMap<T>>(\n type: K,\n listener: OOStructEventListenerFor<T, K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n this.__eventTarget.addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n }\n\n /**\n * Removes an event listener.\n *\n * @param type - The event type to stop listening for.\n * @param listener - The listener to remove.\n * @param options - Listener removal options.\n */\n removeEventListener<K extends keyof OOStructEventMap<T>>(\n type: K,\n listener: OOStructEventListenerFor<T, K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n this.__eventTarget.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n }\n\n /**HELPERS*/\n\n /**\n * Overwrites a field and returns the serialized delta entry for that overwrite.\n *\n * @param key - The field key to overwrite.\n * @param value - The next value for the field.\n * @returns The serialized snapshot entry for the new winning value.\n */\n private overwriteAndReturnSnapshotEntry<K extends keyof T>(\n key: K,\n value: T[K]\n ): OOStructSnapshotEntry<T[K]> {\n const target = this.__state[key]\n const old = { ...target }\n target.__uuidv7 = uuidv7()\n target.__value = value\n target.__after = old.__uuidv7\n target.__overwrites.add(old.__uuidv7)\n this.__live[key] = value\n return parseStateEntryToSnapshotEntry(target)\n }\n}\n","/**\n * Error codes thrown by {@link OOStruct}.\n */\nexport type OOStructErrorCode =\n | 'DEFAULTS_NOT_CLONEABLE'\n | 'VALUE_NOT_CLONEABLE'\n | 'VALUE_TYPE_MISMATCH'\n\n/**\n * Represents a typed OO-Struct runtime error.\n */\nexport class OOStructError extends Error {\n /**\n * The semantic error code for the failure.\n */\n readonly code: OOStructErrorCode\n\n /**\n * Creates a typed OO-Struct error.\n *\n * @param code - The semantic error code.\n * @param message - An optional human-readable detail message.\n */\n constructor(code: OOStructErrorCode, message?: string) {\n const detail = message ?? code\n super(`{@sovereignbase/observed-overwrite-struct} ${detail}`)\n this.code = code\n this.name = 'OOStructError'\n }\n}\n","import type {\n OOStructSnapshotEntry,\n OOStructStateEntry,\n} from '../../.types/index.js'\nimport { isUuidV7, prototype, safeStructuredClone } from '@sovereignbase/utils'\n\n/**\n * Validates and converts a serialized field entry into internal replica state.\n *\n * Invalid entries are rejected by returning `false`.\n *\n * @param defaultValue - The default value for the field used for runtime type comparison.\n * @param snapshotEntry - The serialized entry to validate and parse.\n * @returns The parsed state entry, or `false` when the entry is invalid.\n */\nexport function parseSnapshotEntryToStateEntry<V>(\n defaultValue: V,\n snapshotEntry: OOStructSnapshotEntry<V>\n): OOStructStateEntry<V> | false {\n if (\n prototype(snapshotEntry) !== 'record' ||\n !Object.hasOwn(snapshotEntry, '__value') ||\n !isUuidV7(snapshotEntry.__uuidv7) ||\n !isUuidV7(snapshotEntry.__after) ||\n !Array.isArray(snapshotEntry.__overwrites)\n )\n return false\n\n const [cloned, copiedValue] = safeStructuredClone(snapshotEntry.__value)\n if (!cloned || prototype(copiedValue) !== prototype(defaultValue))\n return false\n\n const overwrites = new Set<string>([])\n for (const overwrite of snapshotEntry.__overwrites) {\n if (\n !isUuidV7(overwrite) ||\n overwrite ===\n snapshotEntry.__uuidv7 /**if it was actually overwritten the current uuid would be different so this must be malicious*/\n )\n continue\n overwrites.add(overwrite)\n }\n\n if (!overwrites.has(snapshotEntry.__after)) return false\n\n return {\n __uuidv7: snapshotEntry.__uuidv7,\n __value: copiedValue,\n __after: snapshotEntry.__after,\n __overwrites: overwrites,\n }\n}\n","import type {\n OOStructStateEntry,\n OOStructSnapshotEntry,\n} from '../../.types/index.js'\n\n/**\n * Serializes a field state entry into a snapshot entry.\n *\n * @param stateEntry - The internal state entry to serialize.\n * @returns The serialized snapshot entry.\n */\nexport function parseStateEntryToSnapshotEntry<K>(\n stateEntry: OOStructStateEntry<K>\n): OOStructSnapshotEntry<K> {\n return {\n __uuidv7: stateEntry.__uuidv7,\n __value: structuredClone(stateEntry.__value),\n __after: stateEntry.__after,\n __overwrites: Array.from(stateEntry.__overwrites),\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "module",
|
|
3
|
-
"license": "Apache-2.0",
|
|
4
|
-
"name": "@sovereignbase/convergent-replicated-struct",
|
|
5
|
-
"version": "
|
|
6
|
-
"description": "State-based CRDT for fixed-key object structs with per-field overwrite tracking.",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"crdt",
|
|
9
|
-
"state-based-crdt",
|
|
10
|
-
"eventual-consistency",
|
|
11
|
-
"local-first",
|
|
12
|
-
"replication",
|
|
13
|
-
"convergent-replicated-struct",
|
|
14
|
-
"cr-struct",
|
|
15
|
-
"uuidv7",
|
|
16
|
-
"lww-register",
|
|
17
|
-
"delta",
|
|
18
|
-
"snapshot",
|
|
19
|
-
"typescript"
|
|
20
|
-
],
|
|
21
|
-
"main": "./dist/index.cjs",
|
|
22
|
-
"module": "./dist/index.js",
|
|
23
|
-
"types": "./dist/index.d.ts",
|
|
24
|
-
"exports": {
|
|
25
|
-
".": {
|
|
26
|
-
"types": "./dist/index.d.ts",
|
|
27
|
-
"import": "./dist/index.js",
|
|
28
|
-
"require": "./dist/index.cjs",
|
|
29
|
-
"default": "./dist/index.js"
|
|
30
|
-
},
|
|
31
|
-
"./package.json": "./package.json"
|
|
32
|
-
},
|
|
33
|
-
"engines": {
|
|
34
|
-
"node": ">=20"
|
|
35
|
-
},
|
|
36
|
-
"files": [
|
|
37
|
-
"dist",
|
|
38
|
-
"LICENSE",
|
|
39
|
-
"README.md"
|
|
40
|
-
],
|
|
41
|
-
"sideEffects": false,
|
|
42
|
-
"scripts": {
|
|
43
|
-
"format": "prettier . --write",
|
|
44
|
-
"build": "tsup",
|
|
45
|
-
"test": "npm run build && node test/run-coverage.mjs && node test/e2e/run.mjs",
|
|
46
|
-
"bench": "npm run build && node benchmark/bench.js",
|
|
47
|
-
"prepare": "husky",
|
|
48
|
-
"prepublishOnly": "npm run test && npm run bench"
|
|
49
|
-
},
|
|
50
|
-
"c8": {
|
|
51
|
-
"all": true,
|
|
52
|
-
"include": [
|
|
53
|
-
"dist/**/*.js"
|
|
54
|
-
],
|
|
55
|
-
"exclude": [
|
|
56
|
-
"dist/**/*.d.ts",
|
|
57
|
-
"test/**",
|
|
58
|
-
"benchmark/**",
|
|
59
|
-
"playwright.config.ts"
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
"devDependencies": {
|
|
63
|
-
"@commitlint/cli": "^20.5.0",
|
|
64
|
-
"@commitlint/config-conventional": "^20.5.0",
|
|
65
|
-
"@playwright/test": "^1.59.1",
|
|
66
|
-
"@types/node": "^25.5.0",
|
|
67
|
-
"c8": "^11.0.0",
|
|
68
|
-
"edge-runtime": "^4.0.1",
|
|
69
|
-
"fast-glob": "^3.3.3",
|
|
70
|
-
"husky": "^9.1.7",
|
|
71
|
-
"playwright": "^1.59.1",
|
|
72
|
-
"prettier": "^3.8.1",
|
|
73
|
-
"tsup": "^8.5.1",
|
|
74
|
-
"typescript": "^5.9.3",
|
|
75
|
-
"wrangler": "^4.79.0"
|
|
76
|
-
},
|
|
77
|
-
"dependencies": {
|
|
78
|
-
"@sovereignbase/utils": "^1.1.0",
|
|
79
|
-
"uuid": "^13.0.0"
|
|
80
|
-
},
|
|
81
|
-
"repository": {
|
|
82
|
-
"type": "git",
|
|
83
|
-
"url": "git+https://github.com/sovereignbase/convergent-replicated-struct.git"
|
|
84
|
-
},
|
|
85
|
-
"bugs": {
|
|
86
|
-
"url": "https://github.com/sovereignbase/convergent-replicated-struct/issues"
|
|
87
|
-
},
|
|
88
|
-
"homepage": "https://sovereignbase.github.io/convergent-replicated-struct"
|
|
89
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"license": "Apache-2.0",
|
|
4
|
+
"name": "@sovereignbase/convergent-replicated-struct",
|
|
5
|
+
"version": "1.0.1",
|
|
6
|
+
"description": "State-based CRDT for fixed-key object structs with per-field overwrite tracking.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"crdt",
|
|
9
|
+
"state-based-crdt",
|
|
10
|
+
"eventual-consistency",
|
|
11
|
+
"local-first",
|
|
12
|
+
"replication",
|
|
13
|
+
"convergent-replicated-struct",
|
|
14
|
+
"cr-struct",
|
|
15
|
+
"uuidv7",
|
|
16
|
+
"lww-register",
|
|
17
|
+
"delta",
|
|
18
|
+
"snapshot",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
21
|
+
"main": "./dist/index.cjs",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"require": "./dist/index.cjs",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"scripts": {
|
|
43
|
+
"format": "prettier . --write",
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"test": "npm run build && node test/run-coverage.mjs && node test/e2e/run.mjs",
|
|
46
|
+
"bench": "npm run build && node benchmark/bench.js",
|
|
47
|
+
"prepare": "husky",
|
|
48
|
+
"prepublishOnly": "npm run test && npm run bench"
|
|
49
|
+
},
|
|
50
|
+
"c8": {
|
|
51
|
+
"all": true,
|
|
52
|
+
"include": [
|
|
53
|
+
"dist/**/*.js"
|
|
54
|
+
],
|
|
55
|
+
"exclude": [
|
|
56
|
+
"dist/**/*.d.ts",
|
|
57
|
+
"test/**",
|
|
58
|
+
"benchmark/**",
|
|
59
|
+
"playwright.config.ts"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@commitlint/cli": "^20.5.0",
|
|
64
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
65
|
+
"@playwright/test": "^1.59.1",
|
|
66
|
+
"@types/node": "^25.5.0",
|
|
67
|
+
"c8": "^11.0.0",
|
|
68
|
+
"edge-runtime": "^4.0.1",
|
|
69
|
+
"fast-glob": "^3.3.3",
|
|
70
|
+
"husky": "^9.1.7",
|
|
71
|
+
"playwright": "^1.59.1",
|
|
72
|
+
"prettier": "^3.8.1",
|
|
73
|
+
"tsup": "^8.5.1",
|
|
74
|
+
"typescript": "^5.9.3",
|
|
75
|
+
"wrangler": "^4.79.0"
|
|
76
|
+
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"@sovereignbase/utils": "^1.1.0",
|
|
79
|
+
"uuid": "^13.0.0"
|
|
80
|
+
},
|
|
81
|
+
"repository": {
|
|
82
|
+
"type": "git",
|
|
83
|
+
"url": "git+https://github.com/sovereignbase/convergent-replicated-struct.git"
|
|
84
|
+
},
|
|
85
|
+
"bugs": {
|
|
86
|
+
"url": "https://github.com/sovereignbase/convergent-replicated-struct/issues"
|
|
87
|
+
},
|
|
88
|
+
"homepage": "https://sovereignbase.github.io/convergent-replicated-struct"
|
|
89
|
+
}
|