@x18n/core 0.0.6 → 0.0.8

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/index.d.mts CHANGED
@@ -1,29 +1,163 @@
1
+ import { Diagnostic as Diagnostic$1, Diagnostics } from "nostics";
1
2
  import * as v from "valibot";
2
3
  //#region src/errors.d.ts
4
+ /** A place in a file; `line` is 1-based. */
5
+ interface At {
6
+ readonly path: string;
7
+ readonly line?: number | undefined;
8
+ }
3
9
  /**
4
10
  * Every coded error. Groups: 0xxx config, 1xxx plugin, 2xxx discovery and parse,
5
- * 3xxx scanning, 4xxx edits, 5xxx reviews.
11
+ * 3xxx scanning, 4xxx edits, 5xxx reviews. `fix` stays static so the docs can show it.
6
12
  */
7
- type ErrorCode = "X18N0001" | "X18N0002" | "X18N0003" | "X18N1001" | "X18N1002" | "X18N1003" | "X18N1004" | "X18N2001" | "X18N2002" | "X18N2003" | "X18N2004" | "X18N3001" | "X18N4001" | "X18N4002" | "X18N4003" | "X18N4004" | "X18N4005" | "X18N4006" | "X18N4007" | "X18N4008";
8
- export declare const errorCodes: { readonly [C in ErrorCode]: {
9
- readonly message: string;
10
- readonly hint: string;
11
- }; };
12
- interface ErrorInfo {
13
- readonly code: ErrorCode;
14
- readonly message: string;
15
- readonly hint: string;
16
- readonly docs: string;
17
- }
18
- export declare class X18nError extends Error {
13
+ declare const errorDefinitions: {
14
+ X18N0001: {
15
+ why: (p: {
16
+ issue: string;
17
+ }) => string;
18
+ fix: string;
19
+ };
20
+ X18N0002: {
21
+ why: (p: {
22
+ file: string;
23
+ reason: string;
24
+ }) => string;
25
+ fix: string;
26
+ };
27
+ X18N0003: {
28
+ why: (p: {
29
+ name: string | undefined;
30
+ available: readonly string[];
31
+ }) => string;
32
+ fix: string;
33
+ };
34
+ X18N1001: {
35
+ why: (p: {
36
+ specifier: string;
37
+ reason: string;
38
+ }) => string;
39
+ fix: string;
40
+ };
41
+ X18N1002: {
42
+ why: (p: {
43
+ kind: string;
44
+ id: string;
45
+ }) => string;
46
+ fix: string;
47
+ };
48
+ X18N1003: {
49
+ why: (p: {
50
+ preset: string;
51
+ }) => string;
52
+ fix: string;
53
+ };
54
+ X18N1004: {
55
+ why: (p: {
56
+ file: string;
57
+ issues: string;
58
+ }) => string;
59
+ fix: string;
60
+ };
61
+ X18N2001: {
62
+ why: (p: {
63
+ path: string;
64
+ }) => string;
65
+ fix: string;
66
+ };
67
+ X18N2002: {
68
+ why: (p: At & {
69
+ reason: string;
70
+ }) => string;
71
+ fix: string;
72
+ };
73
+ X18N2003: {
74
+ why: (p: At & {
75
+ reason?: string | undefined;
76
+ }) => string;
77
+ fix: string;
78
+ };
79
+ X18N2004: {
80
+ why: (p: At & {
81
+ reason: string;
82
+ }) => string;
83
+ fix: string;
84
+ };
85
+ X18N3001: {
86
+ why: (p: {
87
+ path: string;
88
+ reason: string;
89
+ }) => string;
90
+ fix: string;
91
+ };
92
+ X18N4001: {
93
+ why: (p: {
94
+ path: string;
95
+ }) => string;
96
+ fix: string;
97
+ };
98
+ X18N4002: {
99
+ why: (p: {
100
+ key: string;
101
+ files: readonly string[];
102
+ }) => string;
103
+ fix: string;
104
+ };
105
+ X18N4003: {
106
+ why: (p: {
107
+ decisions: readonly string[];
108
+ }) => string;
109
+ fix: string;
110
+ };
111
+ X18N4004: {
112
+ why: (p: {
113
+ path?: string | undefined;
114
+ }) => string;
115
+ fix: string;
116
+ };
117
+ X18N4005: {
118
+ why: (p: {
119
+ key: string;
120
+ locale: string;
121
+ }) => string;
122
+ fix: string;
123
+ };
124
+ X18N4006: {
125
+ why: (p: {
126
+ key: string;
127
+ }) => string;
128
+ fix: string;
129
+ };
130
+ X18N4007: {
131
+ why: (p: {
132
+ count: number;
133
+ }) => string;
134
+ fix: string;
135
+ };
136
+ X18N4008: {
137
+ why: (p: {
138
+ reason: string;
139
+ }) => string;
140
+ fix: string;
141
+ };
142
+ };
143
+ type ErrorCode = keyof typeof errorDefinitions;
144
+ type X18nError = Diagnostic$1 & {
145
+ readonly name: ErrorCode;
19
146
  readonly code: ErrorCode;
20
- readonly hint: string;
21
- readonly docs: string;
22
- readonly details: readonly string[];
23
- constructor(code: ErrorCode, detail?: string, details?: readonly string[]);
24
- toJSON(): ErrorInfo;
147
+ };
148
+ type ErrorHandles = { readonly [C in ErrorCode]: (...args: Parameters<Diagnostics<typeof errorDefinitions, []>[C]>) => X18nError; };
149
+ /** Raise with `throw errors.X18N2002({ path, reason })`. Core has no reporters: surfaces print what they catch. */
150
+ export declare const errors: ErrorHandles;
151
+ /** JSON form of an error: the `toJSON()` of a nostics diagnostic, without `stack`, `cause` and `data`. */
152
+ interface ErrorInfo {
153
+ /** The code, such as `X18N2002`, or the JS error name of an error without a code. */
154
+ readonly name: string;
155
+ readonly why: string;
156
+ readonly fix?: string | undefined;
157
+ readonly docs?: string | undefined;
25
158
  }
26
159
  export declare function isX18nError(error: unknown): error is X18nError;
160
+ export declare function toErrorInfo(error: unknown): ErrorInfo;
27
161
  //#endregion
28
162
  //#region src/types/model.d.ts
29
163
  /** A locale code exactly as written in paths and files: `en`, `zh_CN`, `pt-BR`. Never normalized. */
@@ -184,8 +318,8 @@ interface JsonObject {
184
318
  }
185
319
  //#endregion
186
320
  //#region src/contracts.d.ts
187
- /** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any `data` shape. */
188
- export declare const contractVersion = 1;
321
+ /** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any shape. */
322
+ export declare const contractVersion = 2;
189
323
  interface WireRange {
190
324
  /** 1-based. */
191
325
  readonly line: number;
@@ -1313,4 +1447,4 @@ export declare function rangeContains(range: TextRange, offset: number): boolean
1313
1447
  /** Fast non-cryptographic string hash (cyrb53), as hex. */
1314
1448
  export declare function hashText(text: string): string;
1315
1449
  //#endregion
1316
- export type { Analysis, Answers, ApplyResult, ArgSource, Builtins, CacheStore, CallSpec, CapabilityConflict, Check, CheckContext, CheckIds, CheckLevel, CheckSetting, ConfigContext, ConfigDraft, ConfigOrigin, CreateProjectOptions, Decision, DecisionOption, DetectContext, Diagnostic, DiagnosticInput, Disposable, EditOp, Envelope, ErrorCode, ErrorInfo, ExtractItem, ExtractOptions, ExtractTemplate, Extractor, FileChange, FileEvent, FileScan, FinderContext, Fix, Format, FormatContext, GlobOptions, GlossaryEntry, HardString, HistoryConfigs, HistoryContext, HistoryOptions, HistoryProvider, HistorySettings, Host, JsonObject, JsonPrimitive, JsonValue, KeyContext, KeyId, KeyInfo, KeyQuery, KeyString, KeyTarget, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageChange, MessageHistory, MessageLeaf, MessageOp, MessageTree, NamespaceFromPath, NamespaceOptions, NodeRange, OverlayHost, ParseResult, Placeholder, PlaceholderComparer, PlaceholderProblem, PlaceholderSyntax, Plan, Plugin, PluginConfigs, PluginFile, PluginOption, PluginRegistry, PluginSpecifier, Preset, PresetExplanation, PresetFactory, PresetOption, Project, ProjectChange, ProjectExplanation, PromptContext, PromptData, PromptDraft, PromptItem, PromptOptions, RegexScannerSpec, ReportInput, ReportSummary, Reporter, ReporterContext, ResolveConfigOptions, ResolvedConfig, Resource, ResourceBlock, ResourceCandidate, ResourceId, ResourceNode, ResourceSpec, ResourceTransform, ResourceTransformContext, ScanContext, ScanResult, Scanner, SchemaIssue, Scope, SerializableConfig, Setup, Severity, SourceFile, TextEdit, TextRange, TreeLeaf, Usage, UsageCandidate, UsageContext, UsageKind, UsageMatch, UsageOptions, UsageQuery, UserConfig, WireDiagnostic, WireRange, WriteOptions };
1450
+ export type { Analysis, Answers, ApplyResult, ArgSource, Builtins, CacheStore, CallSpec, CapabilityConflict, Check, CheckContext, CheckIds, CheckLevel, CheckSetting, ConfigContext, ConfigDraft, ConfigOrigin, CreateProjectOptions, Decision, DecisionOption, DetectContext, Diagnostic, DiagnosticInput, Disposable, EditOp, Envelope, ErrorCode, ErrorInfo, ExtractItem, ExtractOptions, ExtractTemplate, Extractor, FileChange, FileEvent, FileScan, FinderContext, Fix, Format, FormatContext, GlobOptions, GlossaryEntry, HardString, HistoryConfigs, HistoryContext, HistoryOptions, HistoryProvider, HistorySettings, Host, JsonObject, JsonPrimitive, JsonValue, KeyContext, KeyId, KeyInfo, KeyQuery, KeyString, KeyTarget, LineIndex, LinePosition, LocaleCode, LocaleCoverage, LocaleFinder, LocaleInfo, Location, Logger, ManifestReader, MemoryHost, Message, MessageChange, MessageHistory, MessageLeaf, MessageOp, MessageTree, NamespaceFromPath, NamespaceOptions, NodeRange, OverlayHost, ParseResult, Placeholder, PlaceholderComparer, PlaceholderProblem, PlaceholderSyntax, Plan, Plugin, PluginConfigs, PluginFile, PluginOption, PluginRegistry, PluginSpecifier, Preset, PresetExplanation, PresetFactory, PresetOption, Project, ProjectChange, ProjectExplanation, PromptContext, PromptData, PromptDraft, PromptItem, PromptOptions, RegexScannerSpec, ReportInput, ReportSummary, Reporter, ReporterContext, ResolveConfigOptions, ResolvedConfig, Resource, ResourceBlock, ResourceCandidate, ResourceId, ResourceNode, ResourceSpec, ResourceTransform, ResourceTransformContext, ScanContext, ScanResult, Scanner, SchemaIssue, Scope, SerializableConfig, Setup, Severity, SourceFile, TextEdit, TextRange, TreeLeaf, Usage, UsageCandidate, UsageContext, UsageKind, UsageMatch, UsageOptions, UsageQuery, UserConfig, WireDiagnostic, WireRange, WriteOptions, X18nError };
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { Diagnostic, defineDiagnostics } from "nostics";
1
2
  import * as v from "valibot";
2
3
  //#region src/text.ts
3
4
  /** One edit covering only the part that differs, so bytes outside it stay the same. */
@@ -85,8 +86,8 @@ function hashText(text) {
85
86
  }
86
87
  //#endregion
87
88
  //#region src/contracts.ts
88
- /** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any `data` shape. */
89
- const contractVersion = 1;
89
+ /** Version of every JSON contract (CLI `--json`, LSP `x18n/*`, RPC, MCP). Bumped on breaking changes to any shape. */
90
+ const contractVersion = 2;
90
91
  /** Line and column form of an offset range in a text. */
91
92
  function toWireRange(text, start, end) {
92
93
  const index = createLineIndex(text ?? "");
@@ -117,7 +118,7 @@ function toWireDiagnostic(project, diagnostic, text) {
117
118
  }
118
119
  function envelope(command, data, diagnostics = [], errors = []) {
119
120
  return {
120
- x18n: 1,
121
+ x18n: 2,
121
122
  command,
122
123
  data,
123
124
  diagnostics,
@@ -137,113 +138,115 @@ function defineConfig(config) {
137
138
  }
138
139
  //#endregion
139
140
  //#region src/errors.ts
140
- const errorCodes = {
141
+ const at = (p) => p.line === void 0 ? p.path : `${p.path}:${p.line}`;
142
+ /**
143
+ * Every coded error. Groups: 0xxx config, 1xxx plugin, 2xxx discovery and parse,
144
+ * 3xxx scanning, 4xxx edits, 5xxx reviews. `fix` stays static so the docs can show it.
145
+ */
146
+ const errorDefinitions = {
141
147
  X18N0001: {
142
- message: "Invalid config",
143
- hint: "Check the reported field against the config schema (`x18n schema config`)."
148
+ why: (p) => `Invalid config: ${p.issue}`,
149
+ fix: "Check the reported field against the config schema (`x18n schema config`)."
144
150
  },
145
151
  X18N0002: {
146
- message: "Config file could not be loaded",
147
- hint: "Fix the syntax error, or run `x18n doctor` for details."
152
+ why: (p) => `Config file ${p.file} could not be loaded: ${p.reason}`,
153
+ fix: "Fix the syntax error, or run `x18n doctor` for details."
148
154
  },
149
155
  X18N0003: {
150
- message: "Unknown project",
151
- hint: "Pick a project name listed by `x18n status`."
156
+ why: (p) => `${p.name === void 0 ? "Pick a project" : `Unknown project "${p.name}"`} (${p.available.join(", ")})`,
157
+ fix: "Pick a project name listed by `x18n status`, for example with `--project`."
152
158
  },
153
159
  X18N1001: {
154
- message: "Plugin could not be loaded",
155
- hint: "Install the plugin package, or check the path."
160
+ why: (p) => `Plugin ${p.specifier} could not be loaded: ${p.reason}`,
161
+ fix: "Install the plugin package, or check the path."
156
162
  },
157
163
  X18N1002: {
158
- message: "Duplicate capability id",
159
- hint: "Rename the id, or set `override: true` on the later one."
164
+ why: (p) => `Duplicate ${p.kind} id "${p.id}"`,
165
+ fix: "Rename the id, or set `override: true` on the later one."
160
166
  },
161
167
  X18N1003: {
162
- message: "Unknown preset",
163
- hint: "Use a preset name listed in the docs, or a preset object."
168
+ why: (p) => `Unknown preset "${p.preset}"`,
169
+ fix: "Use a preset name listed in the docs, or a preset object."
164
170
  },
165
171
  X18N1004: {
166
- message: "Invalid YAML plugin",
167
- hint: "Validate the file against `schema/plugin.json`."
172
+ why: (p) => `Invalid YAML plugin ${p.file}: ${p.issues}`,
173
+ fix: "Validate the file against `schema/plugin.json`."
168
174
  },
169
175
  X18N2001: {
170
- message: "No format for resource",
171
- hint: "Map the extension in `formats`, or add a format plugin."
176
+ why: (p) => `No format for resource ${p.path}`,
177
+ fix: "Map the extension in `formats`, or add a format plugin."
172
178
  },
173
179
  X18N2002: {
174
- message: "Resource could not be parsed",
175
- hint: "Fix the syntax error in the file."
180
+ why: (p) => `Resource ${at(p)} could not be parsed: ${p.reason}`,
181
+ fix: "Fix the syntax error in the file."
176
182
  },
177
183
  X18N2003: {
178
- message: "Resource is not an object of messages",
179
- hint: "Locale files hold nested objects of strings, numbers, booleans or null."
184
+ why: (p) => `Resource ${at(p)} is not an object of messages${p.reason ? `: ${p.reason}` : ""}`,
185
+ fix: "Locale files hold nested objects of strings, numbers, booleans or null."
180
186
  },
181
187
  X18N2004: {
182
- message: "Locale file is not static",
183
- hint: "Use JSON or YAML, or opt in to execution with `execute: true` on the resource pattern."
188
+ why: (p) => `Locale file ${at(p)} is not static: ${p.reason}`,
189
+ fix: "Use JSON or YAML, or opt in to execution with `execute: true` on the resource pattern."
184
190
  },
185
191
  X18N3001: {
186
- message: "Source file could not be scanned",
187
- hint: "Fix the syntax error, or exclude the file with `usage.exclude`."
192
+ why: (p) => `Source file ${p.path} could not be scanned: ${p.reason}`,
193
+ fix: "Fix the syntax error, or exclude the file with `usage.exclude`."
188
194
  },
189
195
  X18N4001: {
190
- message: "Resource is read-only",
191
- hint: "Edit the source of computed values, or use a writable format."
196
+ why: (p) => `Resource ${p.path} is read-only`,
197
+ fix: "Edit the source of computed values, or use a writable format."
192
198
  },
193
199
  X18N4002: {
194
- message: "Rename target already exists",
195
- hint: "Delete or rename the existing key first."
200
+ why: (p) => `Rename target "${p.key}" already exists${p.files.length ? ` in ${p.files.join(", ")}` : ""}`,
201
+ fix: "Delete or rename the existing key first."
196
202
  },
197
203
  X18N4003: {
198
- message: "A decision is needed",
199
- hint: "Answer the listed decisions, or pass `--yes` to accept defaults."
204
+ why: (p) => `A decision is needed: ${p.decisions.join(" ")}`,
205
+ fix: "Answer the listed decisions, or pass `--yes` to accept defaults."
200
206
  },
201
207
  X18N4004: {
202
- message: "Plan is stale",
203
- hint: "Files changed since the plan was made. Plan again."
208
+ why: (p) => p.path === void 0 ? "Plan is stale" : `Plan is stale: ${p.path} changed since the plan was made`,
209
+ fix: "Files changed since the plan was made. Plan again."
204
210
  },
205
211
  X18N4005: {
206
- message: "No resource for the key",
207
- hint: "Add a resource pattern for the locale, or create the locale file."
212
+ why: (p) => `No resource for the key ${p.key} in ${p.locale}`,
213
+ fix: "Add a resource pattern for the locale, or create the locale file."
208
214
  },
209
215
  X18N4006: {
210
- message: "Key not found",
211
- hint: "List keys with `x18n keys`."
216
+ why: (p) => `Key not found: "${p.key}"`,
217
+ fix: "List keys with `x18n keys` (agents: the `x18n_keys` tool)."
212
218
  },
213
219
  X18N4007: {
214
- message: "Placeholder mismatch",
215
- hint: "Keep every placeholder of the source value, or pass `--force`."
220
+ why: (p) => `Placeholder mismatch in ${p.count} values`,
221
+ fix: "Keep every placeholder of the source value, or pass `--force`."
216
222
  },
217
223
  X18N4008: {
218
- message: "Invalid edit input",
219
- hint: "Check the input shape against the docs."
220
- }
221
- };
222
- var X18nError = class extends Error {
223
- code;
224
- hint;
225
- docs;
226
- details;
227
- constructor(code, detail, details = []) {
228
- const base = errorCodes[code];
229
- super(detail ? `${base.message}: ${detail}` : base.message);
230
- this.name = "X18nError";
231
- this.code = code;
232
- this.hint = base.hint;
233
- this.docs = `https://x18n.antfu.dev/errors/${code.toLowerCase()}`;
234
- this.details = details;
235
- }
236
- toJSON() {
237
- return {
238
- code: this.code,
239
- message: this.message,
240
- hint: this.hint,
241
- docs: this.docs
242
- };
224
+ why: (p) => `Invalid edit input: ${p.reason}`,
225
+ fix: "Check the input shape against the docs."
243
226
  }
244
227
  };
228
+ /** Raise with `throw errors.X18N2002({ path, reason })`. Core has no reporters: surfaces print what they catch. */
229
+ const errors = defineDiagnostics({
230
+ docsBase: "https://x18n.antfu.dev/errors",
231
+ codes: errorDefinitions
232
+ });
245
233
  function isX18nError(error) {
246
- return error instanceof X18nError;
234
+ return error instanceof Diagnostic && Object.hasOwn(errorDefinitions, error.code);
235
+ }
236
+ function toErrorInfo(error) {
237
+ if (isX18nError(error)) return {
238
+ name: error.name,
239
+ why: error.why,
240
+ fix: error.fix,
241
+ docs: error.docs
242
+ };
243
+ return error instanceof Error ? {
244
+ name: error.name,
245
+ why: error.message
246
+ } : {
247
+ name: "Error",
248
+ why: String(error)
249
+ };
247
250
  }
248
251
  //#endregion
249
252
  //#region src/keys.ts
@@ -547,7 +550,10 @@ function sortByEnforce(plugins) {
547
550
  function uniqueById(items, kind) {
548
551
  const out = /* @__PURE__ */ new Map();
549
552
  for (const item of items) {
550
- if (out.has(item.id) && !item.override) throw new X18nError("X18N1002", `${kind} "${item.id}"`);
553
+ if (out.has(item.id) && !item.override) throw errors.X18N1002({
554
+ kind,
555
+ id: item.id
556
+ });
551
557
  out.set(item.id, item);
552
558
  }
553
559
  return out;
@@ -799,12 +805,12 @@ function isLeaf(value) {
799
805
  }
800
806
  /** Parses the batch shape `{ "<locale>": { "<key>": "<value>" } }` (as in `x18n apply`) into set ops. */
801
807
  function parseMessageBatch(project, data) {
802
- if (!isJsonObject(data)) throw new X18nError("X18N4008", "expected { \"<locale>\": { \"<key>\": \"<value>\" } }");
808
+ if (!isJsonObject(data)) throw errors.X18N4008({ reason: "expected { \"<locale>\": { \"<key>\": \"<value>\" } }" });
803
809
  const ops = [];
804
810
  for (const [locale, entries] of Object.entries(data)) {
805
- if (!isJsonObject(entries)) throw new X18nError("X18N4008", `"${locale}" must map keys to values`);
811
+ if (!isJsonObject(entries)) throw errors.X18N4008({ reason: `"${locale}" must map keys to values` });
806
812
  for (const [raw, value] of Object.entries(entries)) {
807
- if (!isLeaf(value)) throw new X18nError("X18N4008", `"${locale}.${raw}" must be a string, number, boolean or null`);
813
+ if (!isLeaf(value)) throw errors.X18N4008({ reason: `"${locale}.${raw}" must be a string, number, boolean or null` });
808
814
  ops.push({
809
815
  type: "set",
810
816
  key: project.parseKey(raw),
@@ -1186,7 +1192,7 @@ var PlanBuilder = class {
1186
1192
  /** ---- Targets ---- */
1187
1193
  targetOf(s) {
1188
1194
  const format = this.options.registry.formatById(s.resource.format);
1189
- if (!format) throw new X18nError("X18N2001", s.resource.path);
1195
+ if (!format) throw errors.X18N2001({ path: s.resource.path });
1190
1196
  return {
1191
1197
  id: s.resource.id,
1192
1198
  path: s.resource.path,
@@ -1212,7 +1218,7 @@ var PlanBuilder = class {
1212
1218
  }
1213
1219
  writable(id) {
1214
1220
  const s = this.stateById.get(id);
1215
- if (s.resource.readonly) throw new X18nError("X18N4001", s.resource.path);
1221
+ if (s.resource.readonly) throw errors.X18N4001({ path: s.resource.path });
1216
1222
  return s;
1217
1223
  }
1218
1224
  keyId(key) {
@@ -1263,7 +1269,10 @@ var PlanBuilder = class {
1263
1269
  const format = model && this.options.registry.formatById(model.resource.format);
1264
1270
  let path = model && mirrorPath(model.resource.path, model.resource.locale, locale);
1265
1271
  if (model && path && namespace !== void 0 && model.resource.namespace !== void 0 && model.resource.namespace !== namespace) path = mirrorPath(path, model.resource.namespace, namespace);
1266
- if (!model || !format?.serialize || model.resource.block || !path || exists(path)) throw new X18nError("X18N4005", `${key} in ${locale}`);
1272
+ if (!model || !format?.serialize || model.resource.block || !path || exists(path)) throw errors.X18N4005({
1273
+ key,
1274
+ locale
1275
+ });
1267
1276
  const target = {
1268
1277
  id: path,
1269
1278
  path,
@@ -1478,7 +1487,10 @@ var PlanBuilder = class {
1478
1487
  const existing = this.options.messages(to);
1479
1488
  if (!existing && !this.options.parents().has(to)) return;
1480
1489
  const files = [...new Set([...existing?.values() ?? []].flatMap((list) => list.map((m) => this.stateById.get(m.resource)?.resource.path ?? m.resource)))];
1481
- throw new X18nError("X18N4002", `"${to}"${files.length ? ` in ${files.join(", ")}` : ""}`, files);
1490
+ throw errors.X18N4002({
1491
+ key: to,
1492
+ files
1493
+ });
1482
1494
  }
1483
1495
  rename(op) {
1484
1496
  if (op.from === op.to) return;
@@ -1490,7 +1502,7 @@ var PlanBuilder = class {
1490
1502
  if (op.type === "rename-prefix") keys = under;
1491
1503
  else if (isLeaf) keys = [op.from, ...derived];
1492
1504
  else keys = under.length > 0 ? under : derived;
1493
- if (keys.length === 0) throw new X18nError("X18N4006", op.from);
1505
+ if (keys.length === 0) throw errors.X18N4006({ key: op.from });
1494
1506
  const pairs = keys.map((key) => [key, replaceKeyPrefix(key, op.from, op.to)]);
1495
1507
  for (const [, to] of pairs) this.conflictCheck(to);
1496
1508
  const renamed = keys.includes(op.from) ? /* @__PURE__ */ new Set() : this.renameNode(op.from, op.to);
@@ -1504,7 +1516,7 @@ var PlanBuilder = class {
1504
1516
  }
1505
1517
  delete(op) {
1506
1518
  const keys = this.options.messages(op.key) ? [op.key] : this.options.allKeys().filter((key) => isKeyUnder(key, op.key));
1507
- if (keys.length === 0) throw new X18nError("X18N4006", op.key);
1519
+ if (keys.length === 0) throw errors.X18N4006({ key: op.key });
1508
1520
  for (const key of keys) {
1509
1521
  this.touched.add(key);
1510
1522
  for (const [locale, list] of this.options.messages(key) ?? []) if (op.locales === void 0 || op.locales === "all" || op.locales.includes(locale)) list.forEach((m) => this.deleteMessage(m));
@@ -1563,7 +1575,7 @@ var PlanBuilder = class {
1563
1575
  const sort = this.config.write.sort !== false;
1564
1576
  const edit = target.format.edit;
1565
1577
  if (entry.formatOnly || sort || !edit) {
1566
- if (!target.format.serialize) throw new X18nError("X18N4001", target.path);
1578
+ if (!target.format.serialize) throw errors.X18N4001({ path: target.path });
1567
1579
  if (!entry.formatOnly && !edit) this.warnings.push(`${target.path} was re-written in full (the ${target.format.id} format cannot edit in place); formatting was reset.`);
1568
1580
  return target.format.serialize(sort || entry.formatOnly ? sortTree(entry.tree, sort ? this.compare() : () => 0) : entry.tree, ctx);
1569
1581
  }
@@ -1713,8 +1725,8 @@ function createPlanner(options) {
1713
1725
  },
1714
1726
  async apply(plan, answers) {
1715
1727
  const unanswered = plan.decisions.filter((d) => answers[d.id] === void 0);
1716
- if (unanswered.length > 0) throw new X18nError("X18N4003", unanswered.map((d) => d.message).join(" "), unanswered.map((d) => d.id));
1717
- if (plan.revision !== options.state().revision) throw new X18nError("X18N4004");
1728
+ if (unanswered.length > 0) throw errors.X18N4003({ decisions: unanswered.map((d) => d.message) });
1729
+ if (plan.revision !== options.state().revision) throw errors.X18N4004();
1718
1730
  const fresh = plan.decisions.length > 0 ? planWith(plan.ops, answers) : {
1719
1731
  plan,
1720
1732
  texts: texts.get(plan.id) ?? /* @__PURE__ */ new Map()
@@ -1722,7 +1734,7 @@ function createPlanner(options) {
1722
1734
  for (const change of fresh.plan.changes) {
1723
1735
  const current = await options.host.readFile(change.path);
1724
1736
  const expected = fresh.texts.get(change.path);
1725
- if (change.type === "edit" && expected !== void 0 && current !== expected || change.type === "create" && current !== void 0) throw new X18nError("X18N4004", change.path);
1737
+ if (change.type === "edit" && expected !== void 0 && current !== expected || change.type === "create" && current !== void 0) throw errors.X18N4004({ path: change.path });
1726
1738
  }
1727
1739
  const events = [];
1728
1740
  for (const change of fresh.plan.changes) events.push(await write(change));
@@ -2105,13 +2117,13 @@ function createProject(options) {
2105
2117
  tree: {},
2106
2118
  rawTree: {},
2107
2119
  ranges: /* @__PURE__ */ new Map(),
2108
- error: new X18nError("X18N2001", candidate.path)
2120
+ error: errors.X18N2001({ path: candidate.path })
2109
2121
  };
2110
2122
  try {
2111
2123
  const parsed = format.parse(content, ctx);
2112
2124
  let tree = parsed.tree;
2113
2125
  for (const transform of transforms) if (transform.applies?.(ctx) !== false) tree = transform.read(tree, ctx);
2114
- if (!isMessageTree(tree)) throw new X18nError("X18N2003", candidate.path);
2126
+ if (!isMessageTree(tree)) throw errors.X18N2003({ path: candidate.path });
2115
2127
  return {
2116
2128
  ...base,
2117
2129
  tree,
@@ -2120,7 +2132,11 @@ function createProject(options) {
2120
2132
  error: void 0
2121
2133
  };
2122
2134
  } catch (error) {
2123
- const coded = isX18nError(error) ? error : new X18nError("X18N2002", `${candidate.path}: ${error instanceof Error ? error.message : String(error)}`);
2135
+ const coded = isX18nError(error) ? error : errors.X18N2002({
2136
+ path: candidate.path,
2137
+ reason: error instanceof Error ? error.message : String(error),
2138
+ cause: error
2139
+ });
2124
2140
  return {
2125
2141
  ...base,
2126
2142
  tree: {},
@@ -2249,7 +2265,11 @@ function createProject(options) {
2249
2265
  try {
2250
2266
  store(file, await scanOne(file));
2251
2267
  } catch (error) {
2252
- store(file, void 0, new X18nError("X18N3001", `${file.path}: ${error instanceof Error ? error.message : String(error)}`));
2268
+ store(file, void 0, errors.X18N3001({
2269
+ path: file.path,
2270
+ reason: error instanceof Error ? error.message : String(error),
2271
+ cause: error
2272
+ }));
2253
2273
  }
2254
2274
  }));
2255
2275
  }
@@ -2554,7 +2574,7 @@ function createProject(options) {
2554
2574
  path
2555
2575
  }] : [])
2556
2576
  ].map(({ error, path }) => ({
2557
- ...error.toJSON(),
2577
+ ...toErrorInfo(error),
2558
2578
  path
2559
2579
  })),
2560
2580
  config
@@ -2934,7 +2954,7 @@ function validateLayers(layers) {
2934
2954
  for (const layer of layers) {
2935
2955
  if (!layer) continue;
2936
2956
  const result = validate(ConfigSchema, serializablePart(layer));
2937
- if (!result.success) throw new X18nError("X18N0001", result.issues.map((issue) => `${issue.path || "(root)"}: ${issue.message}`).join("; "));
2957
+ if (!result.success) throw errors.X18N0001({ issue: result.issues.map((issue) => `${issue.path || "(root)"}: ${issue.message}`).join("; ") });
2938
2958
  }
2939
2959
  }
2940
2960
  function createLoader(options) {
@@ -2944,7 +2964,7 @@ function createLoader(options) {
2944
2964
  const [specifier, pluginOptions] = typeof option === "string" ? [option, void 0] : [option[0], option[1]];
2945
2965
  const factory = options.builtins.presets[specifier];
2946
2966
  if (factory) return factory(pluginOptions);
2947
- if (!options.loadPlugin) throw new X18nError("X18N1003", specifier);
2967
+ if (!options.loadPlugin) throw errors.X18N1003({ preset: specifier });
2948
2968
  return options.loadPlugin(specifier, pluginOptions);
2949
2969
  };
2950
2970
  }
@@ -3043,4 +3063,4 @@ async function resolveConfig(options) {
3043
3063
  };
3044
3064
  }
3045
3065
  //#endregion
3046
- export { ConfigSchema, PluginFileSchema, X18nError, applyMessageOps, applyTextEdits, buildTree, contractVersion, createLineIndex, createMemoryHost, createOverlayHost, createPlaceholderComparer, createProject, createRegistry, defineConfig, definePlugin, definePreset, diffText, envelope, errorCodes, extensionOf, globToRegExp, hashText, isJsonObject, isKeyUnder, isMessageLeaf, isMessageTree, isPreset, isX18nError, joinKey, keyMatcher, localeSpellingKey, parseLocaleInfo, parseMessageBatch, pathId, pathMatcher, placeholderProblems, rangeContains, renderPrompt, replaceKeyPrefix, resolveConfig, sortTree, splitKey, toWireDiagnostic, toWireRange, validate, walkLeaves };
3066
+ export { ConfigSchema, PluginFileSchema, applyMessageOps, applyTextEdits, buildTree, contractVersion, createLineIndex, createMemoryHost, createOverlayHost, createPlaceholderComparer, createProject, createRegistry, defineConfig, definePlugin, definePreset, diffText, envelope, errors, extensionOf, globToRegExp, hashText, isJsonObject, isKeyUnder, isMessageLeaf, isMessageTree, isPreset, isX18nError, joinKey, keyMatcher, localeSpellingKey, parseLocaleInfo, parseMessageBatch, pathId, pathMatcher, placeholderProblems, rangeContains, renderPrompt, replaceKeyPrefix, resolveConfig, sortTree, splitKey, toErrorInfo, toWireDiagnostic, toWireRange, validate, walkLeaves };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@x18n/core",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "description": "Runtime-neutral engine of x18n: domain model, plugin API and project analysis",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "schema"
26
26
  ],
27
27
  "dependencies": {
28
+ "nostics": "^1.3.0",
28
29
  "valibot": "^1.5.0"
29
30
  },
30
31
  "devDependencies": {