@voidhash/mimic-effect 0.0.2 → 0.0.4
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/.turbo/turbo-build.log +99 -14
- package/dist/DocumentManager.cjs +118 -0
- package/dist/DocumentManager.d.cts +45 -0
- package/dist/DocumentManager.d.cts.map +1 -0
- package/dist/DocumentManager.d.mts +45 -0
- package/dist/DocumentManager.d.mts.map +1 -0
- package/dist/DocumentManager.mjs +105 -0
- package/dist/DocumentManager.mjs.map +1 -0
- package/dist/DocumentProtocol.cjs +94 -0
- package/dist/DocumentProtocol.d.cts +113 -0
- package/dist/DocumentProtocol.d.cts.map +1 -0
- package/dist/DocumentProtocol.d.mts +113 -0
- package/dist/DocumentProtocol.d.mts.map +1 -0
- package/dist/DocumentProtocol.mjs +89 -0
- package/dist/DocumentProtocol.mjs.map +1 -0
- package/dist/MimicAuthService.cjs +55 -0
- package/dist/MimicAuthService.d.cts +65 -0
- package/dist/MimicAuthService.d.cts.map +1 -0
- package/dist/MimicAuthService.d.mts +65 -0
- package/dist/MimicAuthService.d.mts.map +1 -0
- package/dist/MimicAuthService.mjs +47 -0
- package/dist/MimicAuthService.mjs.map +1 -0
- package/dist/MimicConfig.cjs +52 -0
- package/dist/MimicConfig.d.cts +115 -0
- package/dist/MimicConfig.d.cts.map +1 -0
- package/dist/MimicConfig.d.mts +115 -0
- package/dist/MimicConfig.d.mts.map +1 -0
- package/dist/MimicConfig.mjs +43 -0
- package/dist/MimicConfig.mjs.map +1 -0
- package/dist/MimicDataStorage.cjs +83 -0
- package/dist/MimicDataStorage.d.cts +113 -0
- package/dist/MimicDataStorage.d.cts.map +1 -0
- package/dist/MimicDataStorage.d.mts +113 -0
- package/dist/MimicDataStorage.d.mts.map +1 -0
- package/dist/MimicDataStorage.mjs +74 -0
- package/dist/MimicDataStorage.mjs.map +1 -0
- package/dist/MimicServer.cjs +122 -0
- package/dist/MimicServer.d.cts +106 -0
- package/dist/MimicServer.d.cts.map +1 -0
- package/dist/MimicServer.d.mts +106 -0
- package/dist/MimicServer.d.mts.map +1 -0
- package/dist/MimicServer.mjs +116 -0
- package/dist/MimicServer.mjs.map +1 -0
- package/dist/PresenceManager.cjs +108 -0
- package/dist/PresenceManager.d.cts +91 -0
- package/dist/PresenceManager.d.cts.map +1 -0
- package/dist/PresenceManager.d.mts +91 -0
- package/dist/PresenceManager.d.mts.map +1 -0
- package/dist/PresenceManager.mjs +95 -0
- package/dist/PresenceManager.mjs.map +1 -0
- package/dist/WebSocketHandler.cjs +365 -0
- package/dist/WebSocketHandler.d.cts +34 -0
- package/dist/WebSocketHandler.d.cts.map +1 -0
- package/dist/WebSocketHandler.d.mts +34 -0
- package/dist/WebSocketHandler.d.mts.map +1 -0
- package/dist/WebSocketHandler.mjs +355 -0
- package/dist/WebSocketHandler.mjs.map +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.cjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.mjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.cjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.mjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.mjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.cjs +18 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.mjs +12 -0
- package/dist/_virtual/rolldown_runtime.cjs +43 -0
- package/dist/{chunk-C6wwvPpM.mjs → _virtual/rolldown_runtime.mjs} +1 -1
- package/dist/auth/NoAuth.cjs +43 -0
- package/dist/auth/NoAuth.d.cts +22 -0
- package/dist/auth/NoAuth.d.cts.map +1 -0
- package/dist/auth/NoAuth.d.mts +22 -0
- package/dist/auth/NoAuth.d.mts.map +1 -0
- package/dist/auth/NoAuth.mjs +36 -0
- package/dist/auth/NoAuth.mjs.map +1 -0
- package/dist/errors.cjs +74 -0
- package/dist/errors.d.cts +89 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +89 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +67 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +29 -1227
- package/dist/index.d.cts +12 -795
- package/dist/index.d.mts +12 -795
- package/dist/index.mjs +13 -1162
- package/dist/storage/InMemoryDataStorage.cjs +57 -0
- package/dist/storage/InMemoryDataStorage.d.cts +19 -0
- package/dist/storage/InMemoryDataStorage.d.cts.map +1 -0
- package/dist/storage/InMemoryDataStorage.d.mts +19 -0
- package/dist/storage/InMemoryDataStorage.d.mts.map +1 -0
- package/dist/storage/InMemoryDataStorage.mjs +48 -0
- package/dist/storage/InMemoryDataStorage.mjs.map +1 -0
- package/package.json +3 -3
- package/src/DocumentManager.ts +2 -2
- package/src/MimicConfig.ts +22 -1
- package/src/MimicServer.ts +11 -161
- package/tests/DocumentManager.test.ts +61 -0
- package/tests/MimicConfig.test.ts +72 -0
- package/tests/MimicServer.test.ts +55 -162
- package/tsdown.config.ts +1 -1
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_MimicDataStorage = require('../MimicDataStorage.cjs');
|
|
3
|
+
let effect_Effect = require("effect/Effect");
|
|
4
|
+
effect_Effect = require_rolldown_runtime.__toESM(effect_Effect);
|
|
5
|
+
let effect_Layer = require("effect/Layer");
|
|
6
|
+
effect_Layer = require_rolldown_runtime.__toESM(effect_Layer);
|
|
7
|
+
let effect_Ref = require("effect/Ref");
|
|
8
|
+
effect_Ref = require_rolldown_runtime.__toESM(effect_Ref);
|
|
9
|
+
let effect_HashMap = require("effect/HashMap");
|
|
10
|
+
effect_HashMap = require_rolldown_runtime.__toESM(effect_HashMap);
|
|
11
|
+
|
|
12
|
+
//#region src/storage/InMemoryDataStorage.ts
|
|
13
|
+
/**
|
|
14
|
+
* @since 0.0.1
|
|
15
|
+
* In-memory data storage implementation for Mimic documents.
|
|
16
|
+
* Provides ephemeral storage - data is lost when the server restarts.
|
|
17
|
+
*/
|
|
18
|
+
var InMemoryDataStorage_exports = /* @__PURE__ */ require_rolldown_runtime.__export({
|
|
19
|
+
layer: () => layer,
|
|
20
|
+
layerDefault: () => layerDefault
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Create an in-memory storage service.
|
|
24
|
+
* Uses a HashMap to store documents in memory.
|
|
25
|
+
*/
|
|
26
|
+
const makeInMemoryStorage = effect_Effect.gen(function* () {
|
|
27
|
+
const store = yield* effect_Ref.make(effect_HashMap.empty());
|
|
28
|
+
return {
|
|
29
|
+
load: (documentId) => effect_Effect.gen(function* () {
|
|
30
|
+
const current = yield* effect_Ref.get(store);
|
|
31
|
+
const result = effect_HashMap.get(current, documentId);
|
|
32
|
+
return result._tag === "Some" ? result.value : void 0;
|
|
33
|
+
}),
|
|
34
|
+
save: (documentId, state) => effect_Ref.update(store, (map) => effect_HashMap.set(map, documentId, state)),
|
|
35
|
+
delete: (documentId) => effect_Ref.update(store, (map) => effect_HashMap.remove(map, documentId)),
|
|
36
|
+
onLoad: (state) => effect_Effect.succeed(state),
|
|
37
|
+
onSave: (state) => effect_Effect.succeed(state)
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Layer that provides in-memory data storage.
|
|
42
|
+
* This is the default storage implementation - ephemeral and non-persistent.
|
|
43
|
+
*/
|
|
44
|
+
const layer = effect_Layer.effect(require_MimicDataStorage.MimicDataStorageTag, makeInMemoryStorage);
|
|
45
|
+
/**
|
|
46
|
+
* Default layer alias for convenience.
|
|
47
|
+
*/
|
|
48
|
+
const layerDefault = layer;
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
Object.defineProperty(exports, 'InMemoryDataStorage_exports', {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function () {
|
|
54
|
+
return InMemoryDataStorage_exports;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
exports.layerDefault = layerDefault;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MimicDataStorageTag } from "../MimicDataStorage.cjs";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
|
|
4
|
+
//#region src/storage/InMemoryDataStorage.d.ts
|
|
5
|
+
declare namespace InMemoryDataStorage_d_exports {
|
|
6
|
+
export { layer, layerDefault };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Layer that provides in-memory data storage.
|
|
10
|
+
* This is the default storage implementation - ephemeral and non-persistent.
|
|
11
|
+
*/
|
|
12
|
+
declare const layer: Layer.Layer<MimicDataStorageTag>;
|
|
13
|
+
/**
|
|
14
|
+
* Default layer alias for convenience.
|
|
15
|
+
*/
|
|
16
|
+
declare const layerDefault: Layer.Layer<MimicDataStorageTag, never, never>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { InMemoryDataStorage_d_exports };
|
|
19
|
+
//# sourceMappingURL=InMemoryDataStorage.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InMemoryDataStorage.d.cts","names":[],"sources":["../../src/storage/InMemoryDataStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAyDa,OAAO,KAAA,CAAM,MAAM;;;AAAhC;AAQa,cAAA,YAAY,EAAA,KAAA,CAAA,KAAA,CAAA,mBAAA,EAAA,KAAA,EAAA,KAAA,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MimicDataStorageTag } from "../MimicDataStorage.mjs";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
|
|
4
|
+
//#region src/storage/InMemoryDataStorage.d.ts
|
|
5
|
+
declare namespace InMemoryDataStorage_d_exports {
|
|
6
|
+
export { layer, layerDefault };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Layer that provides in-memory data storage.
|
|
10
|
+
* This is the default storage implementation - ephemeral and non-persistent.
|
|
11
|
+
*/
|
|
12
|
+
declare const layer: Layer.Layer<MimicDataStorageTag>;
|
|
13
|
+
/**
|
|
14
|
+
* Default layer alias for convenience.
|
|
15
|
+
*/
|
|
16
|
+
declare const layerDefault: Layer.Layer<MimicDataStorageTag, never, never>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { InMemoryDataStorage_d_exports };
|
|
19
|
+
//# sourceMappingURL=InMemoryDataStorage.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InMemoryDataStorage.d.mts","names":[],"sources":["../../src/storage/InMemoryDataStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAyDa,OAAO,KAAA,CAAM,MAAM;;;AAAhC;AAQa,cAAA,YAAY,EAAA,KAAA,CAAA,KAAA,CAAA,mBAAA,EAAA,KAAA,EAAA,KAAA,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { __export } from "../_virtual/rolldown_runtime.mjs";
|
|
2
|
+
import { MimicDataStorageTag } from "../MimicDataStorage.mjs";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as Ref from "effect/Ref";
|
|
6
|
+
import * as HashMap from "effect/HashMap";
|
|
7
|
+
|
|
8
|
+
//#region src/storage/InMemoryDataStorage.ts
|
|
9
|
+
/**
|
|
10
|
+
* @since 0.0.1
|
|
11
|
+
* In-memory data storage implementation for Mimic documents.
|
|
12
|
+
* Provides ephemeral storage - data is lost when the server restarts.
|
|
13
|
+
*/
|
|
14
|
+
var InMemoryDataStorage_exports = /* @__PURE__ */ __export({
|
|
15
|
+
layer: () => layer,
|
|
16
|
+
layerDefault: () => layerDefault
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Create an in-memory storage service.
|
|
20
|
+
* Uses a HashMap to store documents in memory.
|
|
21
|
+
*/
|
|
22
|
+
const makeInMemoryStorage = Effect.gen(function* () {
|
|
23
|
+
const store = yield* Ref.make(HashMap.empty());
|
|
24
|
+
return {
|
|
25
|
+
load: (documentId) => Effect.gen(function* () {
|
|
26
|
+
const current = yield* Ref.get(store);
|
|
27
|
+
const result = HashMap.get(current, documentId);
|
|
28
|
+
return result._tag === "Some" ? result.value : void 0;
|
|
29
|
+
}),
|
|
30
|
+
save: (documentId, state) => Ref.update(store, (map) => HashMap.set(map, documentId, state)),
|
|
31
|
+
delete: (documentId) => Ref.update(store, (map) => HashMap.remove(map, documentId)),
|
|
32
|
+
onLoad: (state) => Effect.succeed(state),
|
|
33
|
+
onSave: (state) => Effect.succeed(state)
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Layer that provides in-memory data storage.
|
|
38
|
+
* This is the default storage implementation - ephemeral and non-persistent.
|
|
39
|
+
*/
|
|
40
|
+
const layer = Layer.effect(MimicDataStorageTag, makeInMemoryStorage);
|
|
41
|
+
/**
|
|
42
|
+
* Default layer alias for convenience.
|
|
43
|
+
*/
|
|
44
|
+
const layerDefault = layer;
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { InMemoryDataStorage_exports, layerDefault };
|
|
48
|
+
//# sourceMappingURL=InMemoryDataStorage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InMemoryDataStorage.mjs","names":["layer: Layer.Layer<MimicDataStorageTag>"],"sources":["../../src/storage/InMemoryDataStorage.ts"],"sourcesContent":["/**\n * @since 0.0.1\n * In-memory data storage implementation for Mimic documents.\n * Provides ephemeral storage - data is lost when the server restarts.\n */\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as Ref from \"effect/Ref\";\nimport * as HashMap from \"effect/HashMap\";\n\nimport {\n MimicDataStorageTag,\n type MimicDataStorage,\n} from \"../MimicDataStorage.js\";\n\n// =============================================================================\n// In-Memory Storage Implementation\n// =============================================================================\n\n/**\n * Create an in-memory storage service.\n * Uses a HashMap to store documents in memory.\n */\nconst makeInMemoryStorage = Effect.gen(function* () {\n // Create a mutable reference to a HashMap for storing documents\n const store = yield* Ref.make(HashMap.empty<string, unknown>());\n\n const storage: MimicDataStorage = {\n load: (documentId: string) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const result = HashMap.get(current, documentId);\n return result._tag === \"Some\" ? result.value : undefined;\n }),\n\n save: (documentId: string, state: unknown) =>\n Ref.update(store, (map) => HashMap.set(map, documentId, state)),\n\n delete: (documentId: string) =>\n Ref.update(store, (map) => HashMap.remove(map, documentId)),\n\n onLoad: (state: unknown) => Effect.succeed(state),\n\n onSave: (state: unknown) => Effect.succeed(state),\n };\n\n return storage;\n});\n\n// =============================================================================\n// Layer\n// =============================================================================\n\n/**\n * Layer that provides in-memory data storage.\n * This is the default storage implementation - ephemeral and non-persistent.\n */\nexport const layer: Layer.Layer<MimicDataStorageTag> = Layer.effect(\n MimicDataStorageTag,\n makeInMemoryStorage\n);\n\n/**\n * Default layer alias for convenience.\n */\nexport const layerDefault = layer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,sBAAsB,OAAO,IAAI,aAAa;CAElD,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAAwB,CAAC;AAqB/D,QAnBkC;EAChC,OAAO,eACL,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;GACrC,MAAM,SAAS,QAAQ,IAAI,SAAS,WAAW;AAC/C,UAAO,OAAO,SAAS,SAAS,OAAO,QAAQ;IAC/C;EAEJ,OAAO,YAAoB,UACzB,IAAI,OAAO,QAAQ,QAAQ,QAAQ,IAAI,KAAK,YAAY,MAAM,CAAC;EAEjE,SAAS,eACP,IAAI,OAAO,QAAQ,QAAQ,QAAQ,OAAO,KAAK,WAAW,CAAC;EAE7D,SAAS,UAAmB,OAAO,QAAQ,MAAM;EAEjD,SAAS,UAAmB,OAAO,QAAQ,MAAM;EAClD;EAGD;;;;;AAUF,MAAaA,QAA0C,MAAM,OAC3D,qBACA,oBACD;;;;AAKD,MAAa,eAAe"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidhash/mimic-effect",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"typescript": "5.8.3",
|
|
25
25
|
"vite-tsconfig-paths": "^5.1.4",
|
|
26
26
|
"vitest": "^3.2.4",
|
|
27
|
-
"@voidhash/tsconfig": "0.0.
|
|
27
|
+
"@voidhash/tsconfig": "0.0.4"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"effect": "^3.19.12",
|
|
31
|
-
"@voidhash/mimic": "0.0.
|
|
31
|
+
"@voidhash/mimic": "0.0.4"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsdown",
|
package/src/DocumentManager.ts
CHANGED
|
@@ -113,10 +113,10 @@ const makeDocumentManager = Effect.gen(function* () {
|
|
|
113
113
|
() => Effect.succeed(undefined)
|
|
114
114
|
);
|
|
115
115
|
|
|
116
|
-
// Transform loaded state with onLoad hook
|
|
116
|
+
// Transform loaded state with onLoad hook, or use configured initial state for new docs
|
|
117
117
|
const initialState = rawState !== undefined
|
|
118
118
|
? yield* storage.onLoad(rawState)
|
|
119
|
-
:
|
|
119
|
+
: config.initial;
|
|
120
120
|
|
|
121
121
|
// Create PubSub for broadcasting
|
|
122
122
|
const pubsub = yield* PubSub.unbounded<Protocol.ServerBroadcast>();
|
package/src/MimicConfig.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as Context from "effect/Context";
|
|
|
6
6
|
import * as Duration from "effect/Duration";
|
|
7
7
|
import type { DurationInput } from "effect/Duration";
|
|
8
8
|
import * as Layer from "effect/Layer";
|
|
9
|
-
import
|
|
9
|
+
import { Primitive, Presence } from "@voidhash/mimic";
|
|
10
10
|
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Mimic Server Configuration
|
|
@@ -54,6 +54,13 @@ export interface MimicServerConfig<TSchema extends Primitive.AnyPrimitive = Prim
|
|
|
54
54
|
* @default undefined (presence disabled)
|
|
55
55
|
*/
|
|
56
56
|
readonly presence: Presence.AnyPresence | undefined;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Initial state for new documents.
|
|
60
|
+
* Used when a document is created and no existing state is found in storage.
|
|
61
|
+
* @default undefined (documents start empty)
|
|
62
|
+
*/
|
|
63
|
+
readonly initial: Primitive.InferState<TSchema> | undefined;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
/**
|
|
@@ -95,6 +102,17 @@ export interface MimicServerConfigOptions<TSchema extends Primitive.AnyPrimitive
|
|
|
95
102
|
* @default undefined (presence disabled)
|
|
96
103
|
*/
|
|
97
104
|
readonly presence?: Presence.AnyPresence;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Initial state for new documents.
|
|
108
|
+
* Used when a document is created and no existing state is found in storage.
|
|
109
|
+
*
|
|
110
|
+
* Type-safe: required fields (without defaults) must be provided,
|
|
111
|
+
* while optional fields and fields with defaults can be omitted.
|
|
112
|
+
*
|
|
113
|
+
* @default undefined (documents start empty or use schema defaults)
|
|
114
|
+
*/
|
|
115
|
+
readonly initial?: Primitive.InferSetInput<TSchema>;
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
/**
|
|
@@ -109,6 +127,9 @@ export const make = <TSchema extends Primitive.AnyPrimitive>(
|
|
|
109
127
|
heartbeatInterval: Duration.decode(options.heartbeatInterval ?? "30 seconds"),
|
|
110
128
|
heartbeatTimeout: Duration.decode(options.heartbeatTimeout ?? "10 seconds"),
|
|
111
129
|
presence: options.presence,
|
|
130
|
+
initial: options.initial !== undefined
|
|
131
|
+
? Primitive.applyDefaults(options.schema, options.initial as Partial<Primitive.InferState<TSchema>>)
|
|
132
|
+
: undefined,
|
|
112
133
|
});
|
|
113
134
|
|
|
114
135
|
// =============================================================================
|
package/src/MimicServer.ts
CHANGED
|
@@ -20,20 +20,6 @@ import * as NoAuth from "./auth/NoAuth.js";
|
|
|
20
20
|
import { HttpLayerRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform";
|
|
21
21
|
import { PathInput } from "@effect/platform/HttpRouter";
|
|
22
22
|
|
|
23
|
-
// =============================================================================
|
|
24
|
-
// Handler Tag
|
|
25
|
-
// =============================================================================
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Tag for the WebSocket handler function.
|
|
29
|
-
*/
|
|
30
|
-
export class MimicWebSocketHandler extends Context.Tag(
|
|
31
|
-
"@voidhash/mimic-server-effect/MimicWebSocketHandler"
|
|
32
|
-
)<
|
|
33
|
-
MimicWebSocketHandler,
|
|
34
|
-
(socket: Socket.Socket, documentId: string) => Effect.Effect<void, unknown>
|
|
35
|
-
>() {}
|
|
36
|
-
|
|
37
23
|
// =============================================================================
|
|
38
24
|
// Layer Composition Options
|
|
39
25
|
// =============================================================================
|
|
@@ -61,121 +47,18 @@ export interface MimicLayerOptions<TSchema extends Primitive.AnyPrimitive> {
|
|
|
61
47
|
* When provided, enables presence features on WebSocket connections.
|
|
62
48
|
*/
|
|
63
49
|
readonly presence?: Presence.AnyPresence;
|
|
50
|
+
/**
|
|
51
|
+
* Initial state for new documents.
|
|
52
|
+
* Used when a document is created and no existing state is found in storage.
|
|
53
|
+
*
|
|
54
|
+
* Type-safe: required fields (without defaults) must be provided,
|
|
55
|
+
* while optional fields and fields with defaults can be omitted.
|
|
56
|
+
*
|
|
57
|
+
* @default undefined (documents start empty or use schema defaults)
|
|
58
|
+
*/
|
|
59
|
+
readonly initial?: Primitive.InferSetInput<TSchema>;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
// =============================================================================
|
|
67
|
-
// Layer Composition
|
|
68
|
-
// =============================================================================
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Create a Mimic WebSocket handler layer.
|
|
72
|
-
*
|
|
73
|
-
* This layer provides a handler function that can be used with any WebSocket server
|
|
74
|
-
* implementation. The handler takes a socket and document ID and manages the
|
|
75
|
-
* document synchronization.
|
|
76
|
-
*
|
|
77
|
-
* By default, uses in-memory storage and no authentication.
|
|
78
|
-
* Override these by providing MimicDataStorage and MimicAuthService layers.
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```typescript
|
|
82
|
-
* import { MimicServer, MimicAuthService } from "@voidhash/mimic-effect";
|
|
83
|
-
* import { Primitive } from "@voidhash/mimic";
|
|
84
|
-
*
|
|
85
|
-
* const TodoSchema = Primitive.Struct({
|
|
86
|
-
* title: Primitive.String(),
|
|
87
|
-
* completed: Primitive.Boolean(),
|
|
88
|
-
* });
|
|
89
|
-
*
|
|
90
|
-
* // Create the handler layer with defaults
|
|
91
|
-
* const HandlerLayer = MimicServer.layer({
|
|
92
|
-
* basePath: "/mimic/todo",
|
|
93
|
-
* schema: TodoSchema
|
|
94
|
-
* });
|
|
95
|
-
*
|
|
96
|
-
* // Or with custom auth
|
|
97
|
-
* const HandlerLayerWithAuth = MimicServer.layer({
|
|
98
|
-
* basePath: "/mimic/todo",
|
|
99
|
-
* schema: TodoSchema
|
|
100
|
-
* }).pipe(
|
|
101
|
-
* Layer.provideMerge(MimicAuthService.layer({
|
|
102
|
-
* authHandler: (token) => ({ success: true, userId: "user-123" })
|
|
103
|
-
* }))
|
|
104
|
-
* );
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
export const layer = <TSchema extends Primitive.AnyPrimitive>(
|
|
108
|
-
options: MimicLayerOptions<TSchema>
|
|
109
|
-
): Layer.Layer<MimicWebSocketHandler | DocumentManager.DocumentManagerTag> => {
|
|
110
|
-
const configLayer = MimicConfig.layer({
|
|
111
|
-
schema: options.schema,
|
|
112
|
-
maxTransactionHistory: options.maxTransactionHistory,
|
|
113
|
-
presence: options.presence,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return Layer.merge(
|
|
117
|
-
// Handler layer
|
|
118
|
-
Layer.effect(MimicWebSocketHandler, WebSocketHandler.makeHandler).pipe(
|
|
119
|
-
Layer.provide(DocumentManager.layer),
|
|
120
|
-
Layer.provide(PresenceManager.layer),
|
|
121
|
-
Layer.provide(configLayer)
|
|
122
|
-
),
|
|
123
|
-
// Document manager layer
|
|
124
|
-
DocumentManager.layer.pipe(Layer.provide(configLayer))
|
|
125
|
-
).pipe(
|
|
126
|
-
// Provide defaults if not overridden
|
|
127
|
-
Layer.provide(InMemoryDataStorage.layerDefault),
|
|
128
|
-
Layer.provide(NoAuth.layerDefault)
|
|
129
|
-
);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Create the Mimic server handler layer.
|
|
134
|
-
* This layer provides the WebSocket handler that can be used with any WebSocket server.
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```typescript
|
|
138
|
-
* import { MimicServer } from "@voidhash/mimic-server-effect";
|
|
139
|
-
* import { SocketServer } from "@effect/platform/SocketServer";
|
|
140
|
-
* import { Primitive } from "@voidhash/mimic";
|
|
141
|
-
*
|
|
142
|
-
* // Define your document schema
|
|
143
|
-
* const TodoSchema = Primitive.Struct({
|
|
144
|
-
* title: Primitive.String(),
|
|
145
|
-
* completed: Primitive.Boolean(),
|
|
146
|
-
* });
|
|
147
|
-
*
|
|
148
|
-
* // Create the server layer
|
|
149
|
-
* const serverLayer = MimicServer.handlerLayer({
|
|
150
|
-
* schema: TodoSchema,
|
|
151
|
-
* });
|
|
152
|
-
*
|
|
153
|
-
* // Run with your socket server
|
|
154
|
-
* Effect.gen(function* () {
|
|
155
|
-
* const handler = yield* MimicServer.MimicWebSocketHandler;
|
|
156
|
-
* const server = yield* SocketServer;
|
|
157
|
-
*
|
|
158
|
-
* yield* server.run((socket) =>
|
|
159
|
-
* // Extract document ID from request and call handler
|
|
160
|
-
* handler(socket, "my-document-id")
|
|
161
|
-
* );
|
|
162
|
-
* }).pipe(
|
|
163
|
-
* Effect.provide(serverLayer),
|
|
164
|
-
* Effect.provide(YourSocketServerLayer),
|
|
165
|
-
* );
|
|
166
|
-
* ```
|
|
167
|
-
*/
|
|
168
|
-
export const handlerLayer = <TSchema extends Primitive.AnyPrimitive>(
|
|
169
|
-
options: MimicConfig.MimicServerConfigOptions<TSchema>
|
|
170
|
-
): Layer.Layer<MimicWebSocketHandler> =>
|
|
171
|
-
Layer.effect(MimicWebSocketHandler, WebSocketHandler.makeHandler).pipe(
|
|
172
|
-
Layer.provide(DocumentManager.layer),
|
|
173
|
-
Layer.provide(PresenceManager.layer),
|
|
174
|
-
Layer.provide(MimicConfig.layer(options)),
|
|
175
|
-
// Provide defaults
|
|
176
|
-
Layer.provide(InMemoryDataStorage.layerDefault),
|
|
177
|
-
Layer.provide(NoAuth.layerDefault)
|
|
178
|
-
);
|
|
179
62
|
|
|
180
63
|
/**
|
|
181
64
|
* Create the document manager layer.
|
|
@@ -190,40 +73,6 @@ export const documentManagerLayer = <TSchema extends Primitive.AnyPrimitive>(
|
|
|
190
73
|
Layer.provide(NoAuth.layerDefault)
|
|
191
74
|
);
|
|
192
75
|
|
|
193
|
-
// =============================================================================
|
|
194
|
-
// Convenience Functions
|
|
195
|
-
// =============================================================================
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Run a Mimic WebSocket server with the provided handler.
|
|
199
|
-
*
|
|
200
|
-
* This is a helper that:
|
|
201
|
-
* 1. Gets the WebSocket handler from context
|
|
202
|
-
* 2. Runs the socket server with the handler
|
|
203
|
-
*
|
|
204
|
-
* Note: The document ID extraction from socket is implementation-specific.
|
|
205
|
-
* You may need to customize this based on your socket server.
|
|
206
|
-
*/
|
|
207
|
-
export const run = (
|
|
208
|
-
extractDocumentId: (socket: Socket.Socket) => Effect.Effect<string>
|
|
209
|
-
) =>
|
|
210
|
-
Effect.gen(function* () {
|
|
211
|
-
const handler = yield* MimicWebSocketHandler;
|
|
212
|
-
const server = yield* SocketServer;
|
|
213
|
-
|
|
214
|
-
yield* server.run((socket) =>
|
|
215
|
-
Effect.gen(function* () {
|
|
216
|
-
const documentId = yield* extractDocumentId(socket);
|
|
217
|
-
yield* handler(socket, documentId);
|
|
218
|
-
}).pipe(
|
|
219
|
-
Effect.catchAll((error) =>
|
|
220
|
-
Effect.logError("Connection error", error)
|
|
221
|
-
)
|
|
222
|
-
)
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
|
|
227
76
|
/**
|
|
228
77
|
* Create the HTTP handler effect for WebSocket upgrade.
|
|
229
78
|
* This handler:
|
|
@@ -339,6 +188,7 @@ export const layerHttpLayerRouter = <TSchema extends Primitive.AnyPrimitive>(
|
|
|
339
188
|
schema: options.schema,
|
|
340
189
|
maxTransactionHistory: options.maxTransactionHistory,
|
|
341
190
|
presence: options.presence,
|
|
191
|
+
initial: options.initial,
|
|
342
192
|
});
|
|
343
193
|
|
|
344
194
|
// Use provided layers or defaults
|
|
@@ -337,4 +337,65 @@ describe("DocumentManager", () => {
|
|
|
337
337
|
expect(result).toBe(true);
|
|
338
338
|
});
|
|
339
339
|
});
|
|
340
|
+
|
|
341
|
+
describe("initial state", () => {
|
|
342
|
+
const makeTestLayerWithInitial = (initial: { title?: string; count?: number }) => {
|
|
343
|
+
const configLayer = MimicConfig.layer({
|
|
344
|
+
schema: TestSchema,
|
|
345
|
+
maxTransactionHistory: 100,
|
|
346
|
+
initial,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return DocumentManager.layer.pipe(
|
|
350
|
+
Layer.provide(configLayer),
|
|
351
|
+
Layer.provide(InMemoryDataStorage.layer)
|
|
352
|
+
);
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
it("should use initial state for new documents", async () => {
|
|
356
|
+
const result = await Effect.runPromise(
|
|
357
|
+
Effect.gen(function* () {
|
|
358
|
+
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
359
|
+
return yield* manager.getSnapshot("new-doc");
|
|
360
|
+
}).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
expect(result.type).toBe("snapshot");
|
|
364
|
+
expect(result.version).toBe(0);
|
|
365
|
+
expect(result.state).toEqual({ title: "Initial Title", count: 42 });
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should apply defaults for omitted fields in initial state", async () => {
|
|
369
|
+
const result = await Effect.runPromise(
|
|
370
|
+
Effect.gen(function* () {
|
|
371
|
+
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
372
|
+
return yield* manager.getSnapshot("new-doc");
|
|
373
|
+
}).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Only Title" })))
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(result.type).toBe("snapshot");
|
|
377
|
+
// count should be 0 (default)
|
|
378
|
+
expect(result.state).toEqual({ title: "Only Title", count: 0 });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("should prefer stored state over initial state", async () => {
|
|
382
|
+
const result = await Effect.runPromise(
|
|
383
|
+
Effect.gen(function* () {
|
|
384
|
+
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
385
|
+
|
|
386
|
+
// Apply a transaction to modify the document
|
|
387
|
+
const tx = createValidTransaction("tx-1", "Modified Title");
|
|
388
|
+
yield* manager.submit("doc-1", tx);
|
|
389
|
+
|
|
390
|
+
// Get snapshot - should show modified state, not initial
|
|
391
|
+
return yield* manager.getSnapshot("doc-1");
|
|
392
|
+
}).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
expect(result.type).toBe("snapshot");
|
|
396
|
+
expect((result.state as any).title).toBe("Modified Title");
|
|
397
|
+
// count should still be 42 since we only modified title
|
|
398
|
+
expect((result.state as any).count).toBe(42);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
340
401
|
});
|
|
@@ -172,4 +172,76 @@ describe("MimicConfig", () => {
|
|
|
172
172
|
expect(config.presence).toBe(CursorPresence);
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
|
+
|
|
176
|
+
describe("initial state configuration", () => {
|
|
177
|
+
it("should have undefined initial state by default", () => {
|
|
178
|
+
const config = MimicConfig.make({
|
|
179
|
+
schema: TestSchema,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(config.initial).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should accept initial state option", () => {
|
|
186
|
+
const config = MimicConfig.make({
|
|
187
|
+
schema: TestSchema,
|
|
188
|
+
initial: { title: "My Document", count: 42 },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(config.initial).toEqual({ title: "My Document", count: 42 });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should apply defaults for omitted fields in initial state", () => {
|
|
195
|
+
const config = MimicConfig.make({
|
|
196
|
+
schema: TestSchema,
|
|
197
|
+
initial: { title: "My Document" }, // count has default of 0
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(config.initial).toEqual({ title: "My Document", count: 0 });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should provide initial state through layer", async () => {
|
|
204
|
+
const testLayer = MimicConfig.layer({
|
|
205
|
+
schema: TestSchema,
|
|
206
|
+
initial: { title: "From Layer", count: 100 },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const result = await Effect.runPromise(
|
|
210
|
+
Effect.gen(function* () {
|
|
211
|
+
const config = yield* MimicConfig.MimicServerConfigTag;
|
|
212
|
+
return config.initial;
|
|
213
|
+
}).pipe(Effect.provide(testLayer))
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(result).toEqual({ title: "From Layer", count: 100 });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should work with schema that has required fields without defaults", () => {
|
|
220
|
+
const SchemaWithRequired = Primitive.Struct({
|
|
221
|
+
name: Primitive.String().required(),
|
|
222
|
+
optional: Primitive.String().default("default"),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const config = MimicConfig.make({
|
|
226
|
+
schema: SchemaWithRequired,
|
|
227
|
+
initial: { name: "Required Name" },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(config.initial).toEqual({ name: "Required Name", optional: "default" });
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should work with all options including initial", () => {
|
|
234
|
+
const config = MimicConfig.make({
|
|
235
|
+
schema: TestSchema,
|
|
236
|
+
maxIdleTime: "10 minutes",
|
|
237
|
+
maxTransactionHistory: 500,
|
|
238
|
+
initial: { title: "Full Options", count: 999 },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect(config.schema).toBe(TestSchema);
|
|
242
|
+
expect(Duration.toMillis(config.maxIdleTime)).toBe(10 * 60 * 1000);
|
|
243
|
+
expect(config.maxTransactionHistory).toBe(500);
|
|
244
|
+
expect(config.initial).toEqual({ title: "Full Options", count: 999 });
|
|
245
|
+
});
|
|
246
|
+
});
|
|
175
247
|
});
|