@anby/platform-sdk 0.1.0 → 0.7.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/cjs/apps/publish.d.ts +23 -0
- package/dist/cjs/apps/publish.d.ts.map +1 -1
- package/dist/cjs/apps/publish.js +65 -5
- package/dist/cjs/apps/publish.js.map +1 -1
- package/dist/cjs/auth/index.d.ts +33 -3
- package/dist/cjs/auth/index.d.ts.map +1 -1
- package/dist/cjs/auth/index.js +105 -24
- package/dist/cjs/auth/index.js.map +1 -1
- package/dist/cjs/bootstrap/cache.d.ts +4 -0
- package/dist/cjs/bootstrap/cache.d.ts.map +1 -0
- package/dist/cjs/bootstrap/cache.js +52 -0
- package/dist/cjs/bootstrap/cache.js.map +1 -0
- package/dist/cjs/bootstrap/index.d.ts +79 -0
- package/dist/cjs/bootstrap/index.d.ts.map +1 -0
- package/dist/cjs/bootstrap/index.js +280 -0
- package/dist/cjs/bootstrap/index.js.map +1 -0
- package/dist/cjs/bootstrap/types.d.ts +53 -0
- package/dist/cjs/bootstrap/types.d.ts.map +1 -0
- package/dist/cjs/bootstrap/types.js +14 -0
- package/dist/cjs/bootstrap/types.js.map +1 -0
- package/dist/cjs/events/http-transport.d.ts +38 -0
- package/dist/cjs/events/http-transport.d.ts.map +1 -0
- package/dist/cjs/events/http-transport.js +63 -0
- package/dist/cjs/events/http-transport.js.map +1 -0
- package/dist/cjs/events/index.d.ts +49 -0
- package/dist/cjs/events/index.d.ts.map +1 -1
- package/dist/cjs/events/index.js +14 -1
- package/dist/cjs/events/index.js.map +1 -1
- package/dist/cjs/index.d.ts +6 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +30 -11
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/vite/index.d.ts +20 -0
- package/dist/cjs/vite/index.d.ts.map +1 -0
- package/dist/cjs/vite/index.js +154 -0
- package/dist/cjs/vite/index.js.map +1 -0
- package/dist/esm/apps/publish.js +31 -5
- package/dist/esm/apps/publish.js.map +1 -1
- package/dist/esm/auth/index.js +102 -23
- package/dist/esm/auth/index.js.map +1 -1
- package/dist/esm/bootstrap/cache.js +48 -0
- package/dist/esm/bootstrap/cache.js.map +1 -0
- package/dist/esm/bootstrap/index.js +272 -0
- package/dist/esm/bootstrap/index.js.map +1 -0
- package/dist/esm/bootstrap/types.js +11 -0
- package/dist/esm/bootstrap/types.js.map +1 -0
- package/dist/esm/events/http-transport.js +59 -0
- package/dist/esm/events/http-transport.js.map +1 -0
- package/dist/esm/events/index.js +14 -1
- package/dist/esm/events/index.js.map +1 -1
- package/dist/esm/index.js +8 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/package.json +1 -0
- package/dist/esm/vite/index.js +151 -0
- package/dist/esm/vite/index.js.map +1 -0
- package/package.json +20 -7
- package/src/apps/publish.ts +45 -6
- package/src/auth/index.test.ts +249 -0
- package/src/auth/index.ts +126 -32
- package/src/bootstrap/cache.ts +60 -0
- package/src/bootstrap/index.test.ts +277 -0
- package/src/bootstrap/index.ts +350 -0
- package/src/bootstrap/types.ts +56 -0
- package/src/events/http-transport.test.ts +135 -0
- package/src/events/http-transport.ts +77 -0
- package/src/events/index.ts +73 -2
- package/src/index.ts +29 -1
- package/src/vite/index.ts +195 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Third-party app bootstrap (PLAN-app-bootstrap.md PR2).
|
|
4
|
+
*
|
|
5
|
+
* `bootstrapFromToken({ appToken })` is the SINGLE platform-init call a
|
|
6
|
+
* third-party app needs at boot. Given a connection-string token, it:
|
|
7
|
+
*
|
|
8
|
+
* 1. Parses the token (sync, no network) → appId, platformUrl, privateKey
|
|
9
|
+
* 2. Fetches GET ${platformUrl}/registry/discovery (cached on success)
|
|
10
|
+
* 3. Fetches GET ${endpoints.authPublicKeyUrl} (the user JWT verification key)
|
|
11
|
+
* 4. Configures the SDK's auth, platform, and entity-identity layers
|
|
12
|
+
* 5. Schedules a background refresh at 80% of cacheTtlSeconds
|
|
13
|
+
*
|
|
14
|
+
* After this returns, the app can use `requireAuth()`, `verifyUserJwt()`,
|
|
15
|
+
* `publishEvent()`, the entity client, etc. exactly as if it had been
|
|
16
|
+
* configured via the legacy env-based path.
|
|
17
|
+
*
|
|
18
|
+
* Cache: discovery is cached to disk so cold starts survive a brief
|
|
19
|
+
* registry outage. The token itself is NOT cached — it lives in the env
|
|
20
|
+
* var. Cached entries are per-app and contain only public information
|
|
21
|
+
* (URLs and the auth public key PEM, no secrets).
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.ANBY_TOKEN_PREFIX = void 0;
|
|
25
|
+
exports._resetBootstrapForTests = _resetBootstrapForTests;
|
|
26
|
+
exports.getDiscoveredEndpoints = getDiscoveredEndpoints;
|
|
27
|
+
exports.getDiscoveredRegistryBaseUrl = getDiscoveredRegistryBaseUrl;
|
|
28
|
+
exports.parseAppToken = parseAppToken;
|
|
29
|
+
exports.bootstrapFromToken = bootstrapFromToken;
|
|
30
|
+
const index_js_1 = require("../auth/index.js");
|
|
31
|
+
const index_js_2 = require("../config/index.js");
|
|
32
|
+
const identity_js_1 = require("../entities/identity.js");
|
|
33
|
+
const index_js_3 = require("../events/index.js");
|
|
34
|
+
const http_transport_js_1 = require("../events/http-transport.js");
|
|
35
|
+
const cache_js_1 = require("./cache.js");
|
|
36
|
+
const types_js_1 = require("./types.js");
|
|
37
|
+
Object.defineProperty(exports, "ANBY_TOKEN_PREFIX", { enumerable: true, get: function () { return types_js_1.ANBY_TOKEN_PREFIX; } });
|
|
38
|
+
let _state = null;
|
|
39
|
+
/** For tests: clears the module-level bootstrap state so a fresh
|
|
40
|
+
* bootstrapFromToken call re-runs from scratch. Not exported from the
|
|
41
|
+
* root barrel. */
|
|
42
|
+
function _resetBootstrapForTests() {
|
|
43
|
+
_state = null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns the discovery response cached by the most recent successful
|
|
47
|
+
* bootstrapFromToken call. Throws if bootstrap has not yet started.
|
|
48
|
+
*
|
|
49
|
+
* Resolves the in-progress promise if bootstrap is still in flight, so
|
|
50
|
+
* callers can `await getDiscoveredEndpoints()` from anywhere safely.
|
|
51
|
+
*/
|
|
52
|
+
async function getDiscoveredEndpoints() {
|
|
53
|
+
if (!_state) {
|
|
54
|
+
throw new Error('bootstrap not started — call bootstrapFromToken() before reading discovery state');
|
|
55
|
+
}
|
|
56
|
+
await _state.promise;
|
|
57
|
+
if (!_state.discovery) {
|
|
58
|
+
throw new Error('bootstrap completed but no discovery cached');
|
|
59
|
+
}
|
|
60
|
+
return _state.discovery.endpoints;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns the registry HOST root (e.g. "http://localhost:3003"), without
|
|
64
|
+
* the /registry path suffix. Use this for callers that already append
|
|
65
|
+
* /registry/... themselves (autoPublishOnBoot, RegistryPublicKeyVerifier).
|
|
66
|
+
*
|
|
67
|
+
* Falls back to discovery.endpoints.registryUrl with /registry stripped
|
|
68
|
+
* if the registryBaseUrl field is missing (older registries that haven't
|
|
69
|
+
* deployed PR3 yet).
|
|
70
|
+
*/
|
|
71
|
+
async function getDiscoveredRegistryBaseUrl() {
|
|
72
|
+
const endpoints = await getDiscoveredEndpoints();
|
|
73
|
+
if (endpoints.registryBaseUrl)
|
|
74
|
+
return endpoints.registryBaseUrl;
|
|
75
|
+
// Backward compat: strip /registry suffix from registryUrl.
|
|
76
|
+
return endpoints.registryUrl.replace(/\/registry\/?$/, '');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parse an `anby_v1_<base64json>` token. Throws on malformed input.
|
|
80
|
+
*
|
|
81
|
+
* Sync, no network. Validates structure but does NOT verify the
|
|
82
|
+
* Ed25519 private key against any server — that happens later when the
|
|
83
|
+
* SDK tries to mint a scoped-token.
|
|
84
|
+
*/
|
|
85
|
+
function parseAppToken(token) {
|
|
86
|
+
if (!token || typeof token !== 'string') {
|
|
87
|
+
throw new Error('ANBY_APP_TOKEN is empty');
|
|
88
|
+
}
|
|
89
|
+
if (!token.startsWith(types_js_1.ANBY_TOKEN_PREFIX)) {
|
|
90
|
+
throw new Error(`ANBY_APP_TOKEN must start with "${types_js_1.ANBY_TOKEN_PREFIX}". Did you paste the right value?`);
|
|
91
|
+
}
|
|
92
|
+
const b64 = token.slice(types_js_1.ANBY_TOKEN_PREFIX.length);
|
|
93
|
+
let json;
|
|
94
|
+
try {
|
|
95
|
+
json = Buffer.from(b64, 'base64url').toString('utf-8');
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
throw new Error(`ANBY_APP_TOKEN payload is not valid base64url: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = JSON.parse(json);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
throw new Error(`ANBY_APP_TOKEN payload is not valid JSON: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
const obj = parsed;
|
|
108
|
+
if (obj?.v !== 1 ||
|
|
109
|
+
typeof obj.appId !== 'string' ||
|
|
110
|
+
typeof obj.platformUrl !== 'string' ||
|
|
111
|
+
typeof obj.privateKey !== 'string') {
|
|
112
|
+
throw new Error('ANBY_APP_TOKEN payload missing required fields (v, appId, platformUrl, privateKey)');
|
|
113
|
+
}
|
|
114
|
+
if (!obj.privateKey.includes('PRIVATE KEY')) {
|
|
115
|
+
throw new Error('ANBY_APP_TOKEN.privateKey is not a PEM');
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
v: 1,
|
|
119
|
+
appId: obj.appId,
|
|
120
|
+
platformUrl: obj.platformUrl.replace(/\/$/, ''),
|
|
121
|
+
privateKey: obj.privateKey,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function fetchDiscovery(platformUrl, fetchImpl) {
|
|
125
|
+
const url = `${platformUrl}/registry/discovery`;
|
|
126
|
+
const res = await fetchImpl(url, { headers: { accept: 'application/json' } });
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
throw new Error(`discovery fetch failed: ${res.status} ${res.statusText}`);
|
|
129
|
+
}
|
|
130
|
+
const body = (await res.json());
|
|
131
|
+
if (body?.v !== 1 || !body?.endpoints?.authPublicKeyUrl) {
|
|
132
|
+
throw new Error('discovery response is missing required fields');
|
|
133
|
+
}
|
|
134
|
+
return body;
|
|
135
|
+
}
|
|
136
|
+
async function fetchAuthPublicKey(url, fetchImpl) {
|
|
137
|
+
const res = await fetchImpl(url, { headers: { accept: 'text/plain' } });
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
throw new Error(`auth-public-key fetch failed: ${res.status} ${res.statusText}`);
|
|
140
|
+
}
|
|
141
|
+
const pem = await res.text();
|
|
142
|
+
// CRITICAL leak check FIRST: a buggy auth-service that accidentally
|
|
143
|
+
// serves a private key on this endpoint must be rejected loudly,
|
|
144
|
+
// before any other validation that might mask the security finding.
|
|
145
|
+
if (pem.includes('PRIVATE KEY')) {
|
|
146
|
+
throw new Error('auth-public-key endpoint returned PRIVATE KEY material — refusing to use it');
|
|
147
|
+
}
|
|
148
|
+
if (!pem.includes('BEGIN PUBLIC KEY')) {
|
|
149
|
+
throw new Error('auth-public-key response is not a PEM public key');
|
|
150
|
+
}
|
|
151
|
+
return pem;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The single bootstrap entrypoint a third-party app calls at boot.
|
|
155
|
+
*
|
|
156
|
+
* Idempotent: if called multiple times in the same process (e.g. once
|
|
157
|
+
* from entry.server.tsx and again from a refresh timer), the second call
|
|
158
|
+
* returns the same in-flight promise instead of starting a parallel
|
|
159
|
+
* bootstrap.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* import { bootstrapFromToken, requireAuth } from '@anby/platform-sdk';
|
|
164
|
+
*
|
|
165
|
+
* await bootstrapFromToken({ appToken: process.env.ANBY_APP_TOKEN! });
|
|
166
|
+
*
|
|
167
|
+
* app.get('/api/widgets', requireAuth(), handler);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
function bootstrapFromToken(opts) {
|
|
171
|
+
// PLAN-app-bootstrap-phase2 PR3: dedupe concurrent calls. Multiple
|
|
172
|
+
// entry points (entry.server, autoPublishOnBoot, refresh timer) can
|
|
173
|
+
// all await the same shared promise without spawning parallel work.
|
|
174
|
+
if (_state)
|
|
175
|
+
return _state.promise;
|
|
176
|
+
_state = {
|
|
177
|
+
promise: doBootstrap(opts).catch((err) => {
|
|
178
|
+
// Failed bootstrap clears state so the next call retries.
|
|
179
|
+
_state = null;
|
|
180
|
+
throw err;
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
return _state.promise;
|
|
184
|
+
}
|
|
185
|
+
async function doBootstrap(opts) {
|
|
186
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
187
|
+
if (!fetchImpl) {
|
|
188
|
+
throw new Error('No fetch implementation available. Pass opts.fetchImpl or run on Node 18+.');
|
|
189
|
+
}
|
|
190
|
+
const cacheDir = opts.cacheDir ?? `${process.cwd()}/.anby-cache`;
|
|
191
|
+
// 1. Parse the token (sync, no network)
|
|
192
|
+
const token = parseAppToken(opts.appToken);
|
|
193
|
+
if (_state)
|
|
194
|
+
_state.appToken = token;
|
|
195
|
+
// 2. Try fetching fresh discovery + auth public key. Fall back to disk
|
|
196
|
+
// cache if the network is unreachable on cold start.
|
|
197
|
+
let discovery;
|
|
198
|
+
let authPublicKeyPem;
|
|
199
|
+
let usedCache = false;
|
|
200
|
+
try {
|
|
201
|
+
discovery = await fetchDiscovery(token.platformUrl, fetchImpl);
|
|
202
|
+
authPublicKeyPem = await fetchAuthPublicKey(discovery.endpoints.authPublicKeyUrl, fetchImpl);
|
|
203
|
+
}
|
|
204
|
+
catch (fetchErr) {
|
|
205
|
+
const cached = await (0, cache_js_1.readCache)(cacheDir, token.appId);
|
|
206
|
+
if (!cached) {
|
|
207
|
+
throw new Error(`bootstrap failed and no cache available: ${fetchErr.message}`);
|
|
208
|
+
}
|
|
209
|
+
discovery = cached.discovery;
|
|
210
|
+
authPublicKeyPem = cached.authPublicKeyPem;
|
|
211
|
+
usedCache = true;
|
|
212
|
+
}
|
|
213
|
+
// Cache discovery into the shared module state so other modules can
|
|
214
|
+
// read it via getDiscoveredEndpoints() / getDiscoveredRegistryBaseUrl().
|
|
215
|
+
if (_state)
|
|
216
|
+
_state.discovery = discovery;
|
|
217
|
+
// 3. Configure SDK subsystems
|
|
218
|
+
(0, index_js_1.configureAuth)({ jwtPublicKey: authPublicKeyPem });
|
|
219
|
+
(0, index_js_2.configurePlatform)({
|
|
220
|
+
appId: token.appId,
|
|
221
|
+
registryUrl: discovery.endpoints.registryUrl,
|
|
222
|
+
});
|
|
223
|
+
(0, identity_js_1.configureAppIdentity)({
|
|
224
|
+
appId: token.appId,
|
|
225
|
+
privateKeyPem: token.privateKey,
|
|
226
|
+
});
|
|
227
|
+
// PLAN-app-bootstrap-phase2 PR3: auto-wire HttpEventTransport so dev
|
|
228
|
+
// calls to publishEvent() work without any manual configuration. The
|
|
229
|
+
// events endpoint URL comes from discovery — fall back to deriving it
|
|
230
|
+
// from registryBaseUrl if older discovery responses lack the explicit
|
|
231
|
+
// eventsUrl field.
|
|
232
|
+
const eventsUrl = discovery.endpoints.eventsUrl ??
|
|
233
|
+
`${discovery.endpoints.registryBaseUrl ?? discovery.endpoints.registryUrl.replace(/\/registry\/?$/, '')}/registry/events`;
|
|
234
|
+
(0, index_js_3.configureEventTransport)(new http_transport_js_1.HttpEventTransport({
|
|
235
|
+
endpoint: eventsUrl,
|
|
236
|
+
identity: { appId: token.appId, privateKeyPem: token.privateKey },
|
|
237
|
+
}));
|
|
238
|
+
// 4. Persist to cache (only on successful network fetch — don't
|
|
239
|
+
// overwrite a fresh cache with a stale-cache value)
|
|
240
|
+
if (!usedCache) {
|
|
241
|
+
const fetchedAt = new Date();
|
|
242
|
+
const staleAt = new Date(fetchedAt.getTime() + discovery.cacheTtlSeconds * 1000);
|
|
243
|
+
const entry = {
|
|
244
|
+
fetchedAt: fetchedAt.toISOString(),
|
|
245
|
+
staleAt: staleAt.toISOString(),
|
|
246
|
+
discovery,
|
|
247
|
+
authPublicKeyPem,
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
await (0, cache_js_1.writeCache)(cacheDir, token.appId, entry);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
// Cache write failure is non-fatal. The SDK still works in memory;
|
|
254
|
+
// the next cold start just can't use the disk fallback.
|
|
255
|
+
console.warn(`[anby] failed to write bootstrap cache: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// 5. Schedule background refresh at 80% of TTL.
|
|
259
|
+
const refreshInMs = Math.max(60_000, discovery.cacheTtlSeconds * 1000 * 0.8);
|
|
260
|
+
scheduleRefresh(opts, refreshInMs);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Background refresh loop. Re-runs bootstrapFromToken at the scheduled
|
|
264
|
+
* interval. On failure, logs and keeps using the existing in-memory
|
|
265
|
+
* config — there's no degraded mode because the data we cache (URLs +
|
|
266
|
+
* public key) is not security-critical and can be stale.
|
|
267
|
+
*/
|
|
268
|
+
function scheduleRefresh(opts, delayMs) {
|
|
269
|
+
const handle = setTimeout(() => {
|
|
270
|
+
bootstrapFromToken(opts).catch((err) => {
|
|
271
|
+
console.warn(`[anby] bootstrap refresh failed: ${err.message} (will retry on next interval)`);
|
|
272
|
+
// Re-schedule with the same delay even on failure.
|
|
273
|
+
scheduleRefresh(opts, delayMs);
|
|
274
|
+
});
|
|
275
|
+
}, delayMs);
|
|
276
|
+
// Don't keep the event loop alive just for refresh.
|
|
277
|
+
if (typeof handle.unref === 'function')
|
|
278
|
+
handle.unref();
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/bootstrap/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the third-party app bootstrap flow
|
|
3
|
+
* (PLAN-app-bootstrap.md PR2).
|
|
4
|
+
*
|
|
5
|
+
* `AnbyAppToken` is the parsed payload of the `ANBY_APP_TOKEN` env var.
|
|
6
|
+
* The wire format is `anby_v1_<base64url(json(AnbyAppToken))>`.
|
|
7
|
+
*
|
|
8
|
+
* `DiscoveryResponse` is what `GET /registry/discovery` returns.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ANBY_TOKEN_PREFIX = "anby_v1_";
|
|
11
|
+
export interface AnbyAppToken {
|
|
12
|
+
v: 1;
|
|
13
|
+
appId: string;
|
|
14
|
+
/** Externally-reachable platform base URL, e.g. "https://anby.io". */
|
|
15
|
+
platformUrl: string;
|
|
16
|
+
/** PEM-encoded Ed25519 private key for service-to-service signing. */
|
|
17
|
+
privateKey: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DiscoveryResponse {
|
|
20
|
+
v: 1;
|
|
21
|
+
platform: {
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
};
|
|
25
|
+
endpoints: {
|
|
26
|
+
authPublicKeyUrl: string;
|
|
27
|
+
scopedTokenUrl: string;
|
|
28
|
+
entityTokenPublicKeyUrl: string;
|
|
29
|
+
/** PR1 of phase2: events ingestion endpoint. Optional for back-compat
|
|
30
|
+
* with older registries that don't expose it yet. */
|
|
31
|
+
eventsUrl?: string;
|
|
32
|
+
gatewayUrl: string;
|
|
33
|
+
/** Registry HTTP API root with /registry suffix already appended.
|
|
34
|
+
* Existing callers expect this exact shape. */
|
|
35
|
+
registryUrl: string;
|
|
36
|
+
/** PR3 of phase2: registry HOST root WITHOUT /registry suffix.
|
|
37
|
+
* Optional for back-compat with older registries. */
|
|
38
|
+
registryBaseUrl?: string;
|
|
39
|
+
tenantServiceUrl: string;
|
|
40
|
+
eventRouterUrl: string;
|
|
41
|
+
};
|
|
42
|
+
cacheTtlSeconds: number;
|
|
43
|
+
}
|
|
44
|
+
export interface CachedBootstrap {
|
|
45
|
+
/** When the discovery response was fetched (ISO 8601). */
|
|
46
|
+
fetchedAt: string;
|
|
47
|
+
/** When this cache entry should be considered stale (ISO 8601). */
|
|
48
|
+
staleAt: string;
|
|
49
|
+
discovery: DiscoveryResponse;
|
|
50
|
+
/** PEM string of the auth-service public key (RS256). */
|
|
51
|
+
authPublicKeyPem: string;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/bootstrap/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,CAAC,CAAC;IACL,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,EAAE,CAAC,CAAC;IACL,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,SAAS,EAAE;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,MAAM,CAAC;QACvB,uBAAuB,EAAE,MAAM,CAAC;QAChC;8DACsD;QACtD,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB;wDACgD;QAChD,WAAW,EAAE,MAAM,CAAC;QACpB;8DACsD;QACtD,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,CAAC;CAC1B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for the third-party app bootstrap flow
|
|
4
|
+
* (PLAN-app-bootstrap.md PR2).
|
|
5
|
+
*
|
|
6
|
+
* `AnbyAppToken` is the parsed payload of the `ANBY_APP_TOKEN` env var.
|
|
7
|
+
* The wire format is `anby_v1_<base64url(json(AnbyAppToken))>`.
|
|
8
|
+
*
|
|
9
|
+
* `DiscoveryResponse` is what `GET /registry/discovery` returns.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ANBY_TOKEN_PREFIX = void 0;
|
|
13
|
+
exports.ANBY_TOKEN_PREFIX = 'anby_v1_';
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/bootstrap/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEU,QAAA,iBAAiB,GAAG,UAAU,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AnbyEvent } from '@anby/contracts';
|
|
2
|
+
import { type AppIdentity } from '../entities/identity.js';
|
|
3
|
+
import type { EventTransport } from './index.js';
|
|
4
|
+
/**
|
|
5
|
+
* HttpEventTransport (PLAN-app-bootstrap-phase2 PR2).
|
|
6
|
+
*
|
|
7
|
+
* Replaces PostgresEventTransport for third-party apps. POSTs an AnbyEvent
|
|
8
|
+
* envelope (or array, batched) to the platform's POST /registry/events
|
|
9
|
+
* endpoint, signed with the app's per-request Ed25519 signature using the
|
|
10
|
+
* canonical scheme:
|
|
11
|
+
*
|
|
12
|
+
* ANBY-APP-V1\n{appId}\n{tenantId}\n{iso}\n{bodySha256}
|
|
13
|
+
*
|
|
14
|
+
* Headers attached:
|
|
15
|
+
* x-anby-app, x-anby-timestamp, x-anby-body-sha256, x-anby-signature
|
|
16
|
+
*
|
|
17
|
+
* Same scheme as POST /registry/scoped-token, so no scoped-token round-trip
|
|
18
|
+
* is needed.
|
|
19
|
+
*
|
|
20
|
+
* Configured automatically by `bootstrapFromToken` once the discovery
|
|
21
|
+
* response and the app's per-app private key are available. App code never
|
|
22
|
+
* instantiates this directly.
|
|
23
|
+
*/
|
|
24
|
+
export declare class HttpEventTransport implements EventTransport {
|
|
25
|
+
private readonly endpoint;
|
|
26
|
+
private readonly identity;
|
|
27
|
+
private readonly fetchImpl;
|
|
28
|
+
constructor(opts: {
|
|
29
|
+
/** Full URL of POST /registry/events on the registry. */
|
|
30
|
+
endpoint: string;
|
|
31
|
+
/** Per-app identity from ANBY_APP_TOKEN. */
|
|
32
|
+
identity: AppIdentity;
|
|
33
|
+
/** Override for tests. */
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
});
|
|
36
|
+
publish(event: AnbyEvent): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=http-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../../../src/events/http-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,IAAI,EAAE;QAChB,yDAAyD;QACzD,QAAQ,EAAE,MAAM,CAAC;QACjB,4CAA4C;QAC5C,QAAQ,EAAE,WAAW,CAAC;QACtB,0BAA0B;QAC1B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;KAC1B;IAWK,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CA6B/C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpEventTransport = void 0;
|
|
4
|
+
const identity_js_1 = require("../entities/identity.js");
|
|
5
|
+
/**
|
|
6
|
+
* HttpEventTransport (PLAN-app-bootstrap-phase2 PR2).
|
|
7
|
+
*
|
|
8
|
+
* Replaces PostgresEventTransport for third-party apps. POSTs an AnbyEvent
|
|
9
|
+
* envelope (or array, batched) to the platform's POST /registry/events
|
|
10
|
+
* endpoint, signed with the app's per-request Ed25519 signature using the
|
|
11
|
+
* canonical scheme:
|
|
12
|
+
*
|
|
13
|
+
* ANBY-APP-V1\n{appId}\n{tenantId}\n{iso}\n{bodySha256}
|
|
14
|
+
*
|
|
15
|
+
* Headers attached:
|
|
16
|
+
* x-anby-app, x-anby-timestamp, x-anby-body-sha256, x-anby-signature
|
|
17
|
+
*
|
|
18
|
+
* Same scheme as POST /registry/scoped-token, so no scoped-token round-trip
|
|
19
|
+
* is needed.
|
|
20
|
+
*
|
|
21
|
+
* Configured automatically by `bootstrapFromToken` once the discovery
|
|
22
|
+
* response and the app's per-app private key are available. App code never
|
|
23
|
+
* instantiates this directly.
|
|
24
|
+
*/
|
|
25
|
+
class HttpEventTransport {
|
|
26
|
+
endpoint;
|
|
27
|
+
identity;
|
|
28
|
+
fetchImpl;
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
this.endpoint = opts.endpoint;
|
|
31
|
+
this.identity = opts.identity;
|
|
32
|
+
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
33
|
+
if (!this.fetchImpl) {
|
|
34
|
+
throw new Error('HttpEventTransport: no fetch implementation. Run on Node 18+ or pass fetchImpl.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async publish(event) {
|
|
38
|
+
// Body is the canonical JSON of the event envelope. The signed
|
|
39
|
+
// body-hash is computed over these EXACT bytes — the receiver hashes
|
|
40
|
+
// its raw request body and compares.
|
|
41
|
+
const body = JSON.stringify(event);
|
|
42
|
+
const headers = (0, identity_js_1.signAppRequest)({
|
|
43
|
+
identity: this.identity,
|
|
44
|
+
tenantId: event.tenantId,
|
|
45
|
+
body,
|
|
46
|
+
});
|
|
47
|
+
const res = await this.fetchImpl(this.endpoint, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'content-type': 'application/json',
|
|
51
|
+
accept: 'application/json',
|
|
52
|
+
...headers,
|
|
53
|
+
},
|
|
54
|
+
body,
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const text = await res.text().catch(() => '');
|
|
58
|
+
throw new Error(`HttpEventTransport: POST ${this.endpoint} failed with ${res.status}: ${text}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.HttpEventTransport = HttpEventTransport;
|
|
63
|
+
//# sourceMappingURL=http-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-transport.js","sourceRoot":"","sources":["../../../src/events/http-transport.ts"],"names":[],"mappings":";;;AACA,yDAA2E;AAG3E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,kBAAkB;IACZ,QAAQ,CAAS;IACjB,QAAQ,CAAc;IACtB,SAAS,CAAe;IAEzC,YAAY,IAOX;QACC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAgB;QAC5B,+DAA+D;QAC/D,qEAAqE;QACrE,qCAAqC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAEnC,MAAM,OAAO,GAAG,IAAA,4BAAc,EAAC;YAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,IAAI;SACL,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC9C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;gBAC1B,GAAG,OAAO;aACX;YACD,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,CAAC,QAAQ,gBAAgB,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AApDD,gDAoDC"}
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
import type { AnbyEvent } from '@anby/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* PLAN-app-bootstrap-phase2 PR5: type-safe event registry.
|
|
4
|
+
*
|
|
5
|
+
* Codegen extends these interfaces via TypeScript declaration merging
|
|
6
|
+
* in `.anby/types.d.ts` (auto-generated by `@anby/platform-sdk/vite`
|
|
7
|
+
* from each app's `anby-app.manifest.json`). When empty (no codegen
|
|
8
|
+
* has run), `publishEvent` falls through to the loose `AnbyEvent`
|
|
9
|
+
* overload, so existing untyped callers keep working.
|
|
10
|
+
*
|
|
11
|
+
* Generated file looks like:
|
|
12
|
+
* ```ts
|
|
13
|
+
* declare module '@anby/platform-sdk' {
|
|
14
|
+
* interface AppProvidedEvents {
|
|
15
|
+
* 'org.node.created': Record<string, unknown>;
|
|
16
|
+
* 'org.node.updated': Record<string, unknown>;
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface AppProvidedEvents {
|
|
22
|
+
}
|
|
23
|
+
export interface AppRequiredEvents {
|
|
24
|
+
}
|
|
2
25
|
export interface EventTransport {
|
|
3
26
|
publish(event: AnbyEvent): Promise<void>;
|
|
4
27
|
}
|
|
@@ -20,6 +43,32 @@ export declare function createEvent<T>(params: {
|
|
|
20
43
|
data: T;
|
|
21
44
|
correlationId?: string;
|
|
22
45
|
}): AnbyEvent<T>;
|
|
46
|
+
/**
|
|
47
|
+
* Publish an event through the configured transport.
|
|
48
|
+
*
|
|
49
|
+
* Two overloads:
|
|
50
|
+
* 1. Strongly-typed shorthand — pass `{ type, tenantId, actor, data }`
|
|
51
|
+
* where `type` is constrained to declared `AppProvidedEvents` keys
|
|
52
|
+
* and `data` is narrowed to that key's payload type. Codegen sets up
|
|
53
|
+
* this constraint by extending AppProvidedEvents in `.anby/types.d.ts`.
|
|
54
|
+
* This overload only matches when codegen has run AND the type is a
|
|
55
|
+
* known event name. Catches typos at compile time.
|
|
56
|
+
*
|
|
57
|
+
* 2. Loose escape hatch — pass a full AnbyEvent built via createEvent().
|
|
58
|
+
* Always works, even without codegen. Use for events not declared in
|
|
59
|
+
* the manifest, or for cross-app forwarding.
|
|
60
|
+
*/
|
|
61
|
+
export declare function publishEvent<K extends keyof AppProvidedEvents>(event: {
|
|
62
|
+
type: K;
|
|
63
|
+
tenantId: string;
|
|
64
|
+
source?: string;
|
|
65
|
+
actor: {
|
|
66
|
+
userId: string;
|
|
67
|
+
email: string;
|
|
68
|
+
};
|
|
69
|
+
data: AppProvidedEvents[K];
|
|
70
|
+
correlationId?: string;
|
|
71
|
+
}): Promise<void>;
|
|
23
72
|
export declare function publishEvent(event: AnbyEvent): Promise<void>;
|
|
24
73
|
/**
|
|
25
74
|
* PostgresEventTransport writes events to the app_events DB table.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/events/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,cAAM,iBAAkB,YAAW,cAAc;IAC/C,OAAO,CAAC,MAAM,CAAmB;IAE3B,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,SAAS,IAAI,SAAS,EAAE;IAIxB,KAAK,IAAI,IAAI;CAGd;AAID,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAEvE;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,SAAS,CAAC,CAAC,CAAC,CAYf;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/events/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,iBAAiB;CAAG;AACrC,MAAM,WAAW,iBAAiB;CAAG;AAErC,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,cAAM,iBAAkB,YAAW,cAAc;IAC/C,OAAO,CAAC,MAAM,CAAmB;IAE3B,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,SAAS,IAAI,SAAS,EAAE;IAIxB,KAAK,IAAI,IAAI;CAGd;AAID,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAEvE;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,SAAS,CAAC,CAAC,CAAC,CAYf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,CAChC,CAAC,SAAS,MAAM,iBAAiB,EACjC,KAAK,EAAE;IACP,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAClB,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AA4BpE;;;;;GAKG;AACH,qBAAa,sBAAuB,YAAW,cAAc;IAG/C,OAAO,CAAC,gBAAgB;IAFpC,OAAO,CAAC,IAAI,CAAM;gBAEE,gBAAgB,EAAE,MAAM;YAE9B,OAAO;IAWf,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
package/dist/cjs/events/index.js
CHANGED
|
@@ -72,7 +72,20 @@ function createEvent(params) {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
async function publishEvent(event) {
|
|
75
|
-
|
|
75
|
+
// If the caller passed a partial shape (no id/timestamp/version), build
|
|
76
|
+
// a full envelope on their behalf. Otherwise pass through unchanged.
|
|
77
|
+
const isFullEnvelope = 'id' in event && 'timestamp' in event && 'version' in event;
|
|
78
|
+
const envelope = isFullEnvelope
|
|
79
|
+
? event
|
|
80
|
+
: createEvent({
|
|
81
|
+
type: event.type,
|
|
82
|
+
source: event.source ?? '',
|
|
83
|
+
tenantId: event.tenantId,
|
|
84
|
+
actor: event.actor,
|
|
85
|
+
data: event.data,
|
|
86
|
+
correlationId: event.correlationId,
|
|
87
|
+
});
|
|
88
|
+
await _transport.publish(envelope);
|
|
76
89
|
}
|
|
77
90
|
/**
|
|
78
91
|
* PostgresEventTransport writes events to the app_events DB table.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/events/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/events/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
export { configureAuth,
|
|
2
|
-
export {
|
|
1
|
+
export { configureAuth, verifyUserJwt, verifyInternalJwt, verifyHmac, signHmac, authenticateRequest, requireAuth, JWT_ISSUER, JWT_AUDIENCE, TYP_USER, TYP_INTERNAL, TYP_OAUTH_STATE, type AuthUser, type AuthConfig, } from './auth/index.js';
|
|
2
|
+
export { bootstrapFromToken, parseAppToken, ANBY_TOKEN_PREFIX, type AnbyAppToken, type DiscoveryResponse, type BootstrapOptions, } from './bootstrap/index.js';
|
|
3
|
+
export { configureEventTransport, createEvent, publishEvent, InMemoryTransport, PostgresEventTransport, type EventTransport, type AppProvidedEvents, type AppRequiredEvents, } from './events/index.js';
|
|
4
|
+
export { HttpEventTransport } from './events/http-transport.js';
|
|
5
|
+
export { getDiscoveredEndpoints, getDiscoveredRegistryBaseUrl, } from './bootstrap/index.js';
|
|
3
6
|
export { configurePlatform, getPlatformConfig, type PlatformConfig, } from './config/index.js';
|
|
4
|
-
export { publishAppFromManifest, autoPublishOnBoot, type PublishAppOptions, type PublishAppResult, } from './apps/publish.js';
|
|
7
|
+
export { publishAppFromManifest, autoPublishOnBoot, getInlinedManifest, type PublishAppOptions, type PublishAppResult, } from './apps/publish.js';
|
|
5
8
|
export { getPreviewModeFromRequest, isPreviewMode, type PreviewMode, } from './preview.js';
|
|
6
9
|
export * from './entities/index.js';
|
|
7
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,KAAK,QAAQ,EACb,KAAK,UAAU,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAKhE,OAAO,EACL,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,yBAAyB,EACzB,aAAa,EACb,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AAKtB,cAAc,qBAAqB,CAAC"}
|