@kokimoki/app 1.0.2 → 1.0.5

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.
@@ -55,4 +55,10 @@ export declare namespace KokimokiSchema {
55
55
  constructor(schema: T, defaultValue: T["defaultValue"] | null);
56
56
  }
57
57
  function nullable<T extends Generic<unknown>>(schema: T, defaultValue?: T["defaultValue"] | null): Nullable<T>;
58
+ class Optional<T extends Generic<unknown>> extends Generic<T["defaultValue"] | undefined> {
59
+ schema: T;
60
+ defaultValue: T["defaultValue"] | undefined;
61
+ constructor(schema: T, defaultValue?: T["defaultValue"] | undefined);
62
+ }
63
+ function optional<T extends Generic<unknown>>(schema: T, defaultValue?: T["defaultValue"] | undefined): Optional<T>;
58
64
  }
@@ -103,4 +103,18 @@ export var KokimokiSchema;
103
103
  return new Nullable(schema, defaultValue);
104
104
  }
105
105
  KokimokiSchema.nullable = nullable;
106
+ class Optional extends Generic {
107
+ schema;
108
+ defaultValue;
109
+ constructor(schema, defaultValue = undefined) {
110
+ super();
111
+ this.schema = schema;
112
+ this.defaultValue = defaultValue;
113
+ }
114
+ }
115
+ KokimokiSchema.Optional = Optional;
116
+ function optional(schema, defaultValue = undefined) {
117
+ return new Optional(schema, defaultValue);
118
+ }
119
+ KokimokiSchema.optional = optional;
106
120
  })(KokimokiSchema || (KokimokiSchema = {}));
