@haverstack/server 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +8 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +70 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/changeStreams.d.ts +17 -0
- package/dist/lib/changeStreams.d.ts.map +1 -0
- package/{src/lib/changeStreams.ts → dist/lib/changeStreams.js} +12 -12
- package/dist/lib/changeStreams.js.map +1 -0
- package/dist/lib/frameGate.d.ts +26 -0
- package/dist/lib/frameGate.d.ts.map +1 -0
- package/dist/lib/frameGate.js +44 -0
- package/dist/lib/frameGate.js.map +1 -0
- package/dist/lib/json.d.ts +10 -0
- package/dist/lib/json.d.ts.map +1 -0
- package/dist/lib/json.js +27 -0
- package/dist/lib/json.js.map +1 -0
- package/dist/lib/nonceStore.d.ts +50 -0
- package/dist/lib/nonceStore.d.ts.map +1 -0
- package/dist/lib/nonceStore.js +96 -0
- package/dist/lib/nonceStore.js.map +1 -0
- package/dist/lib/queryLimit.d.ts +4 -0
- package/dist/lib/queryLimit.d.ts.map +1 -0
- package/{src/lib/queryLimit.ts → dist/lib/queryLimit.js} +5 -6
- package/dist/lib/queryLimit.js.map +1 -0
- package/dist/lib/queryWorker/pool.d.ts +60 -0
- package/dist/lib/queryWorker/pool.d.ts.map +1 -0
- package/dist/lib/queryWorker/pool.js +278 -0
- package/dist/lib/queryWorker/pool.js.map +1 -0
- package/{src/lib/queryWorker/protocol.ts → dist/lib/queryWorker/protocol.d.ts} +21 -18
- package/dist/lib/queryWorker/protocol.d.ts.map +1 -0
- package/dist/lib/queryWorker/protocol.js +2 -0
- package/dist/lib/queryWorker/protocol.js.map +1 -0
- package/dist/lib/queryWorker/worker.d.ts +2 -0
- package/dist/lib/queryWorker/worker.d.ts.map +1 -0
- package/{src/lib/queryWorker/worker.ts → dist/lib/queryWorker/worker.js} +19 -22
- package/dist/lib/queryWorker/worker.js.map +1 -0
- package/dist/lib/resumeBuffer.d.ts +143 -0
- package/dist/lib/resumeBuffer.d.ts.map +1 -0
- package/dist/lib/resumeBuffer.js +216 -0
- package/dist/lib/resumeBuffer.js.map +1 -0
- package/dist/lib/resumeCursor.d.ts +16 -0
- package/dist/lib/resumeCursor.d.ts.map +1 -0
- package/{src/lib/resumeCursor.ts → dist/lib/resumeCursor.js} +22 -24
- package/dist/lib/resumeCursor.js.map +1 -0
- package/dist/lib/tokenSweep.d.ts +17 -0
- package/dist/lib/tokenSweep.d.ts.map +1 -0
- package/dist/lib/tokenSweep.js +40 -0
- package/dist/lib/tokenSweep.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +46 -0
- package/dist/main.js.map +1 -0
- package/dist/middleware/auth.d.ts +25 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +74 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/errors.d.ts +17 -0
- package/dist/middleware/errors.d.ts.map +1 -0
- package/dist/middleware/errors.js +64 -0
- package/dist/middleware/errors.js.map +1 -0
- package/dist/routes/attachments.d.ts +5 -0
- package/dist/routes/attachments.d.ts.map +1 -0
- package/dist/routes/attachments.js +104 -0
- package/dist/routes/attachments.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +110 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/changes.d.ts +22 -0
- package/dist/routes/changes.d.ts.map +1 -0
- package/dist/routes/changes.js +233 -0
- package/dist/routes/changes.js.map +1 -0
- package/dist/routes/entity.d.ts +5 -0
- package/dist/routes/entity.d.ts.map +1 -0
- package/dist/routes/entity.js +30 -0
- package/dist/routes/entity.js.map +1 -0
- package/dist/routes/health.d.ts +4 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +7 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/records.d.ts +5 -0
- package/dist/routes/records.d.ts.map +1 -0
- package/dist/routes/records.js +231 -0
- package/dist/routes/records.js.map +1 -0
- package/dist/routes/tokens.d.ts +5 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +65 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/routes/types.d.ts +5 -0
- package/dist/routes/types.d.ts.map +1 -0
- package/dist/routes/types.js +46 -0
- package/dist/routes/types.js.map +1 -0
- package/dist/routes/wellknown.d.ts +6 -0
- package/dist/routes/wellknown.d.ts.map +1 -0
- package/dist/routes/wellknown.js +45 -0
- package/dist/routes/wellknown.js.map +1 -0
- package/dist/shutdown.d.ts +23 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +32 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/stack.d.ts +20 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/stack.js +50 -0
- package/dist/stack.js.map +1 -0
- package/dist/testing.d.ts +49 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +130 -0
- package/dist/testing.js.map +1 -0
- package/{src/types.ts → dist/types.d.ts} +5 -5
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/{src/wireError.ts → dist/wireError.d.ts} +2 -9
- package/dist/wireError.d.ts.map +1 -0
- package/dist/wireError.js +13 -0
- package/dist/wireError.js.map +1 -0
- package/package.json +37 -12
- package/.claude/settings.local.json +0 -22
- package/.dockerignore +0 -10
- package/.env.example +0 -90
- package/.github/pull_request_template.md +0 -23
- package/.github/workflows/ci.yml +0 -55
- package/.github/workflows/release.yml +0 -99
- package/.prettierignore +0 -3
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -91
- package/CONTRIBUTING.md +0 -234
- package/Dockerfile +0 -30
- package/SECURITY.md +0 -54
- package/docs/api.md +0 -277
- package/docs/deployment.md +0 -212
- package/eslint.config.mjs +0 -18
- package/pnpm-workspace.yaml +0 -13
- package/src/app.ts +0 -86
- package/src/config.ts +0 -176
- package/src/index.ts +0 -54
- package/src/lib/frameGate.ts +0 -42
- package/src/lib/json.ts +0 -27
- package/src/lib/nonceStore.ts +0 -103
- package/src/lib/queryWorker/pool.ts +0 -324
- package/src/lib/resumeBuffer.ts +0 -268
- package/src/lib/tokenSweep.ts +0 -43
- package/src/middleware/auth.ts +0 -77
- package/src/middleware/errors.ts +0 -73
- package/src/routes/attachments.ts +0 -126
- package/src/routes/auth.ts +0 -137
- package/src/routes/changes.ts +0 -279
- package/src/routes/entity.ts +0 -33
- package/src/routes/health.ts +0 -8
- package/src/routes/records.ts +0 -259
- package/src/routes/tokens.ts +0 -85
- package/src/routes/types.ts +0 -57
- package/src/routes/wellknown.ts +0 -55
- package/src/shutdown.ts +0 -56
- package/src/stack.ts +0 -96
- package/tests/app.test.ts +0 -86
- package/tests/changeFeedClient.ts +0 -156
- package/tests/config.test.ts +0 -112
- package/tests/conformance.test.ts +0 -2322
- package/tests/fixtures/blockingWorker.ts +0 -27
- package/tests/fixtures/brokenWorker.ts +0 -7
- package/tests/lib/changeFeedClient.test.ts +0 -126
- package/tests/lib/frameGate.test.ts +0 -93
- package/tests/lib/queryLimit.test.ts +0 -33
- package/tests/lib/queryWorker.test.ts +0 -60
- package/tests/lib/queryWorkerPool.test.ts +0 -129
- package/tests/lib/resumeBuffer.test.ts +0 -341
- package/tests/lib/resumeCursor.test.ts +0 -46
- package/tests/middleware/errors.test.ts +0 -176
- package/tests/routes/associations.test.ts +0 -170
- package/tests/routes/attachments.test.ts +0 -621
- package/tests/routes/auth.test.ts +0 -448
- package/tests/routes/changes.test.ts +0 -567
- package/tests/routes/contentQuery.test.ts +0 -68
- package/tests/routes/entity.test.ts +0 -170
- package/tests/routes/groups.test.ts +0 -69
- package/tests/routes/records.test.ts +0 -1965
- package/tests/routes/relatedToQuery.test.ts +0 -182
- package/tests/routes/tokens.test.ts +0 -157
- package/tests/routes/tombstones.test.ts +0 -93
- package/tests/routes/types.test.ts +0 -129
- package/tests/routes/versions.test.ts +0 -177
- package/tests/routes/wellknown.test.ts +0 -71
- package/tests/setup.ts +0 -154
- package/tests/shutdown.test.ts +0 -121
- package/tests/stack.test.ts +0 -198
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -19
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -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"}
|
package/dist/shutdown.js
ADDED
|
@@ -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"}
|
package/dist/stack.d.ts
ADDED
|
@@ -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"}
|