@byline/core 4.0.0 → 4.1.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/codegen/index.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
|
-
const FORMAT_VERSION =
|
|
10
|
-
const HASH_DOMAIN = '@byline/core/codegen:collection-types:
|
|
9
|
+
const FORMAT_VERSION = 2;
|
|
10
|
+
const HASH_DOMAIN = '@byline/core/codegen:collection-types:v2\n';
|
|
11
11
|
const IMPORT_ORDER = ['JsonObject', 'JsonValue', 'RelatedDocumentValue', 'StoredFileValue'];
|
|
12
12
|
function compareStrings(a, b) {
|
|
13
13
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -411,7 +411,38 @@ function emitBody(analysis) {
|
|
|
411
411
|
const importLines = usedImports.length === 0
|
|
412
412
|
? []
|
|
413
413
|
: ['import type {', ...usedImports.map((name) => ` ${name},`), "} from '@byline/core'", ''];
|
|
414
|
-
|
|
414
|
+
// Every type declaration lives inside the `@byline/generated-types`
|
|
415
|
+
// declaration-merge block, so the published stub package is the one
|
|
416
|
+
// canonical import path for app collection types
|
|
417
|
+
// (`import type { NewsFields } from '@byline/generated-types'`). The
|
|
418
|
+
// file's top-level `@byline/core` imports remain visible inside the
|
|
419
|
+
// block (module augmentation is lexically scoped).
|
|
420
|
+
const declarationLines = declarations
|
|
421
|
+
.join('\n')
|
|
422
|
+
.replace(/\n+$/, '')
|
|
423
|
+
.split('\n')
|
|
424
|
+
.map((line) => (line.length === 0 ? line : ` ${line}`));
|
|
425
|
+
// Register the app's collection registry with @byline/client (a second
|
|
426
|
+
// declaration merge). Every bare `BylineClient` in the app's program —
|
|
427
|
+
// including the `@byline/client/server` getters — then resolves to
|
|
428
|
+
// `BylineClient<CollectionFieldsByPath>`. Both `@byline/generated-types`
|
|
429
|
+
// and `@byline/client` must be resolvable in the consuming program; apps
|
|
430
|
+
// that generate types depend on both.
|
|
431
|
+
const registerLines = [
|
|
432
|
+
"declare module '@byline/client' {",
|
|
433
|
+
' interface Register {',
|
|
434
|
+
" collections: import('@byline/generated-types').CollectionFieldsByPath",
|
|
435
|
+
' }',
|
|
436
|
+
'}',
|
|
437
|
+
];
|
|
438
|
+
return `${[
|
|
439
|
+
...importLines,
|
|
440
|
+
"declare module '@byline/generated-types' {",
|
|
441
|
+
...declarationLines,
|
|
442
|
+
'}',
|
|
443
|
+
'',
|
|
444
|
+
...registerLines,
|
|
445
|
+
].join('\n')}\n`;
|
|
415
446
|
}
|
|
416
447
|
/**
|
|
417
448
|
* Emit deterministic application collection types from evaluated runtime definitions.
|
|
@@ -28,13 +28,31 @@ describe('emitCollectionTypes', () => {
|
|
|
28
28
|
it('emits a versioned header, verifiable body hash, and canonical formatting', () => {
|
|
29
29
|
const { source, hash } = emitCollectionTypes([collection('plain', [])]);
|
|
30
30
|
const body = source.split('\n').slice(4).join('\n');
|
|
31
|
-
expect(source).toMatch(/^\/\/ Generated by @byline\/core\/codegen\n\/\/ Format version:
|
|
32
|
-
expect(hash).toBe(createHash('sha256').update(`@byline/core/codegen:collection-types:
|
|
31
|
+
expect(source).toMatch(/^\/\/ Generated by @byline\/core\/codegen\n\/\/ Format version: 2\n\/\/ Hash: [a-f0-9]{64}\n\n/);
|
|
32
|
+
expect(hash).toBe(createHash('sha256').update(`@byline/core/codegen:collection-types:v2\n${body}`).digest('hex'));
|
|
33
33
|
expect(source).toContain(`// Hash: ${hash}`);
|
|
34
34
|
expect(source.endsWith('\n')).toBe(true);
|
|
35
35
|
expect(source).not.toContain('\r');
|
|
36
36
|
expect(source).not.toContain(';');
|
|
37
37
|
});
|
|
38
|
+
it('declares every type inside the @byline/generated-types augmentation block', () => {
|
|
39
|
+
const { source } = emitCollectionTypes([collection('plain', [{ name: 'title', type: 'text' }])]);
|
|
40
|
+
expect(source).toContain("declare module '@byline/generated-types' {");
|
|
41
|
+
expect(source).toContain(' export type PlainFields = {');
|
|
42
|
+
expect(source).toContain(' export type CollectionPath = keyof CollectionFieldsByPath');
|
|
43
|
+
// No module-level type exports outside the augmentation blocks.
|
|
44
|
+
expect(source).not.toMatch(/^export /m);
|
|
45
|
+
});
|
|
46
|
+
it('emits the @byline/client Register augmentation referencing the generated registry', () => {
|
|
47
|
+
const { source } = emitCollectionTypes([collection('plain', [])]);
|
|
48
|
+
expect(source).toContain([
|
|
49
|
+
"declare module '@byline/client' {",
|
|
50
|
+
' interface Register {',
|
|
51
|
+
" collections: import('@byline/generated-types').CollectionFieldsByPath",
|
|
52
|
+
' }',
|
|
53
|
+
'}',
|
|
54
|
+
].join('\n'));
|
|
55
|
+
});
|
|
38
56
|
it('sorts collections and declarations without changing field, select, or block union order', () => {
|
|
39
57
|
const alpha = collection('alpha', [
|
|
40
58
|
{
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The host-framework request bridge — the seam that makes the server-side
|
|
10
|
+
* client stack (`@byline/client/server`) host-agnostic.
|
|
11
|
+
*
|
|
12
|
+
* Everything request-scoped in that stack bottoms out in three
|
|
13
|
+
* primitives: "which request am I in" (identity for per-request
|
|
14
|
+
* memoization), "read a cookie", and "write a cookie" (admin session
|
|
15
|
+
* refresh rotates tokens). A host adapter implements those three over its
|
|
16
|
+
* framework's runtime and registers the bridge once at server boot;
|
|
17
|
+
* `@byline/client/server` is written against the interface and never
|
|
18
|
+
* imports a framework.
|
|
19
|
+
*
|
|
20
|
+
* Registration follows the same pattern as the config singletons in
|
|
21
|
+
* `config/config.ts`: a `Symbol.for` slot on `globalThis`, so every copy
|
|
22
|
+
* of this module (Vite SSR can resolve workspace-linked packages through
|
|
23
|
+
* different module graphs) shares the same state. Server-only by nature —
|
|
24
|
+
* hosts register from their boot path (side-effect imports guarantee
|
|
25
|
+
* registration before any request is handled), and nothing in a browser
|
|
26
|
+
* graph should ever reach for it.
|
|
27
|
+
*/
|
|
28
|
+
export interface HostCookieSetOptions {
|
|
29
|
+
httpOnly?: boolean;
|
|
30
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
31
|
+
secure?: boolean;
|
|
32
|
+
path?: string;
|
|
33
|
+
maxAge?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface HostRequestBridge {
|
|
36
|
+
/**
|
|
37
|
+
* A stable identity object for the current HTTP request, or `undefined`
|
|
38
|
+
* when running outside a request (seed scripts, background jobs, unit
|
|
39
|
+
* tests). Used purely as a WeakMap key for per-request memoization —
|
|
40
|
+
* never inspected.
|
|
41
|
+
*/
|
|
42
|
+
getRequest(): object | undefined;
|
|
43
|
+
/** Read a request cookie. Returns `undefined` when not present. */
|
|
44
|
+
getCookie(name: string): string | undefined;
|
|
45
|
+
/** Write a response cookie (admin session refresh, preview toggles). */
|
|
46
|
+
setCookie(name: string, value: string, options?: HostCookieSetOptions): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Register the host adapter's bridge. Idempotent and last-write-wins —
|
|
50
|
+
* host adapters register from side-effect imports, which may evaluate
|
|
51
|
+
* more than once across module graphs.
|
|
52
|
+
*/
|
|
53
|
+
export declare function registerHostRequestBridge(bridge: HostRequestBridge): void;
|
|
54
|
+
/** The registered bridge, or `undefined` when no host has registered one. */
|
|
55
|
+
export declare function tryGetHostRequestBridge(): HostRequestBridge | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The registered bridge, throwing with setup guidance when absent. Cookie
|
|
58
|
+
* reads/writes require a host; scripts and tests that have no request
|
|
59
|
+
* should pass an explicit `requestContext` to `createBylineClient`
|
|
60
|
+
* instead of using the request-bound getters.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getHostRequestBridge(): HostRequestBridge;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
const BYLINE_HOST_REQUEST_BRIDGE = Symbol.for('__byline_host_request_bridge__');
|
|
9
|
+
/**
|
|
10
|
+
* Register the host adapter's bridge. Idempotent and last-write-wins —
|
|
11
|
+
* host adapters register from side-effect imports, which may evaluate
|
|
12
|
+
* more than once across module graphs.
|
|
13
|
+
*/
|
|
14
|
+
export function registerHostRequestBridge(bridge) {
|
|
15
|
+
;
|
|
16
|
+
globalThis[BYLINE_HOST_REQUEST_BRIDGE] = bridge;
|
|
17
|
+
}
|
|
18
|
+
/** The registered bridge, or `undefined` when no host has registered one. */
|
|
19
|
+
export function tryGetHostRequestBridge() {
|
|
20
|
+
return globalThis[BYLINE_HOST_REQUEST_BRIDGE] ?? undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The registered bridge, throwing with setup guidance when absent. Cookie
|
|
24
|
+
* reads/writes require a host; scripts and tests that have no request
|
|
25
|
+
* should pass an explicit `requestContext` to `createBylineClient`
|
|
26
|
+
* instead of using the request-bound getters.
|
|
27
|
+
*/
|
|
28
|
+
export function getHostRequestBridge() {
|
|
29
|
+
const bridge = tryGetHostRequestBridge();
|
|
30
|
+
if (!bridge) {
|
|
31
|
+
throw new Error('No HostRequestBridge registered. A host adapter (e.g. ' +
|
|
32
|
+
'@byline/host-tanstack-start) must call registerHostRequestBridge() ' +
|
|
33
|
+
'at server boot before request-bound client getters can resolve ' +
|
|
34
|
+
'cookies. Scripts and tests should pass an explicit requestContext ' +
|
|
35
|
+
'to createBylineClient instead.');
|
|
36
|
+
}
|
|
37
|
+
return bridge;
|
|
38
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { validateAdminConfigs } from './config/validate-admin-configs.js';
|
|
|
6
6
|
export { RESERVED_FIELD_NAMES } from './config/validate-collections.js';
|
|
7
7
|
export { type BylineCore, getBylineCore, initBylineCore } from './core.js';
|
|
8
8
|
export * from './defaults/default-values.js';
|
|
9
|
+
export { getHostRequestBridge, type HostCookieSetOptions, type HostRequestBridge, registerHostRequestBridge, tryGetHostRequestBridge, } from './host/host-request-bridge.js';
|
|
9
10
|
export { BylineError, ERR_AUDIT_UNSUPPORTED, ERR_CONFLICT, ERR_DATABASE, ERR_NOT_FOUND, ERR_READ_BUDGET_EXCEEDED, ERR_READ_RECURSION, ERR_STORAGE, ERR_TREE_HOOK_COMMITTED, ERR_UNHANDLED, ERR_VALIDATION, ErrorCodes, type ErrorReport, TREE_HOOK_COMMITTED_MARKER, TREE_PLACEMENT_STALE_MARKER, } from './lib/errors.js';
|
|
10
11
|
export { generateKeyBetween, generateNKeysBetween, validateOrderKey, } from './lib/fractional-index.js';
|
|
11
12
|
export { type BylineLogger, getLogger } from './lib/logger.js';
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export { validateAdminConfigs } from './config/validate-admin-configs.js';
|
|
|
22
22
|
export { RESERVED_FIELD_NAMES } from './config/validate-collections.js';
|
|
23
23
|
export { getBylineCore, initBylineCore } from './core.js';
|
|
24
24
|
export * from './defaults/default-values.js';
|
|
25
|
+
export { getHostRequestBridge, registerHostRequestBridge, tryGetHostRequestBridge, } from './host/host-request-bridge.js';
|
|
25
26
|
export { BylineError, ERR_AUDIT_UNSUPPORTED, ERR_CONFLICT, ERR_DATABASE, ERR_NOT_FOUND, ERR_READ_BUDGET_EXCEEDED, ERR_READ_RECURSION, ERR_STORAGE, ERR_TREE_HOOK_COMMITTED, ERR_UNHANDLED, ERR_VALIDATION, ErrorCodes, TREE_HOOK_COMMITTED_MARKER, TREE_PLACEMENT_STALE_MARKER, } from './lib/errors.js';
|
|
26
27
|
export { generateKeyBetween, generateNKeysBetween, validateOrderKey, } from './lib/fractional-index.js';
|
|
27
28
|
export { getLogger } from './lib/logger.js';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/core",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.1.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"pino": "^10.3.1",
|
|
82
82
|
"sharp": "^0.35.3",
|
|
83
83
|
"zod": "^4.4.3",
|
|
84
|
-
"@byline/auth": "4.
|
|
84
|
+
"@byline/auth": "4.1.0"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@biomejs/biome": "2.5.2",
|