Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions fix-esm-imports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

function fixEsmImports(dir) {
const files = fs.readdirSync(dir);

for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);

if (stat.isDirectory()) {
fixEsmImports(filePath);
} else if (file.endsWith('.js')) {
let content = fs.readFileSync(filePath, 'utf8');

// Fix relative imports: ./file or ../file
content = content.replace(
/from\s+['"](\.\/.+?|(?:\.\.\/.+?))['"];/g,
(match, importPath) => {
if (importPath.endsWith('.js')) {
return match; // Already has .js extension
}

const resolvedPath = path.resolve(path.dirname(filePath), importPath);

// Check if it's a directory that exists
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
return `from '${importPath}/index.js';`;
}

// Check if file.js exists
if (fs.existsSync(resolvedPath + '.js')) {
return `from '${importPath}.js';`;
}

// Default to adding .js
return `from '${importPath}.js';`;
}
);

content = content.replace(
/import\s+['"](\.\/.+?|(?:\.\.\/.+?))['"];/g,
(match, importPath) => {
if (importPath.endsWith('.js')) {
return match; // Already has .js extension
}

const resolvedPath = path.resolve(path.dirname(filePath), importPath);

// Check if it's a directory that exists
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
return `import '${importPath}/index.js';`;
}

// Check if file.js exists
if (fs.existsSync(resolvedPath + '.js')) {
return `import '${importPath}.js';`;
}

// Default to adding .js
return `import '${importPath}.js';`;
}
);

// Fix problematic external imports
content = content.replace(
/import\s*\{\s*printTree\s*\}\s*from\s*['"]tree-dump\/lib\/printTree['"];/g,
`// ESM compatibility: tree-dump doesn't support ESM properly
let printTree;
try {
printTree = (await import('tree-dump/lib/printTree.js')).printTree;
} catch {
printTree = (...args) => '';
}`
);

content = content.replace(
/import\s*\{\s*AvlMap\s*\}\s*from\s*['"]sonic-forest\/lib\/avl\/AvlMap['"];/g,
`// ESM compatibility workaround for sonic-forest
let AvlMap;
try {
AvlMap = (await import('sonic-forest/lib/avl/AvlMap.js')).AvlMap;
} catch {
// Fallback to Map if AvlMap is not available
AvlMap = Map;
}`
);

fs.writeFileSync(filePath, content);
}
}
}

console.log('Fixing ESM imports...');
fixEsmImports(path.join(__dirname, 'esm'));
console.log('ESM imports fixed!');
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"lint:fix": "biome lint --write ./src",
"clean": "npx [email protected] lib es6 es2019 es2020 esm typedocs coverage gh-pages yarn-error.log src/**/__bench__/node_modules src/**/__bench__/yarn-error.log",
"build:es2020": "tsc --project tsconfig.build.json --module commonjs --target es2020 --outDir lib",
"build:esm": "tsc --project tsconfig.build.json --module ESNext --target ESNEXT --outDir esm",
"build:esm": "tsc --project tsconfig.build.json --module ESNext --target ESNEXT --outDir esm && node fix-esm-imports.js",
"build:all": "npx [email protected] \"yarn build:es2020\" \"yarn build:esm\"",
"build": "yarn build:es2020",
"jest": "jest",
Expand Down
69 changes: 41 additions & 28 deletions src/json-crdt/codec/structural/binary/Decoder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import * as nodes from '../../../nodes';
import {
ConNode,
ValNode,
RootNode,
ObjNode,
VecNode,
StrNode,
StrChunk,
BinNode,
BinChunk,
ArrNode,
ArrChunk,
JsonNode
} from '../../../nodes';
import {ClockDecoder} from '../../../../json-crdt-patch/codec/clock/ClockDecoder';
import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtReader';
import {type ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock';
Expand Down Expand Up @@ -34,7 +47,7 @@ export class Decoder extends CborDecoderBase<CrdtReader> {
}
}
this.doc = model;
model.root = new nodes.RootNode(this.doc, this.cRoot().id);
model.root = new RootNode(this.doc, this.cRoot().id);
this.clockDecoder = undefined;
return model;
}
Expand Down Expand Up @@ -67,13 +80,13 @@ export class Decoder extends CborDecoderBase<CrdtReader> {
}
}

