@forgeax/engine-net 0.0.0-dev.8d955ade1c79

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.
Files changed (51) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +196 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/endpoint/endpoint.d.ts +43 -0
  5. package/dist/endpoint/endpoint.d.ts.map +1 -0
  6. package/dist/endpoint/errors.d.ts +82 -0
  7. package/dist/endpoint/errors.d.ts.map +1 -0
  8. package/dist/endpoint/memory.d.ts +15 -0
  9. package/dist/endpoint/memory.d.ts.map +1 -0
  10. package/dist/index.d.ts +21 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.mjs +1648 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/replication/authority.d.ts +19 -0
  15. package/dist/replication/authority.d.ts.map +1 -0
  16. package/dist/replication/codec.d.ts +8 -0
  17. package/dist/replication/codec.d.ts.map +1 -0
  18. package/dist/replication/constants.d.ts +5 -0
  19. package/dist/replication/constants.d.ts.map +1 -0
  20. package/dist/replication/errors.d.ts +86 -0
  21. package/dist/replication/errors.d.ts.map +1 -0
  22. package/dist/replication/handshake.d.ts +5 -0
  23. package/dist/replication/handshake.d.ts.map +1 -0
  24. package/dist/replication/profile.d.ts +31 -0
  25. package/dist/replication/profile.d.ts.map +1 -0
  26. package/dist/replication/protocol.d.ts +63 -0
  27. package/dist/replication/protocol.d.ts.map +1 -0
  28. package/dist/replication/replica.d.ts +30 -0
  29. package/dist/replication/replica.d.ts.map +1 -0
  30. package/dist/session/net-session.d.ts +66 -0
  31. package/dist/session/net-session.d.ts.map +1 -0
  32. package/dist/session/recovery.d.ts +93 -0
  33. package/dist/session/recovery.d.ts.map +1 -0
  34. package/dist/session/session-plugin.d.ts +14 -0
  35. package/dist/session/session-plugin.d.ts.map +1 -0
  36. package/package.json +58 -0
  37. package/src/endpoint/endpoint.ts +58 -0
  38. package/src/endpoint/errors.ts +164 -0
  39. package/src/endpoint/memory.ts +194 -0
  40. package/src/index.ts +90 -0
  41. package/src/replication/authority.ts +177 -0
  42. package/src/replication/codec.ts +323 -0
  43. package/src/replication/constants.ts +5 -0
  44. package/src/replication/errors.ts +78 -0
  45. package/src/replication/handshake.ts +18 -0
  46. package/src/replication/profile.ts +111 -0
  47. package/src/replication/protocol.ts +86 -0
  48. package/src/replication/replica.ts +301 -0
  49. package/src/session/net-session.ts +697 -0
  50. package/src/session/recovery.ts +204 -0
  51. package/src/session/session-plugin.ts +68 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/replication/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { Component, EntityHandle, World } from '@forgeax/engine-ecs';
