@ocap/indexdb 1.6.5 → 1.6.10

Sign up to get free protection for your applications and to get access to all the features.
package/lib/db.js CHANGED
@@ -1,15 +1,57 @@
1
+ /* eslint-disable no-unused-vars */
1
2
  const Ready = require('@ocap/util/lib/ready');
3
+ const { indexes } = require('@ocap/state');
2
4
 
3
5
  class BaseIndexDB extends Ready {
4
6
  constructor() {
5
7
  super();
6
8
 
7
- this.readyMarks = {
8
- account: false,
9
- asset: false,
10
- delegation: false,
11
- tx: false,
12
- };
9
+ this.readyMarks = indexes.reduce((acc, x) => ({ ...acc, [x]: false }), {});
10
+ this.readyListenersAttached = false;
11
+ }
12
+
13
+ attachReadyListeners() {
14
+ if (this.readyListenersAttached) {
15
+ return;
16
+ }
17
+
18
+ for (const index of indexes) {
19
+ if (typeof this[index] === 'undefined') {
20
+ throw new Error(`Missing index ${index} in indexdb adapter: ${this.name}`);
21
+ }
22
+
23
+ this[index].onReady(() => this.markReady(index));
24
+ }
25
+
26
+ this.readyListenersAttached = true;
27
+ }
28
+
29
+ listTransactions({ addressFilter = {}, paging = {}, timeFilter = {}, typeFilter = {} } = {}) {
30
+ throw new Error('listTransactions should be implemented in adapter');
31
+ }
32
+
33
+ listAssets({ ownerAddress, paging } = {}) {
34
+ throw new Error('listAssets should be implemented in adapter');
35
+ }
36
+
37
+ listFactories({ ownerAddress, paging } = {}) {
38
+ throw new Error('listFactories should be implemented in adapter');
39
+ }
40
+
41
+ listTopAccounts({ paging, tokenAddress } = {}) {
42
+ throw new Error('listTopAccounts should be implemented in adapter');
43
+ }
44
+
45
+ listTokens({ paging }) {
46
+ throw new Error('listTokens should be implemented in adapter');
47
+ }
48
+
49
+ listStakes({ paging = {}, addressFilter = {}, timeFilter = {}, assetFilter = {} } = {}) {
50
+ throw new Error('listStakes should be implemented in adapter');
51
+ }
52
+
53
+ listBlocks() {
54
+ return [];
13
55
  }
14
56
  }
15
57
 
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  const Kareem = require('kareem');
3
+ const omit = require('lodash/omit');
3
4
  const Ready = require('@ocap/util/lib/ready');
4
5
 
5
6
  class BaseIndex extends Ready {
@@ -21,6 +22,11 @@ class BaseIndex extends Ready {
21
22
  hooks.execPreSync(x, args);
22
23
  const result = await this[`_${x}`](...args);
23
24
  hooks.execPostSync(x, result);
25
+
26
+ if (['insert', 'update'].includes(x)) {
27
+ this.emit(x, omit(result, '$loki'));
28
+ }
29
+
24
30
  return result;
25
31
  },
26
32
  });
