@korajs/core 1.0.0-beta.12 → 1.0.0-beta.13

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/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @korajs/core
2
2
 
3
- Schema definitions, operations, Hybrid Logical Clock, version vectors, and type inference for Kora.js. This is the foundation package -- all other packages depend on it.
3
+ Schema definitions, type inference, operations, the hybrid logical clock, version vectors and the record fold (the per-field merge every replica runs) for Kora.js. It is the foundation: every other package depends on it.
4
4
 
5
5
  > Most developers don't install this directly. Use [`korajs`](https://www.npmjs.com/package/korajs) instead.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- pnpm add @korajs/core
10
+ pnpm add @korajs/core@beta
11
11
  ```
12
12
 
13
13
  ## Usage
@@ -25,7 +25,7 @@ const schema = defineSchema({
25
25
  title: t.string(),
26
26
  completed: t.boolean().default(false),
27
27
  tags: t.array(t.string()).default([]),
28
- notes: t.richtext(),
28
+ notes: t.richtext().optional(),
29
29
  priority: t.enum(['low', 'medium', 'high']).default('medium'),
30
30
  createdAt: t.timestamp().auto(),
31
31
  },
@@ -48,29 +48,37 @@ const ts2 = clock.now()
48
48
  // Timestamps are always monotonically increasing
49
49
  HybridLogicalClock.compare(ts1, ts2) // negative (ts1 < ts2)
50
50
 
51
- // Merge with a remote timestamp
52
- const ts3 = clock.receive(remoteTimestamp)
51
+ // Merge a remote timestamp (rejected, without changing the clock, if malformed or
52
+ // more than 5 minutes in the future)
53
+ const ts3 = clock.receive({ wallTime: ts2.wallTime + 5, logical: 0, nodeId: 'node-2' })
53
54
  ```
54
55
 
55
56
  ### Version Vectors
56
57
 
57
58
  ```typescript
58
- import { mergeVectors, deltaOperations } from '@korajs/core'
59
+ import { computeDelta, mergeVectors, type Operation, type VersionVector } from '@korajs/core'
60
+
61
+ declare const localVector: VersionVector
62
+ declare const remoteVector: VersionVector
63
+ declare const log: { getRange(nodeId: string, from: number, to: number): Promise<Operation[]> }
59
64
 
60
65
  const merged = mergeVectors(localVector, remoteVector)
61
- const missing = deltaOperations(localVector, remoteVector, operationLog)
66
+ const missing = await computeDelta(localVector, remoteVector, log) // causal order
62
67
  ```
63
68
 
64
69
  ## What's Inside
65
70
 
66
- - **Schema system** -- `defineSchema`, `t` field builders, full TypeScript type inference
67
- - **Operation type** -- immutable, content-addressed mutation records
68
- - **Hybrid Logical Clock** -- causal ordering without synchronized clocks
69
- - **Version vectors** -- efficient delta sync computation
70
- - **Error types** -- structured `KoraError` base class
71
+ - **Schema system**: `defineSchema`, `t` field builders, full TypeScript type inference
72
+ - **Operation type**: immutable, content-addressed mutation records
73
+ - **Hybrid Logical Clock**: causal ordering without synchronized clocks
74
+ - **Version vectors**: delta computation for uploads
75
+ - **Record fold**: `foldRecord`, `mergeOp`, `materialize`, `joinStates`: one deterministic per-field CRDT
76
+ - **Atomic ops**: `op.increment`, `op.append` and friends
77
+ - **Migrations**: `migrate()` builders for schema versions
78
+ - **Error types**: structured `KoraError` base class with codes
71
79
 
72
80
  ## License
73
81
 
74
82
  MIT
75
83
 
