@elaraai/e3-api-server 1.0.84 → 1.0.86

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 (153) hide show
  1. package/README.md +88 -11
  2. package/dist/src/errors.d.ts +15 -0
  3. package/dist/src/errors.d.ts.map +1 -1
  4. package/dist/src/errors.js +22 -1
  5. package/dist/src/errors.js.map +1 -1
  6. package/dist/src/handlers/dataflow.d.ts +89 -18
  7. package/dist/src/handlers/dataflow.d.ts.map +1 -1
  8. package/dist/src/handlers/dataflow.js +198 -123
  9. package/dist/src/handlers/dataflow.js.map +1 -1
  10. package/dist/src/handlers/dataflow.spec.js +257 -7
  11. package/dist/src/handlers/dataflow.spec.js.map +1 -1
  12. package/dist/src/handlers/datasets.d.ts +56 -15
  13. package/dist/src/handlers/datasets.d.ts.map +1 -1
  14. package/dist/src/handlers/datasets.js +200 -122
  15. package/dist/src/handlers/datasets.js.map +1 -1
  16. package/dist/src/handlers/datasets.spec.js +92 -2
  17. package/dist/src/handlers/datasets.spec.js.map +1 -1
  18. package/dist/src/handlers/functions.d.ts +11 -2
  19. package/dist/src/handlers/functions.d.ts.map +1 -1
  20. package/dist/src/handlers/functions.js +50 -20
  21. package/dist/src/handlers/functions.js.map +1 -1
  22. package/dist/src/handlers/functions.spec.js +12 -1
  23. package/dist/src/handlers/functions.spec.js.map +1 -1
  24. package/dist/src/handlers/index.d.ts +2 -2
  25. package/dist/src/handlers/index.d.ts.map +1 -1
  26. package/dist/src/handlers/index.js +2 -2
  27. package/dist/src/handlers/index.js.map +1 -1
  28. package/dist/src/handlers/packages.d.ts +1 -1
  29. package/dist/src/handlers/packages.d.ts.map +1 -1
  30. package/dist/src/handlers/packages.js +1 -1
  31. package/dist/src/handlers/packages.js.map +1 -1
  32. package/dist/src/handlers/records.d.ts +27 -7
  33. package/dist/src/handlers/records.d.ts.map +1 -1
  34. package/dist/src/handlers/records.js +36 -16
  35. package/dist/src/handlers/records.js.map +1 -1
  36. package/dist/src/handlers/records.spec.js +160 -26
  37. package/dist/src/handlers/records.spec.js.map +1 -1
  38. package/dist/src/handlers/repository.d.ts +9 -1
  39. package/dist/src/handlers/repository.d.ts.map +1 -1
  40. package/dist/src/handlers/repository.js +14 -5
  41. package/dist/src/handlers/repository.js.map +1 -1
  42. package/dist/src/handlers/tasks.d.ts +18 -6
  43. package/dist/src/handlers/tasks.d.ts.map +1 -1
  44. package/dist/src/handlers/tasks.js +31 -22
  45. package/dist/src/handlers/tasks.js.map +1 -1
  46. package/dist/src/handlers/tasks.spec.js +61 -12
  47. package/dist/src/handlers/tasks.spec.js.map +1 -1
  48. package/dist/src/handlers/workspaces.d.ts +11 -6
  49. package/dist/src/handlers/workspaces.d.ts.map +1 -1
  50. package/dist/src/handlers/workspaces.js +14 -8
  51. package/dist/src/handlers/workspaces.js.map +1 -1
  52. package/dist/src/handlers/workspaces.spec.js +24 -8
  53. package/dist/src/handlers/workspaces.spec.js.map +1 -1
  54. package/dist/src/identity.d.ts +22 -0
  55. package/dist/src/identity.d.ts.map +1 -0
  56. package/dist/src/identity.js +6 -0
  57. package/dist/src/identity.js.map +1 -0
  58. package/dist/src/index.d.ts +1 -0
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/index.js +3 -0
  61. package/dist/src/index.js.map +1 -1
  62. package/dist/src/local-dataflow.d.ts +14 -1
  63. package/dist/src/local-dataflow.d.ts.map +1 -1
  64. package/dist/src/local-dataflow.js +12 -7
  65. package/dist/src/local-dataflow.js.map +1 -1
  66. package/dist/src/middleware/auth.d.ts +1 -11
  67. package/dist/src/middleware/auth.d.ts.map +1 -1
  68. package/dist/src/middleware/auth.js.map +1 -1
  69. package/dist/src/middleware/repository.d.ts +1 -1
  70. package/dist/src/middleware/repository.d.ts.map +1 -1
  71. package/dist/src/middleware/repository.js +4 -10
  72. package/dist/src/middleware/repository.js.map +1 -1
  73. package/dist/src/middleware/repository.spec.js +7 -6
  74. package/dist/src/middleware/repository.spec.js.map +1 -1
  75. package/dist/src/portable.d.ts +47 -0
  76. package/dist/src/portable.d.ts.map +1 -0
  77. package/dist/src/portable.js +79 -0
  78. package/dist/src/portable.js.map +1 -0
  79. package/dist/src/portable.spec.d.ts +6 -0
  80. package/dist/src/portable.spec.d.ts.map +1 -0
  81. package/dist/src/portable.spec.js +325 -0
  82. package/dist/src/portable.spec.js.map +1 -0
  83. package/dist/src/routes/dataset-path.d.ts +28 -0
  84. package/dist/src/routes/dataset-path.d.ts.map +1 -0
  85. package/dist/src/routes/dataset-path.js +52 -0
  86. package/dist/src/routes/dataset-path.js.map +1 -0
  87. package/dist/src/routes/datasets.d.ts +7 -1
  88. package/dist/src/routes/datasets.d.ts.map +1 -1
  89. package/dist/src/routes/datasets.js +5 -11
  90. package/dist/src/routes/datasets.js.map +1 -1
  91. package/dist/src/routes/executions.d.ts +4 -2
  92. package/dist/src/routes/executions.d.ts.map +1 -1
  93. package/dist/src/routes/executions.js +31 -10
  94. package/dist/src/routes/executions.js.map +1 -1
  95. package/dist/src/routes/functions.d.ts +16 -8
  96. package/dist/src/routes/functions.d.ts.map +1 -1
  97. package/dist/src/routes/functions.js +21 -11
  98. package/dist/src/routes/functions.js.map +1 -1
  99. package/dist/src/routes/index.d.ts +4 -3
  100. package/dist/src/routes/index.d.ts.map +1 -1
  101. package/dist/src/routes/index.js +1 -0
  102. package/dist/src/routes/index.js.map +1 -1
  103. package/dist/src/routes/objects.d.ts +4 -2
  104. package/dist/src/routes/objects.d.ts.map +1 -1
  105. package/dist/src/routes/objects.js +5 -8
  106. package/dist/src/routes/objects.js.map +1 -1
  107. package/dist/src/routes/package-transfer.d.ts +1 -1
  108. package/dist/src/routes/package-transfer.d.ts.map +1 -1
  109. package/dist/src/routes/package-transfer.js +19 -22
  110. package/dist/src/routes/package-transfer.js.map +1 -1
  111. package/dist/src/routes/packages.d.ts +1 -1
  112. package/dist/src/routes/packages.d.ts.map +1 -1
  113. package/dist/src/routes/query.d.ts +53 -0
  114. package/dist/src/routes/query.d.ts.map +1 -0
  115. package/dist/src/routes/query.js +86 -0
  116. package/dist/src/routes/query.js.map +1 -0
  117. package/dist/src/routes/query.spec.d.ts +6 -0
  118. package/dist/src/routes/query.spec.d.ts.map +1 -0
  119. package/dist/src/routes/query.spec.js +49 -0
  120. package/dist/src/routes/query.spec.js.map +1 -0
  121. package/dist/src/routes/records.d.ts +32 -3
  122. package/dist/src/routes/records.d.ts.map +1 -1
  123. package/dist/src/routes/records.js +34 -7
  124. package/dist/src/routes/records.js.map +1 -1
  125. package/dist/src/routes/repositories.d.ts +1 -1
  126. package/dist/src/routes/repositories.d.ts.map +1 -1
  127. package/dist/src/routes/repository.d.ts +6 -1
  128. package/dist/src/routes/repository.d.ts.map +1 -1
  129. package/dist/src/routes/repository.js +5 -0
  130. package/dist/src/routes/repository.js.map +1 -1
  131. package/dist/src/routes/repository.spec.d.ts +6 -0
  132. package/dist/src/routes/repository.spec.d.ts.map +1 -0
  133. package/dist/src/routes/repository.spec.js +84 -0
  134. package/dist/src/routes/repository.spec.js.map +1 -0
  135. package/dist/src/routes/tasks.d.ts +1 -1
  136. package/dist/src/routes/tasks.d.ts.map +1 -1
  137. package/dist/src/routes/tasks.js +16 -4
  138. package/dist/src/routes/tasks.js.map +1 -1
  139. package/dist/src/routes/transfer.d.ts +1 -1
  140. package/dist/src/routes/transfer.d.ts.map +1 -1
  141. package/dist/src/routes/transfer.js +7 -27
  142. package/dist/src/routes/transfer.js.map +1 -1
  143. package/dist/src/routes/workspaces.d.ts +1 -1
  144. package/dist/src/routes/workspaces.d.ts.map +1 -1
  145. package/dist/src/routes/workspaces.js +34 -22
  146. package/dist/src/routes/workspaces.js.map +1 -1
  147. package/dist/src/server.js +1 -1
  148. package/dist/src/server.js.map +1 -1
  149. package/dist/src/types.d.ts +63 -5
  150. package/dist/src/types.d.ts.map +1 -1
  151. package/dist/src/types.js +7 -5
  152. package/dist/src/types.js.map +1 -1
  153. package/package.json +8 -4
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ import { parseKeypath } from '@elaraai/e3-types';
6
+ /** A whole number as a query spells it: decimal digits, nothing else. */
7
+ const DIGITS = /^\d+$/;
8
+ /** What a parameter taking each least must be, as the dataset routes word it. */
9
+ const TAKES = {
10
+ 0: 'a non-negative integer',
11
+ 1: 'a positive integer',
12
+ };
13
+ /**
14
+ * The `400 bad_request` that refuses a malformed query, as the dataset routes
15
+ * answer one: JSON naming what was wrong, which the client reads as an
16
+ * `ApiError` of that type.
17
+ *
18
+ * @param message - What was wrong, naming the parameter and what it was given
19
+ * @returns The response
20
+ */
21
+ export function badQuery(message) {
22
+ return new Response(JSON.stringify({ error: { type: 'bad_request', message } }), {
23
+ status: 400,
24
+ headers: { 'Content-Type': 'application/json' },
25
+ });
26
+ }
27
+ /**
28
+ * Reads whole-number query parameters, each at or above the least it takes.
29
+ *
30
+ * @remarks
31
+ * A parameter absent, or given empty, is left out. One given that is not a
32
+ * whole number at or above its least — `abc`, `-1`, `1.5`, `NaN` — or that is
33
+ * past the integers a number holds exactly, is refused before any store is
34
+ * asked: `400 bad_request`, naming it and what it was given.
35
+ *
36
+ * @param c - The request's context
37
+ * @param least - Each parameter by name, and the least value it takes
38
+ * @returns The parameters given, by name; or the 400 that refuses one
39
+ */
40
+ export function wholeQuery(c, least) {
41
+ const read = {};
42
+ for (const name of Object.keys(least)) {
43
+ const given = c.req.query(name);
44
+ if (given === undefined || given === '')
45
+ continue;
46
+ const value = DIGITS.test(given) ? Number(given) : NaN;
47
+ if (!(value >= least[name])) {
48
+ return badQuery(`${name} must be ${TAKES[least[name]]}, got ${JSON.stringify(given)}`);
49
+ }
50
+ if (!Number.isSafeInteger(value)) {
51
+ return badQuery(`${name} must be at most ${Number.MAX_SAFE_INTEGER}, got ${JSON.stringify(given)}`);
52
+ }
53
+ read[name] = value;
54
+ }
55
+ return read;
56
+ }
57
+ /**
58
+ * Reads the dataset paths a query names: each `path` parameter one keypath, as
59
+ * a status names a dataset (`.inputs.x`, `` .inputs.`my field` ``).
60
+ *
61
+ * @remarks
62
+ * Absent, or given only empty, there are none. One that is not a keypath —
63
+ * `inputs.x`, `.inputs.` — is refused before any store is asked:
64
+ * `400 bad_request`, naming it. Whether a path names anything is the route's
65
+ * to judge.
66
+ *
67
+ * @param c - The request's context
68
+ * @returns The paths given, or `undefined` when none is; or the 400 that
69
+ * refuses one
70
+ */
71
+ export function pathsQuery(c) {
72
+ const given = (c.req.queries('path') ?? []).filter((path) => path !== '');
73
+ if (given.length === 0)
74
+ return undefined;
75
+ const paths = [];
76
+ for (const path of given) {
77
+ try {
78
+ paths.push(parseKeypath(path));
79
+ }
80
+ catch (err) {
81
+ return badQuery(`path must be a dataset's path, as a status names it (.inputs.x), got ${JSON.stringify(path)}: ${err instanceof Error ? err.message : String(err)}`);
82
+ }
83
+ }
84
+ return paths;
85
+ }
86
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/routes/query.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAE,YAAY,EAAiB,MAAM,mBAAmB,CAAC;AAEhE,yEAAyE;AACzE,MAAM,MAAM,GAAG,OAAO,CAAC;AAOvB,iFAAiF;AACjF,MAAM,KAAK,GAA+B;IACxC,CAAC,EAAE,wBAAwB;IAC3B,CAAC,EAAE,oBAAoB;CACxB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE;QAC/E,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CAAmB,CAAU,EAAE,KAAsC;IAC7F,MAAM,IAAI,GAA+B,EAAE,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAQ,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;YAAE,SAAS;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACvD,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC,GAAG,IAAI,YAAY,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,QAAQ,CAAC,GAAG,IAAI,oBAAoB,MAAM,CAAC,gBAAgB,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtG,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,UAAU,CAAC,CAAU;IACnC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC,wEAAwE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvK,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=query.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.spec.d.ts","sourceRoot":"","sources":["../../../src/routes/query.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ /**
6
+ * What a route reads from a query, as a host's own route reads it through the
7
+ * helpers both entries export: a window's offset and limit, refused as the
8
+ * dataset routes refuse them.
9
+ */
10
+ import { describe, it } from 'node:test';
11
+ import assert from 'node:assert/strict';
12
+ import { Hono } from 'hono';
13
+ import * as root from '../index.js';
14
+ import * as portable from '../portable.js';
15
+ import { badQuery, pathsQuery, wholeQuery } from './query.js';
16
+ /** A host's own route over a window, reading its offset and limit as upstream's do. */
17
+ function windowRoute() {
18
+ const app = new Hono();
19
+ app.get('/window', (c) => {
20
+ const read = wholeQuery(c, { offset: 0, limit: 1 });
21
+ return read instanceof Response ? read : c.json(read);
22
+ });
23
+ return app;
24
+ }
25
+ describe('the query a route reads', () => {
26
+ it('reads a window\'s offset and limit, and refuses one that is no whole number at or above its least', async () => {
27
+ const app = windowRoute();
28
+ assert.deepEqual(await (await app.request('/window?offset=0&limit=5')).json(), { offset: 0, limit: 5 });
29
+ assert.deepEqual(await (await app.request('/window?limit=')).json(), {}, 'a parameter given empty is left out');
30
+ for (const [query, message] of [
31
+ ['limit=0', 'limit must be a positive integer, got "0"'],
32
+ ['offset=-1', 'offset must be a non-negative integer, got "-1"'],
33
+ ['offset=1.5', 'offset must be a non-negative integer, got "1.5"'],
34
+ ['limit=9007199254740993', 'limit must be at most 9007199254740991, got "9007199254740993"'],
35
+ ]) {
36
+ const refused = await app.request(`/window?${query}`);
37
+ assert.equal(refused.status, 400, query);
38
+ assert.deepEqual(await refused.json(), { error: { type: 'bad_request', message } }, query);
39
+ }
40
+ });
41
+ it('is read through the helpers both entries export', () => {
42
+ for (const entry of [root, portable]) {
43
+ assert.equal(entry.wholeQuery, wholeQuery);
44
+ assert.equal(entry.badQuery, badQuery);
45
+ assert.equal(entry.pathsQuery, pathsQuery);
46
+ }
47
+ });
48
+ });
49
+ //# sourceMappingURL=query.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.spec.js","sourceRoot":"","sources":["../../../src/routes/query.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE9D,uFAAuF;AACvF,SAAS,WAAW;IAClB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACpD,OAAO,IAAI,YAAY,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,mGAAmG,EAAE,KAAK,IAAI,EAAE;QACjH,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;QAC1B,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACxG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,qCAAqC,CAAC,CAAC;QAChH,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI;YAC7B,CAAC,SAAS,EAAE,2CAA2C,CAAC;YACxD,CAAC,WAAW,EAAE,iDAAiD,CAAC;YAChE,CAAC,YAAY,EAAE,kDAAkD,CAAC;YAClE,CAAC,wBAAwB,EAAE,gEAAgE,CAAC;SACpF,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;YACtD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACzC,MAAM,CAAC,SAAS,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -12,12 +12,41 @@
12
12
  * When auth is configured the committed `actor` is derived from the verified
