@ixo/oracle-runtime-workers 0.29.1 → 0.31.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 (74) hide show
  1. package/package.json +7 -5
  2. package/src/attachments/channel-links.ts +135 -0
  3. package/src/attachments/channel.ts +203 -0
  4. package/src/attachments/download.ts +23 -6
  5. package/src/attachments/index.ts +23 -0
  6. package/src/attachments/pipeline.ts +120 -42
  7. package/src/attachments/retention.ts +11 -3
  8. package/src/attachments/sanitize.ts +20 -8
  9. package/src/attachments/types.ts +7 -0
  10. package/src/attachments/view.ts +24 -7
  11. package/src/channels/contract.ts +69 -0
  12. package/src/channels/turns.ts +23 -1
  13. package/src/core/main-agent-types.ts +2 -0
  14. package/src/core/runtime-context.ts +3 -0
  15. package/src/delivery/plan.ts +3 -1
  16. package/src/do/contracts.ts +3 -0
  17. package/src/do/turn-attachments.ts +103 -0
  18. package/src/do/user-oracle-do.ts +142 -42
  19. package/src/hub-actions/client.ts +87 -7
  20. package/src/hub-actions/hub-text.ts +82 -0
  21. package/src/hub-actions/request.ts +7 -4
  22. package/src/hub-actions/store.ts +5 -1
  23. package/src/hub-actions/transfer.ts +23 -6
  24. package/src/index.ts +1 -0
  25. package/src/plugin-api/index.ts +1 -0
  26. package/src/plugin-api/types.ts +43 -2
  27. package/src/plugins/attachments/view-attachment-tool.ts +7 -3
  28. package/src/plugins/hub-actions/hub-actions.plugin.ts +96 -17
  29. package/src/plugins/ixo-chain/balances.ts +141 -0
  30. package/src/plugins/ixo-chain/blocksync.ts +17 -8
  31. package/src/plugins/ixo-chain/denoms.ts +84 -36
  32. package/src/plugins/ixo-chain/http.ts +315 -70
  33. package/src/plugins/ixo-chain/ixo-chain.plugin.ts +182 -301
  34. package/src/plugins/ixo-chain/kinds/chain/account.ts +516 -0
  35. package/src/plugins/ixo-chain/kinds/chain/claims.ts +287 -0
  36. package/src/plugins/ixo-chain/kinds/chain/economics.ts +237 -0
  37. package/src/plugins/ixo-chain/kinds/chain/governance.ts +455 -0
  38. package/src/plugins/ixo-chain/kinds/chain/grants.ts +682 -0
  39. package/src/plugins/ixo-chain/kinds/chain/index.ts +23 -0
  40. package/src/plugins/ixo-chain/kinds/chain/liquid-staking.ts +279 -0
  41. package/src/plugins/ixo-chain/kinds/chain/shared.ts +286 -0
  42. package/src/plugins/ixo-chain/kinds/chain/staking.ts +220 -0
  43. package/src/plugins/ixo-chain/kinds/coins.ts +112 -0
  44. package/src/plugins/ixo-chain/kinds/core.ts +246 -0
  45. package/src/plugins/ixo-chain/kinds/indexed/agent-deposits.ts +124 -0
  46. package/src/plugins/ixo-chain/kinds/indexed/dao-proposals.ts +379 -0
  47. package/src/plugins/ixo-chain/kinds/indexed/daos.ts +319 -0
  48. package/src/plugins/ixo-chain/kinds/indexed/disputes.ts +159 -0
  49. package/src/plugins/ixo-chain/kinds/indexed/evaluations.ts +172 -0
  50. package/src/plugins/ixo-chain/kinds/indexed/index.ts +21 -0
  51. package/src/plugins/ixo-chain/kinds/indexed/liquid-stake-history.ts +160 -0
  52. package/src/plugins/ixo-chain/kinds/indexed/member-budgets.ts +175 -0
  53. package/src/plugins/ixo-chain/kinds/indexed/shared.ts +211 -0
  54. package/src/plugins/ixo-chain/kinds/indexed/token-history.ts +205 -0
  55. package/src/plugins/ixo-chain/kinds/registry.ts +43 -0
  56. package/src/plugins/ixo-chain/kinds/types.ts +156 -0
  57. package/src/plugins/ixo-chain/lookup.ts +470 -0
  58. package/src/plugins/ixo-chain/overview.ts +1060 -0
  59. package/src/plugins/ixo-chain/queries.ts +170 -138
  60. package/src/plugins/ixo-chain/rest.ts +135 -54
  61. package/src/plugins/ixo-chain/summaries.ts +3 -2
  62. package/src/plugins/yoma/catalog.ts +780 -0
  63. package/src/plugins/yoma/clef.ts +98 -0
  64. package/src/plugins/yoma/drafts.ts +339 -0
  65. package/src/plugins/yoma/engine.ts +926 -0
  66. package/src/plugins/yoma/index.ts +13 -0
  67. package/src/plugins/yoma/judge.ts +358 -0
  68. package/src/plugins/yoma/permission.ts +62 -0
  69. package/src/plugins/yoma/reads.ts +195 -0
  70. package/src/plugins/yoma/submission.ts +534 -0
  71. package/src/plugins/yoma/text.ts +16 -0
  72. package/src/plugins/yoma/work.ts +190 -0
  73. package/src/plugins/yoma/yoma.plugin.ts +2162 -0
  74. package/src/sqlite/serialization.ts +2 -0
