envio 3.10.0-subgraph → 3.11.0

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.
Files changed (76) hide show
  1. package/evm.schema.json +21 -4
  2. package/fuel.schema.json +21 -4
  3. package/index.d.ts +1 -1
  4. package/package.json +6 -6
  5. package/src/ChainState.res +9 -73
  6. package/src/ChainState.res.mjs +6 -58
  7. package/src/Config.res +73 -27
  8. package/src/Config.res.mjs +71 -21
  9. package/src/Core.res +0 -15
  10. package/src/Core.res.mjs +0 -11
  11. package/src/EventConfigBuilder.res +14 -10
  12. package/src/EventConfigBuilder.res.mjs +6 -4
  13. package/src/EventProcessing.res +6 -6
  14. package/src/EventProcessing.res.mjs +6 -8
  15. package/src/HandlerLoader.res +8 -20
  16. package/src/HandlerLoader.res.mjs +2 -11
  17. package/src/HandlerRegister.res +12 -11
  18. package/src/HandlerRegister.res.mjs +6 -5
  19. package/src/Internal.res +20 -4
  20. package/src/Internal.res.mjs +9 -0
  21. package/src/Main.res +8 -1
  22. package/src/Main.res.mjs +3 -3
  23. package/src/Persistence.res +11 -1
  24. package/src/Persistence.res.mjs +6 -2
  25. package/src/PgStorage.res +1 -1
  26. package/src/PgStorage.res.mjs +1 -1
  27. package/src/SimulateItems.res +13 -6
  28. package/src/SimulateItems.res.mjs +7 -6
  29. package/src/TestIndexer.res +6 -5
  30. package/src/TestIndexer.res.mjs +5 -4
  31. package/src/UserContext.res +51 -358
  32. package/src/UserContext.res.mjs +35 -271
  33. package/src/db/InternalTable.res +1 -1
  34. package/src/db/InternalTable.res.mjs +2 -1
  35. package/src/sources/ChainSources.res +72 -0
  36. package/src/sources/ChainSources.res.mjs +55 -0
  37. package/src/sources/EvmHyperSyncSource.res +10 -22
  38. package/src/sources/EvmHyperSyncSource.res.mjs +9 -41
  39. package/src/sources/FuelHyperSyncSource.res +10 -18
  40. package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
  41. package/src/sources/HyperSync.res +31 -0
  42. package/src/sources/HyperSync.res.mjs +31 -0
  43. package/src/sources/HyperSync.resi +16 -0
  44. package/src/sources/StartBlockResolver.res +173 -0
  45. package/src/sources/StartBlockResolver.res.mjs +134 -0
  46. package/src/sources/Svm.res +0 -51
  47. package/src/sources/Svm.res.mjs +0 -46
  48. package/src/sources/SvmHyperSyncSource.res +20 -9
  49. package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
  50. package/svm.schema.json +78 -63
  51. package/src/subgraph/blocks.ts +0 -176
  52. package/src/subgraph/calls.ts +0 -217
  53. package/src/subgraph/conformance.ts +0 -102
  54. package/src/subgraph/division.ts +0 -133
  55. package/src/subgraph/errors.ts +0 -90
  56. package/src/subgraph/graph-ts-types/VERSION +0 -2
  57. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  58. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  59. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  60. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  61. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  62. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  63. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  64. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  65. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  66. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  67. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  68. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  69. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  70. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  71. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  72. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  73. package/src/subgraph/graph-ts.ts +0 -1948
  74. package/src/subgraph/hosts.ts +0 -148
  75. package/src/subgraph/runtime.ts +0 -863
  76. package/src/subgraph/scope.ts +0 -66