76
- See the [full documentation](https://github.com/ehoneahobed/kora) for guides, API reference, and examples.
84
+ See the [Core API reference](https://korajs.dev/api/core) and the [guides](https://korajs.dev).
@@ -1,9 +1,9 @@
1
- import { p as KoraEventEmitter } from '../events-D7JCtQSD.cjs';
2
- import { S as ScopeMap } from '../build-scope-map-CItP1gsz.cjs';
1
+ import { X as KoraEventEmitter } from '../events-BSU-0z5O.cjs';
2
+ import { S as ScopeMap } from '../build-scope-map-ClIq_doj.cjs';
3
3
 
4
4
  /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
5
  interface KoraBindingSyncStatus {
6
- status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error' | 'auth-required';
6
+ status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error' | 'auth-required' | 'encryption-locked';
7
7
  reconnecting: boolean;
8
8
  pendingOperations: number;
9
9
  lastSyncedAt: number | null;
@@ -62,8 +62,14 @@ interface UseMutationResultBase<TData, TArgs extends unknown[]> {
62
62
  * Created by `createKoraAuthSync()` in `@korajs/auth`.
63
63
  */
64
64
  interface AuthSyncBinding {
65
- /** Returns the access token for sync handshake (empty string when signed out). */
66
- auth: () => Promise<{
65
+ /**
66
+ * Returns the access token for sync handshake (empty string when signed out).
67
+ * Called with `{ forceRefresh: true }` after the server ended the session with
68
+ * `AUTH_EXPIRED` / `AUTH_REVOKED`: refresh instead of returning the cached token.
69
+ */
70
+ auth: (options?: {
71
+ forceRefresh?: boolean;
72
+ }) => Promise<{
67
73
  token: string;
68
74
  }>;
69
75
  /** Resolve whether sync may currently open a transport. Tokens are never used as readiness signals. */
@@ -92,7 +98,21 @@ type AuthSyncState = {
92
98
  } | {
93
99
  state: 'authenticated';
94
100
  userId: string;
95
- token: string;
101
+ /**
102
+ * Fresh access token, or null while authenticated-offline: the identity
103
+ * is known from stored credentials but no fresh token can be minted now.
104
+ * The local store opens either way; a sync transport waits for a token.
105
+ */
106
+ token: string | null;
107
+ /** Device id of the stored credentials, when known. */
108
+ deviceId?: string;
109
+ /** True while the session is authenticated-offline (or locked). */
110
+ offline?: boolean;
111
+ /**
112
+ * True when the offline grace period ran out or the device clock moved
113
+ * backwards. The UI should lock; local data is never wiped.
114
+ */
115
+ locked?: boolean;
96
116
  };
97
117
 
98
118
  interface MutationControllerState {
@@ -1,9 +1,9 @@
1
- import { p as KoraEventEmitter } from '../events-D7JCtQSD.js';
2
- import { S as ScopeMap } from '../build-scope-map-Cv5HfCQP.js';
1
+ import { X as KoraEventEmitter } from '../events-BSU-0z5O.js';
2
+ import { S as ScopeMap } from '../build-scope-map-CIFa58oA.js';
3
3
 
4
4
  /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
5
  interface KoraBindingSyncStatus {
6
- status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error' | 'auth-required';
6
+ status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error' | 'auth-required' | 'encryption-locked';
7
7
  reconnecting: boolean;
8
8
  pendingOperations: number;
9
9
  lastSyncedAt: number | null;
@@ -62,8 +62,14 @@ interface UseMutationResultBase<TData, TArgs extends unknown[]> {
62
62
  * Created by `createKoraAuthSync()` in `@korajs/auth`.
63
63
  */
64
64
  interface AuthSyncBinding {
65
- /** Returns the access token for sync handshake (empty string when signed out). */
66
- auth: () => Promise<{
65
+ /**
66
+ * Returns the access token for sync handshake (empty string when signed out).
67
+ * Called with `{ forceRefresh: true }` after the server ended the session with
68
+ * `AUTH_EXPIRED` / `AUTH_REVOKED`: refresh instead of returning the cached token.
69
+ */
70
+ auth: (options?: {
71
+ forceRefresh?: boolean;
72
+ }) => Promise<{
67
73
  token: string;
68
74
  }>;
69
75
  /** Resolve whether sync may currently open a transport. Tokens are never used as readiness signals. */
@@ -92,7 +98,21 @@ type AuthSyncState = {
92
98
  } | {
93
99
  state: 'authenticated';
94
100
  userId: string;
95
- token: string;
101
+ /**
102
+ * Fresh access token, or null while authenticated-offline: the identity
103
+ * is known from stored credentials but no fresh token can be minted now.
104
+ * The local store opens either way; a sync transport waits for a token.
105
+ */
106
+ token: string | null;
107
+ /** Device id of the stored credentials, when known. */
108
+ deviceId?: string;
109
+ /** True while the session is authenticated-offline (or locked). */
110
+ offline?: boolean;
111
+ /**
112
+ * True when the offline grace period ran out or the device clock moved
113
+ * backwards. The UI should lock; local data is never wiped.
114
+ */
115
+ locked?: boolean;
96
116
  };
97
117
 
98
118
  interface MutationControllerState {
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './events-D7JCtQSD.js';
1
+ import { S as SchemaDefinition } from './events-BSU-0z5O.js';
2
2
 
3
3
  /**
4
4
  * Per-collection scope map: `{ collectionName: { field: value, ... } }`
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './events-D7JCtQSD.cjs';
1
+ import { S as SchemaDefinition } from './events-BSU-0z5O.cjs';
2
2
 
3
3
  /**
4
4
  * Per-collection scope map: `{ collectionName: { field: value, ... } }`