@fizzyflow/endless-vector 0.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.
@@ -0,0 +1,219 @@
1
+ import EndlessVectorHistory from './EndlessVectorHistory.js';
2
+
3
+ /**
4
+ * @typedef {import('@mysten/sui/client').SuiClient} SuiClient
5
+ * @typedef {import('@mysten/sui/client').GetDynamicFieldsParams} GetDynamicFieldsParams
6
+ * @typedef {import('./EndlessVector.js').default} EndlessVector
7
+ */
8
+
9
+ export default class EndlessVectorArchive {
10
+ /**
11
+ * Creates a new EndlessVectorArchive instance.
12
+ * @param {Object} params - Configuration parameters
13
+ * @param {SuiClient} [params.suiClient] - Sui client instance for blockchain interactions
14
+ * @param {string} [params.id] - ID or address of the EndlessVectorArchive on the Sui blockchain
15
+ * @param {number} [params.index=0] - Index position of this archive item in the sequence
16
+ * @param {EndlessVector} [params.endlessVector] - Reference to the parent EndlessVector instance
17
+ * @param {Object} [params.fields] - Raw field data from the blockchain object
18
+ */
19
+ constructor(params = {}) {
20
+ /** @type {SuiClient} */
21
+ this.suiClient = params.suiClient;
22
+ /** @type {string} */
23
+ this.id = params.id;
24
+ /** @type {number} */
25
+ this.index = params.index || 0;
26
+ /** @type {?EndlessVector} */
27
+ this._endlessVector = params.endlessVector || null; // parent instance of EndlessVector class
28
+
29
+ /** @type {string} */
30
+ this.historyTableId = null; // id of the dynamic field table that contains history items of this EndlessVector
31
+ /** @type {number} */
32
+ this.historyItemsCount = 0; // data from EndlessVectorArchive object .history field
33
+ this._history = {}; // EndlessVectorHistory instances
34
+
35
+ /** @type {Array<Uint8Array>} */
36
+ this._items = []; // final items in the archived vector
37
+
38
+ /** @type {boolean} */
39
+ this._isInitialized = false;
40
+
41
+ this._fields = params.fields || null;
42
+ }
43
+
44
+ /**
45
+ * Sets the fields data for this archive item. Called by loader of EndlessVector.
46
+ * @param {Object} fields - The fields data from the blockchain object
47
+ */
48
+ setFields(fields) {
49
+ this._fields = fields;
50
+ this.historyTableId = fields?.history?.fields?.id?.id;
51
+ this.historyItemsCount = parseInt(fields?.history?.fields?.size || '0');
52
+ }
53
+
54
+ /**
55
+ * Checks if this archive item has been initialized and is ready for use.
56
+ * @returns {boolean} True if the archive item is initialized
57
+ */
58
+ isReady() {
59
+ return this._isInitialized;
60
+ }
61
+
62
+ /**
63
+ * Gets the total number of items stored in this archive.
64
+ * @returns {number} The length of the archived vector segment
65
+ */
66
+ get length() {
67
+ if (this._fields && this._fields.length) {
68
+ return parseInt(this._fields.length);
69
+ }
70
+ return 0;
71
+ }
72
+
73
+ /**
74
+ * Gets the first index position that this archive covers.
75
+ * @returns {number} The starting index (inclusive)
76
+ */
77
+ get startsAt() {
78
+ return this.endsAt - this.length;
79
+ }
80
+
81
+ /**
82
+ * Gets the last index position that this archive covers.
83
+ * @returns {number|undefined} The ending index (inclusive), or undefined if not available
84
+ */
85
+ get endsAt() {
86
+ if (this._fields && this._fields.archived_at_length) {
87
+ return parseInt(this._fields.archived_at_length) - 1;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Initializes this archive item by loading its data from the blockchain.
93
+ * Uses promise-based synchronization to prevent multiple concurrent initializations.
94
+ * @returns {Promise<boolean>} True when initialization is complete
95
+ */
96
+ async initialize() {
97
+ if (this._isInitialized) {
98
+ return true;
99
+ }
100
+ if (this.__initializationPromise) {
101
+ return await this.__initializationPromise;
102
+ }
103
+
104
+ this.__initializationPromiseResolver = null;
105
+ this.__initializationPromise = new Promise((res)=>{ this.__initializationPromiseResolver = res; });
106
+
107
+ await this._endlessVector.loadArchiveItem(this);
108
+
109
+ this._isInitialized = true;
110
+ this.__initializationPromiseResolver();
111
+
112
+ delete this.__initializationPromise;
113
+ delete this.__initializationPromiseResolver;
114
+ }
115
+
116
+ /**
117
+ * Gets a history item within this archive by its index.
118
+ * @param {number|string} historyIndex - The index of the history item to retrieve
119
+ * @returns {Promise<EndlessVectorHistory>} The history item at the specified index
120
+ * @throws {Error} If historyTableId is not set or history item not found
121
+ */
122
+ async getHistory(historyIndex) {
123
+ // @todo: check by historyItemsCount
124
+
125
+ const historyIndexInt = parseInt(historyIndex);
126
+
127
+ if (this._history[historyIndexInt]) {
128
+ await this._history[historyIndexInt].initialize();
129
+ return this._history[historyIndexInt];
130
+ }
131
+
132
+ // go throught the dynamic fields of the history table to find the id of the needed history item
133
+ if (!this.historyTableId) {
134
+ throw new Error('historyTableId is not set');
135
+ }
136
+
137
+ /** @type {GetDynamicFieldsParams} */
138
+ const getDynamicFieldsParams = {
139
+ parentId: this.historyTableId,
140
+ options: {
141
+ showContent: true,
142
+ showType: true,
143
+ },
144
+ };
145
+
146
+ let resp = null;
147
+ let haveToLookMore = true;
148
+
149
+ do {
150
+ resp = await this.suiClient.getDynamicFields(getDynamicFieldsParams);
151
+ if (resp && resp.data && resp.data.length) {
152
+ for (const df of resp.data) {
153
+ if (df?.objectId) {
154
+ const itemHistoryIndex = parseInt(df.name.value);
155
+ const endlessVectorHistory = new EndlessVectorHistory({
156
+ suiClient: this.suiClient,
157
+ id: df.objectId,
158
+ index: itemHistoryIndex,
159
+ endlessVector: this._endlessVector,
160
+ endlessVectorArchive: this,
161
+ });
162
+ this._history[itemHistoryIndex] = endlessVectorHistory;
163
+ if (itemHistoryIndex === historyIndexInt) {
164
+ haveToLookMore = false;
165
+ }
166
+ }
167
+ }
168
+ getDynamicFieldsParams.cursor = resp.nextCursor;
169
+ }
170
+ } while (resp?.hasNextPage && haveToLookMore);
171
+
172
+ if (!this._history[historyIndexInt]) {
173
+ throw new Error(`History not found for index ${historyIndexInt}`);
174
+ }
175
+
176
+ await this._history[historyIndexInt].initialize();
177
+
178
+ return this._history[historyIndexInt];
179
+ }
180
+
181
+ /**
182
+ * Retrieves the byte array at the specified index within this archive.
183
+ * @param {number} i - The index to retrieve
184
+ * @returns {Promise<Uint8Array>} The byte array at the specified index
185
+ * @throws {Error} If the index is out of range for this archive item
186
+ */
187
+ async at(i) {
188
+ if (i <= this.endsAt) {
189
+ for (let j = 0; j < this.historyItemsCount; j++) {
190
+ const historyItem = await this.getHistory(j);
191
+ if (j == 0 && i < historyItem.startsAt) {
192
+ throw new Error('at() is out of range for this archive item');
193
+ }
194
+ if (historyItem.startsAt <= i && i <= historyItem.endsAt) {
195
+ return historyItem.at(i);
196
+ }
197
+ }
198
+ }
199
+
200
+ throw new Error('at() is out of range for this history item');
201
+ }
202
+
203
+ /**
204
+ * Gets suffix bytes from a history item at the specified index within this archive.
205
+ * @param {number} i - The history item index
206
+ * @returns {Promise<Uint8Array>} The suffix bytes from the history item
207
+ * @throws {Error} If the index is out of range for this archive item
208
+ */
209
+ async getSuffixFromHistoryItemOfIndex(i) {
210
+ if (i < this.historyItemsCount) {
211
+ const historyItem = await this.getHistory(i);
212
+ if (historyItem) {
213
+ return historyItem.getSuffixStoredBytes();
214
+ }
215
+ }
216
+
217
+ throw new Error('getSuffixFromHistoryItemOfIndex() is out of range for this archive item');
218
+ }
219
+ }
@@ -0,0 +1,187 @@
1
+
2
+ /**
3
+ * @typedef {import('@mysten/sui/client').SuiClient} SuiClient
4
+ * @typedef {import('./EndlessVector.js').default} EndlessVector
5
+ * @typedef {import('./EndlessVectorArchive.js').default} EndlessVectorArchive
6
+ */
7
+
8
+ /**
9
+ * Represents a history item in an EndlessVector, managing a segment of the vector's data.
10
+ * Each history item stores a portion of the vector's elements and maintains metadata
11
+ * about its position and relationships with adjacent history items.
12
+ */
13
+ export default class EndlessVectorHistory {
14
+ /**
15
+ * Creates a new EndlessVectorHistory instance.
16
+ * @param {Object} params - Configuration parameters
17
+ * @param {SuiClient} [params.suiClient] - Sui client instance for blockchain interactions
18
+ * @param {string} [params.id] - Unique identifier for this history item
19
+ * @param {number} [params.index=0] - Index position of this history item in the sequence
20
+ * @param {?Object} [params.fields] - Raw field data from the blockchain object
21
+ * @param {EndlessVector} [params.endlessVector] - Reference to the parent EndlessVector instance
22
+ * @param {?EndlessVectorArchive} [params.endlessVectorArchive] - Reference to the parent EndlessVectorArchive instance
23
+ */
24
+ constructor(params = {}) {
25
+ /** @type {SuiClient} */
26
+ this.suiClient = params.suiClient;
27
+ /** @type {string} */
28
+ this.id = params.id;
29
+ /** @type {number} */
30
+ this.index = params.index || 0;
31
+
32
+ /** @type {?Object} */
33
+ this._fields = params.fields || null;
34
+ /** @type {EndlessVector} */
35
+ this._endlessVector = params.endlessVector || null; // parent instance of EndlessVector class
36
+ /** @type {?EndlessVectorArchive} */
37
+ this._endlessVectorArchive = params.endlessVectorArchive || null; // parent instance of EndlessVectorArchive class
38
+
39
+ /** @type {boolean} */
40
+ this._isInitialized = false;
41
+ }
42
+
43
+ /**
44
+ * Sets the fields data for this history item. Called by loader of EndlessVector.
45
+ * @param {Object} fields - The fields data from the blockchain object
46
+ */
47
+ setFields(fields) {
48
+ this._fields = fields;
49
+ }
50
+
51
+ /**
52
+ * Checks if this history item has been initialized and is ready for use.
53
+ * @returns {boolean} True if the history item is initialized
54
+ */
55
+ isReady() {
56
+ return this._isInitialized;
57
+ }
58
+
59
+ /**
60
+ * Initializes this history item by loading its data from the blockchain.
61
+ * Uses promise-based synchronization to prevent multiple concurrent initializations.
62
+ * @returns {Promise<boolean>} True when initialization is complete
63
+ */
64
+ async initialize() {
65
+ if (this._isInitialized) {
66
+ return true;
67
+ }
68
+ if (this.__initializationPromise) {
69
+ return await this.__initializationPromise;
70
+ }
71
+
72
+ this.__initializationPromiseResolver = null;
73
+ this.__initializationPromise = new Promise((res)=>{ this.__initializationPromiseResolver = res; });
74
+
75
+ await this._endlessVector.loadHistoryItem(this);
76
+
77
+ this._isInitialized = true;
78
+ this.__initializationPromiseResolver();
79
+
80
+ delete this.__initializationPromise;
81
+ delete this.__initializationPromiseResolver;
82
+ }
83
+
84
+ /**
85
+ * Gets the last index position that this history item covers.
86
+ * @returns {number|undefined} The ending index (inclusive), or undefined if not available
87
+ */
88
+ get endsAt() {
89
+ if (this._fields && this._fields.saved_at_length) {
90
+ return parseInt(this._fields.saved_at_length) - 1;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Indicates whether the first item in this history contains suffix bytes that should be
96
+ * added to the last item from the previous history segment.
97
+ * @returns {boolean} True if the first item contains suffix bytes for the previous history
98
+ */
99
+ get firstItemIsFromPreviousHistory() {
100
+ if (this._fields && this._fields.first_item_is_from_previous_history) {
101
+ return this._fields.first_item_is_from_previous_history;
102
+ }
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * Gets the first index position that this history item covers.
108
+ * Calculation adjusts for whether the first item contains suffix bytes for the previous history.
109
+ * @returns {number} The starting index (inclusive)
110
+ */
111
+ get startsAt() {
112
+ let innerItemsCount = 0;
113
+ if (this._fields && this._fields.items && this._fields.items.length) {
114
+ innerItemsCount = this._fields.items.length;
115
+ }
116
+ if (this.firstItemIsFromPreviousHistory) {
117
+ return this.endsAt - innerItemsCount + 2;
118
+ }
119
+ return this.endsAt - innerItemsCount + 1;
120
+ }
121
+
122
+ /**
123
+ * Gets the number of bytes from the next history item that should be appended
124
+ * to the last item in this history segment. This should equal the byte length
125
+ * of the first item of the next history.
126
+ * @returns {number} Number of bytes to append from the next history item
127
+ */
128
+ get followedByNextBytes() {
129
+ if (this._fields && this._fields.followed_by_next_bytes) {
130
+ return parseInt(this._fields.followed_by_next_bytes);
131
+ }
132
+ return 0;
133
+ }
134
+
135
+ /**
136
+ * Retrieves the byte array at the specified index within this history segment.
137
+ * Handles combining items with suffix bytes from the next history when needed.
138
+ * @param {number} i - The index to retrieve
139
+ * @returns {Promise<Uint8Array>} The byte array at the specified index
140
+ * @throws {Error} If the index is out of range for this history item
141
+ */
142
+ async at(i) {
143
+ if (this.startsAt <= i && i <= this.endsAt) {
144
+ let indexInItems = i - this.startsAt;
145
+ if (this.firstItemIsFromPreviousHistory) {
146
+ indexInItems = i - this.startsAt + 1;
147
+ }
148
+
149
+ if (indexInItems < (this._fields.items.length - 1)) {
150
+ return new Uint8Array(this._fields.items[indexInItems]);
151
+ } else if (indexInItems === (this._fields.items.length - 1)) {
152
+ if (this.followedByNextBytes) {
153
+ // if this item is child of archive, get suffix from next item of archive, otherwise from endless vector
154
+ const suffix = this._endlessVectorArchive ?
155
+ (await this._endlessVectorArchive.getSuffixFromHistoryItemOfIndex(this.index + 1)) :
156
+ (await this._endlessVector.getSuffixFromHistoryItemOfIndex(this.index + 1));
157
+
158
+ if (suffix.length !== this.followedByNextBytes) {
159
+ throw new Error('suffix bytes length mismatch');
160
+ }
161
+
162
+ const current = new Uint8Array(this._fields.items[indexInItems]);
163
+ const combined = new Uint8Array(current.length + suffix.length);
164
+ combined.set(current);
165
+ combined.set(suffix, current.length);
166
+ return combined;
167
+ } else {
168
+ return new Uint8Array(this._fields.items[indexInItems]);
169
+ }
170
+ }
171
+ }
172
+
173
+ throw new Error('at() is out of range for this history item');
174
+ }
175
+
176
+ /**
177
+ * Gets the suffix bytes stored in this history item that should be appended
178
+ * to the last item of the previous history segment.
179
+ * @returns {Uint8Array} The suffix bytes, or empty array if none stored
180
+ */
181
+ getSuffixStoredBytes() {
182
+ if (this.firstItemIsFromPreviousHistory) {
183
+ return new Uint8Array(this._fields.items[0]);
184
+ }
185
+ return new Uint8Array();
186
+ }
187
+ }