@@ -1,176 +0,0 @@
1
- /**
2
- * A block handler's `block.timestamp`.
3
- *
4
- * envio hands a block handler only the block number, so the timestamp has to be
5
- * fetched. Every handler invocation in a batch asks within the same microtask,
6
- * so the requests are collected and answered by a single HyperSync range query
7
- * rather than one round trip per block. An RPC endpoint, if one is configured,
8
- * is the fallback for whatever HyperSync couldn't answer.
9
- */
10
-
11
- import { createPublicClient, fallback, http } from "viem";
12
-
13
- const API_TOKEN_ENV_VAR = "ENVIO_API_TOKEN";
14
-
15
- const hypersyncUrl = (chainId: number) => `https://${chainId}.hypersync.xyz/query`;
16
-
17
- type Waiter = {
18
- resolve: (timestamp: bigint) => void;
19
- reject: (error: unknown) => void;
20
- };
21
-
22
- /** Blocks asked for since the last flush, by chain then block number. */
23
- let pending = new Map<number, Map<number, Waiter[]>>();
24
- let flushScheduled = false;
25
-
26
- let rpcUrls: string[] = [];
27
- let client: ReturnType<typeof createPublicClient> | null = null;
28
-
29
- export function configureBlockTimestamps(urls: string[]) {
30
- rpcUrls = urls;
31
- client = null;
32
- pending = new Map();
33
- flushScheduled = false;
34
- }
35
-
36
- export function requestBlockTimestamp(chainId: number, blockNumber: number): Promise<bigint> {
37
- return new Promise((resolve, reject) => {
38
- let blocks = pending.get(chainId);
39
- if (!blocks) {
40
- blocks = new Map();
41
- pending.set(chainId, blocks);
42
- }
43
- const waiters = blocks.get(blockNumber);
44
- if (waiters) {
45
- waiters.push({ resolve, reject });
46
- } else {
47
- blocks.set(blockNumber, [{ resolve, reject }]);
48
- }
49
-
50
- if (!flushScheduled) {
51
- flushScheduled = true;
52
- queueMicrotask(() => {
53
- flushScheduled = false;
54
- const collected = pending;
55
- pending = new Map();
56
- for (const [chain, blocksForChain] of collected) {
57
- void answer(chain, blocksForChain);
58
- }
59
- });
60
- }
61
- });
62
- }
63
-
64
- async function answer(chainId: number, blocks: Map<number, Waiter[]>) {
65
- let timestamps = new Map<number, bigint>();
66
-
67
- // A batch can hold thousands of blocks, and one argument per block would
68
- // overflow the call stack inside the try, where it would read as a HyperSync
69
- // failure and fall back to one round trip per block.
70
- let lowest = Infinity;
71
- let highest = -Infinity;
72
- for (const blockNumber of blocks.keys()) {
73
- if (blockNumber < lowest) lowest = blockNumber;
74
- if (blockNumber > highest) highest = blockNumber;
75
- }
76
-
77
- try {
78
- timestamps = await fromHyperSync(chainId, lowest, highest);
79
- } catch (error) {
80
- if (rpcUrls.length === 0) {
81
- const message = error instanceof Error ? error.message : String(error);
82
- for (const waiters of blocks.values()) {
83
- for (const waiter of waiters) {
84
- waiter.reject(
85
- new Error(
86
- `Envio Subgraph couldn't read a block timestamp from HyperSync: ${message}\n` +
87
- `Set ${API_TOKEN_ENV_VAR} in .env or the environment — create one at\n` +
88
- `https://envio.dev/app/api-tokens — or set ENVIO_SUBGRAPH_RPC to fall back to RPC.`,
89
- ),
90
- );
91
- }
92
- }
93
- return;
94
- }
95
- }
96
-
97
- for (const [blockNumber, waiters] of blocks) {
98
- const known = timestamps.get(blockNumber);
99
- if (known !== undefined) {
100
- for (const waiter of waiters) waiter.resolve(known);
101
- continue;
102
- }
103
- try {
104
- const timestamp = await fromRpc(blockNumber);
105
- for (const waiter of waiters) waiter.resolve(timestamp);
106
- } catch (error) {
107
- for (const waiter of waiters) waiter.reject(error);
108
- }
109
- }
110
- }
111
-
112
- type HyperSyncResponse = {
113
- data: { blocks?: { number: number; timestamp: string }[] }[];
114
- next_block: number;
115
- };
116
-
117
- async function fromHyperSync(
118
- chainId: number,
119
- fromBlock: number,
120
- toBlock: number,
121
- ): Promise<Map<number, bigint>> {
122
- const token = process.env[API_TOKEN_ENV_VAR];
123
- const timestamps = new Map<number, bigint>();
124
-
125
- let cursor = fromBlock;
126
- // HyperSync answers as much of the range as one response holds and points at
127
- // where to resume, so a wide range takes more than one round trip.
128
- while (cursor <= toBlock) {
129
- const response = await fetch(hypersyncUrl(chainId), {
130
- method: "POST",
131
- headers: {
132
- "content-type": "application/json",
133
- ...(token ? { authorization: `Bearer ${token}` } : {}),
134
- },
135
- body: JSON.stringify({
136
- from_block: cursor,
137
- to_block: toBlock + 1,
138
- include_all_blocks: true,
139
- field_selection: { block: ["number", "timestamp"] },
140
- }),
141
- });
142
-
143
- if (!response.ok) {
144
- throw new Error(`HyperSync returned ${response.status} ${await response.text()}`);
145
- }
146
-
147
- const body = (await response.json()) as HyperSyncResponse;
148
- for (const page of body.data ?? []) {
149
- for (const block of page.blocks ?? []) {
150
- timestamps.set(block.number, BigInt(block.timestamp));
151
- }
152
- }
153
-
154
- if (!body.next_block || body.next_block <= cursor) {
155
- break;
156
- }
157
- cursor = body.next_block;
158
- }
159
-
160
- return timestamps;
161
- }
162
-
163
- async function fromRpc(blockNumber: number): Promise<bigint> {
164
- if (rpcUrls.length === 0) {
165
- throw new Error(
166
- `Envio Subgraph couldn't read the timestamp of block ${blockNumber}: HyperSync didn't\n` +
167
- `return it, and no RPC fallback is configured. Set ENVIO_SUBGRAPH_RPC in .env or the\n` +
168
- `environment.`,
169
- );
170
- }
171
- client ??= createPublicClient({
172
- transport: fallback(rpcUrls.map((url) => http(url, { retryCount: 0 }))),
173
- });
174
- const block = await client.getBlock({ blockNumber: BigInt(blockNumber) });
175
- return block.timestamp;
176
- }
@@ -1,217 +0,0 @@
1
- /**
2
- * `Contract.bind(x).foo()` -> an envio effect over viem.
3
- *
4
- * graph-node evaluates a mapping's contract calls against the block the event
5
- * came from, so the block number is part of the effect's input — and therefore
6
- * of its cache key, which is what makes a cached call safe to reuse.
7
- */
8
-
9
- import {
10
- createPublicClient,
11
- decodeFunctionResult,
12
- encodeFunctionData,
13
- fallback,
14
- http,
15
- parseAbiParameters,
16
- type Abi,
17
- } from "viem";
18
- import * as Sury from "rescript-schema";
19
- import { createEffect } from "../Envio.res.mjs";
20
- import { missingRpcMessage } from "./errors.ts";
21
-
22
- export type ParsedSignature = {
23
- name: string;
24
- inputs: string;
25
- outputs: string;
26
- };
27
-
28
- /**
29
- * graph codegen emits `"balanceOf(address):(uint256)"`. Types only, no names —
30
- * which is all `parseAbiParameters` needs.
31
- */
32
- export function parseSignature(signature: string): ParsedSignature {
33
- const open = signature.indexOf("(");
34
- if (open === -1) {
35
- throw new Error(`Unreadable contract call signature "${signature}"`);
36
- }
37
- const name = signature.slice(0, open);
38
-
39
- let depth = 0;
40
- let close = -1;
41
- for (let index = open; index < signature.length; index++) {
42
- const char = signature[index];
43
- if (char === "(") depth++;
44
- if (char === ")") {
45
- depth--;
46
- if (depth === 0) {
47
- close = index;
48
- break;
49
- }
50
- }
51
- }
52
- if (close === -1) {
53
- throw new Error(`Unreadable contract call signature "${signature}"`);
54
- }
55
-
56
- const inputs = signature.slice(open + 1, close);
57
- const rest = signature.slice(close + 1).replace(/^:/, "");
58
- const outputs = rest.startsWith("(") ? rest.slice(1, -1) : rest;
59
-
60
- return { name, inputs, outputs };
61
- }
62
-
63
- export function abiItemFor(signature: string): Abi {
64
- const { name, inputs, outputs } = parseSignature(signature);
65
- return [
66
- {
67
- type: "function",
68
- name,
69
- stateMutability: "view",
70
- inputs: inputs.trim() === "" ? [] : [...parseAbiParameters(inputs)],
71
- outputs: outputs.trim() === "" ? [] : [...parseAbiParameters(outputs)],
72
- },
73
- ] as Abi;
74
- }
75
-
76
- /**
77
- * A revert is data: the mapping's `try_` sees `{reverted: true}`. A transport
78
- * failure is not — it throws as the handler error so envio retries, and a flaky
79
- * RPC never fabricates reverted data.
80
- */
81
- export function isRevert(error: unknown): boolean {
82
- const parts: string[] = [];
83
- let current: any = error;
84
- for (let depth = 0; current && depth < 10; depth++) {
85
- parts.push(current.name, current.shortMessage, current.details, current.message);
86
- current = current.cause;
87
- }
88
- const text = parts.filter(Boolean).join(" | ");
89
-
90
- if (/revert|ContractFunctionZeroDataError|invalid opcode|out of gas/i.test(text)) {
91
- return true;
92
- }
93
- return false;
94
- }
95
-
96
- export type CallInput = {
97
- chainId: number;
98
- address: string;
99
- signature: string;
100
- args: unknown[];
101
- blockNumber: number;
102
- };
103
-
104
- export type CallOutput = {
105
- reverted: boolean;
106
- values: unknown[] | null;
107
- };
108
-
109
- let clients: Map<string, ReturnType<typeof createPublicClient>> = new Map();
110
-
111
- function clientFor(rpcUrls: string[]) {
112
- const key = rpcUrls.join("|");
113
- let client = clients.get(key);
114
- if (!client) {
115
- client = createPublicClient({
116
- // Retrying is envio's job: a failed call becomes a handler error and
117
- // the batch is retried with the effect's dedup still in place.
118
- transport: fallback(rpcUrls.map((url) => http(url, { retryCount: 0 }))),
119
- });
120
- clients.set(key, client);
121
- }
122
- return client;
123
- }
124
-
125
- /** Reset between test indexers, which each bring their own endpoints. */
126
- export function resetClients() {
127
- clients = new Map();
128
- }
129
-
130
- /**
131
- * BigInt and Bytes cross the effect boundary through Sury, which needs a
132
- * serialisable shape — so the call is described in plain JSON and the graph-ts
133
- * values are converted on both sides by the shim.
134
- */
135
- export function makeCallEffect(rpcUrls: string[]) {
136
- return createEffect(
137
- {
138
- name: "envio_subgraph_eth_call",
139
- // Both sides travel as JSON text: the cache key is then the exact call
140
- // description, and the cached row is readable.
141
- input: Sury.string,
142
- output: Sury.string,
143
- rateLimit: false,
144
- cache: true,
145
- crossChain: false,
146
- },
147
- async ({ input: encoded }: { input: string }) => {
148
- const input = JSON.parse(encoded) as CallInput;
149
- if (rpcUrls.length === 0) {
150
- throw new Error(missingRpcMessage(input.signature));
151
- }
152
- const abi = abiItemFor(input.signature);
153
- const { name } = parseSignature(input.signature);
154
- const data = encodeFunctionData({
155
- abi,
156
- functionName: name,
157
- args: input.args.map(decodeArg),
158
- });
159
-
160
- let result;
161
- try {
162
- result = await clientFor(rpcUrls).call({
163
- to: input.address as `0x${string}`,
164
- data,
165
- blockNumber: BigInt(input.blockNumber),
166
- });
167
- } catch (error) {
168
- if (isRevert(error)) {
169
- return JSON.stringify({ reverted: true, values: null });
170
- }
171
- throw error;
172
- }
173
-
174
- try {
175
- const decoded = decodeFunctionResult({
176
- abi,
177
- functionName: name,
178
- data: (result.data ?? "0x") as `0x${string}`,
179
- });
180
- // graph-ts returns one `ethereum.Value` per ABI output. A single
181
- // tuple/struct is therefore `[tuple]`, not the flattened fields —
182
- // generated bindings call `result[0].toTuple()`.
183
- const outputs = (abi[0] as { outputs?: unknown[] }).outputs ?? [];
184
- const values = outputs.length === 1 ? [decoded] : Array.isArray(decoded) ? decoded : [decoded];
185
- return JSON.stringify({ reverted: false, values: values.map(encodeArg) });
186
- } catch {
187
- // Output the declared signature can't decode counts as a failed call,
188
- // which is what lets a mapping retry through a different ABI — the
189
- // bytes32-vs-string ERC20 name is the case every token subgraph hits.
190
- return JSON.stringify({ reverted: true, values: null });
191
- }
192
- },
193
- );
194
- }
195
-
196
- /** Effect inputs/outputs travel as strings, tagged so the type survives. */
197
- export function encodeArg(value: unknown): string {
198
- if (typeof value === "bigint") return `i:${value.toString()}`;
199
- if (typeof value === "boolean") return `b:${value ? "1" : "0"}`;
200
- if (Array.isArray(value)) return `a:${JSON.stringify(value.map(encodeArg))}`;
201
- return `s:${String(value)}`;
202
- }
203
-
204
- export function decodeArg(value: string): unknown {
205
- const tag = value.slice(0, 2);
206
- const body = value.slice(2);
207
- switch (tag) {
208
- case "i:":
209
- return BigInt(body);
210
- case "b:":
211
- return body === "1";
212
- case "a:":
213
- return (JSON.parse(body) as string[]).map(decodeArg);
214
- default:
215
- return body;
216
- }
217
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * The shim, checked against the types a subgraph developer codes against.
3
- *
4
- * A subgraph's mappings are type-checked by `asc` against
5
- * `@graphprotocol/graph-ts`, and then run by this shim. Nothing else makes
6
- * those two agree, so a gap shows up as green types and a runtime error — the
7
- * worst feedback loop there is. This file closes it: every export the shim
8
- * claims to provide has to be assignable to the real one.
9
- *
10
- * The declarations come from the real package (see
11
- * `scripts/generate-graph-ts-types.mjs`). This file is type-checked, never run.
12
- *
13
- * A `@ts-expect-error` below is a known gap, and it cleans itself up: closing
14
- * the gap makes the directive unused, which is itself an error, so the comment
15
- * has to go with the fix. Adding a new one is a deliberate act.
16
- */
17
-
18
- import type * as GraphTs from "./graph-ts-types/index.js";
19
- import * as Shim from "./graph-ts.ts";
20
-
21
- // @ts-expect-error graph-ts' BigInt extends Uint8Array and is constructed
22
- // from a byte length; this one is constructed from the value it holds. No
23
- // mapping calls `new BigInt(...)` — they go through `BigInt.from*` — and
24
- // every other member matches. The five below are the same difference,
25
- // reached through a BigInt in their own signatures.
26
- const _BigInt: typeof GraphTs.BigInt = Shim.BigInt;
27
-
28
- // @ts-expect-error constructor takes a BigInt (see above).
29
- const _BigDecimal: typeof GraphTs.BigDecimal = Shim.BigDecimal;
30
-
31
- const _Bytes: typeof GraphTs.Bytes = Shim.Bytes;
32
-
33
- const _ByteArray: typeof GraphTs.ByteArray = Shim.ByteArray;
34
-
35
- const _Address: typeof GraphTs.Address = Shim.Address;
36
-
37
- const _TypedMap: typeof GraphTs.TypedMap = Shim.TypedMap;
38
-
39
- const _TypedMapEntry: typeof GraphTs.TypedMapEntry = Shim.TypedMapEntry;
40
-
41
- const _Entity: typeof GraphTs.Entity = Shim.Entity;
42
-
43
- const _Value: typeof GraphTs.Value = Shim.Value;
44
-
45
- const _ValueKind: typeof GraphTs.ValueKind = Shim.ValueKind;
46
-
47
- const _store: typeof GraphTs.store = Shim.store;
48
-
49
- // @ts-expect-error `ethereum.Value` carries a BigInt (see above).
50
- const _ethereum: typeof GraphTs.ethereum = Shim.ethereum;
51
-
52
- const _dataSource: typeof GraphTs.dataSource = Shim.dataSource;
53
-
54
- const _DataSourceTemplate: typeof GraphTs.DataSourceTemplate = Shim.DataSourceTemplate;
55
-
56
- const _DataSourceContext: typeof GraphTs.DataSourceContext = Shim.DataSourceContext;
57
-
58
- // @ts-expect-error `log` formats a BigInt (see above).
59
- const _log: typeof GraphTs.log = Shim.log;
60
-
61
- const _crypto: typeof GraphTs.crypto = Shim.crypto;
62
-
63
- const _EthereumUtils: typeof GraphTs.EthereumUtils = Shim.EthereumUtils;
64
-
65
- // @ts-expect-error `json.toBigInt` returns a BigInt (see above).
66
- const _json: typeof GraphTs.json = Shim.json;
67
-
68
- // @ts-expect-error `JSONValue.toBigInt` returns a BigInt (see above).
69
- const _JSONValue: typeof GraphTs.JSONValue = Shim.JSONValue;
70
-
71
- const _JSONValueKind: typeof GraphTs.JSONValueKind = Shim.JSONValueKind;
72
-
73
- const _ipfs: typeof GraphTs.ipfs = Shim.ipfs;
74
-
75
- const _ens: typeof GraphTs.ens = Shim.ens;
76
-
77
- export type {};
78
- void [
79
- _BigInt,
80
- _BigDecimal,
81
- _Bytes,
82
- _ByteArray,
83
- _Address,
84
- _TypedMap,
85
- _TypedMapEntry,
86
- _Entity,
87
- _Value,
88
- _ValueKind,
89
- _store,
90
- _ethereum,
91
- _dataSource,
92
- _DataSourceTemplate,
93
- _DataSourceContext,
94
- _log,
95
- _crypto,
96
- _EthereumUtils,
97
- _json,
98
- _JSONValue,
99
- _JSONValueKind,
100
- _ipfs,
101
- _ens,
102
- ];
@@ -1,133 +0,0 @@
1
- /**
2
- * AssemblyScript divides two integers as integers. JavaScript doesn't.
3
- *
4
- * `let dayID = timestamp / 86400` is an i32 division in a mapping and a float
5
- * division here, and the difference travels: Uniswap multiplies the bucket back
6
- * out and writes it to an `Int` column, where `1588712972.0000002` is a type
7
- * error rather than a wrong number — but an entity id built the same way would
8
- * simply be wrong. Nothing in the shim can intercept `/` between two plain
9
- * numbers, so the operator is rewritten as the mapping is loaded.
10
- *
11
- * The rewrite is textual, driven by the TypeScript parser every subgraph project
12
- * already has: each `a / b` becomes `__envio_idiv(a, b)`, which truncates only
13
- * when both operands really are integers and otherwise divides as before — an
14
- * `f64` in AssemblyScript divides as a float too.
15
- */
16
-
17
- import { createRequire } from "node:module";
18
- import path from "node:path";
19
-
20
- type Edit = { at: number; length: number; text: string };
21
-
22
- let typescript: any | null | undefined;
23
-
24
- /**
25
- * The project's own TypeScript, which `graph-cli` puts there. Resolving from the
26
- * project rather than from here keeps the shim free of a parser dependency.
27
- */
28
- export function loadTypeScript(root: string): any | null {
29
- if (typescript !== undefined) return typescript;
30
- try {
31
- const require = createRequire(path.join(path.resolve(root), "package.json"));
32
- typescript = require("typescript");
33
- } catch {
34
- typescript = null;
35
- }
36
- return typescript;
37
- }
38
-
39
- export function integerDivision(a: unknown, b: unknown): unknown {
40
- const left = typeof a === "object" && a !== null ? (a as any).valueOf() : a;
41
- const right = typeof b === "object" && b !== null ? (b as any).valueOf() : b;
42
-
43
- const integral = (v: unknown) => typeof v === "bigint" || (typeof v === "number" && Number.isInteger(v));
44
- if (integral(left) && integral(right)) {
45
- // Only i64 / i64 stays 64-bit; anything narrower is an i32 in AssemblyScript
46
- // and must come back as a number, or the bigint spreads through every
47
- // arithmetic that follows.
48
- if (typeof left === "bigint" && typeof right === "bigint") {
49
- return left / right;
50
- }
51
- return Math.trunc(Number(left) / Number(right));
52
- }
53
- return (left as number) / (right as number);
54
- }
55
-
56
- export const DIVIDE_HELPER = "__envio_idiv";
57
- export const RETAG_HELPER = "__envio_retag";
58
-
59
- /** `changetype<Foo>(x)` → `__envio_retag(Foo, x)` when Foo is a class name. */
60
- export function rewriteChangetype(source: string): string {
61
- if (!source.includes("changetype<")) return source;
62
- return source.replace(
63
- /changetype\s*<\s*([A-Za-z_][A-Za-z0-9_]*)\s*>\s*\(/g,
64
- `${RETAG_HELPER}($1, `,
65
- );
66
- }
67
-
68
- /** Returns the source unchanged when it divides nothing, or can't be parsed. */
69
- export function rewriteDivision(source: string): string {
70
- if (!source.includes("/")) return source;
71
- const ts = typescript;
72
- if (!ts) return source;
73
-
74
- let file: any;
75
- try {
76
- file = ts.createSourceFile("mapping.ts", source, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
77
- } catch {
78
- return source;
79
- }
80
-
81
- const isSimpleTarget = (node: any): boolean =>
82
- node.kind === ts.SyntaxKind.Identifier ||
83
- (node.kind === ts.SyntaxKind.PropertyAccessExpression && isSimpleTarget(node.expression)) ||
84
- node.kind === ts.SyntaxKind.ThisKeyword;
85
-
86
- const edits: Edit[] = [];
87
- const visit = (node: any) => {
88
- if (
89
- node.kind === ts.SyntaxKind.BinaryExpression &&
90
- node.operatorToken?.kind === ts.SyntaxKind.SlashToken
91
- ) {
92
- const left = ts.skipTrivia(source, node.left.pos);
93
- const operator = ts.skipTrivia(source, node.operatorToken.pos);
94
- edits.push({ at: left, length: 0, text: `${DIVIDE_HELPER}(` });
95
- edits.push({ at: operator, length: node.operatorToken.end - operator, text: "," });
96
- edits.push({ at: node.right.end, length: 0, text: ")" });
97
- }
98
- // `x /= y` divides too. Rewriting it needs the target twice, so it is only
99
- // safe when reading the target has no effect of its own — an identifier or
100
- // a dotted path. `a[i++] /= y` is left alone rather than evaluated twice.
101
- if (
102
- node.kind === ts.SyntaxKind.BinaryExpression &&
103
- node.operatorToken?.kind === ts.SyntaxKind.SlashEqualsToken &&
104
- isSimpleTarget(node.left)
105
- ) {
106
- const left = ts.skipTrivia(source, node.left.pos);
107
- const target = source.slice(left, node.left.end);
108
- const operator = ts.skipTrivia(source, node.operatorToken.pos);
109
- edits.push({
110
- at: operator,
111
- length: node.operatorToken.end - operator,
112
- text: `= ${DIVIDE_HELPER}(${target},`,
113
- });
114
- edits.push({ at: node.right.end, length: 0, text: ")" });
115
- }
116
- ts.forEachChild(node, visit);
117
- };
118
- try {
119
- ts.forEachChild(file, visit);
120
- } catch {
121
- return source;
122
- }
123
- if (edits.length === 0) return source;
124
-
125
- // Applied back to front so an outer division's insertion points stay valid
126
- // while an inner one is rewritten.
127
- edits.sort((a, b) => b.at - a.at || b.length - a.length);
128
- let out = source;
129
- for (const edit of edits) {
130
- out = out.slice(0, edit.at) + edit.text + out.slice(edit.at + edit.length);
131
- }
132
- return out;
133
- }