@ketvietlab/ketjs 0.1.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.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/agent/capabilities.d.ts +62 -0
- package/dist/agent/capabilities.d.ts.map +1 -0
- package/dist/agent/capabilities.js +89 -0
- package/dist/agent/capabilities.js.map +1 -0
- package/dist/agent/permissions.d.ts +56 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +209 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +698 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen/dts.d.ts +3 -0
- package/dist/codegen/dts.d.ts.map +1 -0
- package/dist/codegen/dts.js +94 -0
- package/dist/codegen/dts.js.map +1 -0
- package/dist/data/adapter.d.ts +4 -0
- package/dist/data/adapter.d.ts.map +1 -0
- package/dist/data/adapter.js +18 -0
- package/dist/data/adapter.js.map +1 -0
- package/dist/data/changeset.d.ts +49 -0
- package/dist/data/changeset.d.ts.map +1 -0
- package/dist/data/changeset.js +206 -0
- package/dist/data/changeset.js.map +1 -0
- package/dist/data/expr.d.ts +59 -0
- package/dist/data/expr.d.ts.map +1 -0
- package/dist/data/expr.js +61 -0
- package/dist/data/expr.js.map +1 -0
- package/dist/data/fleet.d.ts +28 -0
- package/dist/data/fleet.d.ts.map +1 -0
- package/dist/data/fleet.js +82 -0
- package/dist/data/fleet.js.map +1 -0
- package/dist/data/list-search.d.ts +93 -0
- package/dist/data/list-search.d.ts.map +1 -0
- package/dist/data/list-search.js +268 -0
- package/dist/data/list-search.js.map +1 -0
- package/dist/data/migrate.d.ts +75 -0
- package/dist/data/migrate.d.ts.map +1 -0
- package/dist/data/migrate.js +136 -0
- package/dist/data/migrate.js.map +1 -0
- package/dist/data/pool.d.ts +20 -0
- package/dist/data/pool.d.ts.map +1 -0
- package/dist/data/pool.js +92 -0
- package/dist/data/pool.js.map +1 -0
- package/dist/data/query.d.ts +111 -0
- package/dist/data/query.d.ts.map +1 -0
- package/dist/data/query.js +268 -0
- package/dist/data/query.js.map +1 -0
- package/dist/data/sqlite.d.ts +3 -0
- package/dist/data/sqlite.d.ts.map +1 -0
- package/dist/data/sqlite.js +107 -0
- package/dist/data/sqlite.js.map +1 -0
- package/dist/data/time.d.ts +9 -0
- package/dist/data/time.d.ts.map +1 -0
- package/dist/data/time.js +107 -0
- package/dist/data/time.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/apps.d.ts +43 -0
- package/dist/kernel/apps.d.ts.map +1 -0
- package/dist/kernel/apps.js +234 -0
- package/dist/kernel/apps.js.map +1 -0
- package/dist/kernel/compose.d.ts +6 -0
- package/dist/kernel/compose.d.ts.map +1 -0
- package/dist/kernel/compose.js +976 -0
- package/dist/kernel/compose.js.map +1 -0
- package/dist/kernel/define.d.ts +9 -0
- package/dist/kernel/define.d.ts.map +1 -0
- package/dist/kernel/define.js +140 -0
- package/dist/kernel/define.js.map +1 -0
- package/dist/kernel/diff.d.ts +11 -0
- package/dist/kernel/diff.d.ts.map +1 -0
- package/dist/kernel/diff.js +106 -0
- package/dist/kernel/diff.js.map +1 -0
- package/dist/kernel/errors.d.ts +17 -0
- package/dist/kernel/errors.d.ts.map +1 -0
- package/dist/kernel/errors.js +43 -0
- package/dist/kernel/errors.js.map +1 -0
- package/dist/kernel/graph.d.ts +3 -0
- package/dist/kernel/graph.d.ts.map +1 -0
- package/dist/kernel/graph.js +56 -0
- package/dist/kernel/graph.js.map +1 -0
- package/dist/kernel/i18n.d.ts +43 -0
- package/dist/kernel/i18n.d.ts.map +1 -0
- package/dist/kernel/i18n.js +79 -0
- package/dist/kernel/i18n.js.map +1 -0
- package/dist/kernel/index.d.ts +7 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +7 -0
- package/dist/kernel/index.js.map +1 -0
- package/dist/kernel/layout.d.ts +17 -0
- package/dist/kernel/layout.d.ts.map +1 -0
- package/dist/kernel/layout.js +77 -0
- package/dist/kernel/layout.js.map +1 -0
- package/dist/kernel/menu.d.ts +27 -0
- package/dist/kernel/menu.d.ts.map +1 -0
- package/dist/kernel/menu.js +65 -0
- package/dist/kernel/menu.js.map +1 -0
- package/dist/kernel/modules.d.ts +31 -0
- package/dist/kernel/modules.d.ts.map +1 -0
- package/dist/kernel/modules.js +322 -0
- package/dist/kernel/modules.js.map +1 -0
- package/dist/kernel/routes.d.ts +24 -0
- package/dist/kernel/routes.d.ts.map +1 -0
- package/dist/kernel/routes.js +98 -0
- package/dist/kernel/routes.js.map +1 -0
- package/dist/kernel/types.d.ts +8 -0
- package/dist/kernel/types.d.ts.map +1 -0
- package/dist/kernel/types.js +67 -0
- package/dist/kernel/types.js.map +1 -0
- package/dist/kernel/workspace.d.ts +62 -0
- package/dist/kernel/workspace.d.ts.map +1 -0
- package/dist/kernel/workspace.js +156 -0
- package/dist/kernel/workspace.js.map +1 -0
- package/dist/scaffold/index.d.ts +2 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +49 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/templates/app.test.ts.tmpl +17 -0
- package/dist/scaffold/templates/biome.json.tmpl +30 -0
- package/dist/scaffold/templates/dev.mjs.tmpl +16 -0
- package/dist/scaffold/templates/gitignore.tmpl +24 -0
- package/dist/scaffold/templates/ket.workspace.ts.tmpl +18 -0
- package/dist/scaffold/templates/module.ts.tmpl +27 -0
- package/dist/scaffold/templates/package.json.tmpl +25 -0
- package/dist/scaffold/templates/tsconfig.json.tmpl +15 -0
- package/dist/server/boot.d.ts +173 -0
- package/dist/server/boot.d.ts.map +1 -0
- package/dist/server/boot.js +659 -0
- package/dist/server/boot.js.map +1 -0
- package/dist/server/config.d.ts +48 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +82 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/ctx.d.ts +13 -0
- package/dist/server/ctx.d.ts.map +1 -0
- package/dist/server/ctx.js +496 -0
- package/dist/server/ctx.js.map +1 -0
- package/dist/server/fn.d.ts +34 -0
- package/dist/server/fn.d.ts.map +1 -0
- package/dist/server/fn.js +170 -0
- package/dist/server/fn.js.map +1 -0
- package/dist/server/http.d.ts +79 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +459 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/idem.d.ts +31 -0
- package/dist/server/idem.d.ts.map +1 -0
- package/dist/server/idem.js +68 -0
- package/dist/server/idem.js.map +1 -0
- package/dist/server/jobs.d.ts +5 -0
- package/dist/server/jobs.d.ts.map +1 -0
- package/dist/server/jobs.js +24 -0
- package/dist/server/jobs.js.map +1 -0
- package/dist/server/multipart.d.ts +18 -0
- package/dist/server/multipart.d.ts.map +1 -0
- package/dist/server/multipart.js +183 -0
- package/dist/server/multipart.js.map +1 -0
- package/dist/server/project.d.ts +9 -0
- package/dist/server/project.d.ts.map +1 -0
- package/dist/server/project.js +67 -0
- package/dist/server/project.js.map +1 -0
- package/dist/server/queue.d.ts +80 -0
- package/dist/server/queue.d.ts.map +1 -0
- package/dist/server/queue.js +494 -0
- package/dist/server/queue.js.map +1 -0
- package/dist/server/respond.d.ts +91 -0
- package/dist/server/respond.d.ts.map +1 -0
- package/dist/server/respond.js +99 -0
- package/dist/server/respond.js.map +1 -0
- package/dist/server/runtime.d.ts +14 -0
- package/dist/server/runtime.d.ts.map +1 -0
- package/dist/server/runtime.js +25 -0
- package/dist/server/runtime.js.map +1 -0
- package/dist/server/session.d.ts +55 -0
- package/dist/server/session.d.ts.map +1 -0
- package/dist/server/session.js +172 -0
- package/dist/server/session.js.map +1 -0
- package/dist/server/sessionstore.d.ts +54 -0
- package/dist/server/sessionstore.d.ts.map +1 -0
- package/dist/server/sessionstore.js +232 -0
- package/dist/server/sessionstore.js.map +1 -0
- package/dist/server/storage/index.d.ts +11 -0
- package/dist/server/storage/index.d.ts.map +1 -0
- package/dist/server/storage/index.js +31 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/storage/local.d.ts +5 -0
- package/dist/server/storage/local.d.ts.map +1 -0
- package/dist/server/storage/local.js +165 -0
- package/dist/server/storage/local.js.map +1 -0
- package/dist/server/storage/s3.d.ts +11 -0
- package/dist/server/storage/s3.d.ts.map +1 -0
- package/dist/server/storage/s3.js +133 -0
- package/dist/server/storage/s3.js.map +1 -0
- package/dist/server/storage/sigv4.d.ts +23 -0
- package/dist/server/storage/sigv4.d.ts.map +1 -0
- package/dist/server/storage/sigv4.js +79 -0
- package/dist/server/storage/sigv4.js.map +1 -0
- package/dist/server/storage/types.d.ts +37 -0
- package/dist/server/storage/types.d.ts.map +1 -0
- package/dist/server/storage/types.js +82 -0
- package/dist/server/storage/types.js.map +1 -0
- package/dist/server/stream.d.ts +43 -0
- package/dist/server/stream.d.ts.map +1 -0
- package/dist/server/stream.js +117 -0
- package/dist/server/stream.js.map +1 -0
- package/dist/server/streamstore.d.ts +29 -0
- package/dist/server/streamstore.d.ts.map +1 -0
- package/dist/server/streamstore.js +140 -0
- package/dist/server/streamstore.js.map +1 -0
- package/dist/server/tenants.d.ts +108 -0
- package/dist/server/tenants.d.ts.map +1 -0
- package/dist/server/tenants.js +172 -0
- package/dist/server/tenants.js.map +1 -0
- package/dist/server/transport/index.d.ts +5 -0
- package/dist/server/transport/index.d.ts.map +1 -0
- package/dist/server/transport/index.js +3 -0
- package/dist/server/transport/index.js.map +1 -0
- package/dist/server/transport/memory.d.ts +14 -0
- package/dist/server/transport/memory.d.ts.map +1 -0
- package/dist/server/transport/memory.js +36 -0
- package/dist/server/transport/memory.js.map +1 -0
- package/dist/server/transport/types.d.ts +44 -0
- package/dist/server/transport/types.d.ts.map +1 -0
- package/dist/server/transport/types.js +79 -0
- package/dist/server/transport/types.js.map +1 -0
- package/dist/server/worker.d.ts +33 -0
- package/dist/server/worker.d.ts.map +1 -0
- package/dist/server/worker.js +428 -0
- package/dist/server/worker.js.map +1 -0
- package/dist/testing.d.ts +146 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +406 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme/contracts.d.ts +4 -0
- package/dist/theme/contracts.d.ts.map +1 -0
- package/dist/theme/contracts.js +87 -0
- package/dist/theme/contracts.js.map +1 -0
- package/dist/theme/index.d.ts +8 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +7 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/joints.d.ts +19 -0
- package/dist/theme/joints.d.ts.map +1 -0
- package/dist/theme/joints.js +126 -0
- package/dist/theme/joints.js.map +1 -0
- package/dist/theme/ktl/compile.d.ts +26 -0
- package/dist/theme/ktl/compile.d.ts.map +1 -0
- package/dist/theme/ktl/compile.js +226 -0
- package/dist/theme/ktl/compile.js.map +1 -0
- package/dist/theme/ktl/lexer.d.ts +20 -0
- package/dist/theme/ktl/lexer.d.ts.map +1 -0
- package/dist/theme/ktl/lexer.js +56 -0
- package/dist/theme/ktl/lexer.js.map +1 -0
- package/dist/theme/ktl/parser.d.ts +64 -0
- package/dist/theme/ktl/parser.d.ts.map +1 -0
- package/dist/theme/ktl/parser.js +220 -0
- package/dist/theme/ktl/parser.js.map +1 -0
- package/dist/theme/render.d.ts +19 -0
- package/dist/theme/render.d.ts.map +1 -0
- package/dist/theme/render.js +246 -0
- package/dist/theme/render.js.map +1 -0
- package/dist/theme/templates.d.ts +2 -0
- package/dist/theme/templates.d.ts.map +1 -0
- package/dist/theme/templates.js +40 -0
- package/dist/theme/templates.js.map +1 -0
- package/dist/theme/tokens.d.ts +4 -0
- package/dist/theme/tokens.d.ts.map +1 -0
- package/dist/theme/tokens.js +15 -0
- package/dist/theme/tokens.js.map +1 -0
- package/dist/theme/viewmodel.d.ts +6 -0
- package/dist/theme/viewmodel.d.ts.map +1 -0
- package/dist/theme/viewmodel.js +40 -0
- package/dist/theme/viewmodel.js.map +1 -0
- package/dist/types.d.ts +614 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// One server function, three surfaces: an HTTP endpoint, the typed client that
|
|
2
|
+
// calls it, and an agent tool descriptor — all read off the same manifest entry.
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
|
+
import { isNavigationRequest, navigablePage } from './respond.js';
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
import { join, normalize, extname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { callFn } from './fn.js';
|
|
10
|
+
import { createStreams, dbStreamStore, memoryStreamStore } from './stream.js';
|
|
11
|
+
import { agentDescriptor } from '../agent/capabilities.js';
|
|
12
|
+
import { KetError } from '../kernel/errors.js';
|
|
13
|
+
import { KetError as KetErr } from '../kernel/errors.js';
|
|
14
|
+
import { compileRoutes } from '../kernel/routes.js';
|
|
15
|
+
import { html, trustedMarkup } from '@ketvietlab/ketjs-view';
|
|
16
|
+
const ASSET_MIME = {
|
|
17
|
+
'.css': 'text/css',
|
|
18
|
+
'.js': 'text/javascript',
|
|
19
|
+
'.mjs': 'text/javascript',
|
|
20
|
+
'.svg': 'image/svg+xml',
|
|
21
|
+
'.png': 'image/png',
|
|
22
|
+
'.jpg': 'image/jpeg',
|
|
23
|
+
'.woff2': 'font/woff2',
|
|
24
|
+
'.ico': 'image/x-icon',
|
|
25
|
+
};
|
|
26
|
+
const json = (res, status, body) => {
|
|
27
|
+
const s = JSON.stringify(body, null, 2);
|
|
28
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
29
|
+
res.end(s);
|
|
30
|
+
};
|
|
31
|
+
const contentType = (type) => {
|
|
32
|
+
const lower = type.toLowerCase();
|
|
33
|
+
const textual = lower.startsWith('text/') ||
|
|
34
|
+
lower === 'application/json' ||
|
|
35
|
+
lower.endsWith('+json') ||
|
|
36
|
+
lower === 'application/xml' ||
|
|
37
|
+
lower.endsWith('+xml') ||
|
|
38
|
+
lower === 'image/svg+xml';
|
|
39
|
+
return textual && !lower.includes('charset=') ? `${type}; charset=utf-8` : type;
|
|
40
|
+
};
|
|
41
|
+
const islandScript = '<script type="module" src="/_ket/islands.js"></script>';
|
|
42
|
+
const viewRuntimeUrl = '/_ket/view/index.js';
|
|
43
|
+
const bootstrapDocument = (body) => {
|
|
44
|
+
if ((!body.includes('<ket-island') && !body.includes('data-ket-slot=')) ||
|
|
45
|
+
body.includes('src="/_ket/islands.js"'))
|
|
46
|
+
return body;
|
|
47
|
+
const closingBody = body.lastIndexOf('</body>');
|
|
48
|
+
return closingBody < 0
|
|
49
|
+
? body + islandScript
|
|
50
|
+
: body.slice(0, closingBody) + islandScript + body.slice(closingBody);
|
|
51
|
+
};
|
|
52
|
+
const browserBootstrap = (clients) => `import {
|
|
53
|
+
createIslandManager,
|
|
54
|
+
domHost,
|
|
55
|
+
} from ${JSON.stringify(viewRuntimeUrl)}
|
|
56
|
+
|
|
57
|
+
const definitions = ${JSON.stringify(clients)}
|
|
58
|
+
const registry = Object.create(null)
|
|
59
|
+
const loading = new Map()
|
|
60
|
+
const loadFactory = async (name) => {
|
|
61
|
+
if (registry[name]) return registry[name]
|
|
62
|
+
const definition = definitions[name]
|
|
63
|
+
if (!definition) return null
|
|
64
|
+
if (loading.has(name)) return loading.get(name)
|
|
65
|
+
const pending = (async () => {
|
|
66
|
+
const module = await import(definition.src)
|
|
67
|
+
const factory = module[definition.export]
|
|
68
|
+
if (typeof factory !== 'function') {
|
|
69
|
+
throw new TypeError('island "' + name + '" does not export a factory named "' + definition.export + '"')
|
|
70
|
+
}
|
|
71
|
+
registry[name] = factory
|
|
72
|
+
return factory
|
|
73
|
+
})()
|
|
74
|
+
loading.set(name, pending)
|
|
75
|
+
try { return await pending } finally { loading.delete(name) }
|
|
76
|
+
}
|
|
77
|
+
const loadPlaced = async (root, requireKnown = false) => {
|
|
78
|
+
const names = new Set(Array.from(root.querySelectorAll('ket-island'), (element) => element.getAttribute('data-island')).filter(Boolean))
|
|
79
|
+
if (requireKnown) {
|
|
80
|
+
const unknown = Array.from(names).find((name) => !definitions[name])
|
|
81
|
+
if (unknown) throw new Error('navigation fragment contains unknown island "' + unknown + '"')
|
|
82
|
+
}
|
|
83
|
+
await Promise.all(Array.from(names, loadFactory))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await loadPlaced(document)
|
|
87
|
+
const islands = createIslandManager(domHost(), registry, { strict: false })
|
|
88
|
+
islands.hydrate(document)
|
|
89
|
+
|
|
90
|
+
const event = (name, detail) => document.dispatchEvent(new CustomEvent(name, { detail }))
|
|
91
|
+
const hardNavigate = (target) => window.location.assign(String(target))
|
|
92
|
+
const fragmentsType = 'text/vnd.ket.fragments+html'
|
|
93
|
+
const slotSelector = (name) => '[data-ket-slot="' + name + '"]'
|
|
94
|
+
|
|
95
|
+
const applyFragments = async (markup) => {
|
|
96
|
+
const parsed = new DOMParser().parseFromString(markup, 'text/html')
|
|
97
|
+
const envelope = parsed.querySelector('ket-fragments')
|
|
98
|
+
if (!envelope) throw new Error('navigation response has no ket-fragments envelope')
|
|
99
|
+
const templates = Array.from(envelope.querySelectorAll('template[data-ket-slot]'))
|
|
100
|
+
if (!templates.length) throw new Error('navigation response has no slots')
|
|
101
|
+
const names = new Set()
|
|
102
|
+
for (const template of templates) {
|
|
103
|
+
const name = template.getAttribute('data-ket-slot')
|
|
104
|
+
if (!name || names.has(name)) throw new Error('navigation response has a missing or duplicate slot')
|
|
105
|
+
names.add(name)
|
|
106
|
+
if (document.querySelectorAll(slotSelector(name)).length !== 1)
|
|
107
|
+
throw new Error('current document does not have exactly one slot named "' + name + '"')
|
|
108
|
+
}
|
|
109
|
+
await Promise.all(templates.map((template) => loadPlaced(template.content, true)))
|
|
110
|
+
const changed = []
|
|
111
|
+
for (const template of templates) {
|
|
112
|
+
const name = template.getAttribute('data-ket-slot')
|
|
113
|
+
const slot = document.querySelector(slotSelector(name))
|
|
114
|
+
islands.reconcile(slot, template.content)
|
|
115
|
+
changed.push(slot)
|
|
116
|
+
}
|
|
117
|
+
const title = envelope.getAttribute('data-title')
|
|
118
|
+
if (title !== null) document.title = title
|
|
119
|
+
return changed
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let active = null
|
|
123
|
+
const saveScroll = () => {
|
|
124
|
+
const state = { ...(history.state ?? {}), __ketScroll: [window.scrollX, window.scrollY] }
|
|
125
|
+
history.replaceState(state, '', location.href)
|
|
126
|
+
}
|
|
127
|
+
const focusAfterNavigation = (url, changed, scroll) => {
|
|
128
|
+
if (url.hash) {
|
|
129
|
+
const target = document.getElementById(decodeURIComponent(url.hash.slice(1)))
|
|
130
|
+
if (target) {
|
|
131
|
+
target.focus?.({ preventScroll: true })
|
|
132
|
+
target.scrollIntoView()
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const [x, y] = scroll ?? [0, 0]
|
|
137
|
+
window.scrollTo(x, y)
|
|
138
|
+
const target = changed.find((slot) => slot.matches?.('[data-ket-slot$="content"], main')) ?? changed[0]
|
|
139
|
+
if (!target) return
|
|
140
|
+
const hadTabIndex = target.hasAttribute('tabindex')
|
|
141
|
+
if (!hadTabIndex) target.setAttribute('tabindex', '-1')
|
|
142
|
+
target.focus?.({ preventScroll: true })
|
|
143
|
+
if (!hadTabIndex) target.addEventListener('blur', () => target.removeAttribute('tabindex'), { once: true })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const navigate = async (asked, mode = 'push', scroll = null) => {
|
|
147
|
+
const target = new URL(asked, location.href)
|
|
148
|
+
if (mode === 'push') saveScroll()
|
|
149
|
+
active?.abort()
|
|
150
|
+
const controller = new AbortController()
|
|
151
|
+
active = controller
|
|
152
|
+
document.documentElement.setAttribute('data-ket-navigating', '')
|
|
153
|
+
document.documentElement.setAttribute('aria-busy', 'true')
|
|
154
|
+
event('ket:navigation-start', { url: target.href, mode })
|
|
155
|
+
let fallback = target.href
|
|
156
|
+
try {
|
|
157
|
+
const response = await fetch(target, {
|
|
158
|
+
credentials: 'same-origin',
|
|
159
|
+
headers: {
|
|
160
|
+
accept: fragmentsType + ', text/html;q=0.9',
|
|
161
|
+
'x-ket-navigation': 'fragment-v1',
|
|
162
|
+
},
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
})
|
|
165
|
+
fallback = response.url || fallback
|
|
166
|
+
if (!response.ok || !response.headers.get('content-type')?.toLowerCase().startsWith(fragmentsType))
|
|
167
|
+
throw new Error('server did not return a navigation fragment')
|
|
168
|
+
const changed = await applyFragments(await response.text())
|
|
169
|
+
const finalUrl = new URL(response.url || target.href)
|
|
170
|
+
if (mode === 'push') history.pushState({ __ketScroll: [0, 0] }, '', finalUrl.href)
|
|
171
|
+
focusAfterNavigation(finalUrl, changed, scroll)
|
|
172
|
+
event('ket:navigation-complete', { url: finalUrl.href, mode })
|
|
173
|
+
} catch (caught) {
|
|
174
|
+
if (controller.signal.aborted) return
|
|
175
|
+
event('ket:navigation-error', { url: fallback, mode, error: caught })
|
|
176
|
+
hardNavigate(fallback)
|
|
177
|
+
} finally {
|
|
178
|
+
if (active === controller) {
|
|
179
|
+
active = null
|
|
180
|
+
document.documentElement.removeAttribute('data-ket-navigating')
|
|
181
|
+
document.documentElement.removeAttribute('aria-busy')
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const optedOut = (element) => Boolean(element.closest?.('[data-ket-reload]'))
|
|
187
|
+
const navigationEnabled = Boolean(document.querySelector('[data-ket-slot]'))
|
|
188
|
+
document.addEventListener('click', (click) => {
|
|
189
|
+
if (!navigationEnabled) return
|
|
190
|
+
if (click.defaultPrevented || click.button !== 0 || click.metaKey || click.ctrlKey || click.shiftKey || click.altKey) return
|
|
191
|
+
const anchor = click.target.closest?.('a[href]')
|
|
192
|
+
if (!anchor || optedOut(anchor) || anchor.hasAttribute('download')) return
|
|
193
|
+
if (anchor.target && anchor.target !== '_self') return
|
|
194
|
+
const target = new URL(anchor.href, location.href)
|
|
195
|
+
if (target.origin !== location.origin) return
|
|
196
|
+
if (target.pathname === location.pathname && target.search === location.search && target.hash !== location.hash) return
|
|
197
|
+
click.preventDefault()
|
|
198
|
+
void navigate(target)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
document.addEventListener('submit', (submit) => {
|
|
202
|
+
if (!navigationEnabled) return
|
|
203
|
+
if (submit.defaultPrevented) return
|
|
204
|
+
const form = submit.target
|
|
205
|
+
if (!(form instanceof HTMLFormElement) || optedOut(form) || String(form.method || 'get').toLowerCase() !== 'get') return
|
|
206
|
+
const target = new URL(form.action || location.href, location.href)
|
|
207
|
+
if (target.origin !== location.origin || (form.target && form.target !== '_self')) return
|
|
208
|
+
target.search = ''
|
|
209
|
+
for (const [name, value] of new FormData(form, submit.submitter))
|
|
210
|
+
target.searchParams.append(name, typeof value === 'string' ? value : value.name)
|
|
211
|
+
submit.preventDefault()
|
|
212
|
+
void navigate(target)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
if (navigationEnabled) {
|
|
216
|
+
history.scrollRestoration = 'manual'
|
|
217
|
+
if (!history.state?.__ketScroll) history.replaceState({ ...(history.state ?? {}), __ketScroll: [window.scrollX, window.scrollY] }, '', location.href)
|
|
218
|
+
window.addEventListener('popstate', (pop) => void navigate(location.href, 'pop', pop.state?.__ketScroll ?? [0, 0]))
|
|
219
|
+
}
|
|
220
|
+
globalThis.__ketNavigation = { applyFragments, navigate, islands }
|
|
221
|
+
`;
|
|
222
|
+
const send = async (res, result) => {
|
|
223
|
+
if (typeof result.body === 'string' || result.body instanceof Uint8Array) {
|
|
224
|
+
const body = typeof result.body === 'string' &&
|
|
225
|
+
(result.type ?? 'text/html').toLowerCase().startsWith('text/html') &&
|
|
226
|
+
(result.body.includes('<ket-island') || result.body.includes('data-ket-slot='))
|
|
227
|
+
? bootstrapDocument(result.body)
|
|
228
|
+
: result.body;
|
|
229
|
+
res.writeHead(result.status ?? 200, {
|
|
230
|
+
'content-type': contentType(result.type ?? 'text/html'),
|
|
231
|
+
...result.headers,
|
|
232
|
+
});
|
|
233
|
+
res.end(body);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
res.writeHead(result.status ?? 200, {
|
|
237
|
+
'content-type': contentType(result.type ?? 'text/html'),
|
|
238
|
+
...result.headers,
|
|
239
|
+
});
|
|
240
|
+
// pipeline owns backpressure and destroys the source when the client disappears.
|
|
241
|
+
// Awaiting 'drain' by hand never settles on an abort — the response emits only
|
|
242
|
+
// 'close' — which hung the handler and leaked one fd per cancelled download.
|
|
243
|
+
await pipeline(result.body, res);
|
|
244
|
+
};
|
|
245
|
+
const readBody = async (req) => {
|
|
246
|
+
const chunks = [];
|
|
247
|
+
for await (const c of req)
|
|
248
|
+
chunks.push(c);
|
|
249
|
+
if (!chunks.length)
|
|
250
|
+
return {};
|
|
251
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
252
|
+
};
|
|
253
|
+
export async function createKetServer(o) {
|
|
254
|
+
if (!o.adapter && !o.pool)
|
|
255
|
+
throw new KetErr({ code: 'E_NO_DATABASE', message: 'createKetServer needs either an adapter or a pool' });
|
|
256
|
+
if (o.pool && !o.resolveDatastore)
|
|
257
|
+
throw new KetErr({
|
|
258
|
+
code: 'E_NO_RESOLVER',
|
|
259
|
+
message: 'a pool needs resolveDatastore to know which database a request belongs to',
|
|
260
|
+
});
|
|
261
|
+
// Compile once: matching stays cheap per request, and an ambiguous surface is
|
|
262
|
+
// refused before the server starts rather than depending on object insertion order.
|
|
263
|
+
const matchRoute = compileRoutes(o.routes ?? {});
|
|
264
|
+
// Per-request database resolution. The single-adapter case is the same code path
|
|
265
|
+
// with a resolver that always answers the same thing.
|
|
266
|
+
const withDb = async (url, req, fn) => {
|
|
267
|
+
if (!o.pool)
|
|
268
|
+
return fn(o.adapter);
|
|
269
|
+
const key = o.resolveDatastore(url, req);
|
|
270
|
+
if (!key) {
|
|
271
|
+
throw new KetErr({
|
|
272
|
+
code: 'E_UNKNOWN_TENANT',
|
|
273
|
+
message: `no datastore for ${url.host}${url.pathname}`,
|
|
274
|
+
hint: 'resolveDatastore returned null — the request names a tenant this app does not serve',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return o.pool.with(key, fn);
|
|
278
|
+
};
|
|
279
|
+
// With one database the stream store lives in it. With a database per tenant it
|
|
280
|
+
// does not: whose database a stream belongs to is a separate question, so the
|
|
281
|
+
// default stays in memory and the caller passes a store when they have answered it.
|
|
282
|
+
const configuredMounts = o.assets ? (Array.isArray(o.assets) ? o.assets : [o.assets]) : [];
|
|
283
|
+
// Browser-safe ketjs-view output is framework infrastructure, like /_ket/fn:
|
|
284
|
+
// callers should not need to find and mount a transitive package themselves.
|
|
285
|
+
const mounts = [
|
|
286
|
+
{
|
|
287
|
+
prefix: '/_ket/view/',
|
|
288
|
+
// `tsx` resolves the workspace package to src/index.ts while production
|
|
289
|
+
// resolves its export to dist/index.js. Both entries share this dist root.
|
|
290
|
+
dir: fileURLToPath(new URL('../dist/', import.meta.resolve('@ketvietlab/ketjs-view'))),
|
|
291
|
+
},
|
|
292
|
+
...configuredMounts,
|
|
293
|
+
];
|
|
294
|
+
const resolveTheme = async (url, req) => !o.theme ? null : typeof o.theme === 'function' ? o.theme(url, req) : o.theme;
|
|
295
|
+
const resolveIslandClients = async (url, req, theme) => {
|
|
296
|
+
if (typeof o.islandClients === 'function')
|
|
297
|
+
return o.islandClients(url, req);
|
|
298
|
+
return o.islandClients ?? theme?.clients ?? {};
|
|
299
|
+
};
|
|
300
|
+
const streams = await createStreams(o.streamStore ?? (o.adapter ? dbStreamStore(o.adapter) : memoryStreamStore()));
|
|
301
|
+
const server = createServer(async (req, res) => {
|
|
302
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
303
|
+
try {
|
|
304
|
+
// Static files. Several mounts, because the app has its own and every
|
|
305
|
+
// installed module may ship some — and a module's must stop being served the
|
|
306
|
+
// moment it is switched off, which is what `resolve` is for: it answers per
|
|
307
|
+
// request instead of being fixed when the server was built.
|
|
308
|
+
for (const mount of mounts) {
|
|
309
|
+
if (!url.pathname.startsWith(mount.prefix))
|
|
310
|
+
continue;
|
|
311
|
+
// Path traversal is the one thing a static handler must not get wrong.
|
|
312
|
+
const rel = normalize(url.pathname.slice(mount.prefix.length)).replace(/^(\.\.[/\\])+/, '');
|
|
313
|
+
const file = mount.dir !== undefined
|
|
314
|
+
? rel && !rel.startsWith('..')
|
|
315
|
+
? join(mount.dir, rel)
|
|
316
|
+
: null
|
|
317
|
+
: await mount.resolve(rel, url, req);
|
|
318
|
+
if (file === null)
|
|
319
|
+
break;
|
|
320
|
+
try {
|
|
321
|
+
const body = await readFile(file);
|
|
322
|
+
const type = ASSET_MIME[extname(rel)] ?? 'application/octet-stream';
|
|
323
|
+
res.writeHead(200, { 'content-type': contentType(type), 'cache-control': 'no-cache' });
|
|
324
|
+
return res.end(body);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (mounts.some((m) => url.pathname.startsWith(m.prefix))) {
|
|
331
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
332
|
+
return res.end('not found');
|
|
333
|
+
}
|
|
334
|
+
const route = matchRoute(url.pathname);
|
|
335
|
+
if (route) {
|
|
336
|
+
const r = await route.value(url, req, route.params);
|
|
337
|
+
return await send(res, r);
|
|
338
|
+
}
|
|
339
|
+
if (url.pathname === '/_ket/manifest')
|
|
340
|
+
return json(res, 200, o.manifest);
|
|
341
|
+
if (url.pathname === '/_ket/agent')
|
|
342
|
+
return json(res, 200, agentDescriptor(o.manifest));
|
|
343
|
+
if (url.pathname === '/_ket/islands.js') {
|
|
344
|
+
const theme = await resolveTheme(url, req);
|
|
345
|
+
const clients = await resolveIslandClients(url, req, theme);
|
|
346
|
+
res.writeHead(200, {
|
|
347
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
348
|
+
'cache-control': 'no-cache',
|
|
349
|
+
});
|
|
350
|
+
return res.end(browserBootstrap(clients));
|
|
351
|
+
}
|
|
352
|
+
// Resumable stream: the client reconnects with ?from=<cursor> and gets
|
|
353
|
+
// exactly what it missed, never a duplicate and never a gap.
|
|
354
|
+
if (url.pathname.startsWith('/_ket/stream/')) {
|
|
355
|
+
const id = url.pathname.slice('/_ket/stream/'.length);
|
|
356
|
+
const from = Number(url.searchParams.get('from') ?? 0);
|
|
357
|
+
res.writeHead(200, {
|
|
358
|
+
'content-type': 'text/event-stream',
|
|
359
|
+
'cache-control': 'no-cache',
|
|
360
|
+
connection: 'keep-alive',
|
|
361
|
+
});
|
|
362
|
+
// A client that reloads mid-stream simply disappears. Stop tailing the
|
|
363
|
+
// moment that happens: the durable log keeps the chunks, and the next
|
|
364
|
+
// connection resumes from its cursor.
|
|
365
|
+
let open = true;
|
|
366
|
+
const stop = () => {
|
|
367
|
+
open = false;
|
|
368
|
+
};
|
|
369
|
+
req.on('close', stop);
|
|
370
|
+
res.on('close', stop);
|
|
371
|
+
for await (const chunk of streams.tail(id, from, { timeoutMs: 30_000 })) {
|
|
372
|
+
if (!open || res.writableEnded)
|
|
373
|
+
return;
|
|
374
|
+
res.write(`id: ${chunk.seq}\ndata: ${JSON.stringify(chunk.data)}\n\n`);
|
|
375
|
+
}
|
|
376
|
+
if (!open || res.writableEnded)
|
|
377
|
+
return;
|
|
378
|
+
res.write('event: done\ndata: {}\n\n');
|
|
379
|
+
return res.end();
|
|
380
|
+
}
|
|
381
|
+
if (url.pathname.startsWith('/_ket/fn/') && req.method === 'POST') {
|
|
382
|
+
const fnKey = decodeURIComponent(url.pathname.slice('/_ket/fn/'.length));
|
|
383
|
+
const meta = o.manifest.functions[fnKey];
|
|
384
|
+
if (meta?.exposure === 'internal') {
|
|
385
|
+
throw new KetError({
|
|
386
|
+
code: 'E_FUNCTION_INTERNAL',
|
|
387
|
+
message: `server function "${fnKey}" is internal and has no generic HTTP endpoint`,
|
|
388
|
+
hint: 'call it from the trusted route that owns its security policy',
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
const args = await readBody(req);
|
|
392
|
+
// Resolved before the pool lease, so a session lookup never holds one.
|
|
393
|
+
const scope = await o.resolveScope?.(url, req);
|
|
394
|
+
const allow = await o.resolveAllow?.(url, req);
|
|
395
|
+
const actor = await o.resolveActor?.(url, req);
|
|
396
|
+
const result = await withDb(url, req, (adapter) => callFn(fnKey, args, {
|
|
397
|
+
adapter,
|
|
398
|
+
manifest: o.manifest,
|
|
399
|
+
scope,
|
|
400
|
+
allow,
|
|
401
|
+
actor,
|
|
402
|
+
queueNotify: o.queueNotify,
|
|
403
|
+
dryRun: url.searchParams.get('dryRun') === '1',
|
|
404
|
+
idempotencyKey: req.headers['idempotency-key'] ?? null,
|
|
405
|
+
}));
|
|
406
|
+
return json(res, 200, result);
|
|
407
|
+
}
|
|
408
|
+
if (o.theme) {
|
|
409
|
+
const theme = await resolveTheme(url, req);
|
|
410
|
+
if (!theme) {
|
|
411
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
412
|
+
return res.end('not found');
|
|
413
|
+
}
|
|
414
|
+
const scope = o.pageScope ? await o.pageScope(url, req) : {};
|
|
415
|
+
const pageRegion = o.pageRegion;
|
|
416
|
+
if (pageRegion && isNavigationRequest(req)) {
|
|
417
|
+
const page = scope['page'];
|
|
418
|
+
return send(res, navigablePage(req, {
|
|
419
|
+
title: String(page?.title ?? ''),
|
|
420
|
+
document: () => html ``,
|
|
421
|
+
slots: {
|
|
422
|
+
[pageRegion]: () => html `${trustedMarkup(theme.renderRegion(pageRegion, scope))}`,
|
|
423
|
+
},
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
const fullHtml = bootstrapDocument(theme.renderRegion('layout', scope));
|
|
427
|
+
res.writeHead(200, {
|
|
428
|
+
'content-type': 'text/html; charset=utf-8',
|
|
429
|
+
...(o.pageRegion ? { vary: 'X-Ket-Navigation' } : {}),
|
|
430
|
+
});
|
|
431
|
+
return res.end(fullHtml);
|
|
432
|
+
}
|
|
433
|
+
return json(res, 404, { code: 'E_NOT_FOUND', message: `no route for ${url.pathname}` });
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
// A streaming response has already sent its headers; there is no status code
|
|
437
|
+
// left to send, so the only honest thing is to close the socket.
|
|
438
|
+
if (res.headersSent) {
|
|
439
|
+
if (!res.writableEnded)
|
|
440
|
+
res.destroy(e);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (e instanceof KetError)
|
|
444
|
+
return json(res, e.code === 'E_PAYLOAD_TOO_LARGE' ? 413 : 400, e.toJSON());
|
|
445
|
+
return json(res, 500, { code: 'E_INTERNAL', message: e.message });
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
return {
|
|
449
|
+
server,
|
|
450
|
+
streams,
|
|
451
|
+
listen(port = o.port ?? 3000) {
|
|
452
|
+
return new Promise((resolve) => server.listen(port, () => resolve(server.address().port)));
|
|
453
|
+
},
|
|
454
|
+
close() {
|
|
455
|
+
return new Promise((r) => server.close(() => r()));
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../../../../packages/ketjs/src/server/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Adapter } from '../types.ts';
|
|
2
|
+
export declare const IDEM_DDL = "\nCREATE TABLE IF NOT EXISTS ket_idem (\n key TEXT PRIMARY KEY,\n fn TEXT NOT NULL,\n state TEXT NOT NULL,\n result TEXT,\n created_at TEXT NOT NULL\n);\n";
|
|
3
|
+
export declare const IDEM_DDL_PG: string;
|
|
4
|
+
export type IdemRecord = {
|
|
5
|
+
state: 'pending' | 'done';
|
|
6
|
+
result: unknown;
|
|
7
|
+
};
|
|
8
|
+
export declare function createIdempotency(adapter: Adapter, o?: {
|
|
9
|
+
now?: () => string;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
/**
|
|
12
|
+
* Claim a key. False means somebody else holds it; the primary key decides.
|
|
13
|
+
*
|
|
14
|
+
* A caller that dies between claiming and finishing would otherwise block the
|
|
15
|
+
* key forever, so a claim older than `staleMs` is treated as abandoned and
|
|
16
|
+
* taken over. This is a liveness/safety trade made explicit: too short and a
|
|
17
|
+
* slow call gets run twice, too long and a stuck key blocks retries.
|
|
18
|
+
*/
|
|
19
|
+
claim(key: string, fn: string, staleMs?: number): Promise<boolean>;
|
|
20
|
+
read(key: string): Promise<IdemRecord | null>;
|
|
21
|
+
complete(key: string, result: unknown): Promise<void>;
|
|
22
|
+
/** Drop a claim whose call then failed, so a retry is not locked out. */
|
|
23
|
+
release(key: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Records are only useful for as long as a client might retry. Without this the
|
|
26
|
+
* table grows forever, which is the quiet way a correctness feature becomes an
|
|
27
|
+
* operational problem.
|
|
28
|
+
*/
|
|
29
|
+
sweep(olderThanMs?: number): Promise<number>;
|
|
30
|
+
}>;
|
|
31
|
+
//# sourceMappingURL=idem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idem.d.ts","sourceRoot":"","sources":["../../../../../packages/ketjs/src/server/idem.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,eAAO,MAAM,QAAQ,4LAQpB,CAAA;AAED,eAAO,MAAM,WAAW,QAAkF,CAAA;AAE1G,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAA;AAEvE,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAAO;IAOpF;;;;;;;OAOG;eACc,MAAM,MAAM,MAAM,qBAAyB,OAAO,CAAC,OAAO,CAAC;cAc5D,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;kBAS/B,MAAM,UAAU,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3D,yEAAyE;iBACtD,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC;;;;OAIG;iCAC0C,OAAO,CAAC,MAAM,CAAC;GAM/D"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Idempotency records: durable, shared between instances, and cold. This is the one
|
|
2
|
+
// use that genuinely belongs in the database and always did — it is a keyed lookup,
|
|
3
|
+
// not a log, so it gets a keyed table.
|
|
4
|
+
export const IDEM_DDL = `
|
|
5
|
+
CREATE TABLE IF NOT EXISTS ket_idem (
|
|
6
|
+
key TEXT PRIMARY KEY,
|
|
7
|
+
fn TEXT NOT NULL,
|
|
8
|
+
state TEXT NOT NULL,
|
|
9
|
+
result TEXT,
|
|
10
|
+
created_at TEXT NOT NULL
|
|
11
|
+
);
|
|
12
|
+
`;
|
|
13
|
+
export const IDEM_DDL_PG = IDEM_DDL.replace('created_at TEXT NOT NULL', 'created_at TIMESTAMPTZ NOT NULL');
|
|
14
|
+
export async function createIdempotency(adapter, o = {}) {
|
|
15
|
+
const now = o.now ?? (() => new Date().toISOString());
|
|
16
|
+
const pg = adapter.name === 'postgres';
|
|
17
|
+
const p = (n) => (pg ? `$${n}` : '?');
|
|
18
|
+
await adapter.exec(pg ? IDEM_DDL_PG : IDEM_DDL);
|
|
19
|
+
return {
|
|
20
|
+
/**
|
|
21
|
+
* Claim a key. False means somebody else holds it; the primary key decides.
|
|
22
|
+
*
|
|
23
|
+
* A caller that dies between claiming and finishing would otherwise block the
|
|
24
|
+
* key forever, so a claim older than `staleMs` is treated as abandoned and
|
|
25
|
+
* taken over. This is a liveness/safety trade made explicit: too short and a
|
|
26
|
+
* slow call gets run twice, too long and a stuck key blocks retries.
|
|
27
|
+
*/
|
|
28
|
+
async claim(key, fn, staleMs = 5 * 60_000) {
|
|
29
|
+
const r = await adapter.run(`INSERT INTO ket_idem (key, fn, state, created_at) VALUES (${p(1)}, ${p(2)}, 'pending', ${p(3)}) ON CONFLICT DO NOTHING`, [key, fn, now()]);
|
|
30
|
+
if (r.changes === 1)
|
|
31
|
+
return true;
|
|
32
|
+
const cutoff = new Date(Date.parse(now()) - staleMs).toISOString();
|
|
33
|
+
const taken = await adapter.run(`UPDATE ket_idem SET created_at = ${p(1)} WHERE key = ${p(2)} AND state = 'pending' AND created_at < ${p(3)}`, [now(), key, cutoff]);
|
|
34
|
+
return taken.changes === 1;
|
|
35
|
+
},
|
|
36
|
+
async read(key) {
|
|
37
|
+
const rows = await adapter.all(`SELECT state, result FROM ket_idem WHERE key = ${p(1)}`, [key]);
|
|
38
|
+
const r = rows[0];
|
|
39
|
+
if (!r)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
state: String(r.state),
|
|
43
|
+
result: r.result == null ? null : JSON.parse(String(r.result)),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
async complete(key, result) {
|
|
47
|
+
await adapter.run(`UPDATE ket_idem SET state = 'done', result = ${p(1)} WHERE key = ${p(2)}`, [
|
|
48
|
+
JSON.stringify(result),
|
|
49
|
+
key,
|
|
50
|
+
]);
|
|
51
|
+
},
|
|
52
|
+
/** Drop a claim whose call then failed, so a retry is not locked out. */
|
|
53
|
+
async release(key) {
|
|
54
|
+
await adapter.run(`DELETE FROM ket_idem WHERE key = ${p(1)} AND state = 'pending'`, [key]);
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* Records are only useful for as long as a client might retry. Without this the
|
|
58
|
+
* table grows forever, which is the quiet way a correctness feature becomes an
|
|
59
|
+
* operational problem.
|
|
60
|
+
*/
|
|
61
|
+
async sweep(olderThanMs = 24 * 60 * 60_000) {
|
|
62
|
+
const cutoff = new Date(Date.parse(now()) - olderThanMs).toISOString();
|
|
63
|
+
const r = await adapter.run(`DELETE FROM ket_idem WHERE created_at < ${p(1)}`, [cutoff]);
|
|
64
|
+
return r.changes;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=idem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idem.js","sourceRoot":"","sources":["../../../../../../packages/ketjs/src/server/idem.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JobSpec, KetModule } from '../types.ts';
|
|
2
|
+
export declare function defineJob(spec: JobSpec): JobSpec;
|
|
3
|
+
export declare function registerJobs(modules: KetModule[]): Map<string, JobSpec>;
|
|
4
|
+
export declare const jobDefinition: (key: string) => JobSpec | null;
|
|
5
|
+
//# sourceMappingURL=jobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../../../packages/ketjs/src/server/jobs.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAIrD,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAShD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAKvE;AAED,eAAO,MAAM,aAAa,QAAS,MAAM,KAAG,OAAO,GAAG,IAAiC,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Job declarations are data in the manifest and executable handlers in this
|
|
2
|
+
// process. Keeping the two joined by one qualified key mirrors server functions
|
|
3
|
+
// while allowing `ket worker` to load the same AppSpec without booting HTTP.
|
|
4
|
+
import { KetError } from '../kernel/errors.js';
|
|
5
|
+
const registry = new Map();
|
|
6
|
+
export function defineJob(spec) {
|
|
7
|
+
if (spec.idempotent !== true)
|
|
8
|
+
throw new KetError({
|
|
9
|
+
code: 'E_JOB_NOT_IDEMPOTENT',
|
|
10
|
+
message: 'defineJob() requires idempotent: true because delivery is at-least-once',
|
|
11
|
+
});
|
|
12
|
+
if (typeof spec.handler !== 'function')
|
|
13
|
+
throw new KetError({ code: 'E_JOB_NO_HANDLER', message: 'defineJob() requires a handler' });
|
|
14
|
+
return spec;
|
|
15
|
+
}
|
|
16
|
+
export function registerJobs(modules) {
|
|
17
|
+
registry.clear();
|
|
18
|
+
for (const module of modules)
|
|
19
|
+
for (const [name, spec] of Object.entries(module.jobs))
|
|
20
|
+
registry.set(`${module.name}.${name}`, spec);
|
|
21
|
+
return registry;
|
|
22
|
+
}
|
|
23
|
+
export const jobDefinition = (key) => registry.get(key) ?? null;
|
|
24
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../../../../../packages/ketjs/src/server/jobs.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAG9C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAA;AAE3C,MAAM,UAAU,SAAS,CAAC,IAAa;IACrC,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;QAC1B,MAAM,IAAI,QAAQ,CAAC;YACjB,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,yEAAyE;SACnF,CAAC,CAAA;IACJ,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU;QACpC,MAAM,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC,CAAA;IAC7F,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAoB;IAC/C,QAAQ,CAAC,KAAK,EAAE,CAAA;IAChB,KAAK,MAAM,MAAM,IAAI,OAAO;QAC1B,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,CAAA;IACtG,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAW,EAAkB,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type MultipartPart = {
|
|
2
|
+
name: string;
|
|
3
|
+
filename?: string;
|
|
4
|
+
type?: string;
|
|
5
|
+
body: AsyncIterable<Uint8Array>;
|
|
6
|
+
};
|
|
7
|
+
export type MultipartOptions = {
|
|
8
|
+
maxBytes?: number;
|
|
9
|
+
maxParts?: number;
|
|
10
|
+
maxHeaderBytes?: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Parse multipart sequentially without buffering file bodies. A part body must be
|
|
14
|
+
* consumed before requesting the next part; if a caller stops early, the parser
|
|
15
|
+
* drains only to the next boundary so the remaining parts stay readable.
|
|
16
|
+
*/
|
|
17
|
+
export declare function multipart(source: AsyncIterable<Uint8Array>, contentType: string, options?: MultipartOptions): AsyncGenerator<MultipartPart>;
|
|
18
|
+
//# sourceMappingURL=multipart.d.ts.map
|