@@ -10,7 +10,7 @@ export declare class KokimokiTransaction {
10
10
  private _parseTarget;
11
11
  private _parsePath;
12
12
  private _getClone;
13
- get<T>(target: T): any;
13
+ get<T>(target: T): T;
14
14
  set<T>(target: T, value: T): void;
15
15
  delete<T>(target: T): void;
16
16
  push<T>(target: T[], value: T): void;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const KOKIMOKI_APP_VERSION = "1.0.2";
1
+ export declare const KOKIMOKI_APP_VERSION = "1.0.5";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const KOKIMOKI_APP_VERSION = "1.0.2";
1
+ export const KOKIMOKI_APP_VERSION = "1.0.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokimoki/app",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "Kokimoki app",
6
6
  "main": "dist/index.js",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "events": "^3.3.0",
27
+ "proxy-deep": "^4.0.1",
27
28
  "typed-emitter": "^2.1.0",
28
29
  "valtio": "^1.13.1",
29
30
  "valtio-yjs": "^0.5.1"
@@ -1,57 +0,0 @@
1
- import { HocuspocusProvider } from "@hocuspocus/provider";
2
- import type TypedEmitter from "typed-emitter";
3
- import type { SyncedStore } from "./synced-store";
4
- import type { DocTypeDescription } from "@syncedstore/core/types/doc";
5
- import type { KokimokiClientEvents } from "./types/events";
6
- import type { Upload } from "./types/upload";
7
- import type { Paginated } from "./types/common";
8
- declare const KokimokiClient_base: new <T>() => TypedEmitter<KokimokiClientEvents<T>>;
9
- export declare class KokimokiClient<StatelessDataT = any, ClientContextT = any> extends KokimokiClient_base<StatelessDataT> {
10
- readonly host: string;
11
- readonly appId: string;
12
- readonly code: string;
13
- private _wsUrl;
14
- private _apiUrl;
15
- private _id?;
16
- private _token?;
17
- private _apiHeaders?;
18
- private _providers;
19
- private _serverTimeOffset;
20
- private _clientContext?;
21
- private _connected;
22
- private _lastPongAt;
23
- constructor(host: string, appId: string, code?: string);
24
- get id(): string;
25
- get token(): string;
26
- get apiUrl(): string;
27
- get apiHeaders(): Headers;
28
- get clientContext(): ClientContextT & ({} | null);
29
- connect(): Promise<void>;
30
- serverTimestamp(): number;
31
- private receivePong;
32
- private checkConnectionState;
33
- setProvider<T extends DocTypeDescription>(name: string, store: SyncedStore<T>): Promise<void>;
34
- removeProvider(name: string): void;
35
- getProvider(name: string): HocuspocusProvider | undefined;
36
- sendStatelessToClient(room: string, clientId: string, data: StatelessDataT): void;
37
- sendStatelessToRoom(room: string, data: StatelessDataT, self?: boolean): void;
38
- patchRoomState(room: string, update: Uint8Array): Promise<any>;
39
- private createUpload;
40
- private uploadChunks;
41
- private completeUpload;
42
- upload(name: string, blob: Blob, tags?: string[]): Promise<Upload>;
43
- updateUpload(id: string, update: {
44
- tags?: string[];
45
- }): Promise<Upload>;
46
- listUploads(filter?: {
47
- clientId?: string;
48
- mimeTypes?: string[];
49
- tags?: string[];
50
- }, skip?: number, limit?: number): Promise<Paginated<Upload>>;
51
- deleteUpload(id: string): Promise<{
52
- acknowledged: boolean;
53
- deletedCount: number;
54
- }>;
55
- exposeScriptingContext(context: any): Promise<void>;
56
- }
57
- export {};
@@ -1,259 +0,0 @@
1
- import { HocuspocusProvider } from "@hocuspocus/provider";
2
- import EventEmitter from "events";
3
- import { KOKIMOKI_APP_VERSION } from "./version";
4
- export class KokimokiClient extends EventEmitter {
5
- host;
6
- appId;
7
- code;
8
- _wsUrl;
9
- _apiUrl;
10
- _id;
11
- _token;
12
- _apiHeaders;
13
- _providers = new Map();
14
- _serverTimeOffset = 0;
15
- _clientContext;
16
- _connected = false;
17
- _lastPongAt = 0;
18
- constructor(host, appId, code = "") {
19
- super();
20
- this.host = host;
21
- this.appId = appId;
22
- this.code = code;
23
- // Set up the URLs
24
- const secure = this.host.indexOf(":") === -1;
25
- this._wsUrl = `ws${secure ? "s" : ""}://${this.host}`;
26
- this._apiUrl = `http${secure ? "s" : ""}://${this.host}`;
27
- }
28
- get id() {
29
- if (!this._id) {
30
- throw new Error("Client not connected");
31
- }
32
- return this._id;
33
- }
34
- get token() {
35
- if (!this._token) {
36
- throw new Error("Client not connected");
37
- }
38
- return this._token;
39
- }
40
- get apiUrl() {
41
- if (!this._apiUrl) {
42
- throw new Error("Client not connected");
43
- }
44
- return this._apiUrl;
45
- }
46
- get apiHeaders() {
47
- if (!this._apiHeaders) {
48
- throw new Error("Client not connected");
49
- }
50
- return this._apiHeaders;
51
- }
52
- get clientContext() {
53
- if (this._clientContext === undefined) {
54
- throw new Error("Client not connected");
55
- }
56
- return this._clientContext;
57
- }
58
- async connect() {
59
- // Fetch the auth token
60
- let clientToken = localStorage.getItem("KM_TOKEN");
61
- const startTime = Date.now();
62
- const res = await fetch(`${this.apiUrl}/auth/token?appId=${this.appId}&code=${this.code}&clientVersion=${KOKIMOKI_APP_VERSION}`, {
63
- method: "GET",
64
- headers: new Headers({
65
- "Content-Type": "application/json",
66
- Authorization: `Bearer ${clientToken}`,
67
- }),
68
- });
69
- const { clientId, appToken, serverTime, token, clientContext } = await res.json();
70
- const endTime = Date.now();
71
- const ping = Math.round((endTime - startTime) / 2);
72
- this._id = clientId;
73
- this._token = appToken;
74
- this._serverTimeOffset = Date.now() - serverTime - ping;
75
- this._clientContext = clientContext;
76
- localStorage.setItem("KM_TOKEN", token);
77
- // Set up the auth headers
78
- this._apiHeaders = new Headers({
79
- Authorization: `Bearer ${this.token}`,
80
- "Content-Type": "application/json",
81
- });
82
- // Ping interval
83
- setInterval(() => {
84
- this._providers.forEach((provider) => provider.sendStateless("ping"));
85
- }, 5000);
86
- // Connection state interval
87
- setInterval(() => {
88
- this.checkConnectionState();
89
- }, 1000);
90
- // Check initial connected state
91
- this.receivePong();
92
- }
93
- serverTimestamp() {
94
- return Date.now() - this._serverTimeOffset;
95
- }
96
- receivePong() {
97
- this._lastPongAt = Date.now();
98
- this.checkConnectionState();
99
- }
100
- checkConnectionState() {
101
- const connected = this._providers.size === 0 || Date.now() - this._lastPongAt < 6000;
102
- if (connected && !this._connected) {
103
- this._connected = true;
104
- this.emit("connected");
105
- }
106
- else if (!connected && this._connected) {
107
- this._connected = false;
108
- this.emit("disconnected");
109
- // Reset connections to providers
110
- this._providers.forEach(async (provider) => {
111
- provider.disconnect();
112
- await provider.connect();
113
- });
114
- }
115
- }
116
- // Realtime database
117
- async setProvider(name, store) {
118
- const provider = new HocuspocusProvider({
119
- url: `${this._wsUrl}/connection`,
120
- name: `${this.appId}/${name}`,
121
- document: store.doc,
122
- token: this.token,
123
- parameters: {
124
- clientVersion: KOKIMOKI_APP_VERSION,
125
- },
126
- });
127
- // Handle incoming stateless messages
128
- provider.on("stateless", (e) => {
129
- if (e.payload === "pong") {
130
- this.receivePong();
131
- return;
132
- }
133
- const payload = JSON.parse(e.payload);
134
- this.emit("stateless", name, payload.from, payload.data);
135
- });
136
- // Wait for initial sync
137
- await new Promise((resolve) => {
138
- const handler = () => {
139
- provider.off("synced", handler);
140
- resolve();
141
- };
142
- provider.on("synced", handler);
143
- });
144
- this._lastPongAt = Date.now();
145
- this._providers.set(name, provider);
146
- this.checkConnectionState();
147
- }
148
- removeProvider(name) {
149
- const provider = this._providers.get(name);
150
- if (!provider) {
151
- throw new Error(`No provider for room ${name}`);
152
- }
153
- provider.destroy();
154
- this._providers.delete(name);
155
- // Connection state can change if the removed provider was not connected or synced
156
- this.checkConnectionState();
157
- }
158
- getProvider(name) {
159
- return this._providers.get(name);
160
- }
161
- sendStatelessToClient(room, clientId, data) {
162
- const provider = this._providers.get(room);
163
- if (!provider) {
164
- throw new Error(`No provider for room ${room}`);
165
- }
166
- provider.sendStateless(JSON.stringify({ to: clientId, data }));
167
- }
168
- sendStatelessToRoom(room, data, self = false) {
169
- const provider = this._providers.get(room);
170
- if (!provider) {
171
- throw new Error(`No provider for room ${room}`);
172
- }
173
- provider.sendStateless(JSON.stringify({ data, self }));
174
- }
175
- // Send Y update to room
176
- async patchRoomState(room, update) {
177
- const res = await fetch(`${this._apiUrl}/rooms/${room}`, {
178
- method: "PATCH",
179
- headers: this.apiHeaders,
180
- body: update,
181
- });
182
- return await res.json();
183
- }
184
- // Storage
185
- async createUpload(name, blob, tags) {
186
- const res = await fetch(`${this._apiUrl}/uploads`, {
187
- method: "POST",
188
- headers: this.apiHeaders,
189
- body: JSON.stringify({
190
- name,
191
- size: blob.size,
192
- mimeType: blob.type,
193
- tags,
194
- }),
195
- });
196
- return await res.json();
197
- }
198
- async uploadChunks(blob, chunkSize, signedUrls) {
199
- return await Promise.all(signedUrls.map(async (url, index) => {
200
- const start = index * chunkSize;
201
- const end = Math.min(start + chunkSize, blob.size);
202
- const chunk = blob.slice(start, end);
203
- const res = await fetch(url, { method: "PUT", body: chunk });
204
- return JSON.parse(res.headers.get("ETag") || '""');
205
- }));
206
- }
207
- async completeUpload(id, etags) {
208
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
209
- method: "PUT",
210
- headers: this.apiHeaders,
211
- body: JSON.stringify({ etags }),
212
- });
213
- return await res.json();
214
- }
215
- async upload(name, blob, tags = []) {
216
- const { id, chunkSize, urls } = await this.createUpload(name, blob, tags);
217
- const etags = await this.uploadChunks(blob, chunkSize, urls);
218
- return await this.completeUpload(id, etags);
219
- }
220
- async updateUpload(id, update) {
221
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
222
- method: "PUT",
223
- headers: this.apiHeaders,
224
- body: JSON.stringify(update),
225
- });
226
- return await res.json();
227
- }
228
- async listUploads(filter = {}, skip = 0, limit = 100) {
229
- const url = new URL("/uploads", this._apiUrl);
230
- url.searchParams.set("skip", skip.toString());
231
- url.searchParams.set("limit", limit.toString());
232
- if (filter.clientId) {
233
- url.searchParams.set("clientId", filter.clientId);
234
- }
235
- if (filter.mimeTypes) {
236
- url.searchParams.set("mimeTypes", filter.mimeTypes.join());
237
- }
238
- if (filter.tags) {
239
- url.searchParams.set("tags", filter.tags.join());
240
- }
241
- const res = await fetch(url.href, {
242
- headers: this.apiHeaders,
243
- });
244
- return await res.json();
245
- }
246
- async deleteUpload(id) {
247
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
248
- method: "DELETE",
249
- headers: this.apiHeaders,
250
- });
251
- return await res.json();
252
- }
253
- async exposeScriptingContext(context) {
254
- // @ts-ignore
255
- window.KM_SCRIPTING_CONTEXT = context;
256
- // @ts-ignore
257
- window.dispatchEvent(new CustomEvent("km:scriptingContextExposed"));
258
- }
259
- }
@@ -1,67 +0,0 @@
1
- import type TypedEmitter from "typed-emitter";
2
- import type { KokimokiClientEventsRefactored } from "./types/events";
3
- import type { Upload } from "./types/upload";
4
- import type { Paginated } from "./types/common";
5
- import { KokimokiTransaction } from "./kokimoki-transaction";
6
- import type { KokimokiStore } from "./kokimoki-store";
7
- import type { KokimokiSchema as S } from "./kokimoki-schema";
8
- import { RoomSubscriptionMode } from "./room-subscription-mode";
9
- declare const KokimokiClient_base: new () => TypedEmitter<KokimokiClientEventsRefactored>;
10
- export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
11
- readonly host: string;
12
- readonly appId: string;
13
- readonly code: string;
14
- private _wsUrl;
15
- private _apiUrl;
16
- private _id?;
17
- private _token?;
18
- private _apiHeaders?;
19
- private _serverTimeOffset;
20
- private _clientContext?;
21
- private _ws?;
22
- private _subscriptionsByName;
23
- private _subscriptionsByHash;
24
- private _subscribeReqPromises;
25
- private _transactionPromises;
26
- private _connected;
27
- private _connectPromise?;
28
- private _messageId;
29
- private _reconnectTimeout;
30
- constructor(host: string, appId: string, code?: string);
31
- get id(): string;
32
- get token(): string;
33
- get apiUrl(): string;
34
- get apiHeaders(): Headers;
35
- get clientContext(): ClientContextT & ({} | null);
36
- get connected(): boolean;
37
- get ws(): WebSocket;
38
- connect(): Promise<void>;
39
- private handleInitMessage;
40
- private handleBinaryMessage;
41
- private handleErrorMessage;
42
- private handleSubscribeResMessage;
43
- private handleRoomUpdateMessage;
44
- serverTimestamp(): number;
45
- patchRoomState(room: string, update: Uint8Array): Promise<any>;
46
- private createUpload;
47
- private uploadChunks;
48
- private completeUpload;
49
- upload(name: string, blob: Blob, tags?: string[]): Promise<Upload>;
50
- updateUpload(id: string, update: {
51
- tags?: string[];
52
- }): Promise<Upload>;
53
- listUploads(filter?: {
54
- clientId?: string;
55
- mimeTypes?: string[];
56
- tags?: string[];
57
- }, skip?: number, limit?: number): Promise<Paginated<Upload>>;
58
- deleteUpload(id: string): Promise<{
59
- acknowledged: boolean;
60
- deletedCount: number;
61
- }>;
62
- exposeScriptingContext(context: any): Promise<void>;
63
- private sendSubscriptionReq;
64
- join<T extends S.Generic<unknown>>(store: KokimokiStore<T>, mode?: RoomSubscriptionMode): Promise<void>;
65
- transact(handler: (t: KokimokiTransaction) => void): Promise<void>;
66
- }
67
- export {};