protected cRoot(): nodes.JsonNode {
protected cRoot(): JsonNode {
const reader = this.reader;
const peek = reader.uint8[reader.x];
return !peek ? UNDEFINED : this.cNode();
}

protected cNode(): nodes.JsonNode {
protected cNode(): JsonNode {
const reader = this.reader;
const id = this.ts();
const octet = reader.u8();
Expand All @@ -98,37 +111,37 @@ export class Decoder extends CborDecoderBase<CrdtReader> {
throw new Error('UNKNOWN_NODE');
}

protected cCon(id: ITimestampStruct, length: number): nodes.ConNode {
protected cCon(id: ITimestampStruct, length: number): ConNode {
const doc = this.doc;
const data = !length ? this.val() : this.ts();
const node = new nodes.ConNode(id, data);
const node = new ConNode(id, data);
doc.index.set(id, node);
return node;
}

protected cVal(id: ITimestampStruct): nodes.ValNode {
protected cVal(id: ITimestampStruct): ValNode {
const child = this.cNode();
const doc = this.doc;
const node = new nodes.ValNode(doc, id, child.id);
const node = new ValNode(doc, id, child.id);
doc.index.set(id, node);
return node;
}

protected cObj(id: ITimestampStruct, length: number): nodes.ObjNode {
const obj = new nodes.ObjNode(this.doc, id);
protected cObj(id: ITimestampStruct, length: number): ObjNode {
const obj = new ObjNode(this.doc, id);
for (let i = 0; i < length; i++) this.cObjChunk(obj);
this.doc.index.set(id, obj);
return obj;
}

protected cObjChunk(obj: nodes.ObjNode): void {
protected cObjChunk(obj: ObjNode): void {
const key: string = this.key();
obj.keys.set(key, this.cNode().id);
}

protected cVec(id: ITimestampStruct, length: number): nodes.VecNode {
protected cVec(id: ITimestampStruct, length: number): VecNode {
const reader = this.reader;
const obj = new nodes.VecNode(this.doc, id);
const obj = new VecNode(this.doc, id);
const elements = obj.elements;
for (let i = 0; i < length; i++) {
const octet = reader.peak();
Expand All @@ -141,48 +154,48 @@ export class Decoder extends CborDecoderBase<CrdtReader> {
return obj;
}

protected cStr(id: ITimestampStruct, length: number): nodes.StrNode {
const node = new nodes.StrNode(id);
protected cStr(id: ITimestampStruct, length: number): StrNode {
const node = new StrNode(id);
if (length) node.ingest(length, this.cStrChunk);
this.doc.index.set(id, node);
return node;
}

private cStrChunk = (): nodes.StrChunk => {
private cStrChunk = (): StrChunk => {
const id = this.ts();
const val = this.val();
if (typeof val === 'string') return new nodes.StrChunk(id, val.length, val);
return new nodes.StrChunk(id, ~~(<number>val), '');
if (typeof val === 'string') return new StrChunk(id, val.length, val);
return new StrChunk(id, ~~(<number>val), '');
};

protected cBin(id: ITimestampStruct, length: number): nodes.BinNode {
const node = new nodes.BinNode(id);
protected cBin(id: ITimestampStruct, length: number): BinNode {
const node = new BinNode(id);
if (length) node.ingest(length, this.cBinChunk);
this.doc.index.set(id, node);
return node;
}

private cBinChunk = (): nodes.BinChunk => {
private cBinChunk = (): BinChunk => {
const id = this.ts();
const reader = this.reader;
const [deleted, length] = reader.b1vu56();
if (deleted) return new nodes.BinChunk(id, length, undefined);
return new nodes.BinChunk(id, length, reader.buf(length));
if (deleted) return new BinChunk(id, length, undefined);
return new BinChunk(id, length, reader.buf(length));
};

protected cArr(id: ITimestampStruct, length: number): nodes.ArrNode {
const obj = new nodes.ArrNode(this.doc, id);
protected cArr(id: ITimestampStruct, length: number): ArrNode {
const obj = new ArrNode(this.doc, id);
if (length) obj.ingest(length, this.cArrChunk);
this.doc.index.set(id, obj);
return obj;
}

private readonly cArrChunk = (): nodes.ArrChunk => {
private readonly cArrChunk = (): ArrChunk => {
const id = this.ts();
const [deleted, length] = this.reader.b1vu56();
if (deleted) return new nodes.ArrChunk(id, length, undefined);
if (deleted) return new ArrChunk(id, length, undefined);
const ids: ITimestampStruct[] = [];
for (let i = 0; i < length; i++) ids.push(this.cNode().id);
return new nodes.ArrChunk(id, length, ids);
return new ArrChunk(id, length, ids);
};
}
44 changes: 27 additions & 17 deletions src/json-crdt/codec/structural/binary/Encoder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import * as nodes from '../../../nodes';
import {
ConNode,
ValNode,
RootNode,
ObjNode,
VecNode,
StrNode,
BinNode,
ArrNode,
JsonNode
} from '../../../nodes';
import {ClockEncoder} from '../../../../json-crdt-patch/codec/clock/ClockEncoder';
import {CrdtWriter} from '../../../../json-crdt-patch/util/binary/CrdtWriter';
import {type ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock';
Expand Down Expand Up @@ -74,7 +84,7 @@ export class Encoder extends CborEncoder<CrdtWriter> {

protected ts: (ts: ITimestampStruct) => void = this.tsLogical;

protected cRoot(root: nodes.RootNode): void {
protected cRoot(root: RootNode): void {
const val = root.val;
if (val.sid === SESSION.SYSTEM) this.writer.u8(0);
else this.cNode(root.node());
Expand All @@ -89,18 +99,18 @@ export class Encoder extends CborEncoder<CrdtWriter> {
}
}

protected cNode(node: nodes.JsonNode): void {
protected cNode(node: JsonNode): void {
// TODO: PERF: use a switch?
if (node instanceof nodes.ConNode) this.cCon(node);
else if (node instanceof nodes.ValNode) this.cVal(node);
else if (node instanceof nodes.StrNode) this.cStr(node);
else if (node instanceof nodes.ObjNode) this.cObj(node);
else if (node instanceof nodes.VecNode) this.cVec(node);
else if (node instanceof nodes.ArrNode) this.cArr(node);
else if (node instanceof nodes.BinNode) this.cBin(node);
if (node instanceof ConNode) this.cCon(node);
else if (node instanceof ValNode) this.cVal(node);
else if (node instanceof StrNode) this.cStr(node);
else if (node instanceof ObjNode) this.cObj(node);
else if (node instanceof VecNode) this.cVec(node);
else if (node instanceof ArrNode) this.cArr(node);
else if (node instanceof BinNode) this.cBin(node);
}

protected cCon(node: nodes.ConNode): void {
protected cCon(node: ConNode): void {
const val = node.val;
this.ts(node.id);
if (val instanceof Timestamp) {
Expand All @@ -112,13 +122,13 @@ export class Encoder extends CborEncoder<CrdtWriter> {
}
}

protected cVal(node: nodes.ValNode): void {
protected cVal(node: ValNode): void {
this.ts(node.id);
this.writer.u8(0b00100000); // this.writeTL(CRDT_MAJOR_OVERLAY.VAL, 0);
this.cNode(node.node());
}

protected cObj(node: nodes.ObjNode): void {
protected cObj(node: ObjNode): void {
this.ts(node.id);
const keys = node.keys;
this.writeTL(CRDT_MAJOR_OVERLAY.OBJ, keys.size);
Expand All @@ -130,7 +140,7 @@ export class Encoder extends CborEncoder<CrdtWriter> {
this.cNode(this.doc.index.get(val)!);
};

protected cVec(node: nodes.VecNode): void {
protected cVec(node: VecNode): void {
const elements = node.elements;
const length = elements.length;
this.ts(node.id);
Expand All @@ -143,7 +153,7 @@ export class Encoder extends CborEncoder<CrdtWriter> {
}
}

protected cStr(node: nodes.StrNode): void {
protected cStr(node: StrNode): void {
const ts = this.ts;
ts(node.id);
this.writeTL(CRDT_MAJOR_OVERLAY.STR, node.count);
Expand All @@ -154,7 +164,7 @@ export class Encoder extends CborEncoder<CrdtWriter> {
}
}

protected cBin(node: nodes.BinNode): void {
protected cBin(node: BinNode): void {
const ts = this.ts;
const writer = this.writer;
ts(node.id);
Expand All @@ -169,7 +179,7 @@ export class Encoder extends CborEncoder<CrdtWriter> {
}
}

protected cArr(node: nodes.ArrNode): void {
protected cArr(node: ArrNode): void {
const ts = this.ts;
const writer = this.writer;
ts(node.id);
Expand Down
9 changes: 9 additions & 0 deletions tsconfig.esm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.build.json",
"compilerOptions": {
"module": "ESNext",
"target": "ESNEXT",
"moduleResolution": "Node16",
"outDir": "esm"
}
}
Loading