Skip to content

Commit b070e9d

Browse files
kimjoarclaude
andcommitted
fix: use explicit .js extensions for ESM-compliant imports
This change fixes import resolution errors that occur when consuming loro-mirror in strict ESM environments. ## Problem The package used directory imports like `from "./schema"` which TypeScript compiles as-is. This causes two distinct failures: 1. **Runtime** (Node.js ESM / Vitest): "Directory import is not supported resolving ES modules" - Node.js ESM requires explicit file extensions. 2. **Compile time** (TypeScript with `moduleResolution: "NodeNext"`): "Module 'loro-mirror' has no exported member 'RootSchemaType'" - TypeScript can't resolve `./schema` to `./schema/index.d.ts` in strict mode. Consumers using lenient resolution modes (`bundler`, legacy `node`) were unaffected, which is why this wasn't caught earlier. ## Solution 1. Changed tsconfig.json to use `module: "NodeNext"` and `moduleResolution: "NodeNext"` 2. Added explicit `.js` extensions to all relative imports ## Sources - "How to Create an NPM Package" by Matt Pocock (Total TypeScript) https://www.totaltypescript.com/how-to-create-an-npm-package - "Is NodeNext right for libraries?" by Andrew Branch (TypeScript team) https://blog.andrewbran.ch/is-nodenext-right-for-libraries-that-dont-target-node-js/ ## Consequences - All relative imports must now include explicit `.js` extensions - TypeScript will error at compile time if extensions are missing - Consumers no longer need workarounds like Vitest's `server.deps.inline` - Works with all TypeScript moduleResolution modes (bundler, node, NodeNext) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 5d860a5 commit b070e9d

32 files changed

+71
-74
lines changed

packages/core/src/core/diff.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import {
2222
LoroTreeSchema,
2323
RootSchemaType,
2424
SchemaType,
25-
} from "../schema";
26-
import { ChangeKinds, InferContainerOptions, type Change } from "./mirror";
27-
import { CID_KEY } from "../constants";
25+
} from "../schema/index.js";
26+
import { ChangeKinds, InferContainerOptions, type Change } from "./mirror.js";
27+
import { CID_KEY } from "../constants.js";
2828

2929
import {
3030
containerIdToContainerType,
@@ -42,7 +42,7 @@ import {
4242
isArrayLike,
4343
isTreeID,
4444
defineCidProperty,
45-
} from "./utils";
45+
} from "./utils.js";
4646

4747
/**
4848
* Finds the longest increasing subsequence of a sequence of numbers

packages/core/src/core/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
* Core mirroring functionality for syncing application state with Loro CRDT
33
*/
44

5-
export { Mirror, SyncDirection, toNormalizedJson } from "./mirror";
5+
export { Mirror, SyncDirection, toNormalizedJson } from "./mirror.js";
66
export type {
77
InferContainerOptions,
88
MirrorOptions,
99
SetStateOptions,
1010
SubscriberCallback,
1111
UpdateMetadata,
12-
} from "./mirror";
12+
} from "./mirror.js";

packages/core/src/core/loroEventApply.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable unicorn/consistent-function-scoping */
22
import { describe, it, expect } from "vitest";
33
import { LoroDoc, LoroText, LoroList, LoroMap, LoroCounter } from "loro-crdt";
4-
import { applyEventBatchToState } from "./loroEventApply";
4+
import { applyEventBatchToState } from "./loroEventApply.js";
55

66
const commitAndAssert = (doc: LoroDoc, getState: () => unknown) => {
77
doc.commit();

packages/core/src/core/loroEventApply.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
LoroEventBatch,
99
TreeID,
1010
} from "loro-crdt";
11-
import { defineCidProperty, isTreeID } from "./utils";
11+
import { defineCidProperty, isTreeID } from "./utils.js";
1212

1313
// Plain JSON-like value held in Mirror state (no `any`)
1414
type JSONPrimitive = string | number | boolean | null | undefined;

packages/core/src/core/mirror.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
TreeID,
1919
} from "loro-crdt";
2020

