@syncular/core 0.0.1-60
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/dist/blobs.d.ts +137 -0
- package/dist/blobs.d.ts.map +1 -0
- package/dist/blobs.js +47 -0
- package/dist/blobs.js.map +1 -0
- package/dist/conflict.d.ts +22 -0
- package/dist/conflict.d.ts.map +1 -0
- package/dist/conflict.js +81 -0
- package/dist/conflict.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/kysely-serialize.d.ts +22 -0
- package/dist/kysely-serialize.d.ts.map +1 -0
- package/dist/kysely-serialize.js +147 -0
- package/dist/kysely-serialize.js.map +1 -0
- package/dist/logger.d.ts +46 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +48 -0
- package/dist/logger.js.map +1 -0
- package/dist/proxy/index.d.ts +5 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +5 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/types.d.ts +54 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +7 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/schemas/blobs.d.ts +76 -0
- package/dist/schemas/blobs.d.ts.map +1 -0
- package/dist/schemas/blobs.js +63 -0
- package/dist/schemas/blobs.js.map +1 -0
- package/dist/schemas/common.d.ts +28 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +26 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +7 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/sync.d.ts +391 -0
- package/dist/schemas/sync.d.ts.map +1 -0
- package/dist/schemas/sync.js +156 -0
- package/dist/schemas/sync.js.map +1 -0
- package/dist/scopes/index.d.ts +65 -0
- package/dist/scopes/index.d.ts.map +1 -0
- package/dist/scopes/index.js +67 -0
- package/dist/scopes/index.js.map +1 -0
- package/dist/transforms.d.ts +146 -0
- package/dist/transforms.d.ts.map +1 -0
- package/dist/transforms.js +155 -0
- package/dist/transforms.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
- package/src/__tests__/conflict.test.ts +325 -0
- package/src/blobs.ts +187 -0
- package/src/conflict.ts +92 -0
- package/src/index.ts +30 -0
- package/src/kysely-serialize.ts +214 -0
- package/src/logger.ts +80 -0
- package/src/proxy/index.ts +10 -0
- package/src/proxy/types.ts +57 -0
- package/src/schemas/blobs.ts +101 -0
- package/src/schemas/common.ts +45 -0
- package/src/schemas/index.ts +7 -0
- package/src/schemas/sync.ts +222 -0
- package/src/scopes/index.ts +122 -0
- package/src/transforms.ts +256 -0
- package/src/types.ts +158 -0
package/dist/blobs.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Blob types for media/binary handling
|
|
3
|
+
*
|
|
4
|
+
* Content-addressable blob storage with presigned URL support.
|
|
5
|
+
* Protocol types (BlobRef, BlobMetadata, etc.) live in ./schemas/blobs.ts
|
|
6
|
+
*/
|
|
7
|
+
import type { BlobRef } from './schemas/blobs';
|
|
8
|
+
/**
|
|
9
|
+
* Transport interface for client-server blob communication.
|
|
10
|
+
* This is used by the client blob manager to communicate with the server.
|
|
11
|
+
*/
|
|
12
|
+
export interface BlobTransport {
|
|
13
|
+
/**
|
|
14
|
+
* Initiate a blob upload.
|
|
15
|
+
* Returns presigned URL info or indicates blob already exists (dedup).
|
|
16
|
+
*/
|
|
17
|
+
initiateUpload(args: {
|
|
18
|
+
hash: string;
|
|
19
|
+
size: number;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
exists: boolean;
|
|
23
|
+
uploadUrl?: string;
|
|
24
|
+
uploadMethod?: 'PUT' | 'POST';
|
|
25
|
+
uploadHeaders?: Record<string, string>;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Complete a blob upload.
|
|
29
|
+
* Call this after uploading to the presigned URL.
|
|
30
|
+
*/
|
|
31
|
+
completeUpload(hash: string): Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
error?: string;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Get a presigned download URL.
|
|
37
|
+
*/
|
|
38
|
+
getDownloadUrl(hash: string): Promise<{
|
|
39
|
+
url: string;
|
|
40
|
+
expiresAt: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options for signing an upload URL.
|
|
45
|
+
*/
|
|
46
|
+
export interface BlobSignUploadOptions {
|
|
47
|
+
/** SHA-256 hash (for naming and checksum validation) */
|
|
48
|
+
hash: string;
|
|
49
|
+
/** Content size in bytes */
|
|
50
|
+
size: number;
|
|
51
|
+
/** MIME type */
|
|
52
|
+
mimeType: string;
|
|
53
|
+
/** URL expiration in seconds */
|
|
54
|
+
expiresIn: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Result of signing an upload URL.
|
|
58
|
+
*/
|
|
59
|
+
export interface BlobSignedUpload {
|
|
60
|
+
/** The URL to upload to */
|
|
61
|
+
url: string;
|
|
62
|
+
/** HTTP method */
|
|
63
|
+
method: 'PUT' | 'POST';
|
|
64
|
+
/** Required headers */
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Options for signing a download URL.
|
|
69
|
+
*/
|
|
70
|
+
export interface BlobSignDownloadOptions {
|
|
71
|
+
/** SHA-256 hash */
|
|
72
|
+
hash: string;
|
|
73
|
+
/** URL expiration in seconds */
|
|
74
|
+
expiresIn: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Adapter for blob storage backends (S3, R2, custom).
|
|
78
|
+
* Implementations handle actual storage; the sync server orchestrates.
|
|
79
|
+
*/
|
|
80
|
+
export interface BlobStorageAdapter {
|
|
81
|
+
/** Adapter name for logging/debugging */
|
|
82
|
+
readonly name: string;
|
|
83
|
+
/**
|
|
84
|
+
* Generate a presigned URL for uploading a blob.
|
|
85
|
+
* The URL should enforce checksum validation if the backend supports it.
|
|
86
|
+
*/
|
|
87
|
+
signUpload(options: BlobSignUploadOptions): Promise<BlobSignedUpload>;
|
|
88
|
+
/**
|
|
89
|
+
* Generate a presigned URL for downloading a blob.
|
|
90
|
+
*/
|
|
91
|
+
signDownload(options: BlobSignDownloadOptions): Promise<string>;
|
|
92
|
+
/**
|
|
93
|
+
* Check if a blob exists in storage.
|
|
94
|
+
*/
|
|
95
|
+
exists(hash: string): Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Delete a blob (for garbage collection).
|
|
98
|
+
*/
|
|
99
|
+
delete(hash: string): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Get blob metadata from storage (optional).
|
|
102
|
+
* Used to verify uploads completed successfully.
|
|
103
|
+
*/
|
|
104
|
+
getMetadata?(hash: string): Promise<{
|
|
105
|
+
size: number;
|
|
106
|
+
mimeType?: string;
|
|
107
|
+
} | null>;
|
|
108
|
+
/**
|
|
109
|
+
* Store blob data directly (for adapters that support direct storage).
|
|
110
|
+
* Used for snapshot chunks and other internal data.
|
|
111
|
+
*/
|
|
112
|
+
put?(hash: string, data: Uint8Array, metadata?: Record<string, unknown>): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Get blob data directly (for adapters that support direct retrieval).
|
|
115
|
+
*/
|
|
116
|
+
get?(hash: string): Promise<Uint8Array | null>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Create a BlobRef from upload metadata.
|
|
120
|
+
*/
|
|
121
|
+
export declare function createBlobRef(args: {
|
|
122
|
+
hash: string;
|
|
123
|
+
size: number;
|
|
124
|
+
mimeType: string;
|
|
125
|
+
encrypted?: boolean;
|
|
126
|
+
keyId?: string;
|
|
127
|
+
}): BlobRef;
|
|
128
|
+
/**
|
|
129
|
+
* Parse a blob hash, validating format.
|
|
130
|
+
* @returns The hex hash without prefix, or null if invalid.
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseBlobHash(hash: string): string | null;
|
|
133
|
+
/**
|
|
134
|
+
* Create a blob hash string from hex.
|
|
135
|
+
*/
|
|
136
|
+
export declare function createBlobHash(hexHash: string): string;
|
|
137
|
+
//# sourceMappingURL=blobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blobs.d.ts","sourceRoot":"","sources":["../src/blobs.ts"],"names":[],"mappings":"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"}
|
package/dist/blobs.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Blob types for media/binary handling
|
|
3
|
+
*
|
|
4
|
+
* Content-addressable blob storage with presigned URL support.
|
|
5
|
+
* Protocol types (BlobRef, BlobMetadata, etc.) live in ./schemas/blobs.ts
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Utility Functions
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Create a BlobRef from upload metadata.
|
|
12
|
+
*/
|
|
13
|
+
export function createBlobRef(args) {
|
|
14
|
+
const ref = {
|
|
15
|
+
hash: args.hash,
|
|
16
|
+
size: args.size,
|
|
17
|
+
mimeType: args.mimeType,
|
|
18
|
+
};
|
|
19
|
+
if (args.encrypted) {
|
|
20
|
+
ref.encrypted = true;
|
|
21
|
+
if (args.keyId) {
|
|
22
|
+
ref.keyId = args.keyId;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return ref;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a blob hash, validating format.
|
|
29
|
+
* @returns The hex hash without prefix, or null if invalid.
|
|
30
|
+
*/
|
|
31
|
+
export function parseBlobHash(hash) {
|
|
32
|
+
if (!hash.startsWith('sha256:'))
|
|
33
|
+
return null;
|
|
34
|
+
const hex = hash.slice(7);
|
|
35
|
+
if (hex.length !== 64)
|
|
36
|
+
return null;
|
|
37
|
+
if (!/^[0-9a-f]+$/i.test(hex))
|
|
38
|
+
return null;
|
|
39
|
+
return hex.toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a blob hash string from hex.
|
|
43
|
+
*/
|
|
44
|
+
export function createBlobHash(hexHash) {
|
|
45
|
+
return `sha256:${hexHash.toLowerCase()}`;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=blobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blobs.js","sourceRoot":"","sources":["../src/blobs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwIH,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAM7B,EAAW;IACV,MAAM,GAAG,GAAY;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;IACF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC;QACrB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AAAA,CACZ;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAiB;IACzD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;AAAA,CAC1B;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,EAAU;IACtD,OAAO,UAAU,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;AAAA,CAC1C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Pure conflict detection and field-level merge utilities
|
|
3
|
+
*
|
|
4
|
+
* These are pure functions with no database dependencies.
|
|
5
|
+
* Database-specific conflict detection (triggers, etc.) lives in @syncular/server.
|
|
6
|
+
*/
|
|
7
|
+
import type { MergeResult } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* Performs field-level merge between client changes and server state.
|
|
10
|
+
*
|
|
11
|
+
* Merge logic:
|
|
12
|
+
* - If only client changed a field -> use client's value
|
|
13
|
+
* - If only server changed a field -> keep server's value
|
|
14
|
+
* - If both changed same field to different values -> true conflict
|
|
15
|
+
*
|
|
16
|
+
* @param baseRow - The row state when client started editing (from base_version)
|
|
17
|
+
* @param serverRow - Current server row state
|
|
18
|
+
* @param clientPayload - Client's intended changes
|
|
19
|
+
* @returns MergeResult indicating if merge is possible and the result
|
|
20
|
+
*/
|
|
21
|
+
export declare function performFieldLevelMerge(baseRow: Record<string, unknown> | null, serverRow: Record<string, unknown>, clientPayload: Record<string, unknown>): MergeResult;
|
|
22
|
+
//# sourceMappingURL=conflict.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict.d.ts","sourceRoot":"","sources":["../src/conflict.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACvC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,WAAW,CAqCb"}
|
package/dist/conflict.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Pure conflict detection and field-level merge utilities
|
|
3
|
+
*
|
|
4
|
+
* These are pure functions with no database dependencies.
|
|
5
|
+
* Database-specific conflict detection (triggers, etc.) lives in @syncular/server.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Performs field-level merge between client changes and server state.
|
|
9
|
+
*
|
|
10
|
+
* Merge logic:
|
|
11
|
+
* - If only client changed a field -> use client's value
|
|
12
|
+
* - If only server changed a field -> keep server's value
|
|
13
|
+
* - If both changed same field to different values -> true conflict
|
|
14
|
+
*
|
|
15
|
+
* @param baseRow - The row state when client started editing (from base_version)
|
|
16
|
+
* @param serverRow - Current server row state
|
|
17
|
+
* @param clientPayload - Client's intended changes
|
|
18
|
+
* @returns MergeResult indicating if merge is possible and the result
|
|
19
|
+
*/
|
|
20
|
+
export function performFieldLevelMerge(baseRow, serverRow, clientPayload) {
|
|
21
|
+
// If no base row (new insert), client payload wins entirely
|
|
22
|
+
if (!baseRow) {
|
|
23
|
+
return { canMerge: true, mergedPayload: clientPayload };
|
|
24
|
+
}
|
|
25
|
+
const conflictingFields = [];
|
|
26
|
+
const mergedPayload = { ...serverRow };
|
|
27
|
+
// Check each field in the client payload
|
|
28
|
+
for (const [field, clientValue] of Object.entries(clientPayload)) {
|
|
29
|
+
const baseValue = baseRow[field];
|
|
30
|
+
const serverValue = serverRow[field];
|
|
31
|
+
const clientChanged = !deepEqual(baseValue, clientValue);
|
|
32
|
+
const serverChanged = !deepEqual(baseValue, serverValue);
|
|
33
|
+
if (clientChanged && serverChanged) {
|
|
34
|
+
// Both changed the same field
|
|
35
|
+
if (!deepEqual(clientValue, serverValue)) {
|
|
36
|
+
// Changed to different values - true conflict
|
|
37
|
+
conflictingFields.push(field);
|
|
38
|
+
}
|
|
39
|
+
// If they changed to the same value, no conflict - use either
|
|
40
|
+
mergedPayload[field] = clientValue;
|
|
41
|
+
}
|
|
42
|
+
else if (clientChanged) {
|
|
43
|
+
// Only client changed - use client's value
|
|
44
|
+
mergedPayload[field] = clientValue;
|
|
45
|
+
}
|
|
46
|
+
// If only server changed or neither changed, keep server value (already in mergedPayload)
|
|
47
|
+
}
|
|
48
|
+
if (conflictingFields.length > 0) {
|
|
49
|
+
return { canMerge: false, conflictingFields };
|
|
50
|
+
}
|
|
51
|
+
return { canMerge: true, mergedPayload };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Deep equality check for values (handles primitives, arrays, objects)
|
|
55
|
+
*/
|
|
56
|
+
function deepEqual(a, b) {
|
|
57
|
+
if (a === b)
|
|
58
|
+
return true;
|
|
59
|
+
if (a === null || b === null)
|
|
60
|
+
return a === b;
|
|
61
|
+
if (typeof a !== typeof b)
|
|
62
|
+
return false;
|
|
63
|
+
if (typeof a === 'object') {
|
|
64
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
65
|
+
if (a.length !== b.length)
|
|
66
|
+
return false;
|
|
67
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(a) || Array.isArray(b))
|
|
70
|
+
return false;
|
|
71
|
+
const aObj = a;
|
|
72
|
+
const bObj = b;
|
|
73
|
+
const aKeys = Object.keys(aObj);
|
|
74
|
+
const bKeys = Object.keys(bObj);
|
|
75
|
+
if (aKeys.length !== bKeys.length)
|
|
76
|
+
return false;
|
|
77
|
+
return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=conflict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict.js","sourceRoot":"","sources":["../src/conflict.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Shared types and utilities for sync infrastructure
|
|
3
|
+
*
|
|
4
|
+
* This package contains:
|
|
5
|
+
* - Protocol types (commit-log + subscriptions)
|
|
6
|
+
* - Pure conflict detection and merge utilities
|
|
7
|
+
* - Logging utilities
|
|
8
|
+
* - Data transformation hooks (optional)
|
|
9
|
+
* - Blob types for media/binary handling
|
|
10
|
+
* - Zod schemas for runtime validation and OpenAPI
|
|
11
|
+
*/
|
|
12
|
+
export * from './blobs';
|
|
13
|
+
export * from './conflict';
|
|
14
|
+
export * from './kysely-serialize';
|
|
15
|
+
export * from './logger';
|
|
16
|
+
export * from './proxy';
|
|
17
|
+
export * from './schemas';
|
|
18
|
+
export * from './scopes';
|
|
19
|
+
export * from './transforms';
|
|
20
|
+
export * from './types';
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,cAAc,SAAS,CAAC;AAExB,cAAc,YAAY,CAAC;AAE3B,cAAc,oBAAoB,CAAC;AAEnC,cAAc,UAAU,CAAC;AAEzB,cAAc,SAAS,CAAC;AAExB,cAAc,WAAW,CAAC;AAE1B,cAAc,UAAU,CAAC;AAEzB,cAAc,cAAc,CAAC;AAE7B,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Shared types and utilities for sync infrastructure
|
|
3
|
+
*
|
|
4
|
+
* This package contains:
|
|
5
|
+
* - Protocol types (commit-log + subscriptions)
|
|
6
|
+
* - Pure conflict detection and merge utilities
|
|
7
|
+
* - Logging utilities
|
|
8
|
+
* - Data transformation hooks (optional)
|
|
9
|
+
* - Blob types for media/binary handling
|
|
10
|
+
* - Zod schemas for runtime validation and OpenAPI
|
|
11
|
+
*/
|
|
12
|
+
// Blob transport/storage types and utilities (protocol types come from ./schemas)
|
|
13
|
+
export * from './blobs';
|
|
14
|
+
// Conflict detection utilities
|
|
15
|
+
export * from './conflict';
|
|
16
|
+
// Kysely plugin utilities
|
|
17
|
+
export * from './kysely-serialize';
|
|
18
|
+
// Logging utilities
|
|
19
|
+
export * from './logger';
|
|
20
|
+
// Proxy protocol types
|
|
21
|
+
export * from './proxy';
|
|
22
|
+
// Schemas (Zod)
|
|
23
|
+
export * from './schemas';
|
|
24
|
+
// Scope types, patterns, and utilities
|
|
25
|
+
export * from './scopes';
|
|
26
|
+
// Data transformation hooks
|
|
27
|
+
export * from './transforms';
|
|
28
|
+
// Transport and conflict types (protocol types come from ./schemas)
|
|
29
|
+
export * from './types';
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kFAAkF;AAClF,cAAc,SAAS,CAAC;AACxB,+BAA+B;AAC/B,cAAc,YAAY,CAAC;AAC3B,0BAA0B;AAC1B,cAAc,oBAAoB,CAAC;AACnC,oBAAoB;AACpB,cAAc,UAAU,CAAC;AACzB,uBAAuB;AACvB,cAAc,SAAS,CAAC;AACxB,gBAAgB;AAChB,cAAc,WAAW,CAAC;AAC1B,uCAAuC;AACvC,cAAc,UAAU,CAAC;AACzB,4BAA4B;AAC5B,cAAc,cAAc,CAAC;AAC7B,oEAAoE;AACpE,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type KyselyPlugin, type PluginTransformQueryArgs, type PluginTransformResultArgs, type QueryResult, type RootOperationNode, type UnknownRow } from 'kysely';
|
|
2
|
+
type Serializer = (parameter: unknown) => unknown;
|
|
3
|
+
type Deserializer = (parameter: unknown) => unknown;
|
|
4
|
+
declare class BaseSerializePlugin implements KyselyPlugin {
|
|
5
|
+
#private;
|
|
6
|
+
/**
|
|
7
|
+
* Base class for {@link SerializePlugin}, without default options.
|
|
8
|
+
*/
|
|
9
|
+
constructor(serializer: Serializer, deserializer: Deserializer, skipNodeKind: Array<RootOperationNode['kind']>);
|
|
10
|
+
transformQuery({ node, queryId }: PluginTransformQueryArgs): RootOperationNode;
|
|
11
|
+
transformResult({ result, queryId }: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>>;
|
|
12
|
+
}
|
|
13
|
+
interface SerializePluginOptions {
|
|
14
|
+
serializer?: Serializer;
|
|
15
|
+
deserializer?: Deserializer;
|
|
16
|
+
skipNodeKind?: Array<RootOperationNode['kind']>;
|
|
17
|
+
}
|
|
18
|
+
export declare class SerializePlugin extends BaseSerializePlugin {
|
|
19
|
+
constructor(options?: SerializePluginOptions);
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=kysely-serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kysely-serialize.d.ts","sourceRoot":"","sources":["../src/kysely-serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAE9B,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAEhB,MAAM,QAAQ,CAAC;AAEhB,KAAK,UAAU,GAAG,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAC;AAClD,KAAK,YAAY,GAAG,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAC;AA4HpD,cAAM,mBAAoB,YAAW,YAAY;;IAM/C;;OAEG;IACH,YACE,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,EAW/C;IAED,cAAc,CAAC,EACb,IAAI,EACJ,OAAO,EACR,EAAE,wBAAwB,GAAG,iBAAiB,CAM9C;IAEK,eAAe,CAAC,EACpB,MAAM,EACN,OAAO,EACR,EAAE,yBAAyB,GAAG,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAK9D;CAcF;AAED,UAAU,sBAAsB;IAC9B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,qBAAa,eAAgB,SAAQ,mBAAmB;IACtD,YAAY,OAAO,GAAE,sBAA2B,EAO/C;CACF"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { OperationNodeTransformer, } from 'kysely';
|
|
2
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/;
|
|
3
|
+
function isBufferLike(value) {
|
|
4
|
+
return 'buffer' in value;
|
|
5
|
+
}
|
|
6
|
+
function skipTransform(parameter) {
|
|
7
|
+
if (parameter === undefined ||
|
|
8
|
+
parameter === null ||
|
|
9
|
+
typeof parameter === 'bigint' ||
|
|
10
|
+
typeof parameter === 'number') {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (typeof parameter === 'object') {
|
|
14
|
+
return isBufferLike(parameter);
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
function maybeJson(parameter) {
|
|
19
|
+
return ((parameter.startsWith('{') && parameter.endsWith('}')) ||
|
|
20
|
+
(parameter.startsWith('[') && parameter.endsWith(']')));
|
|
21
|
+
}
|
|
22
|
+
const defaultSerializer = (parameter) => {
|
|
23
|
+
if (skipTransform(parameter) || typeof parameter === 'string') {
|
|
24
|
+
return parameter;
|
|
25
|
+
}
|
|
26
|
+
if (typeof parameter === 'boolean') {
|
|
27
|
+
return String(parameter);
|
|
28
|
+
}
|
|
29
|
+
if (parameter instanceof Date) {
|
|
30
|
+
return parameter.toISOString();
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return JSON.stringify(parameter);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return parameter;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const defaultDeserializer = (parameter) => {
|
|
40
|
+
if (skipTransform(parameter)) {
|
|
41
|
+
return parameter;
|
|
42
|
+
}
|
|
43
|
+
if (typeof parameter !== 'string') {
|
|
44
|
+
return parameter;
|
|
45
|
+
}
|
|
46
|
+
if (parameter === 'true')
|
|
47
|
+
return true;
|
|
48
|
+
if (parameter === 'false')
|
|
49
|
+
return false;
|
|
50
|
+
if (dateRegex.test(parameter))
|
|
51
|
+
return new Date(parameter);
|
|
52
|
+
if (maybeJson(parameter)) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(parameter);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return parameter;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return parameter;
|
|
61
|
+
};
|
|
62
|
+
class SerializeParametersTransformer extends OperationNodeTransformer {
|
|
63
|
+
#serializer;
|
|
64
|
+
constructor(serializer) {
|
|
65
|
+
super();
|
|
66
|
+
this.#serializer = serializer;
|
|
67
|
+
}
|
|
68
|
+
transformPrimitiveValueList(node) {
|
|
69
|
+
return {
|
|
70
|
+
...node,
|
|
71
|
+
values: node.values.map((v) => this.#serializer(v)),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
transformColumnUpdate(node, queryId) {
|
|
75
|
+
const valueNode = node.value;
|
|
76
|
+
if (valueNode.kind !== 'ValueNode') {
|
|
77
|
+
return super.transformColumnUpdate(node, queryId);
|
|
78
|
+
}
|
|
79
|
+
const currentValue = valueNode.value;
|
|
80
|
+
const serializedValue = this.#serializer(currentValue);
|
|
81
|
+
if (currentValue === serializedValue) {
|
|
82
|
+
return super.transformColumnUpdate(node, queryId);
|
|
83
|
+
}
|
|
84
|
+
const updatedValue = {
|
|
85
|
+
...valueNode,
|
|
86
|
+
value: serializedValue,
|
|
87
|
+
};
|
|
88
|
+
return super.transformColumnUpdate({ ...node, value: updatedValue }, queryId);
|
|
89
|
+
}
|
|
90
|
+
transformValue(node) {
|
|
91
|
+
return { ...node, value: this.#serializer(node.value) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
class BaseSerializePlugin {
|
|
95
|
+
#transformer;
|
|
96
|
+
#deserializer;
|
|
97
|
+
#skipNodeSet;
|
|
98
|
+
#ctx;
|
|
99
|
+
/**
|
|
100
|
+
* Base class for {@link SerializePlugin}, without default options.
|
|
101
|
+
*/
|
|
102
|
+
constructor(serializer, deserializer, skipNodeKind) {
|
|
103
|
+
this.#transformer = new SerializeParametersTransformer(serializer);
|
|
104
|
+
this.#deserializer = deserializer;
|
|
105
|
+
if (skipNodeKind.length > 0) {
|
|
106
|
+
this.#skipNodeSet = new Set(skipNodeKind);
|
|
107
|
+
this.#ctx = new WeakSet();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.#skipNodeSet = null;
|
|
111
|
+
this.#ctx = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
transformQuery({ node, queryId, }) {
|
|
115
|
+
if (this.#skipNodeSet?.has(node.kind)) {
|
|
116
|
+
this.#ctx?.add(queryId);
|
|
117
|
+
return node;
|
|
118
|
+
}
|
|
119
|
+
return this.#transformer.transformNode(node);
|
|
120
|
+
}
|
|
121
|
+
async transformResult({ result, queryId, }) {
|
|
122
|
+
if (this.#ctx?.has(queryId)) {
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
return { ...result, rows: this.#parseRows(result.rows) };
|
|
126
|
+
}
|
|
127
|
+
#parseRows(rows) {
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
if (!row)
|
|
131
|
+
continue;
|
|
132
|
+
const parsed = {};
|
|
133
|
+
for (const [key, value] of Object.entries(row)) {
|
|
134
|
+
parsed[key] = this.#deserializer(value);
|
|
135
|
+
}
|
|
136
|
+
out.push(parsed);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export class SerializePlugin extends BaseSerializePlugin {
|
|
142
|
+
constructor(options = {}) {
|
|
143
|
+
const { serializer = defaultSerializer, deserializer = defaultDeserializer, skipNodeKind = [], } = options;
|
|
144
|
+
super(serializer, deserializer, skipNodeKind);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=kysely-serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kysely-serialize.js","sourceRoot":"","sources":["../src/kysely-serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,wBAAwB,GAQzB,MAAM,QAAQ,CAAC;AAKhB,MAAM,SAAS,GAAG,sDAAsD,CAAC;AAEzE,SAAS,YAAY,CAAC,KAAa,EAAgC;IACjE,OAAO,QAAQ,IAAI,KAAK,CAAC;AAAA,CAC1B;AAED,SAAS,aAAa,CAAC,SAAkB,EAAW;IAClD,IACE,SAAS,KAAK,SAAS;QACvB,SAAS,KAAK,IAAI;QAClB,OAAO,SAAS,KAAK,QAAQ;QAC7B,OAAO,SAAS,KAAK,QAAQ,EAC7B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,KAAK,CAAC;AAAA,CACd;AAED,SAAS,SAAS,CAAC,SAAiB,EAAW;IAC7C,OAAO,CACL,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACvD,CAAC;AAAA,CACH;AAED,MAAM,iBAAiB,GAAe,CAAC,SAAS,EAAE,EAAE,CAAC;IACnD,IAAI,aAAa,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC9D,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,SAAS,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,SAAS,YAAY,IAAI,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC,WAAW,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACF,CAAC;AAEF,MAAM,mBAAmB,GAAiB,CAAC,SAAS,EAAE,EAAE,CAAC;IACvD,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,SAAS,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1D,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AAAA,CAClB,CAAC;AAEF,MAAM,8BAA+B,SAAQ,wBAAwB;IAC1D,WAAW,CAAa;IAEjC,YAAY,UAAsB,EAAE;QAClC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;IAAA,CAC/B;IAEkB,2BAA2B,CAC5C,IAA4B,EACJ;QACxB,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;SACpD,CAAC;IAAA,CACH;IAEkB,qBAAqB,CACtC,IAAsB,EACtB,OAAsC,EACpB;QAClB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;QAC7B,IAAI,SAAS,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACnC,OAAO,KAAK,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,YAAY,GAAI,SAAuB,CAAC,KAAK,CAAC;QACpD,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;QACvD,IAAI,YAAY,KAAK,eAAe,EAAE,CAAC;YACrC,OAAO,KAAK,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,YAAY,GAAc;YAC9B,GAAI,SAAuB;YAC3B,KAAK,EAAE,eAAe;SACvB,CAAC;QAEF,OAAO,KAAK,CAAC,qBAAqB,CAChC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,EAChC,OAAO,CACR,CAAC;IAAA,CACH;IAEkB,cAAc,CAAC,IAAe,EAAa;QAC5D,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IAAA,CACzD;CACF;AAED,MAAM,mBAAmB;IACd,YAAY,CAAiC;IAC7C,aAAa,CAAe;IAC5B,YAAY,CAAwC;IACpD,IAAI,CAAyB;IAEtC;;OAEG;IACH,YACE,UAAsB,EACtB,YAA0B,EAC1B,YAA8C,EAC9C;QACA,IAAI,CAAC,YAAY,GAAG,IAAI,8BAA8B,CAAC,UAAU,CAAC,CAAC;QACnE,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;QAClC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;YAC1C,IAAI,CAAC,IAAI,GAAG,IAAI,OAAO,EAAU,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACnB,CAAC;IAAA,CACF;IAED,cAAc,CAAC,EACb,IAAI,EACJ,OAAO,GACkB,EAAqB;QAC9C,IAAI,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAAA,CAC9C;IAED,KAAK,CAAC,eAAe,CAAC,EACpB,MAAM,EACN,OAAO,GACmB,EAAoC;QAC9D,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IAAA,CAC1D;IAED,UAAU,CAAC,IAAkB,EAAgB;QAC3C,MAAM,GAAG,GAAiB,EAAE,CAAC;QAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,MAAM,MAAM,GAA4B,EAAE,CAAC;YAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/C,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,CAAC;IAAA,CACZ;CACF;AAQD,MAAM,OAAO,eAAgB,SAAQ,mBAAmB;IACtD,YAAY,OAAO,GAA2B,EAAE,EAAE;QAChD,MAAM,EACJ,UAAU,GAAG,iBAAiB,EAC9B,YAAY,GAAG,mBAAmB,EAClC,YAAY,GAAG,EAAE,GAClB,GAAG,OAAO,CAAC;QACZ,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IAAA,CAC/C;CACF"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Structured logging utilities for sync operations
|
|
3
|
+
*
|
|
4
|
+
* Outputs JSON lines for easy parsing by log aggregation tools.
|
|
5
|
+
* Each log event includes a timestamp and event type.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Sync log event structure
|
|
9
|
+
*/
|
|
10
|
+
interface SyncLogEvent {
|
|
11
|
+
/** Event type identifier */
|
|
12
|
+
event: string;
|
|
13
|
+
/** User ID (optional) */
|
|
14
|
+
userId?: string;
|
|
15
|
+
/** Operation duration in milliseconds (optional) */
|
|
16
|
+
durationMs?: number;
|
|
17
|
+
/** Number of rows affected (optional) */
|
|
18
|
+
rowCount?: number;
|
|
19
|
+
/** Whether a full reset was required (optional) */
|
|
20
|
+
resetRequired?: boolean;
|
|
21
|
+
/** Error message if operation failed (optional) */
|
|
22
|
+
error?: string;
|
|
23
|
+
/** Additional arbitrary properties */
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Logger function type - allows custom logging implementations
|
|
28
|
+
*/
|
|
29
|
+
type SyncLogger = (event: SyncLogEvent) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Log a sync event using the default logger.
|
|
32
|
+
* For custom logging, create your own logger with createDefaultLogger pattern.
|
|
33
|
+
*/
|
|
34
|
+
export declare const logSyncEvent: SyncLogger;
|
|
35
|
+
/**
|
|
36
|
+
* Create a timer for measuring operation duration.
|
|
37
|
+
* Returns the elapsed time in milliseconds when called.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const elapsed = createSyncTimer();
|
|
41
|
+
* await doSomeWork();
|
|
42
|
+
* logSyncEvent({ event: 'work_complete', durationMs: elapsed() });
|
|
43
|
+
*/
|
|
44
|
+
export declare function createSyncTimer(): () => number;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,UAAU,YAAY;IACpB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,KAAK,UAAU,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AA+BhD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,UAAkC,CAAC;AAE9D;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,MAAM,CAG9C"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Structured logging utilities for sync operations
|
|
3
|
+
*
|
|
4
|
+
* Outputs JSON lines for easy parsing by log aggregation tools.
|
|
5
|
+
* Each log event includes a timestamp and event type.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Default logger that outputs JSON lines to console.
|
|
9
|
+
* Non-blocking - defers logging to avoid blocking the event loop.
|
|
10
|
+
*
|
|
11
|
+
* On server (Node.js), uses setImmediate.
|
|
12
|
+
* On client (browser), uses setTimeout(0).
|
|
13
|
+
*/
|
|
14
|
+
function createDefaultLogger() {
|
|
15
|
+
// Detect environment
|
|
16
|
+
const isNode = typeof globalThis !== 'undefined' &&
|
|
17
|
+
typeof globalThis.setImmediate === 'function';
|
|
18
|
+
const defer = isNode
|
|
19
|
+
? (fn) => globalThis.setImmediate(fn)
|
|
20
|
+
: (fn) => setTimeout(fn, 0);
|
|
21
|
+
return (event) => {
|
|
22
|
+
defer(() => {
|
|
23
|
+
console.log(JSON.stringify({
|
|
24
|
+
timestamp: new Date().toISOString(),
|
|
25
|
+
...event,
|
|
26
|
+
}));
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Log a sync event using the default logger.
|
|
32
|
+
* For custom logging, create your own logger with createDefaultLogger pattern.
|
|
33
|
+
*/
|
|
34
|
+
export const logSyncEvent = createDefaultLogger();
|
|
35
|
+
/**
|
|
36
|
+
* Create a timer for measuring operation duration.
|
|
37
|
+
* Returns the elapsed time in milliseconds when called.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const elapsed = createSyncTimer();
|
|
41
|
+
* await doSomeWork();
|
|
42
|
+
* logSyncEvent({ event: 'work_complete', durationMs: elapsed() });
|
|
43
|
+
*/
|
|
44
|
+
export function createSyncTimer() {
|
|
45
|
+
const start = performance.now();
|
|
46
|
+
return () => Math.round(performance.now() - start);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2BH;;;;;;GAMG;AACH,SAAS,mBAAmB,GAAe;IACzC,qBAAqB;IACrB,MAAM,MAAM,GACV,OAAO,UAAU,KAAK,WAAW;QACjC,OAAO,UAAU,CAAC,YAAY,KAAK,UAAU,CAAC;IAEhD,MAAM,KAAK,GAAG,MAAM;QAClB,CAAC,CAAC,CAAC,EAAc,EAAE,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjD,CAAC,CAAC,CAAC,EAAc,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAE1C,OAAO,CAAC,KAAmB,EAAE,EAAE,CAAC;QAC9B,KAAK,CAAC,GAAG,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CAAC;gBACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,KAAK;aACT,CAAC,CACH,CAAC;QAAA,CACH,CAAC,CAAC;IAAA,CACJ,CAAC;AAAA,CACH;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAe,mBAAmB,EAAE,CAAC;AAE9D;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,GAAiB;IAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAChC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;AAAA,CACpD"}
|