@haverstack/server 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/dist/app.d.ts +8 -0
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +70 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/config.d.ts +21 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +115 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +12 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/changeStreams.d.ts +17 -0
  14. package/dist/lib/changeStreams.d.ts.map +1 -0
  15. package/{src/lib/changeStreams.ts → dist/lib/changeStreams.js} +12 -12
  16. package/dist/lib/changeStreams.js.map +1 -0
  17. package/dist/lib/frameGate.d.ts +26 -0
  18. package/dist/lib/frameGate.d.ts.map +1 -0
  19. package/dist/lib/frameGate.js +44 -0
  20. package/dist/lib/frameGate.js.map +1 -0
  21. package/dist/lib/json.d.ts +10 -0
  22. package/dist/lib/json.d.ts.map +1 -0
  23. package/dist/lib/json.js +27 -0
  24. package/dist/lib/json.js.map +1 -0
  25. package/dist/lib/nonceStore.d.ts +50 -0
  26. package/dist/lib/nonceStore.d.ts.map +1 -0
  27. package/dist/lib/nonceStore.js +96 -0
  28. package/dist/lib/nonceStore.js.map +1 -0
  29. package/dist/lib/queryLimit.d.ts +4 -0
  30. package/dist/lib/queryLimit.d.ts.map +1 -0
  31. package/{src/lib/queryLimit.ts → dist/lib/queryLimit.js} +5 -6
  32. package/dist/lib/queryLimit.js.map +1 -0
  33. package/dist/lib/queryWorker/pool.d.ts +60 -0
  34. package/dist/lib/queryWorker/pool.d.ts.map +1 -0
  35. package/dist/lib/queryWorker/pool.js +278 -0
  36. package/dist/lib/queryWorker/pool.js.map +1 -0
  37. package/{src/lib/queryWorker/protocol.ts → dist/lib/queryWorker/protocol.d.ts} +21 -18
  38. package/dist/lib/queryWorker/protocol.d.ts.map +1 -0
  39. package/dist/lib/queryWorker/protocol.js +2 -0
  40. package/dist/lib/queryWorker/protocol.js.map +1 -0
  41. package/dist/lib/queryWorker/worker.d.ts +2 -0
  42. package/dist/lib/queryWorker/worker.d.ts.map +1 -0
  43. package/{src/lib/queryWorker/worker.ts → dist/lib/queryWorker/worker.js} +19 -22
  44. package/dist/lib/queryWorker/worker.js.map +1 -0
  45. package/dist/lib/resumeBuffer.d.ts +143 -0
  46. package/dist/lib/resumeBuffer.d.ts.map +1 -0
  47. package/dist/lib/resumeBuffer.js +216 -0
  48. package/dist/lib/resumeBuffer.js.map +1 -0
  49. package/dist/lib/resumeCursor.d.ts +16 -0
  50. package/dist/lib/resumeCursor.d.ts.map +1 -0
  51. package/{src/lib/resumeCursor.ts → dist/lib/resumeCursor.js} +22 -24
  52. package/dist/lib/resumeCursor.js.map +1 -0
  53. package/dist/lib/tokenSweep.d.ts +17 -0
  54. package/dist/lib/tokenSweep.d.ts.map +1 -0
  55. package/dist/lib/tokenSweep.js +40 -0
  56. package/dist/lib/tokenSweep.js.map +1 -0
  57. package/dist/main.d.ts +2 -0
  58. package/dist/main.d.ts.map +1 -0
  59. package/dist/main.js +46 -0
  60. package/dist/main.js.map +1 -0
  61. package/dist/middleware/auth.d.ts +25 -0
  62. package/dist/middleware/auth.d.ts.map +1 -0
  63. package/dist/middleware/auth.js +74 -0
  64. package/dist/middleware/auth.js.map +1 -0
  65. package/dist/middleware/errors.d.ts +17 -0
  66. package/dist/middleware/errors.d.ts.map +1 -0
  67. package/dist/middleware/errors.js +64 -0
  68. package/dist/middleware/errors.js.map +1 -0
  69. package/dist/routes/attachments.d.ts +5 -0
  70. package/dist/routes/attachments.d.ts.map +1 -0
  71. package/dist/routes/attachments.js +108 -0
  72. package/dist/routes/attachments.js.map +1 -0
  73. package/dist/routes/auth.d.ts +6 -0
  74. package/dist/routes/auth.d.ts.map +1 -0
  75. package/dist/routes/auth.js +110 -0
  76. package/dist/routes/auth.js.map +1 -0
  77. package/dist/routes/changes.d.ts +22 -0
  78. package/dist/routes/changes.d.ts.map +1 -0
  79. package/dist/routes/changes.js +233 -0
  80. package/dist/routes/changes.js.map +1 -0
  81. package/dist/routes/entity.d.ts +5 -0
  82. package/dist/routes/entity.d.ts.map +1 -0
  83. package/dist/routes/entity.js +30 -0
  84. package/dist/routes/entity.js.map +1 -0
  85. package/dist/routes/health.d.ts +4 -0
  86. package/dist/routes/health.d.ts.map +1 -0
  87. package/dist/routes/health.js +7 -0
  88. package/dist/routes/health.js.map +1 -0
  89. package/dist/routes/records.d.ts +5 -0
  90. package/dist/routes/records.d.ts.map +1 -0
  91. package/dist/routes/records.js +231 -0
  92. package/dist/routes/records.js.map +1 -0
  93. package/dist/routes/tokens.d.ts +5 -0
  94. package/dist/routes/tokens.d.ts.map +1 -0
  95. package/dist/routes/tokens.js +65 -0
  96. package/dist/routes/tokens.js.map +1 -0
  97. package/dist/routes/types.d.ts +5 -0
  98. package/dist/routes/types.d.ts.map +1 -0
  99. package/dist/routes/types.js +46 -0
  100. package/dist/routes/types.js.map +1 -0
  101. package/dist/routes/wellknown.d.ts +6 -0
  102. package/dist/routes/wellknown.d.ts.map +1 -0
  103. package/dist/routes/wellknown.js +45 -0
  104. package/dist/routes/wellknown.js.map +1 -0
  105. package/dist/shutdown.d.ts +23 -0
  106. package/dist/shutdown.d.ts.map +1 -0
  107. package/dist/shutdown.js +32 -0
  108. package/dist/shutdown.js.map +1 -0
  109. package/dist/stack.d.ts +20 -0
  110. package/dist/stack.d.ts.map +1 -0
  111. package/dist/stack.js +50 -0
  112. package/dist/stack.js.map +1 -0
  113. package/dist/testing.d.ts +49 -0
  114. package/dist/testing.d.ts.map +1 -0
  115. package/dist/testing.js +130 -0
  116. package/dist/testing.js.map +1 -0
  117. package/{src/types.ts → dist/types.d.ts} +5 -5
  118. package/dist/types.d.ts.map +1 -0
  119. package/dist/types.js +2 -0
  120. package/dist/types.js.map +1 -0
  121. package/{src/wireError.ts → dist/wireError.d.ts} +2 -9
  122. package/dist/wireError.d.ts.map +1 -0
  123. package/dist/wireError.js +13 -0
  124. package/dist/wireError.js.map +1 -0
  125. package/package.json +42 -17
  126. package/.claude/settings.local.json +0 -22
  127. package/.dockerignore +0 -10
  128. package/.env.example +0 -90
  129. package/.github/pull_request_template.md +0 -23
  130. package/.github/workflows/ci.yml +0 -55
  131. package/.github/workflows/release.yml +0 -99
  132. package/.prettierignore +0 -3
  133. package/.prettierrc +0 -7
  134. package/AGENTS.md +0 -91
  135. package/CONTRIBUTING.md +0 -234
  136. package/Dockerfile +0 -30
  137. package/SECURITY.md +0 -54
  138. package/docs/api.md +0 -277
  139. package/docs/deployment.md +0 -212
  140. package/eslint.config.mjs +0 -18
  141. package/pnpm-workspace.yaml +0 -13
  142. package/src/app.ts +0 -86
  143. package/src/config.ts +0 -176
  144. package/src/index.ts +0 -54
  145. package/src/lib/frameGate.ts +0 -42
  146. package/src/lib/json.ts +0 -27
  147. package/src/lib/nonceStore.ts +0 -103
  148. package/src/lib/queryWorker/pool.ts +0 -324
  149. package/src/lib/resumeBuffer.ts +0 -268
  150. package/src/lib/tokenSweep.ts +0 -43
  151. package/src/middleware/auth.ts +0 -77
  152. package/src/middleware/errors.ts +0 -73
  153. package/src/routes/attachments.ts +0 -126
  154. package/src/routes/auth.ts +0 -137
  155. package/src/routes/changes.ts +0 -279
  156. package/src/routes/entity.ts +0 -33
  157. package/src/routes/health.ts +0 -8
  158. package/src/routes/records.ts +0 -259
  159. package/src/routes/tokens.ts +0 -85
  160. package/src/routes/types.ts +0 -57
  161. package/src/routes/wellknown.ts +0 -55
  162. package/src/shutdown.ts +0 -56
  163. package/src/stack.ts +0 -96
  164. package/tests/app.test.ts +0 -86
  165. package/tests/changeFeedClient.ts +0 -156
  166. package/tests/config.test.ts +0 -112
  167. package/tests/conformance.test.ts +0 -2322
  168. package/tests/fixtures/blockingWorker.ts +0 -27
  169. package/tests/fixtures/brokenWorker.ts +0 -7
  170. package/tests/lib/changeFeedClient.test.ts +0 -126
  171. package/tests/lib/frameGate.test.ts +0 -93
  172. package/tests/lib/queryLimit.test.ts +0 -33
  173. package/tests/lib/queryWorker.test.ts +0 -60
  174. package/tests/lib/queryWorkerPool.test.ts +0 -129
  175. package/tests/lib/resumeBuffer.test.ts +0 -341
  176. package/tests/lib/resumeCursor.test.ts +0 -46
  177. package/tests/middleware/errors.test.ts +0 -176
  178. package/tests/routes/associations.test.ts +0 -170
  179. package/tests/routes/attachments.test.ts +0 -621
  180. package/tests/routes/auth.test.ts +0 -448
  181. package/tests/routes/changes.test.ts +0 -567
  182. package/tests/routes/contentQuery.test.ts +0 -68
  183. package/tests/routes/entity.test.ts +0 -170
  184. package/tests/routes/groups.test.ts +0 -69
  185. package/tests/routes/records.test.ts +0 -1965
  186. package/tests/routes/relatedToQuery.test.ts +0 -182
  187. package/tests/routes/tokens.test.ts +0 -157
  188. package/tests/routes/tombstones.test.ts +0 -93
  189. package/tests/routes/types.test.ts +0 -129
  190. package/tests/routes/versions.test.ts +0 -177
  191. package/tests/routes/wellknown.test.ts +0 -71
  192. package/tests/setup.ts +0 -154
  193. package/tests/shutdown.test.ts +0 -121
  194. package/tests/stack.test.ts +0 -198
  195. package/tsconfig.build.json +0 -9
  196. package/tsconfig.json +0 -19
  197. package/vitest.config.ts +0 -12