21-
import { applyEventBatchToState } from "./loroEventApply";
21+
import { applyEventBatchToState } from "./loroEventApply.js";
2222
import {
2323
ContainerSchemaType,
2424
getDefaultValue,
@@ -36,7 +36,7 @@ import {
3636
RootSchemaType,
3737
SchemaType,
3838
validateSchema,
39-
} from "../schema";
39+
} from "../schema/index.js";
4040
import {
4141
deepEqual,
4242
inferContainerTypeFromValue,
@@ -46,9 +46,9 @@ import {
4646
tryInferContainerType,
4747
getRootContainerByType,
4848
defineCidProperty,
49-
} from "./utils";
50-
import { diffContainer, diffTree } from "./diff";
51-
import { CID_KEY } from "../constants";
49+
} from "./utils.js";
50+
import { diffContainer, diffTree } from "./diff.js";
51+
import { CID_KEY } from "../constants.js";
5252

5353
// Plain JSON-like value used for state snapshots
5454
type JSONPrimitive = string | number | boolean | null | undefined;
@@ -98,7 +98,7 @@ export interface MirrorOptions<S extends SchemaType> {
9898
/**
9999
* Initial state (optional)
100100
*/
101-
initialState?: Partial<import("../schema").InferInputType<S>>;
101+
initialState?: Partial<import("../schema/index.js").InferInputType<S>>;
102102

103103
/**
104104
* Whether to validate state updates against the schema
@@ -726,10 +726,7 @@ export class Mirror<S extends SchemaType> {
726726
/**
727727
* Update Loro based on state changes
728728
*/
729-
private updateLoro(
730-
newState: InferType<S>,
731-
options?: SetStateOptions,
732-
) {
729+
private updateLoro(newState: InferType<S>, options?: SetStateOptions) {
733730
if (this.syncing) return;
734731

735732
this.syncing = true;

packages/core/src/core/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
*/
44

55
import { Container, ContainerID, ContainerType, LoroDoc } from "loro-crdt";
6-
import { SchemaType } from "../schema";
7-
import { Change, InferContainerOptions } from "./mirror";
8-
import { CID_KEY } from "../constants";
6+
import { SchemaType } from "../schema/index.js";
7+
import { Change, InferContainerOptions } from "./mirror.js";
8+
import { CID_KEY } from "../constants.js";
99

1010
export function defineCidProperty(target: unknown, cid: ContainerID) {
1111
if (

packages/core/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
// Re-export all public APIs
7-
export * from "./schema";
7+
export * from "./schema/index.js";
88
export {
99
Mirror,
1010
toNormalizedJson,
@@ -14,11 +14,11 @@ export {
1414
type SubscriberCallback,
1515
type InferContainerOptions,
1616
SyncDirection,
17-
} from "./core";
17+
} from "./core/index.js";
1818

1919
// Default export
20-
import * as schema from "./schema";
21-
import * as core from "./core";
20+
import * as schema from "./schema/index.js";
21+
import * as core from "./core/index.js";
2222

2323
type Combined = typeof schema & typeof core;
2424
const loroMirror: Combined = Object.assign({}, schema, core);

packages/core/src/schema/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import {
2121
SchemaType,
2222
StringSchemaType,
2323
InferType,
24-
} from "./types";
24+
} from "./types.js";
2525

26-
export * from "./types";
27-
export * from "./validators";
26+
export * from "./types.js";
27+
export * from "./validators.js";
2828

2929
/**
3030
* Create a schema definition

packages/core/src/schema/validators.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {
1212
LoroTreeSchema,
1313
RootSchemaType,
1414
SchemaType,
15-
} from "./types";
16-
import { isObject } from "../core/utils";
15+
} from "./types.js";
16+
import { isObject } from "../core/utils.js";
1717

1818
const schemaValidationCache = new WeakMap<object, WeakSet<object>>();
1919

packages/core/tests/cid.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { describe, it, expect, expectTypeOf } from "vitest";
22
import { LoroDoc, LoroMap } from "loro-crdt";
3-
import { Mirror } from "../src/core/mirror";
4-
import { schema } from "../src/schema";
5-
import { InferType } from "../src";
6-
import { CID_KEY } from "../src/constants";
7-
import { diffMap } from "../src/core/diff";
3+
import { Mirror } from "../src/core/mirror.js";
4+
import { schema } from "../src/schema/index.js";
5+
import { InferType } from "../src/index.js";
6+
import { CID_KEY } from "../src/constants.js";
7+
import { diffMap } from "../src/core/diff.js";
88

99
describe("$cid: state injection and write ignoring (always-on for LoroMap)", () => {
1010
it("types: LoroMap includes $cid by default", () => {

0 commit comments

Comments
 (0)