@@ -2,11 +2,12 @@
2
2
  * Chain REST (the node's LCD gateway) reads: one GET, bounded and parsed,
3
3
  * under the shared retry policy.
4
4
  */
5
- import type { z } from 'zod';
5
+ import { z } from 'zod';
6
6
  import {
7
7
  exchange,
8
8
  unavailable,
9
9
  withRetry,
10
+ type Attempt,
10
11
  type ChainCall,
11
12
  type Outcome,
12
13
  } from './http';
@@ -15,65 +16,145 @@ export const REST_MAX_BYTES = 512 * 1024;
15
16
  /** Per-attempt wall bound for one REST read. */
16
17
  export const REST_ATTEMPT_TIMEOUT_MS = 8_000;
17
18
 
19
+ /** How one REST read is bounded, and which answers mean "none". */
20
+ export interface RestOptions {
21
+ /** Names the read in log lines (never the path: it carries an address). */
22
+ label: string;
23
+ /** The log line's tag (`[ixo-chain]` by default). */
24
+ tag?: string;
25
+ timeoutMs?: number;
26
+ maxBytes?: number;
27
+ /** A 404 is a quiet `not_found` failure instead of `unavailable`. */
28
+ notFoundOn404?: boolean;
29
+ /**
30
+ * HTTP statuses that mean "there is none" for this resource (e.g.
31
+ * `[400, 404]` for a vote never cast): such an answer is `Absent`, an
32
+ * `ok` result, neither retried nor logged. Meant for 4xx statuses; it
33
+ * takes precedence over `notFoundOn404`. A 400 counts only when its body
34
+ * is a gRPC-gateway error with code 3 (InvalidArgument) or 5 (NotFound);
35
+ * any other 400 is a failure.
36
+ */
37
+ noneOn?: readonly number[];
38
+ }
39
+
40
+ /** gRPC InvalidArgument and NotFound: the codes of a gateway 400 that says "there is none". */
41
+ const NONE_GRPC_CODES: readonly number[] = [3, 5];
42
+ const grpcError = z.object({ code: z.number() });
43
+
44
+ /** Whether `answer` is one of the `noneOn` statuses, a 400 only with a "none" gRPC code. */
45
+ function isNone(
46
+ answer: Extract<Attempt<string>, { ok: false }>,
47
+ noneOn: readonly number[] | undefined,
48
+ ): boolean {
49
+ if (answer.status === undefined || !noneOn?.includes(answer.status))
50
+ return false;
51
+ if (answer.status !== 400) return true;
52
+ if (answer.errorBody === undefined) return false;
53
+ let body: unknown;
54
+ try {
55
+ body = JSON.parse(answer.errorBody);
56
+ } catch {
57
+ return false;
58
+ }
59
+ const parsed = grpcError.safeParse(body);
60
+ return parsed.success && NONE_GRPC_CODES.includes(parsed.data.code);
61
+ }
62
+
63
+ /** The answer of a read whose upstream answered one of its `noneOn` statuses. */
64
+ export type Absent = { ok: true; absent: true };
65
+
66
+ /** What one attempt found: the parsed body, or a "none" status. */
67
+ type Found<T> = { absent: false; value: T } | { absent: true };
68
+
18
69
  /**
19
- * `GET ${restUrl}${path}` parsed by `schema`. `label` names the read in the
20
- * log line (never the path: it carries an address). A body that is not
21
- * JSON or not the expected shape is `unavailable` and is not retried. A 404
22
- * is `unavailable` unless `notFoundOn404` (a resource that may be absent).
70
+ * `GET ${restUrl}${path}` parsed by `schema`. A body that is not JSON or
71
+ * not the expected shape is `unavailable` and is not retried. A 404 is
72
+ * `unavailable` unless `notFoundOn404` (a resource that may be absent); a
73
+ * status in `noneOn` is {@link Absent} (a 400 only with a gRPC code 3 or
74
+ * 5, read from at most 8 KiB of its body).
23
75
  */
24
76
  export function restJson<S extends z.ZodType>(
25
77
  call: ChainCall,
26
78
  path: string,
27
79
  schema: S,
28
- opts: {
29
- label: string;
30
- timeoutMs?: number;
31
- maxBytes?: number;
32
- notFoundOn404?: boolean;
33
- },
34
- ): Promise<Outcome<z.infer<S>>> {
80
+ opts: RestOptions & { noneOn?: undefined },
81
+ ): Promise<Outcome<z.infer<S>>>;
82
+ export function restJson<S extends z.ZodType>(
83
+ call: ChainCall,
84
+ path: string,
85
+ schema: S,
86
+ opts: RestOptions & { noneOn: readonly number[] },
87
+ ): Promise<Outcome<z.infer<S>> | Absent>;
88
+ export function restJson<S extends z.ZodType>(
89
+ call: ChainCall,
90
+ path: string,
91
+ schema: S,
92
+ opts: RestOptions,
93
+ ): Promise<Outcome<z.infer<S>> | Absent>;
94
+ export async function restJson<S extends z.ZodType>(
95
+ call: ChainCall,
96
+ path: string,
97
+ schema: S,
98
+ opts: RestOptions,
99
+ ): Promise<Outcome<z.infer<S>> | Absent> {
35
100
  const base = call.restUrl.replace(/\/+$/, '');
36
101
  const url = `${base}${path}`;
37
- return withRetry(call, { label: opts.label, upstream: base }, async () => {
38
- const answer = await exchange(
39
- call,
40
- 'chain',
41
- url,
42
- { method: 'GET', headers: { accept: 'application/json' } },
43
- {
44
- timeoutMs: opts.timeoutMs ?? REST_ATTEMPT_TIMEOUT_MS,
45
- maxBytes: opts.maxBytes ?? REST_MAX_BYTES,
46
- notFoundOn404: opts.notFoundOn404 ?? false,
47
- },
48
- );
49
- if (!answer.ok) return answer;
50
- let body: unknown;
51
- try {
52
- body = JSON.parse(answer.value);
53
- } catch {
54
- return {
55
- ok: false,
56
- failure: unavailable(
57
- 'chain',
58
- 'it answered something that is not JSON',
59
- false,
60
- ),
61
- retry: false,
62
- note: 'malformed JSON',
63
- };
64
- }
65
- const parsed = schema.safeParse(body);
66
- if (!parsed.success)
67
- return {
68
- ok: false,
69
- failure: unavailable(
70
- 'chain',
71
- 'it answered in an unexpected shape',
72
- false,
73
- ),
74
- retry: false,
75
- note: 'unexpected shape',
76
- };
77
- return { ok: true, value: parsed.data };
78
- });
102
+ const read = await withRetry<Found<z.infer<S>>>(
103
+ call,
104
+ {
105
+ label: opts.label,
106
+ upstream: base,
107
+ ...(opts.tag ? { tag: opts.tag } : {}),
108
+ },
109
+ async () => {
110
+ const answer = await exchange(
111
+ call,
112
+ 'chain',
113
+ url,
114
+ { method: 'GET', headers: { accept: 'application/json' } },
115
+ {
116
+ timeoutMs: opts.timeoutMs ?? REST_ATTEMPT_TIMEOUT_MS,
117
+ maxBytes: opts.maxBytes ?? REST_MAX_BYTES,
118
+ notFoundOn404: opts.notFoundOn404 ?? false,
119
+ ...(opts.noneOn?.includes(400) ? { errorBodyOn: [400] } : {}),
120
+ },
121
+ );
122
+ if (!answer.ok)
123
+ return isNone(answer, opts.noneOn)
124
+ ? { ok: true, value: { absent: true } }
125
+ : answer;
126
+ let body: unknown;
127
+ try {
128
+ body = JSON.parse(answer.value);
129
+ } catch {
130
+ return {
131
+ ok: false,
132
+ failure: unavailable(
133
+ 'chain',
134
+ 'it answered something that is not JSON',
135
+ false,
136
+ ),
137
+ retry: false,
138
+ note: 'malformed JSON',
139
+ };
140
+ }
141
+ const parsed = schema.safeParse(body);
142
+ if (!parsed.success)
143
+ return {
144
+ ok: false,
145
+ failure: unavailable(
146
+ 'chain',
147
+ 'it answered in an unexpected shape',
148
+ false,
149
+ ),
150
+ retry: false,
151
+ note: 'unexpected shape',
152
+ };
153
+ return { ok: true, value: { absent: false, value: parsed.data } };
154
+ },
155
+ );
156
+ if (!read.ok) return read;
157
+ return read.value.absent
158
+ ? { ok: true, absent: true }
159
+ : { ok: true, value: read.value.value };
79
160
  }
@@ -60,7 +60,8 @@ export const ENTITY_STATUS_NOTE =
60
60
  /** `entities` reads `ID_DESC`, and an entity id is a hash: the order says nothing about age. */
61
61
  export const ENTITIES_ORDER = 'id (text)';
62
62
 
63
- function clip(text: string, max: number): string {
63
+ /** `text` cut to `max` characters, the last one an ellipsis when cut. */
64
+ export function clip(text: string, max: number): string {
64
65
  return text.length > max ? `${text.slice(0, max - 1)}…` : text;
65
66
  }
66
67
 
@@ -69,7 +70,7 @@ export function isRecord(value: unknown): value is Record<string, unknown> {
69
70
  }
70
71
 
71
72
  /** A non-empty string field, clipped; undefined otherwise. */
72
- function text(value: unknown, max = TEXT_CHARS): string | undefined {
73
+ export function text(value: unknown, max = TEXT_CHARS): string | undefined {
73
74
  return typeof value === 'string' && value ? clip(value, max) : undefined;
74
75
  }
75
76