package/lib/util.js ADDED
@@ -0,0 +1,514 @@
1
+ /* eslint-disable no-restricted-globals */
2
+ const get = require('lodash/get');
3
+ const uniq = require('lodash/uniq');
4
+ const pick = require('lodash/pick');
5
+ const createSortedList = require('@ocap/util/lib/create-sorted-list');
6
+ const { BN } = require('@ocap/util');
7
+
8
+ const formatTokenMeta = (token, tokenStates) => {
9
+ if (!tokenStates) {
10
+ return token;
11
+ }
12
+
13
+ const tokenState = tokenStates.find((x) => x.address === token.address);
14
+ token.decimal = tokenState.decimal;
15
+ token.unit = tokenState.unit;
16
+ token.symbol = tokenState.symbol;
17
+
18
+ return token;
19
+ };
20
+
21
+ const createIndexedAccount = async (x, getTokenFn) => {
22
+ const tasks = Object.keys(x.tokens || {}).map((address) => getTokenFn(address));
23
+ const tokenStates = await Promise.all(tasks);
24
+
25
+ return {
26
+ address: x.address,
27
+ balance: x.balance || '0',
28
+ moniker: x.moniker,
29
+ nonce: x.nonce,
30
+ numAssets: x.numAssets || 0,
31
+ numTxs: x.numTxs || 0,
32
+ recentNumTxs: [],
33
+ renaissanceTime: x.context.renaissanceTime,
34
+ tokens: Object.keys(x.tokens || {}).map((address) =>
35
+ formatTokenMeta({ address, balance: x.tokens[address] }, tokenStates)
36
+ ), // eslint-disable-line function-paren-newline
37
+ genesisTime: x.context.genesisTime,
38
+ migratedTo: x.migratedTo[0] || '',
39
+ migratedFrom: x.migratedFrom[0] || '',
40
+ };
41
+ };
42
+
43
+ const createIndexedAsset = (x) => ({
44
+ address: x.address,
45
+ issuer: x.issuer,
46
+ moniker: x.moniker,
47
+ owner: x.owner,
48
+ parent: x.parent,
49
+ readonly: x.readonly,
50
+ transferrable: x.transferrable,
51
+ ttl: x.ttl,
52
+ consumedTime: x.consumedTime,
53
+ genesisTime: x.context.genesisTime,
54
+ renaissanceTime: x.context.renaissanceTime,
55
+ data: x.data,
56
+ display: x.display,
57
+ endpoint: x.endpoint,
58
+ tags: x.tags,
59
+ });
60
+
61
+ const createIndexedDelegation = (x, ctx) => ({
62
+ address: x.address,
63
+ data: x.data,
64
+ genesisTime: x.context.genesisTime,
65
+ renaissanceTime: x.context.renaissanceTime,
66
+ ops: x.ops,
67
+ from: get(ctx, 'senderState.address', ''),
68
+ to: get(ctx, 'receiverState.address', ''),
69
+ });
70
+
71
+ const createIndexedToken = (x) => ({
72
+ address: x.address,
73
+ issuer: x.issuer,
74
+ name: x.name,
75
+ description: x.description,
76
+ symbol: x.symbol,
77
+ unit: x.unit,
78
+ decimal: x.decimal,
79
+ icon: x.icon,
80
+ totalSupply: x.totalSupply,
81
+ initialSupply: x.initialSupply,
82
+ foreignToken: x.foreignToken,
83
+ data: x.data,
84
+ genesisTime: x.context.genesisTime,
85
+ renaissanceTime: x.context.renaissanceTime,
86
+ });
87
+
88
+ const createIndexedFactory = async (factory, context) => {
89
+ if (context.factoryTokens && context.factoryTokens.length > 0) {
90
+ factory.input.tokens = factory.input.tokens.map((token) => formatTokenMeta(token, context.tokenStates));
91
+ }
92
+ return {
93
+ ...pick(factory, [
94
+ 'address',
95
+ 'owner',
96
+ 'name',
97
+ 'description',
98
+ 'settlement',
99
+ 'limit',
100
+ 'trustedIssuers',
101
+ 'input',
102
+ 'output',
103
+ 'hooks',
104
+ 'data',
105
+ 'numMinted',
106
+ 'lastSettlement',
107
+ 'balance',
108
+ 'display',
109
+ ]),
110
+ tokens: Object.keys(factory.tokens || {}).map((address) =>
111
+ formatTokenMeta({ address, balance: factory.tokens[address] }, context.tokenStates)
112
+ ), // eslint-disable-line function-paren-newline
113
+ genesisTime: factory.context.genesisTime,
114
+ renaissanceTime: factory.context.renaissanceTime,
115
+ };
116
+ };
117
+
118
+ // These fields are used internally to filter transactions by some entity
119
+ const createIndexedTransaction = (tx, ctx) => {
120
+ tx.accounts = [tx.tx.from, tx.sender, tx.receiver];
121
+ tx.assets = [];
122
+ tx.tokens = [];
123
+ tx.factories = [];
124
+ tx.stakes = [];
125
+ tx.rollups = [];
126
+
127
+ tx.valid = tx.code === 'OK';
128
+
129
+ // accounts
130
+ if (ctx.senderState) {
131
+ tx.accounts.push(ctx.senderState.address);
132
+ }
133
+ if (ctx.receiverState) {
134
+ tx.accounts.push(ctx.receiverState.address);
135
+ }
136
+ if (ctx.vaultState) {
137
+ tx.accounts.push(ctx.vaultState.address);
138
+ }
139
+ if (ctx.updatedAccounts && Array.isArray(ctx.updatedAccounts)) {
140
+ tx.accounts.push(...ctx.updatedAccounts.map((x) => x.address));
141
+ }
142
+ if (ctx.delegatorState) {
143
+ tx.accounts.push(ctx.delegatorState.address);
144
+ }
145
+ if (Array.isArray(ctx.delegatorStates)) {
146
+ tx.accounts.push(...ctx.delegatorStates.map((x) => x.address));
147
+ }
148
+ if (Array.isArray(ctx.signerStates)) {
149
+ tx.accounts.push(...ctx.signerStates.map((x) => x.address));
150
+ }
151
+ if (Array.isArray(ctx.receiverStates)) {
152
+ tx.accounts.push(...ctx.receiverStates.map((x) => x.address));
153
+ }
154
+
155
+ // assets
156
+ if (ctx.assetState) {
157
+ tx.assets.push(ctx.assetState.address);
158
+ }
159
+ if (ctx.assetStates && Array.isArray(ctx.assetStates)) {
160
+ tx.assets.push(...ctx.assetStates.map((x) => x.address));
161
+ }
162
+
163
+ // tokens
164
+ if (ctx.tokenState) {
165
+ tx.tokens.push(ctx.tokenState.address);
166
+ }
167
+ if (ctx.tokenStates && Array.isArray(ctx.tokenStates)) {
168
+ tx.tokens.push(...ctx.tokenStates.map((x) => x.address));
169
+ }
170
+
171
+ // factories
172
+ if (ctx.factoryState) {
173
+ tx.factories.push(ctx.factoryState.address);
174
+ }
175
+
176
+ // stakes
177
+ if (ctx.stakeState) {
178
+ tx.stakes.push(ctx.stakeState.address);
179
+ }
180
+ if (Array.isArray(ctx.stakeStates)) {
181
+ tx.stakes.push(...ctx.stakeStates.map((x) => x.address));
182
+ }
183
+
184
+ // rollups
185
+ if (ctx.rollupState) {
186
+ tx.rollups.push(ctx.rollupState.address);
187
+ }
188
+
189
+ const pickedFields = ['address', 'symbol', 'decimal', 'unit'];
190
+ if (Array.isArray(ctx.tokenStates)) {
191
+ tx.tokenSymbols = ctx.tokenStates.map((t) => pick(t, pickedFields));
192
+ } else if (ctx.tokenState) {
193
+ tx.tokenSymbols = [pick(ctx.tokenState, pickedFields)];
194
+ } else {
195
+ tx.tokenSymbols = [];
196
+ }
197
+
198
+ tx.accounts = [...tx.accounts, ...tx.stakes];
199
+
200
+ return tx;
201
+ };
202
+
203
+ const createIndexedStake = async (x, ctx) => {
204
+ return {
205
+ ...pick(x, [
206
+ 'address',
207
+ 'sender',
208
+ 'receiver',
209
+ 'assets',
210
+ 'revocable',
211
+ 'data',
212
+ 'message',
213
+ 'revokeWaitingPeriod',
214
+ 'revokedAssets',
215
+ ]),
216
+ tokens: Object.keys(x.tokens || {}).map((address) =>
217
+ formatTokenMeta({ address, balance: x.tokens[address] }, ctx.tokenStates)
218
+ ),
219
+ revokedTokens: Object.keys(x.revokedTokens || {}).map((address) =>
220
+ formatTokenMeta({ address, balance: x.revokedTokens[address] }, ctx.tokenStates)
221
+ ),
222
+ genesisTime: x.context.genesisTime,
223
+ renaissanceTime: x.context.renaissanceTime,
224
+ };
225
+ };
226
+
227
+ const createIndexedRollup = async (x, ctx) => {
228
+ const rollup = {
229
+ ...pick(x, [
230
+ 'address',
231
+ 'paused',
232
+ 'tokenAddress',
233
+ 'contractAddress',
234
+ 'seedValidators',
235
+ 'validators',
236
+ 'minStakeAmount',
237
+ 'maxStakeAmount',
238
+ 'minSignerCount',
239
+ 'maxSignerCount',
240
+ 'minBlockSize',
241
+ 'maxBlockSize',
242
+ 'minBlockInterval',
243
+ 'minBlockConfirmation',
244
+ 'minDepositAmount',
245
+ 'maxDepositAmount',
246
+ 'minWithdrawAmount',
247
+ 'maxWithdrawAmount',
248
+ 'depositFeeRate',
249
+ 'withdrawFeeRate',
250
+ 'proposerFeeShare',
251
+ 'publisherFeeShare',
252
+ 'minDepositFee',
253
+ 'maxDepositFee',
254
+ 'minWithdrawFee',
255
+ 'maxWithdrawFee',
256
+ 'issuer',
257
+ 'blockHeight',
258
+ 'blockHash',
259
+ 'leaveWaitingPeriod',
260
+ 'publishWaitingPeriod',
261
+ 'publishSlashRate',
262
+ 'migrateHistory',
263
+ 'data',
264
+ ]),
265
+ genesisTime: x.context.genesisTime,
266
+ renaissanceTime: x.context.renaissanceTime,
267
+ };
268
+
269
+ if (ctx.tokenStates) {
270
+ const tokenState = ctx.tokenStates.find((t) => t.address === x.tokenAddress);
271
+ rollup.tokenInfo = formatTokenMeta({ address: x.tokenAddress, value: '0' }, ctx.tokenStates);
272
+ rollup.foreignToken = tokenState.foreignToken;
273
+ }
274
+
275
+ return rollup;
276
+ };
277
+
278
+ const createIndexedRollupBlock = async (x, ctx) => {
279
+ return {
280
+ ...pick(x, [
281
+ 'hash',
282
+ 'height',
283
+ 'merkleRoot',
284
+ 'previousHash',
285
+ 'txsHash',
286
+ 'txs',
287
+ 'proposer',
288
+ 'signatures',
289
+ 'rollup',
290
+ 'mintedAmount',
291
+ 'burnedAmount',
292
+ 'rewardAmount',
293
+ 'minReward',
294
+ 'data',
295
+ ]),
296
+ genesisTime: x.context.genesisTime,
297
+ renaissanceTime: x.context.renaissanceTime,
298
+ tokenInfo: formatTokenMeta({ address: ctx.rollupState.tokenAddress, value: '0' }, ctx.tokenStates),
299
+ validators: (x.signaturesList || x.signatures || []).map((v) => v.signer),
300
+ };
301
+ };
302
+
303
+ const formatTxBeforeInsert = (row) => {
304
+ const tx = { ...row };
305
+ tx.sender = tx.sender || '';
306
+ tx.receiver = tx.receiver || '';
307
+
308
+ // index assets
309
+ tx.assets = tx.assets || [];
310
+ if (['transfer', 'transfer_v2'].includes(tx.type) && tx.tx.itxJson.assets) {
311
+ tx.assets.push(...tx.tx.itxJson.assets);
312
+ } else if (['transfer_v3', 'stake'].includes(tx.type)) {
313
+ tx.assets.push(...createSortedList(tx.tx.itxJson.inputs.map((x) => x.assets)));
314
+ } else if (['revoke_stake'].includes(tx.type)) {
315
+ tx.assets.push(...createSortedList(tx.tx.itxJson.outputs.map((x) => x.assets)));
316
+ } else if (['exchange', 'exchange_v2'].includes(tx.type)) {
317
+ tx.assets.push(...(tx.tx.itxJson.sender.assets || []), ...(tx.tx.itxJson.receiver.assets || []));
318
+ } else if (['create_asset', 'update_asset', 'acquire_asset_v2', 'mint_asset'].includes(tx.type)) {
319
+ tx.assets.push(tx.tx.itxJson.address);
320
+ }
321
+
322
+ // index factories
323
+ tx.factories = tx.factories || [];
324
+ if (['acquire_asset_v2', 'acquire_asset_v3', 'mint_asset', 'create_factory'].includes(tx.type)) {
325
+ tx.factories.push(tx.tx.itxJson.factory);
326
+ }
327
+
328
+ // index tokens
329
+ tx.tokens = tx.tokens || [];
330
+ if (tx.type === 'transfer_v2' && tx.tx.itxJson.tokens) {
331
+ tx.tokens.push(...tx.tx.itxJson.tokens.map((x) => x.address));
332
+ } else if (['transfer_v3', 'stake'].includes(tx.type)) {
333
+ tx.tokens.push(...createSortedList(tx.tx.itxJson.inputs.map((x) => x.tokens.map((t) => t.address))));
334
+ } else if (['revoke_stake'].includes(tx.type)) {
335
+ tx.tokens.push(...createSortedList(tx.tx.itxJson.outputs.map((x) => x.tokens.map((t) => t.address))));
336
+ } else if (tx.type === 'exchange_v2') {
337
+ tx.tokens.push(
338
+ ...(tx.tx.itxJson.sender.tokens || []).map((x) => x.address),
339
+ ...(tx.tx.itxJson.receiver.tokens || []).map((x) => x.address)
340
+ );
341
+ }
342
+
343
+ tx.accounts = tx.accounts || [];
344
+ tx.stakes = tx.stakes || [];
345
+
346
+ tx.assets = uniq(tx.assets).filter(Boolean);
347
+ tx.factories = uniq(tx.factories).filter(Boolean);
348
+ tx.tokens = uniq(tx.tokens).filter(Boolean);
349
+ tx.accounts = uniq(tx.accounts).filter(Boolean);
350
+ tx.stakes = uniq(tx.stakes).filter(Boolean);
351
+ tx.rollups = uniq(tx.rollups).filter(Boolean);
352
+
353
+ return tx;
354
+ };
355
+
356
+ const formatTxAfterRead = (tx) => {
357
+ delete tx.assets;
358
+ delete tx.factories;
359
+ delete tx.tokens;
360
+ delete tx.accounts;
361
+ delete tx.stakes;
362
+ delete tx.rollups;
363
+
364
+ return tx;
365
+ };
366
+
367
+ const parseDateTime = (str) => {
368
+ if (!str) {
369
+ return '';
370
+ }
371
+
372
+ const parsed = Date.parse(str);
373
+ if (isNaN(parsed)) {
374
+ return '';
375
+ }
376
+
377
+ return new Date(parsed).toISOString();
378
+ };
379
+
380
+ const PAGE_SIZE_DEFAULT = 20;
381
+ const PAGE_SIZE_MAX = 100;
382
+
383
+ const formatPagination = ({ paging, defaultSortField, supportedSortFields = [] }) => {
384
+ if (!defaultSortField) {
385
+ throw new Error('argument defaultSortField is required');
386
+ }
387
+
388
+ const pagination = paging || { size: PAGE_SIZE_DEFAULT };
389
+ if (Array.isArray(pagination.order)) {
390
+ [pagination.order] = pagination.order;
391
+ }
392
+
393
+ if (!pagination.order) {
394
+ pagination.order = { field: defaultSortField, type: 'desc' };
395
+ }
396
+
397
+ if (!pagination.order.field) {
398
+ pagination.order.field = defaultSortField;
399
+ }
400
+
401
+ if (supportedSortFields.length > 0 && !supportedSortFields.includes(pagination.order.field)) {
402
+ pagination.order.field = defaultSortField;
403
+ }
404
+
405
+ if (!pagination.order.type) {
406
+ pagination.order.type = 'desc';
407
+ }
408
+
409
+ if (pagination.size === null || typeof pagination.size === 'undefined') {
410
+ pagination.size = PAGE_SIZE_DEFAULT;
411
+ }
412
+
413
+ // Since Elasticsearch does not support cursor based pagination, we just use offset as cursor
414
+ if (pagination.cursor) {
415
+ pagination.cursor = Number(pagination.cursor);
416
+ } else {
417
+ pagination.cursor = 0;
418
+ }
419
+
420
+ pagination.size = Math.min(pagination.size, PAGE_SIZE_MAX);
421
+
422
+ return pagination;
423
+ };
424
+
425
+ const formatNextPagination = (total, pagination) => {
426
+ const nextCursor = pagination.cursor + pagination.size;
427
+ if (total > nextCursor) {
428
+ return { cursor: nextCursor, next: true, total };
429
+ }
430
+
431
+ return { cursor: '0', next: false, total };
432
+ };
433
+
434
+ const isDefaultTokenChanged = (tx, token) => {
435
+ const itx = tx.tx.itxJson;
436
+ if (
437
+ [
438
+ 'fg:t:transfer',
439
+ 'fg:t:transfer_v2',
440
+ 'fg:t:transfer_v3',
441
+ 'fg:t:acquire_asset_v2',
442
+ 'fg:t:acquire_asset_v3',
443
+ 'fg:t:exchange',
444
+ 'fg:t:exchange_v2',
445
+ 'fg:t:deposit_token',
446
+ 'fg:t:withdraw_token',
447
+ 'fg:t:faucet',
448
+ 'fg:t:poke',
449
+ 'fg:t:setup_swap',
450
+ 'fg:t:retrieve_swap',
451
+ 'fg:t:stake',
452
+ 'fg:t:revoke_stake',
453
+ ].includes(itx.type_url) === false
454
+ ) {
455
+ return false;
456
+ }
457
+
458
+ const ZERO = new BN(0);
459
+ const isDefaultToken = (id) => [token.address, ''].includes(id);
460
+ const isPositive = (v) => new BN(v || 0).gt(ZERO);
461
+ const isChangedByInput = (input) =>
462
+ isPositive(input.value) || input.tokens.some((x) => isDefaultToken(x.address) && isPositive(x.value));
463
+
464
+ switch (itx.type_url) {
465
+ case 'fg:t:transfer':
466
+ return isPositive(itx.value);
467
+ case 'fg:t:transfer_v2':
468
+ return isChangedByInput(itx);
469
+ case 'fg:t:transfer_v3':
470
+ case 'fg:t:acquire_asset_v3':
471
+ return itx.inputs.some(isChangedByInput);
472
+ case 'fg:t:acquire_asset_v2':
473
+ return tx.receipts.some((r) => r.changes.some((c) => isDefaultToken(c.target) && isPositive(c.value)));
474
+ case 'fg:t:exchange':
475
+ return isPositive(itx.sender.value) || isPositive(itx.receiver.value);
476
+ case 'fg:t:exchange_v2':
477
+ return isChangedByInput(itx.sender) || isChangedByInput(itx.receiver);
478
+ case 'fg:t:deposit_token':
479
+ case 'fg:t:withdraw_token':
480
+ return isPositive(itx.value);
481
+ case 'fg:t:setup_swap':
482
+ case 'fg:t:retrieve_swap':
483
+ return isPositive(itx.value);
484
+ case 'fg:t:poke':
485
+ return true;
486
+ case 'fg:t:faucet':
487
+ return !itx.token;
488
+ case 'fg:t:stake':
489
+ return itx.inputs.some(isChangedByInput);
490
+ case 'fg:t:revoke_stake':
491
+ return itx.outputs.some(isChangedByInput);
492
+ default:
493
+ return false;
494
+ }
495
+ };
496
+
497
+ module.exports = {
498
+ createIndexedAccount,
499
+ createIndexedAsset,
500
+ createIndexedFactory,
501
+ createIndexedDelegation,
502
+ createIndexedToken,
503
+ createIndexedTransaction,
504
+ createIndexedStake,
505
+ createIndexedRollup,
506
+ createIndexedRollupBlock,
507
+ formatPagination,
508
+ formatNextPagination,
509
+ formatTxAfterRead,
510
+ formatTxBeforeInsert,
511
+ parseDateTime,
512
+ formatTokenMeta,
513
+ isDefaultTokenChanged,
514
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.6.5",
6
+ "version": "1.6.10",
7
7
  "description": "Defines the basic interface for OCAP IndexDB",
8
8
  "main": "lib/main.js",
9
9
  "files": [
@@ -12,18 +12,23 @@
12
12
  "scripts": {
13
13
  "lint": "eslint tests lib",
14
14
  "lint:fix": "eslint --fix tests lib",
15
- "test": "node tools/jest.js",
15
+ "test": "jest --forceExit --detectOpenHandles",
16
16
  "coverage": "npm run test -- --coverage"
17
17
  },
18
18
  "keywords": [],
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
+ "contributors": [
21
+ "wangshijun <shijun@arcblock.io> (https://github.com/wangshijun)"
22
+ ],
20
23
  "license": "MIT",
21
24
  "devDependencies": {
22
- "jest": "^26.6.3"
25
+ "jest": "^27.3.1"
23
26
  },
24
27
  "dependencies": {
25
- "@ocap/util": "^1.6.5",
26
- "kareem": "^2.3.2"
28
+ "@ocap/state": "1.6.10",
29
+ "@ocap/util": "1.6.10",
30
+ "kareem": "^2.3.2",
31
+ "lodash": "^4.17.21"
27
32
  },
28
- "gitHead": "5779448f13824de38978df3c84c9da0c1e1ad989"
33
+ "gitHead": "ab272e8db3a15c6571cc7fae7cc3d3e0fdd4bdb1"
29
34
  }