@@ -0,0 +1,30 @@
1
+ import { Hono } from 'hono';
2
+ import { requireAuth, requireOwner } from '../middleware/auth.js';
3
+ import { readJson } from '../lib/json.js';
4
+ import { serializeRecord } from '@haverstack/wire-types';
5
+ import { StackNotFoundError } from '@haverstack/core';
6
+ export function entityRoutes(ctx) {
7
+ const app = new Hono();
8
+ const { stack } = ctx;
9
+ const ownerEntityId = stack.ownerEntityId;
10
+ app.get('/', requireAuth(), async (c) => {
11
+ const auth = c.get('auth');
12
+ const record = await stack.forSession(auth).getOwnerEntity();
13
+ if (!record)
14
+ throw new StackNotFoundError('Entity record not found');
15
+ return c.json(serializeRecord(record));
16
+ });
17
+ app.patch('/', requireOwner(ownerEntityId), async (c) => {
18
+ const auth = c.get('auth');
19
+ const record = await stack.forSession(auth).getOwnerEntity();
20
+ if (!record)
21
+ throw new StackNotFoundError('Entity record not found');
22
+ const body = await readJson(c);
23
+ const updated = await stack
24
+ .forSession(auth)
25
+ .patchContent(record.id, (body.content ?? {}));
26
+ return c.json(serializeRecord(updated));
27
+ });
28
+ return app;
29
+ }
30
+ //# sourceMappingURL=entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.js","sourceRoot":"","sources":["../../src/routes/entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAC;IAC/B,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;IACtB,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;IAE1C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;QAC7D,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,kBAAkB,CAAC,yBAAyB,CAAC,CAAC;QACrE,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACtD,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;QAC7D,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,kBAAkB,CAAC,yBAAyB,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAA0B,CAAC,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,MAAM,KAAK;aACxB,UAAU,CAAC,IAAI,CAAC;aAChB,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC,CAAC;QAC5E,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from '../types.js';
3
+ export declare function healthRoutes(): Hono<AppEnv>;
4
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,wBAAgB,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,CAI3C"}
@@ -0,0 +1,7 @@
1
+ import { Hono } from 'hono';
2
+ export function healthRoutes() {
3
+ const app = new Hono();
4
+ app.get('/', (c) => c.json({ status: 'ok', timestamp: new Date().toISOString() }));
5
+ return app;
6
+ }
7
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAC;IAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;IACnF,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from '../types.js';
3
+ import type { StackContext } from '../stack.js';
4
+ export declare function recordRoutes(ctx: StackContext, queryTimeoutMs: number): Hono<AppEnv>;
5
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/routes/records.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA4BhD,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAoOpF"}
@@ -0,0 +1,231 @@
1
+ import { Hono } from 'hono';
2
+ import { requireAuth, requireOwner } from '../middleware/auth.js';
3
+ import { readJson } from '../lib/json.js';
4
+ import { parseQueryBody, parseQueryParams, parsePositiveInt, parseIfMatch, createOptionsFromWireRecord, changesFromWireBody, } from '@haverstack/core/wire';
5
+ import { clampLimit } from '../lib/queryLimit.js';
6
+ import { serializeRecord, serializeVersion } from '@haverstack/wire-types';
7
+ import { StackValidationError, StackQueryError, StackNotFoundError } from '@haverstack/core';
8
+ // ---------------------------------------------------------------------------
9
+ // Route factory
10
+ // ---------------------------------------------------------------------------
11
+ // Only the routes that can search a large index (POST /query, GET /) run
12
+ // on the query worker pool with a deadline; everything else calls the main
13
+ // thread's ScopedStack directly, being index-bound already. Virtualizing
14
+ // the whole ScopedStack API across the worker boundary would buy no bound
15
+ // the unbounded-cost case needs. See src/lib/queryWorker/pool.ts and
16
+ // docs/spec/wire-format.md § Bounding query cost.
17
+ export function recordRoutes(ctx, queryTimeoutMs) {
18
+ const app = new Hono();
19
+ const { stack, queryWorker } = ctx;
20
+ const ownerEntityId = stack.ownerEntityId;
21
+ /** Scope to a session if authenticated, else the anonymous view. */
22
+ function scopeFor(auth) {
23
+ return auth ? stack.forSession(auth) : stack.asEntity(null);
24
+ }
25
+ // POST /records/query — full query with content-field filters. Optional
26
+ // auth, like GET /records: a superset of the same query surface, so an
27
+ // anonymous caller gets the same public-record subset either way.
28
+ // Registered ahead of the /:id patterns, which would otherwise capture
29
+ // the literal "query" segment.
30
+ app.post('/query', async (c) => {
31
+ const auth = c.get('auth');
32
+ const query = clampLimit(parseQueryBody(await readJson(c)));
33
+ const result = await queryWorker.query(auth, query, queryTimeoutMs);
34
+ const body = {
35
+ records: result.records.map(serializeRecord),
36
+ cursor: result.cursor,
37
+ };
38
+ return c.json(body);
39
+ });
40
+ // GET /records — query by native fields via URL params
41
+ app.get('/', async (c) => {
42
+ const auth = c.get('auth');
43
+ const query = clampLimit(parseQueryParams(new URL(c.req.url)));
44
+ const result = await queryWorker.query(auth, query, queryTimeoutMs);
45
+ const body = {
46
+ records: result.records.map(serializeRecord),
47
+ cursor: result.cursor,
48
+ };
49
+ return c.json(body);
50
+ });
51
+ // POST /records — a full record body, but version, entityId and
52
+ // principalId are stamped here and never trusted from it. Everything else
53
+ // a wire record body can carry — which fields are forwarded as-is, which
54
+ // are owner-acting-alone-only, and which reduce to a boolean rather than
55
+ // their literal value — is `createOptionsFromWireRecord()`'s disposition
56
+ // to make, not this route's: see `@haverstack/core/wire` and
57
+ // docs/spec/wire-format.md § Records.
58
+ app.post('/', requireAuth(), async (c) => {
59
+ const auth = c.get('auth');
60
+ const body = await readJson(c);
61
+ const { typeId, content, options } = createOptionsFromWireRecord(body, auth, ownerEntityId);
62
+ const created = await stack.forSession(auth).create(typeId, content, options);
63
+ return c.json(serializeRecord(created), 200);
64
+ });
65
+ // GET /records/:id
66
+ app.get('/:id', async (c) => {
67
+ const id = c.req.param('id');
68
+ const auth = c.get('auth');
69
+ const record = await scopeFor(auth).get(id);
70
+ if (!record)
71
+ throw new StackNotFoundError('Record not found');
72
+ return c.json(serializeRecord(record));
73
+ });
74
+ // PATCH /records/:id — the body is a change set: an envelope naming any
75
+ // combination of `contentPatch`, `parentId`, `permissions`, `associations`
76
+ // and `unlisted`, applied as one atomic write that produces exactly one
77
+ // version. A conforming client sends { "contentPatch": { "title": "New" } },
78
+ // never the bare patch — content is one aspect among five here, so the
79
+ // envelope is what tells a `parentId` naming a container from a content
80
+ // field of that name. One If-Match fences the whole multi-aspect edit.
81
+ //
82
+ // `changesFromWireBody()` owns the entire read of that envelope: which
83
+ // keys it may carry, the 400 an unrecognized or wholly absent key earns,
84
+ // and the 422 a malformed value earns. Content keys stay Stack's to judge,
85
+ // so nothing here inspects the patch. See docs/spec/wire-format.md § Records.
86
+ //
87
+ // No route-level guard belongs here: ScopedStack owns the _attachment@1
88
+ // immutable-field and _grant@1 owner-only rules (docs/spec/attachments.md,
89
+ // docs/spec/access-control.md) and resolves each change-set key against its
90
+ // own gate, so a denial round-trips as the core error it is rather than a
91
+ // guess made before the permission check ran.
92
+ app.patch('/:id', requireAuth(), async (c) => {
93
+ const id = c.req.param('id');
94
+ const auth = c.get('auth');
95
+ const changes = changesFromWireBody(await readJson(c));
96
+ const updated = await stack
97
+ .forSession(auth)
98
+ .mutate(id, changes, { ifVersion: parseIfMatch(c.req.header('If-Match')) });
99
+ return c.json(serializeRecord(updated));
100
+ });
101
+ // DELETE /records/:id (?hard=true for permanent). A soft delete bumps
102
+ // the version like any other mutation, so it answers with the record it
103
+ // produced (carrying deletedAt); a hard delete leaves nothing to answer
104
+ // with, so that one stays 204.
105
+ app.delete('/:id', requireAuth(), async (c) => {
106
+ const id = c.req.param('id');
107
+ const auth = c.get('auth');
108
+ const hard = new URL(c.req.url).searchParams.get('hard') === 'true';
109
+ const session = stack.forSession(auth);
110
+ await session.delete(id, { hard, ifVersion: parseIfMatch(c.req.header('If-Match')) });
111
+ if (hard)
112
+ return c.body(null, 204);
113
+ return c.json(serializeRecord((await session.get(id))));
114
+ });
115
+ // POST /records/:id/undelete — reverses a soft delete; idempotent
116
+ app.post('/:id/undelete', requireAuth(), async (c) => {
117
+ const id = c.req.param('id');
118
+ const auth = c.get('auth');
119
+ const restored = await stack
120
+ .forSession(auth)
121
+ .undelete(id, { ifVersion: parseIfMatch(c.req.header('If-Match')) });
122
+ return c.json(serializeRecord(restored));
123
+ });
124
+ // ------------------------------------------------------------------
125
+ // Permissions
126
+ // ------------------------------------------------------------------
127
+ app.get('/:id/permissions', async (c) => {
128
+ const id = c.req.param('id');
129
+ const auth = c.get('auth');
130
+ const record = await scopeFor(auth).get(id);
131
+ if (!record)
132
+ throw new StackNotFoundError('Record not found');
133
+ return c.json({ permissions: record.permissions ?? [] });
134
+ });
135
+ // ------------------------------------------------------------------
136
+ // Associations
137
+ // ------------------------------------------------------------------
138
+ app.get('/:id/associations', async (c) => {
139
+ const id = c.req.param('id');
140
+ const auth = c.get('auth');
141
+ const record = await scopeFor(auth).get(id);
142
+ if (!record)
143
+ throw new StackNotFoundError('Record not found');
144
+ let assocs = record.associations ?? [];
145
+ const kind = c.req.query('kind');
146
+ if (kind)
147
+ assocs = assocs.filter((a) => a.kind === kind);
148
+ const label = c.req.query('label');
149
+ if (label)
150
+ assocs = assocs.filter((a) => a.label === label);
151
+ return c.json({ associations: assocs });
152
+ });
153
+ app.post('/:id/associations', requireAuth(), async (c) => {
154
+ const id = c.req.param('id');
155
+ const auth = c.get('auth');
156
+ const body = await readJson(c);
157
+ if (!body.kind || !body.label)
158
+ throw new StackQueryError('kind and label are required');
159
+ const updated = await stack
160
+ .forSession(auth)
161
+ .associate(id, body, { ifVersion: parseIfMatch(c.req.header('If-Match')) });
162
+ return c.json(serializeRecord(updated));
163
+ });
164
+ // POST, not DELETE — a DELETE request body has no defined semantics
165
+ // (RFC 9110 §9.3.5) and is a portability landmine for proxies/gateways
166
+ // that drop or reject it.
167
+ app.post('/:id/associations/delete', requireAuth(), async (c) => {
168
+ const id = c.req.param('id');
169
+ const auth = c.get('auth');
170
+ const body = await readJson(c);
171
+ const updated = await stack
172
+ .forSession(auth)
173
+ .dissociate(id, body, { ifVersion: parseIfMatch(c.req.header('If-Match')) });
174
+ return c.json(serializeRecord(updated));
175
+ });
176
+ // ------------------------------------------------------------------
177
+ // Versions
178
+ // ------------------------------------------------------------------
179
+ app.get('/:id/versions', async (c) => {
180
+ const id = c.req.param('id');
181
+ const auth = c.get('auth');
182
+ const versions = await scopeFor(auth).getVersions(id);
183
+ return c.json(versions.map(serializeVersion));
184
+ });
185
+ app.get('/:id/versions/:version', async (c) => {
186
+ const id = c.req.param('id');
187
+ const vNum = parsePositiveInt(c.req.param('version'), 'version number');
188
+ const auth = c.get('auth');
189
+ const version = await scopeFor(auth).getVersion(id, vNum);
190
+ if (!version)
191
+ throw new StackNotFoundError('Version not found');
192
+ return c.json(serializeVersion(version));
193
+ });
194
+ // POST /records/:id/restore/:version — creates new version, does not rewrite history
195
+ app.post('/:id/restore/:version', requireAuth(), async (c) => {
196
+ const id = c.req.param('id');
197
+ const vNum = parsePositiveInt(c.req.param('version'), 'version number');
198
+ const auth = c.get('auth');
199
+ const restored = await stack
200
+ .forSession(auth)
201
+ .restoreVersion(id, vNum, { ifVersion: parseIfMatch(c.req.header('If-Match')) });
202
+ return c.json(serializeRecord(restored));
203
+ });
204
+ // POST /records/:id/migrate — the only way typeId changes after creation.
205
+ // The body carries the full post-migration content, computed client-side
206
+ // by the type's owning app and validated against toTypeId's schema by
207
+ // ScopedStack.commitMigration(). Owner acting alone only: replacing
208
+ // content and typeId wholesale would have to re-derive every gate
209
+ // create() and update() apply at both ends.
210
+ app.post('/:id/migrate', requireOwner(ownerEntityId), async (c) => {
211
+ const id = c.req.param('id');
212
+ const auth = c.get('auth');
213
+ const body = await readJson(c);
214
+ if (!body.toTypeId || typeof body.toTypeId !== 'string')
215
+ throw new StackQueryError('toTypeId is required');
216
+ if (!body.content || typeof body.content !== 'object')
217
+ throw new StackQueryError('content is required');
218
+ if (!(await stack.getType(body.toTypeId)))
219
+ throw new StackValidationError([
220
+ { path: 'toTypeId', message: `Unknown type: "${body.toTypeId}"` },
221
+ ]);
222
+ const migrated = await stack
223
+ .forSession(auth)
224
+ .commitMigration(id, body.toTypeId, body.content, {
225
+ ifVersion: parseIfMatch(c.req.header('If-Match')),
226
+ });
227
+ return c.json(serializeRecord(migrated));
228
+ });
229
+ return app;
230
+ }
231
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/routes/records.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from '../types.js';
3
+ import type { StackContext } from '../stack.js';
4
+ export declare function tokenRoutes(ctx: StackContext): Hono<AppEnv>;
5
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/routes/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQhD,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CA+D3D"}
@@ -0,0 +1,65 @@
1
+ import { Hono } from 'hono';
2
+ import { requireOwner } from '../middleware/auth.js';
3
+ import { readJson } from '../lib/json.js';
4
+ import { parseDate } from '@haverstack/wire-types';
5
+ import { StackValidationError } from '@haverstack/core';
6
+ import { isValidDid } from '@haverstack/core/did';
7
+ export function tokenRoutes(ctx) {
8
+ const app = new Hono();
9
+ const { tokens, stack } = ctx;
10
+ const ownerEntityId = stack.ownerEntityId;
11
+ // POST /tokens — issue a new token (owner only). `onBehalfOf` asserts a
12
+ // delegation out of band: the owner names the subject the issued
13
+ // principal acts for, per docs/spec/wire-format.md § The session a
14
+ // token names.
15
+ app.post('/', requireOwner(ownerEntityId), async (c) => {
16
+ const body = await readJson(c);
17
+ if (body.entityId !== undefined && !isValidDid(body.entityId))
18
+ throw new StackValidationError([{ path: 'entityId', message: 'Must be a DID' }]);
19
+ if (body.onBehalfOf !== undefined && !isValidDid(body.onBehalfOf))
20
+ throw new StackValidationError([{ path: 'onBehalfOf', message: 'Must be a DID' }]);
21
+ const principalId = body.entityId ?? ownerEntityId;
22
+ const expiresAt = body.expiresAt ? parseDate(body.expiresAt) : undefined;
23
+ if (body.expiresAt && !expiresAt)
24
+ throw new StackValidationError([{ path: 'expiresAt', message: 'Invalid date' }]);
25
+ const { id, token } = await tokens.createToken(principalId, {
26
+ onBehalfOf: body.onBehalfOf,
27
+ label: body.label,
28
+ expiresAt,
29
+ });
30
+ // Read the row back rather than fabricating createdAt here — the store
31
+ // is the source of truth, and GET /tokens must report the same value.
32
+ const stored = (await tokens.listTokens()).find((t) => t.id === id);
33
+ return c.json({
34
+ id,
35
+ token,
36
+ principalId,
37
+ subjectId: body.onBehalfOf ?? principalId,
38
+ label: stored.label ?? null,
39
+ createdAt: stored.createdAt.toISOString(),
40
+ expiresAt: stored.expiresAt?.toISOString() ?? null,
41
+ }, 201);
42
+ });
43
+ // GET /tokens — list all DB-managed tokens; never returns token values
44
+ app.get('/', requireOwner(ownerEntityId), async (c) => {
45
+ const list = await tokens.listTokens();
46
+ return c.json({ tokens: list.map(serializeToken) });
47
+ });
48
+ // DELETE /tokens/:id — revoke a token by its ID
49
+ app.delete('/:id', requireOwner(ownerEntityId), async (c) => {
50
+ await tokens.revokeToken(c.req.param('id'));
51
+ return c.body(null, 204);
52
+ });
53
+ return app;
54
+ }
55
+ function serializeToken(t) {
56
+ return {
57
+ id: t.id,
58
+ principalId: t.principalId,
59
+ subjectId: t.subjectId,
60
+ label: t.label ?? null,
61
+ createdAt: t.createdAt.toISOString(),
62
+ expiresAt: t.expiresAt?.toISOString() ?? null,
63
+ };
64
+ }
65
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/routes/tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from '../types.js';
3
+ import type { StackContext } from '../stack.js';
4
+ export declare function typeRoutes(ctx: StackContext): Hono<AppEnv>;
5
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAYhD,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CA0C1D"}
@@ -0,0 +1,46 @@
1
+ import { Hono } from 'hono';
2
+ import { requireOwner } from '../middleware/auth.js';
3
+ import { readJson } from '../lib/json.js';
4
+ import { serializeType } from '@haverstack/wire-types';
5
+ import { hashSchema, StackQueryError, StackNotFoundError, StackValidationError, } from '@haverstack/core';
6
+ export function typeRoutes(ctx) {
7
+ const app = new Hono();
8
+ const { adapter, stack } = ctx;
9
+ app.get('/', async (c) => {
10
+ const types = await adapter.listTypes();
11
+ return c.json(types.map(serializeType));
12
+ });
13
+ app.get('/:id', async (c) => {
14
+ const id = decodeURIComponent(c.req.param('id'));
15
+ const type = await adapter.getType(id);
16
+ if (!type)
17
+ throw new StackNotFoundError('Type not found');
18
+ return c.json(serializeType(type));
19
+ });
20
+ app.post('/', requireOwner(stack.ownerEntityId), async (c) => {
21
+ const body = await readJson(c);
22
+ if (!body.id || typeof body.id !== 'string')
23
+ throw new StackQueryError('id is required');
24
+ if (!body.name || typeof body.name !== 'string')
25
+ throw new StackQueryError('name is required');
26
+ if (!body.schema || typeof body.schema !== 'object')
27
+ throw new StackQueryError('schema is required');
28
+ if (!body.schemaHash || typeof body.schemaHash !== 'string')
29
+ throw new StackQueryError('schemaHash is required');
30
+ const computedHash = await hashSchema(body.schema);
31
+ if (body.schemaHash !== computedHash)
32
+ throw new StackValidationError([
33
+ { path: 'schemaHash', message: 'schemaHash does not match schema' },
34
+ ]);
35
+ // stack.defineType() (not adapter.saveType() directly) is what runs the
36
+ // schema-drift check: redefining an existing typeId with anything
37
+ // beyond additive evolution throws StackSchemaDriftError, which
38
+ // adapter.saveType() alone has no way to enforce — it's a raw write.
39
+ const type = await stack.defineType(body.id, body.name, body.schema, {
40
+ ...(body.migratesFrom ? { migratesFrom: body.migratesFrom } : {}),
41
+ });
42
+ return c.json(serializeType(type), 201);
43
+ });
44
+ return app;
45
+ }
46
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,UAAU,UAAU,CAAC,GAAiB;IAC1C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAC;IAC/B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;IAE/B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;QACxC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QAC1D,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC3D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAA0B,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ;YAAE,MAAM,IAAI,eAAe,CAAC,gBAAgB,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,IAAI,eAAe,CAAC,kBAAkB,CAAC,CAAC;QAC/F,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;YACjD,MAAM,IAAI,eAAe,CAAC,oBAAoB,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;YACzD,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;QAEtD,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,MAAoB,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,UAAU,KAAK,YAAY;YAClC,MAAM,IAAI,oBAAoB,CAAC;gBAC7B,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,kCAAkC,EAAE;aACpE,CAAC,CAAC;QAEL,wEAAwE;QACxE,kEAAkE;QAClE,gEAAgE;QAChE,qEAAqE;QACrE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAoB,EAAE;YACjF,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5E,CAAC,CAAC;QACH,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Hono } from 'hono';
2
+ import type { AppEnv } from '../types.js';
3
+ import type { StackContext } from '../stack.js';
4
+ import type { Config } from '../config.js';
5
+ export declare function wellknownRoutes(ctx: StackContext, config: Config): Hono<AppEnv>;
6
+ //# sourceMappingURL=wellknown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wellknown.d.ts","sourceRoot":"","sources":["../../src/routes/wellknown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CA2C/E"}
@@ -0,0 +1,45 @@
1
+ import { Hono } from 'hono';
2
+ import { WIRE_PROTOCOL_VERSION, AUTH_METHOD_DID_CHALLENGE, CHANGE_TRANSPORT_SSE, } from '@haverstack/wire-types';
3
+ export function wellknownRoutes(ctx, config) {
4
+ const app = new Hono();
5
+ app.get('/stack', (c) => {
6
+ const body = {
7
+ version: WIRE_PROTOCOL_VERSION,
8
+ entityId: ctx.stack.ownerEntityId,
9
+ timezone: ctx.stack.timezone,
10
+ // The server's own request-size ceilings are authoritative here,
11
+ // overriding the adapter's `null` — nothing at the storage layer
12
+ // imposes a limit, but this server enforces both. See
13
+ // docs/spec/wire-format.md § Discovery.
14
+ capabilities: {
15
+ ...ctx.stack.features,
16
+ limits: {
17
+ attachmentBytes: config.maxAttachmentBytes,
18
+ contentBytes: config.maxContentBytes,
19
+ },
20
+ },
21
+ // The DID challenge-response handshake is always implemented by this
22
+ // server, so it's always advertised — a client holding a DID
23
+ // credential learns at open() that there's a handshake to perform,
24
+ // rather than discovering it as a 404 partway through one.
25
+ auth: { methods: [AUTH_METHOD_DID_CHALLENGE] },
26
+ // Top-level rather than inside `capabilities`, which carries only
27
+ // what the `...ctx.stack.features` spread brings. An object rather
28
+ // than a boolean for the same reason `auth` is: the surface grows
29
+ // entries — another transport, batched frames — not more booleans.
30
+ //
31
+ // Both flags are literals with no override: GET /changes does resume
32
+ // and does honor `?include=record`, and a client acts on this answer
33
+ // without asking again. An override could only ever make discovery
34
+ // lie about the route beside it.
35
+ changes: {
36
+ transports: [CHANGE_TRANSPORT_SSE],
37
+ resume: true,
38
+ records: true,
39
+ },
40
+ };
41
+ return c.json(body);
42
+ });
43
+ return app;
44
+ }
45
+ //# sourceMappingURL=wellknown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wellknown.js","sourceRoot":"","sources":["../../src/routes/wellknown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAMhC,MAAM,UAAU,eAAe,CAAC,GAAiB,EAAE,MAAc;IAC/D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAC;IAE/B,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,IAAI,GAAsB;YAC9B,OAAO,EAAE,qBAAqB;YAC9B,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,aAAa;YACjC,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ;YAC5B,iEAAiE;YACjE,iEAAiE;YACjE,sDAAsD;YACtD,wCAAwC;YACxC,YAAY,EAAE;gBACZ,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ;gBACrB,MAAM,EAAE;oBACN,eAAe,EAAE,MAAM,CAAC,kBAAkB;oBAC1C,YAAY,EAAE,MAAM,CAAC,eAAe;iBACrC;aACF;YACD,qEAAqE;YACrE,6DAA6D;YAC7D,mEAAmE;YACnE,2DAA2D;YAC3D,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,yBAAyB,CAAC,EAAE;YAC9C,kEAAkE;YAClE,mEAAmE;YACnE,kEAAkE;YAClE,mEAAmE;YACnE,EAAE;YACF,qEAAqE;YACrE,qEAAqE;YACrE,mEAAmE;YACnE,iCAAiC;YACjC,OAAO,EAAE;gBACP,UAAU,EAAE,CAAC,oBAAoB,CAAC;gBAClC,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,IAAI;aACd;SACF,CAAC;QACF,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { Logger } from 'pino';
2
+ import type { StackContext } from './stack.js';
3
+ /**
4
+ * The subset of node-server's return value shutdown() needs. `close()`
5
+ * mirrors Node's http.Server: it stops accepting new connections and waits
6
+ * for open ones to end before invoking the callback. `closeAllConnections()`
7
+ * (Node >=18.2) destroys every open connection immediately, which is what
8
+ * makes a pending close() callback fire once the grace period expires.
9
+ * Optional because node-server's `ServerType` also covers Http2Server, whose
10
+ * @types/node surface omits it even though the app only ever serves HTTP/1.
11
+ */
12
+ export type ShutdownServer = {
13
+ close(callback: () => void): void;
14
+ closeAllConnections?(): void;
15
+ };
16
+ /**
17
+ * Builds the shutdown sequence: stop the server (bounded by `timeoutMs` so a
18
+ * client holding a keep-alive connection open can't block it indefinitely),
19
+ * then release the stack's resources. Does not call process.exit — callers
20
+ * decide the exit code so this stays unit-testable.
21
+ */
22
+ export declare function createShutdownHandler(server: ShutdownServer, ctx: StackContext, logger: Logger, timeoutMs: number): (signal: string) => Promise<void>;
23
+ //# sourceMappingURL=shutdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAClC,mBAAmB,CAAC,IAAI,IAAI,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CA2BnC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Builds the shutdown sequence: stop the server (bounded by `timeoutMs` so a
3
+ * client holding a keep-alive connection open can't block it indefinitely),
4
+ * then release the stack's resources. Does not call process.exit — callers
5
+ * decide the exit code so this stays unit-testable.
6
+ */
7
+ export function createShutdownHandler(server, ctx, logger, timeoutMs) {
8
+ return async (signal) => {
9
+ logger.info({ signal }, 'Shutting down');
10
+ // A change-feed stream never drains on its own, so server.close() below
11
+ // would wait out the full grace period every time. See
12
+ // src/lib/changeStreams.ts.
13
+ ctx.changeStreams.closeAll();
14
+ await new Promise((resolve) => {
15
+ const timer = setTimeout(() => {
16
+ logger.warn({ timeoutMs }, 'Shutdown grace period exceeded; forcing connections closed');
17
+ server.closeAllConnections?.();
18
+ }, timeoutMs);
19
+ server.close(() => {
20
+ clearTimeout(timer);
21
+ resolve();
22
+ });
23
+ });
24
+ await ctx.queryWorker.close();
25
+ await ctx.stack.flush();
26
+ await ctx.stack.close();
27
+ await ctx.tokens.close();
28
+ ctx.nonces.close();
29
+ logger.info('Clean shutdown complete');
30
+ };
31
+ }
32
+ //# sourceMappingURL=shutdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAsB,EACtB,GAAiB,EACjB,MAAc,EACd,SAAiB;IAEjB,OAAO,KAAK,EAAE,MAAc,EAAE,EAAE;QAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC;QAEzC,wEAAwE;QACxE,uDAAuD;QACvD,4BAA4B;QAC5B,GAAG,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;QAE7B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,4DAA4D,CAAC,CAAC;gBACzF,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC;YACjC,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBAChB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACzC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { LocalAdapter } from '@haverstack/adapter-local';
2
+ import { Stack } from '@haverstack/core';
3
+ import type { StackTokenStore } from '@haverstack/core/wire';
4
+ import type { Logger } from 'pino';
5
+ import type { Config } from './config.js';
6
+ import { AuthNonceStore } from './lib/nonceStore.js';
7
+ import { QueryWorkerPool } from './lib/queryWorker/pool.js';
8
+ import { ChangeStreamRegistry } from './lib/changeStreams.js';
9
+ export type StackContext = {
10
+ adapter: LocalAdapter;
11
+ stack: Stack;
12
+ tokens: StackTokenStore & {
13
+ close(): Promise<void>;
14
+ };
15
+ nonces: AuthNonceStore;
16
+ queryWorker: QueryWorkerPool;
17
+ changeStreams: ChangeStreamRegistry;
18
+ };
19
+ export declare function initStack(config: Config, logger: Logger): Promise<StackContext>;
20
+ //# sourceMappingURL=stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA2C,MAAM,2BAA2B,CAAC;AAYlG,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAyB,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IAIb,MAAM,EAAE,eAAe,GAAG;QAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAGrD,MAAM,EAAE,cAAc,CAAC;IAIvB,WAAW,EAAE,eAAe,CAAC;IAG7B,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAEF,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwDrF"}