13
13
  * identity (the client-supplied actor is ignored), so the audit trail cannot
14
14
  * be forged. Compaction is gated to an elevated role.
15
+ *
16
+ * A host whose requests have a time limit gives the routes its deadline, which
17
+ * a mutation and a compaction answer under, and the page a history request
18
+ * that names no limit is answered with.
15
19
  */
16
20
  import { Hono } from 'hono';
17
- import type { StorageBackend } from '@elaraai/e3-core';
18
- import type { Identity } from '../middleware/auth.js';
21
+ import type { StorageBackend } from '@elaraai/e3-core/portable';
22
+ import type { Identity } from '../identity.js';
19
23
  import type { GetRunner } from './functions.js';
20
- export declare function createWorkspaceRecordRoutes(storage: StorageBackend, getRepoPath: (repo: string) => string, getRunner: GetRunner): Hono<{
24
+ /** Options for {@link createWorkspaceRecordRoutes}: the limits a host's
25
+ * requests have, which the local server, with none, leaves unset. */
26
+ export interface RecordRoutesOptions {
27
+ /** A mutation's and a compaction's deadline, under the host's request
28
+ * timeout: each answers its typed outcome, `timed_out` or `conflict`, 2 s
29
+ * under it rather than outlasting it. Unset, neither has one but its own
30
+ * limits. */
31
+ syncDeadlineMs?: number;
32
+ /** How many commits a history request that names no `limit` is answered
33
+ * with, the newest first; the client pages on from the last one's parent.
34
+ * Unset, the whole chain. */
35
+ historyLimit?: number;
36
+ }
37
+ /**
38
+ * Record routes, mounted at `/api/repos/:repo/workspaces/:ws/records`.
39
+ *
40
+ * @param storage - Storage backend
41
+ * @param getRepoPath - A repository's identifier from its name
42
+ * @param getRunner - Each repository's task runner, which a mutation's
43
+ * program runs on
44
+ * @param options - The host's sync deadline, and its history page
45
+ * @returns The routes
46
+ * @throws {RangeError} When `syncDeadlineMs` is not a positive number of
47
+ * milliseconds, or `historyLimit` not a positive whole number of commits.
48
+ */
49
+ export declare function createWorkspaceRecordRoutes(storage: StorageBackend, getRepoPath: (repo: string) => string, getRunner: GetRunner, options?: RecordRoutesOptions): Hono<{
21
50
  Variables: {
22
51
  identity?: Identity;
23
52
  };
@@ -1 +1 @@
1
- {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/routes/records.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAYhD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,SAAS,EAAE,SAAS;eAEc;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAE;0CAwD1D"}
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/routes/records.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAahD;sEACsE;AACtE,MAAM,WAAW,mBAAmB;IAClC;;;kBAGc;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;kCAE8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,mBAAwB;eAUC;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAE;0CAyD1D"}
@@ -12,6 +12,10 @@
12
12
  * When auth is configured the committed `actor` is derived from the verified
13
13
  * identity (the client-supplied actor is ignored), so the audit trail cannot
14
14
  * be forged. Compaction is gated to an elevated role.
15
+ *
16
+ * A host whose requests have a time limit gives the routes its deadline, which
17
+ * a mutation and a compaction answer under, and the page a history request
18
+ * that names no limit is answered with.
15
19
  */
16
20
  import { Hono } from 'hono';
17
21
  import { variant } from '@elaraai/east';
@@ -19,6 +23,7 @@ import { decodeBody, sendError } from '../beast2.js';
19
23
  import { errorToVariant } from '../errors.js';
20
24
  import { MutationCallRequestType, MutationResultType, RecordSignatureType } from '../types.js';
21
25
  import { callMutationSync, getRecordHistory, describeRecord, compactRecord } from '../handlers/records.js';
26
+ import { wholeQuery } from './query.js';
22
27
  /** Roles allowed to compact a record's history (drops the prior chain). */
23
28
  const COMPACT_ROLES = ['admin', 'owner'];
24
29
  /** The audited actor + whether it is authoritative (auth-verified, so the
@@ -28,7 +33,27 @@ function resolveActor(identity) {
28
33
  return { actor: `auth:${identity.email ?? identity.sub}`, authoritative: true };
29
34
  return { actor: 'api', authoritative: false };
30
35
  }
31
- export function createWorkspaceRecordRoutes(storage, getRepoPath, getRunner) {
36
+ /**
37
+ * Record routes, mounted at `/api/repos/:repo/workspaces/:ws/records`.
38
+ *
39
+ * @param storage - Storage backend
40
+ * @param getRepoPath - A repository's identifier from its name
41
+ * @param getRunner - Each repository's task runner, which a mutation's
42
+ * program runs on
43
+ * @param options - The host's sync deadline, and its history page
44
+ * @returns The routes
45
+ * @throws {RangeError} When `syncDeadlineMs` is not a positive number of
46
+ * milliseconds, or `historyLimit` not a positive whole number of commits.
47
+ */
48
+ export function createWorkspaceRecordRoutes(storage, getRepoPath, getRunner, options = {}) {
49
+ // A limit no request could meet would refuse every one of them, or answer
50
+ // each with nothing, so a host is told when it mounts the routes.
51
+ if (options.syncDeadlineMs !== undefined && !(options.syncDeadlineMs > 0)) {
52
+ throw new RangeError(`syncDeadlineMs must be a positive number of milliseconds, got ${options.syncDeadlineMs}`);
53
+ }
54
+ if (options.historyLimit !== undefined && !(Number.isSafeInteger(options.historyLimit) && options.historyLimit >= 1)) {
55
+ throw new RangeError(`historyLimit must be a positive whole number of commits, got ${options.historyLimit}`);
56
+ }
32
57
  const app = new Hono();
33
58
  // GET /:rec — describe the record's mutations (for encoding arguments)
34
59
  app.get('/:rec', async (c) => {
@@ -43,11 +68,13 @@ export function createWorkspaceRecordRoutes(storage, getRepoPath, getRunner) {
43
68
  // GET /:rec/history?from=<hash>&limit=N — commit chain, newest first
44
69
  app.get('/:rec/history', async (c) => {
45
70
  const repoPath = getRepoPath(c.req.param('repo'));
46
- const limitRaw = c.req.query('limit');
47
- const parsed = limitRaw !== undefined ? Number(limitRaw) : NaN;
48
- const limit = Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
71
+ // A limit of 0, or a malformed one, is refused rather than read as none:
72
+ // an absent limit asks for the host's page, or the whole chain.
73
+ const window = wholeQuery(c, { limit: 1 });
74
+ if (window instanceof Response)
75
+ return window;
49
76
  const from = c.req.query('from') || undefined;
50
- return getRecordHistory(storage, repoPath, c.req.param('ws'), c.req.param('rec'), limit, from);
77
+ return getRecordHistory(storage, repoPath, c.req.param('ws'), c.req.param('rec'), window.limit ?? options.historyLimit, from);
51
78
  });
52
79
  // POST /:rec/mutations/:mut — apply a mutation synchronously (200 MutationResult)
53
80
  app.post('/:rec/mutations/:mut', async (c) => {
@@ -58,7 +85,7 @@ export function createWorkspaceRecordRoutes(storage, getRepoPath, getRunner) {
58
85
  // Idempotency key travels out-of-band as a header (not the Beast2 body), so
59
86
  // adding it changes no wire type; the request signal aborts on disconnect.
60
87
  const idempotencyKey = c.req.header('Idempotency-Key') || undefined;
61
- return await callMutationSync(storage, repoPath, getRunner(repoPath), c.req.param('ws'), c.req.param('rec'), c.req.param('mut'), req, actor, authoritative, { signal: c.req.raw.signal, idempotencyKey, verbose: c.req.query('verbose') === '1' });
88
+ return await callMutationSync(storage, repoPath, getRunner(repoPath), c.req.param('ws'), c.req.param('rec'), c.req.param('mut'), req, actor, authoritative, { signal: c.req.raw.signal, idempotencyKey, verbose: c.req.query('verbose') === '1', budgetMs: options.syncDeadlineMs });
62
89
  }
63
90
  catch (err) {
64
91
  return sendError(MutationResultType, errorToVariant(err));
@@ -72,7 +99,7 @@ export function createWorkspaceRecordRoutes(storage, getRepoPath, getRunner) {
72
99
  return sendError(MutationResultType, variant('permission_denied', { path: 'compact' }));
73
100
  }
74
101
  try {
75
- return await compactRecord(storage, repoPath, c.req.param('ws'), c.req.param('rec'), resolveActor(identity).actor);
102
+ return await compactRecord(storage, repoPath, c.req.param('ws'), c.req.param('rec'), resolveActor(identity).actor, options.syncDeadlineMs);
76
103
  }
77
104
  catch (err) {
78
105
  return sendError(MutationResultType, errorToVariant(err));
@@ -1 +1 @@
1
- {"version":3,"file":"records.js","sourceRoot":"","sources":["../../../src/routes/records.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../../../src/routes/records.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  * Licensed under BSL 1.1. See LICENSE for details.
4
4
  */
5
5
  import { Hono } from 'hono';
6
- import type { StorageBackend } from '@elaraai/e3-core';
6
+ import type { StorageBackend } from '@elaraai/e3-core/portable';
7
7
  /**
8
8
  * The routes of the repositories a host keeps, mounted at `/api/repos`: list
9
9
  * them, create one and remove one. They go through the storage backend's
@@ -1 +1 @@
1
- {"version":3,"file":"repositories.d.ts","sourceRoot":"","sources":["../../../src/routes/repositories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIvD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,cAAc,8EAa/D;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,+EAyB3C"}
1
+ {"version":3,"file":"repositories.d.ts","sourceRoot":"","sources":["../../../src/routes/repositories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,cAAc,8EAa/D;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,+EAyB3C"}
@@ -3,11 +3,16 @@
3
3
  * Licensed under BSL 1.1. See LICENSE for details.
4
4
  */
5
5
  import { Hono } from 'hono';
6
- import type { StorageBackend, TransferBackend } from '@elaraai/e3-core';
6
+ import type { StorageBackend, TransferBackend } from '@elaraai/e3-core/portable';
7
7
  /**
8
8
  * Repository routes, mounted at `/api/repos/:repo`: its status, its record,
9
9
  * and gc.
10
10
  *
11
+ * @remarks
12
+ * The record is read, never upgraded: mounted without the repository gate, the
13
+ * route answers a repository that owes upgrades as the gate does, 503
14
+ * `repository_upgrade_pending` with a `Retry-After` ({@link getRecord}).
15
+ *
11
16
  * @param storage - Storage backend
12
17
  * @param getRepoPath - The repository identifier for a repo name
13
18
  * @param transferBackend - Files and dispatches the job gc runs as, and holds
@@ -1 +1 @@
1
- {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../../src/routes/repository.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAKxE;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,eAAe,EAAE,eAAe,8EAiCjC"}
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../../src/routes/repository.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAKjF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,eAAe,EAAE,eAAe,8EAiCjC"}
@@ -10,6 +10,11 @@ import { GcRequestType } from '../types.js';
10
10
  * Repository routes, mounted at `/api/repos/:repo`: its status, its record,
11
11
  * and gc.
12
12
  *
13
+ * @remarks
14
+ * The record is read, never upgraded: mounted without the repository gate, the
15
+ * route answers a repository that owes upgrades as the gate does, 503
16
+ * `repository_upgrade_pending` with a `Retry-After` ({@link getRecord}).
17
+ *
13
18
  * @param storage - Storage backend
14
19
  * @param getRepoPath - The repository identifier for a repo name
15
20
  * @param transferBackend - Files and dispatches the job gc runs as, and holds
@@ -1 +1 @@
1
- {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../../src/routes/repository.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAuB,EACvB,WAAqC,EACrC,eAAgC;IAEhC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,sDAAsD;IACtD,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,kFAAkF;IAClF,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;QACnD,OAAO,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,uDAAuD;IACvD,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAE,CAAC;QAChD,OAAO,WAAW,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../../src/routes/repository.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAuB,EACvB,WAAqC,EACrC,eAAgC;IAEhC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,sDAAsD;IACtD,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,kFAAkF;IAClF,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;QACnD,OAAO,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,uDAAuD;IACvD,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAE,CAAC;QAChD,OAAO,WAAW,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=repository.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.spec.d.ts","sourceRoot":"","sources":["../../../src/routes/repository.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ /**
6
+ * The repository routes' record: a read, which applies no upgrade the
7
+ * repository owes and waits on no work running in it, whether the repository
8
+ * gate is mounted ahead of it or not. Without the gate, a repository that owes
9
+ * upgrades is refused as the gate refuses it.
10
+ */
11
+ import { describe, it } from 'node:test';
12
+ import assert from 'node:assert/strict';
13
+ import { Hono } from 'hono';
14
+ import { decodeBeast2For, variant } from '@elaraai/east';
15
+ import { InMemoryTransferBackend, TASKS_LOCK, repositoryUpgradeStep } from '@elaraai/e3-core';
16
+ import { InMemoryStorage } from '@elaraai/e3-core/test';
17
+ import { RepositoryRecordType } from '@elaraai/e3-types';
18
+ import { getRecord } from '../handlers/repository.js';
19
+ import { createRepositoryGate } from '../middleware/repository.js';
20
+ import { ResponseType } from '../types.js';
21
+ import { createRepositoryRoutes } from './repository.js';
22
+ const decodeRecord = decodeBeast2For(ResponseType(RepositoryRecordType));
23
+ /** A repository written before its host's backend shipped a layout step,
24
+ * which it now owes, and the names of the steps applied. */
25
+ async function owing() {
26
+ const storage = new InMemoryStorage();
27
+ await storage.repos.create('owing');
28
+ const applied = [];
29
+ storage.upgrades.push({ name: 'host-layout-2', apply: async () => { applied.push('host-layout-2'); return null; } });
30
+ return { storage, applied };
31
+ }
32
+ /** The refusal the gate answers too, with when to ask again. */
33
+ async function assertRefused(response, what) {
34
+ assert.equal(response.status, 503, what);
35
+ assert.equal(response.headers.get('Retry-After'), '5', what);
36
+ assert.deepEqual(await response.json(), {
37
+ error: {
38
+ type: 'repository_upgrade_pending',
39
+ message: 'the repository owing owes the upgrade "host-layout-2", which a job applies before the repository is read — retry once it has',
40
+ },
41
+ }, what);
42
+ }
43
+ describe('the repository record route', () => {
44
+ it('reads a repository that owes an upgrade without applying it, refused at once as the gate refuses it, while a task runs or not', async () => {
45
+ const { storage, applied } = await owing();
46
+ const app = new Hono();
47
+ app.route('/api/repos/:repo', createRepositoryRoutes(storage, (repo) => repo, new InMemoryTransferBackend({ storage, getRepoPath: (repo) => repo })));
48
+ await assertRefused(await app.request('/api/repos/owing/record'), 'the route, with no gate ahead of it');
49
+ await assertRefused(await getRecord(storage, 'owing'), 'the handler, as a host calls it');
50
+ // A task running in the repository holds no read: the refusal is at once.
51
+ const task = await storage.locks.acquire('owing', TASKS_LOCK, variant('dataflow', null), { mode: 'shared' });
52
+ assert.ok(task !== null);
53
+ try {
54
+ const started = Date.now();
55
+ await assertRefused(await app.request('/api/repos/owing/record'), 'the route, while a task runs');
56
+ assert.ok(Date.now() - started < 5_000, 'the read waited on the running task');
57
+ }
58
+ finally {
59
+ await task.release();
60
+ }
61
+ assert.deepEqual(applied, [], 'no read applies the step');
62
+ // Once the host's job has applied it, the record is read.
63
+ assert.deepEqual(await repositoryUpgradeStep(storage, 'owing', { budgetMs: 10_000 }), { owed: [] });
64
+ const read = decodeRecord(new Uint8Array(await (await app.request('/api/repos/owing/record')).arrayBuffer()));
65
+ assert.equal(read.type, 'success');
66
+ if (read.type !== 'success')
67
+ return;
68
+ assert.ok(read.value.upgrades.some(({ name }) => name === 'host-layout-2'), 'the record names the step the job applied');
69
+ assert.deepEqual(applied, ['host-layout-2']);
70
+ });
71
+ it('answers the record behind the gate as it always has: the gate applies the step, and the route reads it', async () => {
72
+ const { storage, applied } = await owing();
73
+ const app = new Hono();
74
+ app.use('/api/repos/:repo/*', createRepositoryGate(storage, (repo) => repo));
75
+ app.route('/api/repos/:repo', createRepositoryRoutes(storage, (repo) => repo, new InMemoryTransferBackend({ storage, getRepoPath: (repo) => repo })));
76
+ const read = decodeRecord(new Uint8Array(await (await app.request('/api/repos/owing/record')).arrayBuffer()));
77
+ assert.equal(read.type, 'success');
78
+ if (read.type !== 'success')
79
+ return;
80
+ assert.ok(read.value.upgrades.some(({ name }) => name === 'host-layout-2'), 'the record names the step the gate applied');
81
+ assert.deepEqual(applied, ['host-layout-2']);
82
+ });
83
+ });
84
+ //# sourceMappingURL=repository.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.spec.js","sourceRoot":"","sources":["../../../src/routes/repository.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,YAAY,GAAG,eAAe,CAAC,YAAY,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAEzE;6DAC6D;AAC7D,KAAK,UAAU,KAAK;IAClB,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;IACtC,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC;AAED,gEAAgE;AAChE,KAAK,UAAU,aAAa,CAAC,QAAkB,EAAE,IAAY;IAC3D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7D,MAAM,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE;QACtC,KAAK,EAAE;YACL,IAAI,EAAE,4BAA4B;YAClC,OAAO,EAAE,8HAA8H;SACxI;KACF,EAAE,IAAI,CAAC,CAAC;AACX,CAAC;AAED,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,+HAA+H,EAAE,KAAK,IAAI,EAAE;QAC7I,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAEtJ,MAAM,aAAa,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,yBAAyB,CAAC,EAAE,qCAAqC,CAAC,CAAC;QACzG,MAAM,aAAa,CAAC,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,iCAAiC,CAAC,CAAC;QAE1F,0EAA0E;QAC1E,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7G,MAAM,CAAC,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,MAAM,aAAa,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,yBAAyB,CAAC,EAAE,8BAA8B,CAAC,CAAC;YAClG,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,KAAK,EAAE,qCAAqC,CAAC,CAAC;QACjF,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QACD,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,EAAE,0BAA0B,CAAC,CAAC;QAE1D,0DAA0D;QAC1D,MAAM,CAAC,SAAS,CAAC,MAAM,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpG,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC9G,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO;QACpC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,eAAe,CAAC,EAAE,2CAA2C,CAAC,CAAC;QACzH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wGAAwG,EAAE,KAAK,IAAI,EAAE;QACtH,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7E,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAEtJ,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC9G,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO;QACpC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,eAAe,CAAC,EAAE,4CAA4C,CAAC,CAAC;QAC1H,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -3,6 +3,6 @@
3
3
  * Licensed under BSL 1.1. See LICENSE for details.
4
4
  */
5
5
  import { Hono } from 'hono';
6
- import type { StorageBackend } from '@elaraai/e3-core';
6
+ import type { StorageBackend } from '@elaraai/e3-core/portable';
7
7
  export declare function createTaskRoutes(storage: StorageBackend, getRepoPath: (repo: string) => string): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
8
8
  //# sourceMappingURL=tasks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../../src/routes/tasks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,8EAiCtC"}
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../../src/routes/tasks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKhE,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,8EA0CtC"}
@@ -4,6 +4,8 @@
4
4
  */
5
5
  import { Hono } from 'hono';
6
6
  import { listTasks, getTask, listExecutions } from '../handlers/tasks.js';
7
+ import { TASK_EXECUTIONS_PAGE_DEFAULT, TASK_EXECUTIONS_PAGE_MAX } from '../types.js';
8
+ import { wholeQuery } from './query.js';
7
9
  export function createTaskRoutes(storage, getRepoPath) {
8
10
  const app = new Hono();
9
11
  // GET /api/repos/:repo/workspaces/:ws/tasks - List tasks
@@ -21,15 +23,25 @@ export function createTaskRoutes(storage, getRepoPath) {
21
23
  const taskName = c.req.param('task');
22
24
  return getTask(storage, repoPath, ws, taskName);
23
25
  });
24
- // GET /api/repos/:repo/workspaces/:ws/tasks/:task/executions[?all=true] - List execution history
25
- // (the latest attempt per inputs hash; `all=true` lists every attempt)
26
+ // GET /api/repos/:repo/workspaces/:ws/tasks/:task/executions[?limit=<n>&before=<executionId>] - A page
27
+ // of a task's history: its runs, the latest first, at most `limit` of them —
28
+ // TASK_EXECUTIONS_PAGE_DEFAULT unless given, and never more than
29
+ // TASK_EXECUTIONS_PAGE_MAX — those before the run `before` names. A limit
30
+ // that is not a positive integer is refused before any store is asked, and
31
+ // an id that is no UUIDv7 by the store.
26
32
  app.get('/:task/executions', async (c) => {
27
33
  const repo = c.req.param('repo');
28
34
  const repoPath = getRepoPath(repo);
29
35
  const ws = c.req.param('ws');
30
36
  const taskName = c.req.param('task');
31
- const all = c.req.query('all') === 'true';
32
- return listExecutions(storage, repoPath, ws, taskName, all);
37
+ const window = wholeQuery(c, { limit: 1 });
38
+ if (window instanceof Response)
39
+ return window;
40
+ const before = c.req.query('before');
41
+ return listExecutions(storage, repoPath, ws, taskName, {
42
+ limit: Math.min(window.limit ?? TASK_EXECUTIONS_PAGE_DEFAULT, TASK_EXECUTIONS_PAGE_MAX),
43
+ ...(before !== undefined && before !== '' && { before }),
44
+ });
33
45
  });
34
46
  return app;
35
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../../src/routes/tasks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE1E,MAAM,UAAU,gBAAgB,CAC9B,OAAuB,EACvB,WAAqC;IAErC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,yDAAyD;IACzD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC9B,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,qEAAqE;IACrE,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QACtC,OAAO,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,iGAAiG;IACjG,uEAAuE;IACvE,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QACtC,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC;QAC1C,OAAO,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../../src/routes/tasks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,UAAU,gBAAgB,CAC9B,OAAuB,EACvB,WAAqC;IAErC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,yDAAyD;IACzD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC9B,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,qEAAqE;IACrE,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QACtC,OAAO,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,uGAAuG;IACvG,6EAA6E;IAC7E,iEAAiE;IACjE,0EAA0E;IAC1E,2EAA2E;IAC3E,wCAAwC;IACxC,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC;QACtC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM,YAAY,QAAQ;YAAE,OAAO,MAAM,CAAC;QAC9C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE;YACrD,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,4BAA4B,EAAE,wBAAwB,CAAC;YACvF,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SACzD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -3,7 +3,7 @@
3
3
  * Licensed under BSL 1.1. See LICENSE for details.
4
4
  */
5
5
  import { Hono } from 'hono';
6
- import { type StorageBackend, type TransferBackend } from '@elaraai/e3-core';
6
+ import { type StorageBackend, type TransferBackend } from '@elaraai/e3-core/portable';
7
7
  /** Options for {@link createTransferRoutes}. */
8
8
  export interface TransferRouteOptions {
9
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../../src/routes/transfer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAU1B,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,eAAe,EAAE,eAAe,EAChC,OAAO,GAAE,oBAAyB;;EAyLnC"}
1
+ {"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../../src/routes/transfer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,2BAA2B,CAAC;AAWnC,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,eAAe,EAAE,eAAe,EAChC,OAAO,GAAE,oBAAyB;;EAoKnC"}