@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.
@@ -6,8 +6,8 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import { createHash } from 'node:crypto';
9
- const FORMAT_VERSION = 1;
10
- const HASH_DOMAIN = '@byline/core/codegen:collection-types:v1\n';
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
- return `${[...importLines, ...declarations].join('\n').replace(/\n+$/, '')}\n`;
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: 1\n\/\/ Hash: [a-f0-9]{64}\n\n/);
32
- expect(hash).toBe(createHash('sha256').update(`@byline/core/codegen:collection-types:v1\n${body}`).digest('hex'));
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.0.0",
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.0.0"
84
+ "@byline/auth": "4.1.0"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@biomejs/biome": "2.5.2",