2
+ import { type Result } from '@forgeax/engine-types';
3
+ import type { NetEndpoint } from '../endpoint/endpoint';
4
+ import { NetError } from './errors';
5
+ import type { ReplicationLimits, ReplicationProfile } from './profile';
6
+ import type { ReplicationDataPacket } from './protocol';
7
+ export declare class ReplicaCoordinator {
8
+ #private;
9
+ constructor(world: World, profile: ReplicationProfile, _endpoint?: unknown);
10
+ entityFor(id: number): EntityHandle | undefined;
11
+ readComponent(id: number, component: Component): Record<string, unknown> | undefined;
12
+ snapshot(): readonly {
13
+ id: number;
14
+ components: readonly string[];
15
+ }[];
16
+ disconnect(): void;
17
+ /** Remove the last replica baseline when the authority connection closes. */
18
+ clear(): void;
19
+ get stopped(): boolean;
20
+ get tick(): number;
21
+ /** Report the last accepted, duplicate, or stale-epoch packet decision. */
22
+ get lastPacketOutcome(): 'accepted' | 'duplicate' | 'ignored-old-epoch';
23
+ getPendingUnresolvedReferences(): number;
24
+ validate(packet: ReplicationDataPacket): NetError | null;
25
+ apply(packet: ReplicationDataPacket): Result<void, NetError>;
26
+ }
27
+ export declare function createReplicaCoordinator(world: World, profile: ReplicationProfile, endpoint?: NetEndpoint): ReplicaCoordinator;
28
+ export declare function applyReplicationPacket(replica: ReplicaCoordinator, packet: ReplicationDataPacket): Result<void, NetError>;
29
+ export declare function decodeAndApplyReplicationPacket(replica: ReplicaCoordinator, bytes: Uint8Array, limits: ReplicationLimits): Result<void, NetError>;
30
+ //# sourceMappingURL=replica.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replica.d.ts","sourceRoot":"","sources":["../../src/replication/replica.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,qBAAa,kBAAkB;;gBASjB,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAE,OAAO;IAI1E,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAG/C,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAMpF,QAAQ,IAAI,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAAE;IAUpE,UAAU,IAAI,IAAI;IAClB,6EAA6E;IAC7E,KAAK,IAAI,IAAI;IAIb,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,IAAI,IAAI,MAAM,CAEjB;IACD,2EAA2E;IAC3E,IAAI,iBAAiB,IAAI,UAAU,GAAG,WAAW,GAAG,mBAAmB,CAEtE;IACD,8BAA8B,IAAI,MAAM;IASxC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,GAAG,QAAQ,GAAG,IAAI;IAkHxD,KAAK,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC;CAoF7D;AACD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,CAAC,EAAE,WAAW,GACrB,kBAAkB,CAEpB;AACD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,qBAAqB,GAC5B,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAExB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,iBAAiB,GACxB,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAgBxB"}
@@ -0,0 +1,66 @@
1
+ import { type Result } from '@forgeax/engine-types';
2
+ import type { NetEndpoint, NetEndpointConnector, PeerId } from '../endpoint/endpoint';
3
+ import { type EndpointError } from '../endpoint/errors';
4
+ import type { AuthorityCoordinator } from '../replication/authority';
5
+ import { NetError } from '../replication/errors';
6
+ import { type ReplicationLimits } from '../replication/profile';
7
+ import { type ReplicaCoordinator } from '../replication/replica';
8
+ import { type NetRecoveryOutcome, type NetRecoveryPolicy, type NetRecoverySnapshot, type SessionId } from './recovery';
9
+ export interface PeerSnapshot {
10
+ readonly peerIds: ReadonlyArray<PeerId>;
11
+ readonly connected: boolean;
12
+ }
13
+ export interface SessionSnapshot {
14
+ readonly sessionIds: ReadonlyArray<SessionId>;
15
+ readonly connected: boolean;
16
+ }
17
+ export interface NetSessionClock {
18
+ readonly now: () => number;
19
+ readonly schedule: (delayMs: number, callback: () => void) => {
20
+ cancel(): void;
21
+ };
22
+ }
23
+ export interface NetSessionResourceCounts {
24
+ readonly pendingConnects: number;
25
+ readonly timers: number;
26
+ readonly ledgers: number;
27
+ readonly callbacks: number;
28
+ }
29
+ export interface NetSessionConfig {
30
+ readonly endpoint?: NetEndpoint;
31
+ readonly connector?: NetEndpointConnector;
32
+ readonly sessionId?: SessionId | number;
33
+ readonly recovery?: Partial<NetRecoveryPolicy>;
34
+ readonly clock?: NetSessionClock;
35
+ readonly maxRawMessages: number;
36
+ }
37
+ export interface RawMessage {
38
+ readonly peerId: PeerId;
39
+ readonly sessionId: SessionId;
40
+ readonly data: Uint8Array;
41
+ }
42
+ export declare class NetSession {
43
+ #private;
44
+ constructor(config: NetSessionConfig);
45
+ receiveEvents(): readonly NetError[];
46
+ drainRawMessages(): RawMessage[];
47
+ getPeerSnapshot(): PeerSnapshot;
48
+ getSessionSnapshot(): SessionSnapshot;
49
+ /** Return lifecycle, epoch, sequence, ledger, and owned-resource evidence. */
50
+ getRecoverySnapshot(): NetRecoverySnapshot;
51
+ getResourceSnapshot(): NetSessionResourceCounts;
52
+ recover(): NetRecoveryOutcome;
53
+ advanceRecovery(): void;
54
+ sendRaw(peerId: PeerId, data: Uint8Array): Result<void, EndpointError | NetError>;
55
+ /** Send one application command through the current replica attachment. */
56
+ sendToAuthority(sessionId: SessionId, data: Uint8Array): Result<void, EndpointError | NetError>;
57
+ /** Send one application message to an authority-owned logical session. */
58
+ sendToSession(sessionId: SessionId, data: Uint8Array): Result<void, EndpointError | NetError>;
59
+ attachAuthority(authority: AuthorityCoordinator): void;
60
+ requestFullBaseline(peerId: PeerId): void;
61
+ requestFullBaselineForSession(sessionId: SessionId): void;
62
+ attachReplica(coordinator: ReplicaCoordinator, limits: ReplicationLimits): void;
63
+ publish(): Result<void, NetError | EndpointError>;
64
+ dispose(): void;
65
+ }
66
+ //# sourceMappingURL=net-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"net-session.d.ts","sourceRoot":"","sources":["../../src/session/net-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { type Result } from '@forgeax/engine-types';
2
+ import type { NetEndpointConnector } from '../endpoint/endpoint';
3
+ import type { EndpointError } from '../endpoint/errors';
4
+ import { NetError, type NetErrorCode } from '../replication/errors';
5
+ declare const sessionIdBrand: unique symbol;
6
+ /** Authority-issued application identity; it is distinct from transport PeerId. */
7
+ export type SessionId = number & {
8
+ readonly [sessionIdBrand]: true;
9
+ };
10
+ export type NetSessionStateKind = 'connecting' | 'resyncing' | 'active' | 'recovering' | 'failed' | 'retired';
11
+ export type NetSessionFailure = NetError | EndpointError;
12
+ /** Public lifecycle state owned by one logical session. */
13
+ export type NetSessionState = {
14
+ readonly kind: 'connecting';
15
+ readonly sessionId: SessionId;
16
+ } | {
17
+ readonly kind: 'resyncing';
18
+ readonly sessionId: SessionId;
19
+ readonly epoch: number;
20
+ } | {
21
+ readonly kind: 'active';
22
+ readonly sessionId: SessionId;
23
+ readonly epoch: number;
24
+ readonly sequence: number;
25
+ } | {
26
+ readonly kind: 'recovering';
27
+ readonly sessionId: SessionId;
28
+ readonly epoch: number;
29
+ readonly attempt: number;
30
+ } | {
31
+ readonly kind: 'failed';
32
+ readonly sessionId: SessionId;
33
+ readonly error: NetSessionFailure;
34
+ } | {
35
+ readonly kind: 'retired';
36
+ readonly sessionId: SessionId;
37
+ readonly reason: 'disposed' | 'terminal-failure';
38
+ };
39
+ export interface NetRecoveryPolicy {
40
+ readonly maxSessions: number;
41
+ readonly maxPendingPackets: number;
42
+ readonly ackTimeoutMs: number;
43
+ readonly maxPacketRetries: number;
44
+ readonly maxReconnectAttempts: number;
45
+ readonly reconnectDeadlineMs: number;
46
+ readonly reconnectDelaysMs: readonly number[];
47
+ }
48
+ /** Finite, deterministic recovery bounds shared by every transport adapter. */
49
+ export declare const DEFAULT_NET_RECOVERY_POLICY: NetRecoveryPolicy;
50
+ /** Validate a complete policy without mutating it. */
51
+ export declare function validateNetRecoveryPolicy(policy: NetRecoveryPolicy): Result<void, NetError>;
52
+ /** Merge caller overrides with the bounded defaults and validate the result. */
53
+ export declare function resolveNetRecoveryPolicy(overrides?: Partial<NetRecoveryPolicy>): Result<NetRecoveryPolicy, NetError>;
54
+ /** Create a positive application identity without allowing plain numbers to cross the seam. */
55
+ export declare function createSessionId(value: number): Result<SessionId, NetError>;
56
+ /** Observable bounded accounting for one logical recovery session. */
57
+ export interface NetRecoverySnapshot {
58
+ readonly sessionId: SessionId;
59
+ readonly state: NetSessionState;
60
+ readonly pendingPackets: number;
61
+ readonly maxPendingPackets: number;
62
+ readonly acknowledgedSequence: number;
63
+ readonly reconnectAttempts: number;
64
+ readonly epoch: number;
65
+ readonly sequence: number;
66
+ readonly ownedResources: {
67
+ readonly pendingConnects: number;
68
+ readonly timers: number;
69
+ readonly ledgers: number;
70
+ readonly callbacks: number;
71
+ };
72
+ readonly lastError?: NetSessionFailure;
73
+ }
74
+ /** Stable result kinds for repeated recovery requests. */
75
+ export type NetRecoveryOutcome = {
76
+ readonly kind: 'started';
77
+ readonly sessionId: SessionId;
78
+ } | {
79
+ readonly kind: 'already-recovering';
80
+ readonly sessionId: SessionId;
81
+ } | {
82
+ readonly kind: 'already-active';
83
+ readonly sessionId: SessionId;
84
+ } | {
85
+ readonly kind: 'retired';
86
+ readonly sessionId: SessionId;
87
+ };
88
+ export declare function isLegalNetSessionTransition(from: NetSessionStateKind, to: NetSessionStateKind): boolean;
89
+ /** Validate an immutable state replacement before a session publishes it. */
90
+ export declare function transitionNetSessionState(from: NetSessionState, to: NetSessionState): Result<NetSessionState, NetError>;
91
+ export type { NetEndpointConnector };
92
+ export declare const RECOVERY_ERROR_CODES: readonly NetErrorCode[];
93
+ //# sourceMappingURL=recovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/session/recovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEpE,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAE5C,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAErE,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,SAAS,CAAC;AAEd,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEzD,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAC9D;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrF;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;CACnC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAAC;CAClD,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAED,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,EAAE,iBAQxC,CAAC;AAeH,sDAAsD;AACtD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAsB3F;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACzC,MAAM,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAWrC;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAW1E;AAED,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE;QACvB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC;CACxC;AAED,0DAA0D;AAC1D,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC;AAWhE,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,mBAAmB,EACzB,EAAE,EAAE,mBAAmB,GACtB,OAAO,CAET;AAED,6EAA6E;AAC7E,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,eAAe,GAClB,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,CAWnC;AAED,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC,eAAO,MAAM,oBAAoB,EAAE,SAAS,YAAY,EAMvD,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Plugin } from '@forgeax/engine-plugin';
2
+ import type { NetEndpoint, NetEndpointConnector } from '../endpoint/endpoint';
3
+ import { type NetSessionClock } from './net-session';
4
+ import type { NetRecoveryPolicy } from './recovery';
5
+ export interface NetPluginConfig {
6
+ readonly endpoint?: NetEndpoint;
7
+ readonly connector?: NetEndpointConnector;
8
+ readonly sessionId?: number;
9
+ readonly recovery?: Partial<NetRecoveryPolicy>;
10
+ readonly clock?: NetSessionClock;
11
+ readonly maxRawMessages?: number;
12
+ }
13
+ export declare function netPlugin(config: NetPluginConfig): Plugin;
14
+ //# sourceMappingURL=session-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-plugin.d.ts","sourceRoot":"","sources":["../../src/session/session-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAiDzD"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@forgeax/engine-net",
3
+ "version": "0.0.0-dev.8d955ade1c79",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "sideEffects": false,
8
+ "description": "Memory network transport, replication session, and profile-driven ECS sync for forgeax Engine.",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "main": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "dependencies": {
25
+ "@forgeax/engine-ecs": "0.0.0-dev.8d955ade1c79",
26
+ "@forgeax/engine-plugin": "0.0.0-dev.8d955ade1c79",
27
+ "@forgeax/engine-types": "0.0.0-dev.8d955ade1c79"
28
+ },
29
+ "forgeax": {
30
+ "metrics": {
31
+ "bundle-size": {
32
+ "enabled": true,
33
+ "path": "dist/index.mjs",
34
+ "compression": "gzip"
35
+ },
36
+ "fps": {
37
+ "enabled": false,
38
+ "reason": "library package; no runtime canvas; E2E proof deferred to Loop 3"
39
+ },
40
+ "bench": {
41
+ "enabled": false,
42
+ "reason": "no performance benchmarks; deterministic correctness verified by unit and model tests"
43
+ },
44
+ "gate": {
45
+ "enabled": false,
46
+ "reason": "no package-level binary gate; net correctness gated by pnpm test:unit"
47
+ },
48
+ "spike-report": {
49
+ "enabled": false,
50
+ "reason": "not a spike package"
51
+ }
52
+ }
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "test": "vitest run"
57
+ }
58
+ }
@@ -0,0 +1,58 @@
1
+ // @forgeax/engine-net -- transport-only endpoint contract.
2
+ //
3
+ // NetEndpoint is the minimum transport seam: complete bytes, actual PeerId,
4
+ // and connect/disconnect lifecycle. No ECS, profile, or codec dependency.
5
+ // Reliable ordered delivery per peer or explicit connection failure.
6
+ // (requirements AC-02, plan-strategy D-3)
7
+
8
+ import type { Result } from '@forgeax/engine-types';
9
+ import type { EndpointError } from './errors';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // PeerId -- opaque, branded peer identity (transport-level, not NetEntityId)
13
+ // ---------------------------------------------------------------------------
14
+
15
+ declare const __peerIdBrand: unique symbol;
16
+
17
+ /** Opaque transport-level peer identity. Never a plain number. */
18
+ export type PeerId = number & { readonly [__peerIdBrand]: true };
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Endpoint events
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /** Transport-level event emitted by NetEndpoint.poll(). */
25
+ export type EndpointEvent =
26
+ | { readonly kind: 'peer-connected'; readonly peerId: PeerId }
27
+ | { readonly kind: 'peer-disconnected'; readonly peerId: PeerId }
28
+ | { readonly kind: 'message'; readonly peerId: PeerId; readonly data: Uint8Array };
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // NetEndpoint -- transport-only contract
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * Transport-only endpoint contract.
36
+ *
37
+ * Delivers complete Uint8Array messages with actual PeerId and connect/disconnect
38
+ * lifecycle. Reliable ordered delivery per peer or explicit connection failure.
39
+ * No ECS, profile, or codec knowledge.
40
+ */
41
+ export interface NetEndpoint {
42
+ /** Poll for pending events (connect, disconnect, message). Returns empty array when idle. */
43
+ poll(): EndpointEvent[];
44
+
45
+ /** Send a complete message to a connected peer. Returns structured error on failure. */
46
+ send(peerId: PeerId, data: Uint8Array): Result<void, EndpointError>;
47
+
48
+ /** Close the endpoint. No further send/poll should succeed after close. */
49
+ close(): Result<void, EndpointError>;
50
+ }
51
+
52
+ /**
53
+ * Realm-neutral capability for creating a replacement endpoint.
54
+ * Session policy, retry bounds, and replication semantics remain in net.
55
+ */
56
+ export interface NetEndpointConnector {
57
+ connect(signal: AbortSignal): Promise<Result<NetEndpoint, EndpointError>>;
58
+ }
@@ -0,0 +1,164 @@
1
+ // @forgeax/engine-net -- endpoint structured errors.
2
+ //
3
+ // Closed union of transport-level failures. Each variant carries .code, .expected,
4
+ // .hint, and per-code .detail. AI users exhaustively switch on .code without a
5
+ // default branch (requirements AC-13).
6
+
7
+ import type { PeerId } from './endpoint';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // EndpointErrorCode -- closed 5-member union derived from endpointErrorPolicy
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /** Transport-level endpoint error codes (requirements AC-02, AC-13). */
14
+ export type EndpointErrorCode = keyof typeof endpointErrorPolicy;
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Per-code detail payloads
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface EndpointDetailPeerNotFound {
21
+ readonly peerId: PeerId;
22
+ }
23
+
24
+ export interface EndpointDetailConnectionClosed {
25
+ readonly peerId: PeerId;
26
+ }
27
+
28
+ export interface EndpointDetailSendFailed {
29
+ readonly peerId: PeerId;
30
+ readonly cause: string;
31
+ }
32
+
33
+ export interface EndpointDetailAlreadyClosed {
34
+ readonly cause: string;
35
+ }
36
+
37
+ export interface EndpointDetailConnectionFailed {
38
+ readonly address: string;
39
+ readonly cause: string;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Conditional resolver
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export type EndpointErrorDetailFor<C extends EndpointErrorCode> = C extends 'peer-not-found'
47
+ ? EndpointDetailPeerNotFound
48
+ : C extends 'connection-closed'
49
+ ? EndpointDetailConnectionClosed
50
+ : C extends 'send-failed'
51
+ ? EndpointDetailSendFailed
52
+ : C extends 'already-closed'
53
+ ? EndpointDetailAlreadyClosed
54
+ : C extends 'connection-failed'
55
+ ? EndpointDetailConnectionFailed
56
+ : never;
57
+
58
+ /** Tagged union of all endpoint error detail variants. */
59
+ export type EndpointErrorDetail = EndpointErrorDetailFor<EndpointErrorCode>;
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Error class
63
+ // ---------------------------------------------------------------------------
64
+
65
+ class EndpointErrorClass extends Error {
66
+ readonly code: EndpointErrorCode;
67
+ readonly expected: string;
68
+ readonly hint: string;
69
+ readonly detail: EndpointErrorDetail;
70
+
71
+ constructor(args: {
72
+ code: EndpointErrorCode;
73
+ expected: string;
74
+ hint: string;
75
+ detail: EndpointErrorDetail;
76
+ }) {
77
+ let suffix = '';
78
+ if (args.code === 'peer-not-found') {
79
+ const d = args.detail as EndpointDetailPeerNotFound;
80
+ suffix = ` (peerId=${d.peerId})`;
81
+ } else if (args.code === 'connection-closed') {
82
+ const d = args.detail as EndpointDetailConnectionClosed;
83
+ suffix = ` (peerId=${d.peerId})`;
84
+ } else if (args.code === 'send-failed') {
85
+ const d = args.detail as EndpointDetailSendFailed;
86
+ suffix = ` (peerId=${d.peerId}, cause=${d.cause})`;
87
+ } else if (args.code === 'already-closed') {
88
+ const d = args.detail as EndpointDetailAlreadyClosed;
89
+ suffix = ` (cause=${d.cause})`;
90
+ } else if (args.code === 'connection-failed') {
91
+ const d = args.detail as EndpointDetailConnectionFailed;
92
+ suffix = ` (address=${d.address}, cause=${d.cause})`;
93
+ }
94
+ super(`[EndpointError ${args.code}] expected: ${args.expected}; hint: ${args.hint}${suffix}`);
95
+ this.name = 'EndpointError';
96
+ this.code = args.code;
97
+ this.expected = args.expected;
98
+ this.hint = args.hint;
99
+ this.detail = args.detail;
100
+ }
101
+ }
102
+
103
+ type EndpointErrorVariant<C extends EndpointErrorCode> = EndpointErrorClass & {
104
+ readonly code: C;
105
+ readonly detail: EndpointErrorDetailFor<C>;
106
+ };
107
+
108
+ export type EndpointError = {
109
+ [C in EndpointErrorCode]: EndpointErrorVariant<C>;
110
+ }[EndpointErrorCode];
111
+
112
+ interface EndpointErrorConstructor {
113
+ new <C extends EndpointErrorCode>(args: {
114
+ code: C;
115
+ expected: string;
116
+ hint: string;
117
+ detail: EndpointErrorDetailFor<C>;
118
+ }): EndpointErrorVariant<C>;
119
+ readonly prototype: EndpointErrorClass;
120
+ }
121
+
122
+ export const EndpointError: EndpointErrorConstructor =
123
+ EndpointErrorClass as unknown as EndpointErrorConstructor;
124
+
125
+ type EndpointErrorPolicy = { readonly expected: string; readonly hint: string };
126
+
127
+ const endpointErrorPolicy = {
128
+ 'peer-not-found': {
129
+ expected: 'the target peer must exist in the current connection set',
130
+ hint: 'verify the PeerId is from a connect event; check that the peer has not disconnected',
131
+ },
132
+ 'connection-closed': {
133
+ expected: 'the peer connection must be alive for the operation',
134
+ hint: 'the peer disconnected; poll for a disconnect event and handle the lifecycle',
135
+ },
136
+ 'send-failed': {
137
+ expected: 'message bytes must be delivered to the target peer or the connection must fail',
138
+ hint: 'the memory connection is broken; the peer may have disconnected or the buffer is full',
139
+ },
140
+ 'already-closed': {
141
+ expected: 'the endpoint must be open for any operation',
142
+ hint: 'the endpoint is closed; create a new endpoint pair for further communication',
143
+ },
144
+ 'connection-failed': {
145
+ expected:
146
+ 'the endpoint factory must successfully establish a connection or bind to the listen address',
147
+ hint: 'the initial connection or bind failed; verify the address is reachable and the port is not in use, then retry',
148
+ },
149
+ } satisfies Record<string, EndpointErrorPolicy>;
150
+
151
+ /** Expected-invariant table per error code. */
152
+ export const ENDPOINT_EXPECTED: Readonly<Record<EndpointErrorCode, string>> = Object.fromEntries(
153
+ Object.entries(endpointErrorPolicy).map(([code, policy]) => [code, policy.expected]),
154
+ ) as Readonly<Record<EndpointErrorCode, string>>;
155
+
156
+ /** Actionable hint table per error code. */
157
+ export const ENDPOINT_ERROR_HINTS: Readonly<Record<EndpointErrorCode, string>> = Object.fromEntries(
158
+ Object.entries(endpointErrorPolicy).map(([code, policy]) => [code, policy.hint]),
159
+ ) as Readonly<Record<EndpointErrorCode, string>>;
160
+
161
+ /** Type guard for narrowing unknown to EndpointError. */
162
+ export function isEndpointError(err: unknown): err is EndpointError {
163
+ return err instanceof EndpointErrorClass;
164
+ }