sheetom 0.1.0-rc.10 → 0.1.0-rc.11

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/README.md CHANGED
@@ -119,6 +119,13 @@ sheet.serialize();
119
119
  | `style.cssText` | Browser-compatible declaration text, including measured recovery quirks. |
120
120
  | `rule.cssText` | Browser-compatible text for the current rule. |
121
121
  | `sheet.serialize()` | Deterministic, reparsable CSS for a complete stylesheet. |
122
+ | `sheet.serializeStrict()` | Exact-only output that rejects CSSOM states CSS text cannot represent faithfully. |
123
+
124
+ Pending-substitution shorthands survive later longhand edits in reparsable
125
+ output. If an imperative priority combination cannot be represented exactly,
126
+ `serialize()` emits a local best-effort result and an opt-in structured
127
+ diagnostic rather than blocking the whole stylesheet. Use `serializeStrict()`
128
+ when approximation is unacceptable.
122
129
 
123
130
  Invalid property values and priorities are atomic no-ops: the previous state
124
131
  survives unchanged. Static shorthands are stored as canonical longhands, so
package/dist/index.cjs CHANGED
@@ -28,9 +28,9 @@ let node_url = require("node:url");
28
28
  //#endregion
29
29
  //#region src/internal/engine-binding.ts
30
30
  const expectedEngineAbiIdentity = Object.freeze({
31
- abiVersion: 2,
32
- sheetomVersion: "0.1.0-rc.10",
33
- syntaxEngineSetSha256: "97cf2484af7f77edc3810d68f317136dc4d4eee78312a7f9726b9d0acc694351"
31
+ abiVersion: 3,
32
+ sheetomVersion: "0.1.0-rc.11",
33
+ syntaxEngineSetSha256: "8c274f05005d475082dbc0ec0c3b6056cb78043f008ed4c813f49fc5a8a2b250"
34
34
  });
35
35
  var SheetOMEngineBindingError = class extends Error {
36
36
  code;
@@ -114,16 +114,47 @@ function limitValue(value, name, fallback, maximum = maximumUnsignedLong) {
114
114
  throw new RangeError(`${name} must be an integer between 0 and ${maximum}`);
115
115
  }
116
116
  //#endregion
117
+ //#region src/serialization-error.ts
118
+ /** A reparsable stylesheet cannot faithfully represent the current CSSOM state. */
119
+ var SheetOMSerializationError = class extends Error {
120
+ code;
121
+ shorthand;
122
+ conflictingLonghands;
123
+ constructor(shorthand, conflictingLonghands, options) {
124
+ super(`SheetOM cannot serialize the pending ${shorthand} shorthand without changing its semantics.`, options);
125
+ this.name = "SheetOMSerializationError";
126
+ this.code = "UNREPRESENTABLE_PENDING_SHORTHAND";
127
+ this.shorthand = shorthand;
128
+ this.conflictingLonghands = Object.freeze([...conflictingLonghands]);
129
+ }
130
+ };
131
+ const unrepresentablePendingShorthandPattern = /^SHEETOM_UNREPRESENTABLE_PENDING_SHORTHAND: shorthand=([^;]+); conflicting=([a-z0-9,-]+)$/u;
132
+ function serializationErrorFrom(error) {
133
+ if (!(error instanceof Error)) return null;
134
+ const match = unrepresentablePendingShorthandPattern.exec(error.message);
135
+ if (!match) return null;
136
+ const shorthand = match[1];
137
+ const conflicts = match[2];
138
+ if (!shorthand || !conflicts) return null;
139
+ return new SheetOMSerializationError(shorthand, conflicts.split(","), { cause: error });
140
+ }
141
+ function rethrowSerializationError(error) {
142
+ const serializationError = serializationErrorFrom(error);
143
+ if (serializationError) throw serializationError;
144
+ }
145
+ //#endregion
117
146
  //#region src/internal/native-declaration-block.ts
118
147
  /** Thin JS ownership boundary around the Rust declaration state machine. */
119
148
  var NativeDeclarationBlock = class {
120
149
  #state;
121
150
  #reportDiagnostic;
151
+ #reportSerializationDiagnostic;
122
152
  #reservedNestingDepth;
123
153
  #observableCache;
124
154
  #serializationCache = /* @__PURE__ */ new Map();
125
- constructor(reportDiagnostic, context = "style", resourceBudget = defaultResourceBudget, reservedNestingDepth = () => 0) {
155
+ constructor(reportDiagnostic, reportSerializationDiagnostic, context = "style", resourceBudget = defaultResourceBudget, reservedNestingDepth = () => 0) {
126
156
  this.#reportDiagnostic = reportDiagnostic;
157
+ this.#reportSerializationDiagnostic = reportSerializationDiagnostic;
127
158
  this.#reservedNestingDepth = reservedNestingDepth;
128
159
  this.#state = engineBinding.createDeclarationState(context, ...nativeBudgetArguments(resourceBudget));
129
160
  }
@@ -223,13 +254,27 @@ var NativeDeclarationBlock = class {
223
254
  }
224
255
  this.#invalidateSerialization();
225
256
  }
226
- serialize(safe, indent, separator) {
227
- const cacheKey = `${safe ? "1" : "0"}\0${indent}\0${separator}`;
257
+ serialize(safe, indent, separator, strict = false) {
258
+ const cacheKey = `${safe ? "1" : "0"}${strict ? "1" : "0"}\0${indent}\0${separator}`;
228
259
  const cached = this.#serializationCache.get(cacheKey);
229
- if (cached !== void 0) return cached;
230
- const serialized = this.#state.serializeFormatted(safe, indent, separator);
231
- this.#serializationCache.set(cacheKey, serialized);
232
- return serialized;
260
+ if (cached !== void 0) {
261
+ if (typeof cached === "string") return cached;
262
+ this.#reportSerializationIssues(cached.issues);
263
+ return cached.serialized;
264
+ }
265
+ let result;
266
+ try {
267
+ if (strict) result = this.#state.serializeFormatted(safe, indent, separator);
268
+ else result = parseResilientSerializationResult(this.#state.serializeFormattedResilient(safe, indent, separator));
269
+ } catch (error) {
270
+ rethrowResourceBudgetError(error);
271
+ rethrowSerializationError(error);
272
+ throw error;
273
+ }
274
+ this.#serializationCache.set(cacheKey, result);
275
+ if (typeof result === "string") return result;
276
+ this.#reportSerializationIssues(result.issues);
277
+ return result.serialized;
233
278
  }
234
279
  #invalidateSerialization() {
235
280
  this.#observableCache = void 0;
@@ -240,7 +285,29 @@ var NativeDeclarationBlock = class {
240
285
  const [code, input] = outcome === "invalid-priority" ? ["INVALID_PRIORITY", priority] : outcome === "unsupported-shorthand" ? ["UNSUPPORTED_SHORTHAND_VALUE", value] : ["INVALID_PROPERTY_VALUE", value];
241
286
  return this.#reportDiagnostic(code, normalizedName, input);
242
287
  }
288
+ #reportSerializationIssues(issues) {
289
+ for (const issue of issues) this.#reportSerializationDiagnostic(issue.shorthand, issue.conflictingLonghands);
290
+ }
243
291
  };
292
+ function parseResilientSerializationResult(output) {
293
+ const serialized = output[0];
294
+ if (serialized === void 0 || output.length % 2 === 0) throw new Error("SheetOM engine returned an invalid resilient serialization result.");
295
+ if (output.length === 1) return serialized;
296
+ const issues = [];
297
+ for (let index = 1; index < output.length; index += 2) {
298
+ const shorthand = output[index];
299
+ const conflicts = output[index + 1];
300
+ if (!shorthand || !conflicts) throw new Error("SheetOM engine returned an incomplete resilient serialization issue.");
301
+ issues.push({
302
+ shorthand,
303
+ conflictingLonghands: Object.freeze(conflicts.split(","))
304
+ });
305
+ }
306
+ return {
307
+ serialized,
308
+ issues
309
+ };
310
+ }
244
311
  function isEngineMutationOutcome(value) {
245
312
  return value === "invalid-name" || value === "invalid-priority" || value === "invalid-value" || value === "unsupported-shorthand";
246
313
  }
@@ -562,7 +629,8 @@ const ruleTree = new RuleTree((rule, parentRule, parentStyleSheet) => {
562
629
  parentStyleSheet
563
630
  });
564
631
  }, (rule) => rule.parentStyleSheet);
565
- const safeSerializer = new Serializer(describeRuleSafe);
632
+ const safeSerializer = new Serializer((rule) => describeRuleSafe(rule, false));
633
+ const strictSerializer = new Serializer((rule) => describeRuleSafe(rule, true));
566
634
  let activeConstructionResourceBudget = defaultResourceBudget;
567
635
  function constructWithResourceBudget(factory, resourceBudget) {
568
636
  const previous = activeConstructionResourceBudget;
@@ -1227,7 +1295,20 @@ var CSSStyleDeclaration = class {
1227
1295
  (ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)(diagnostic);
1228
1296
  return diagnostic;
1229
1297
  };
1230
- this.#block = new NativeDeclarationBlock(reportDeclarationDiagnostic, parentRule instanceof CSSFontFaceRule ? "font-face" : parentRule instanceof CSSFunctionDeclarations ? "function" : "style", ruleResourceBudgets.get(parentRule) ?? defaultResourceBudget, () => declarationSerializationDepth(parentRule));
1298
+ const reportSerializationDiagnostic = (shorthand, conflictingLonghands) => {
1299
+ const diagnostic = {
1300
+ code: "UNREPRESENTABLE_PENDING_SHORTHAND",
1301
+ severity: "warning",
1302
+ operation: "serialize",
1303
+ message: `The pending ${shorthand} shorthand required a best-effort recovery during serialization.`,
1304
+ property: shorthand,
1305
+ input: "",
1306
+ location: null,
1307
+ conflictingLonghands: Object.freeze([...conflictingLonghands])
1308
+ };
1309
+ (ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)(diagnostic);
1310
+ };
1311
+ this.#block = new NativeDeclarationBlock(reportDeclarationDiagnostic, reportSerializationDiagnostic, parentRule instanceof CSSFontFaceRule ? "font-face" : parentRule instanceof CSSFunctionDeclarations ? "function" : "style", ruleResourceBudgets.get(parentRule) ?? defaultResourceBudget, () => declarationSerializationDepth(parentRule));
1231
1312
  return new Proxy(this, {
1232
1313
  get(target, property) {
1233
1314
  if (typeof property === "string" && arrayIndexPattern.test(property)) return target.item(Number(property)) || void 0;
@@ -1305,8 +1386,8 @@ var CSSStyleDeclaration = class {
1305
1386
  return this.#block.removeProperty(`${name}`);
1306
1387
  }
1307
1388
  /** @internal */
1308
- serializeSafe(indent) {
1309
- return this.#block.serialize(true, indent, "\n");
1389
+ serializeSafe(indent, strict = false) {
1390
+ return this.#block.serialize(true, indent, "\n", strict);
1310
1391
  }
1311
1392
  };
1312
1393
  /** A live `@font-face` descriptor rule. */
@@ -1795,23 +1876,23 @@ function replaceGroupingRules(group, rules) {
1795
1876
  function attachRuleTree(rule, parentRule, parentStyleSheet) {
1796
1877
  ruleTree.attach(rule, parentRule, parentStyleSheet);
1797
1878
  }
1798
- function describeRuleSafe(rule) {
1879
+ function describeRuleSafe(rule, strict) {
1799
1880
  if (rule instanceof CSSStyleRule) return {
1800
1881
  kind: "block",
1801
1882
  header: rule.selectorText,
1802
- declarations: rule.style.serializeSafe(" "),
1883
+ declarations: rule.style.serializeSafe(" ", strict),
1803
1884
  children: ruleTree.children(rule)
1804
1885
  };
1805
1886
  if (rule instanceof CSSNestedDeclarations || rule instanceof CSSFunctionDeclarations) return {
1806
1887
  kind: "declarations",
1807
- declarations: rule.style.serializeSafe("")
1888
+ declarations: rule.style.serializeSafe("", strict)
1808
1889
  };
1809
1890
  if (rule instanceof CSSFontFaceRule || rule instanceof CSSMarginRule || rule instanceof CSSPositionTryRule || rule instanceof CSSKeyframeRule) {
1810
1891
  const blockIndex = rule.cssText.indexOf("{");
1811
1892
  return {
1812
1893
  kind: "block",
1813
1894
  header: rule.cssText.slice(0, blockIndex).trimEnd(),
1814
- declarations: rule.style.serializeSafe(" ")
1895
+ declarations: rule.style.serializeSafe(" ", strict)
1815
1896
  };
1816
1897
  }
1817
1898
  if (rule instanceof CSSKeyframesRule) return {
@@ -1822,7 +1903,7 @@ function describeRuleSafe(rule) {
1822
1903
  if (rule instanceof CSSPageRule) return {
1823
1904
  kind: "block",
1824
1905
  header: `@page${rule.selectorText === "" ? "" : ` ${rule.selectorText}`}`,
1825
- declarations: rule.style.serializeSafe(" "),
1906
+ declarations: rule.style.serializeSafe(" ", strict),
1826
1907
  children: ruleTree.children(rule)
1827
1908
  };
1828
1909
  if (!(rule instanceof CSSGroupingRule)) return {
@@ -2219,7 +2300,11 @@ var CSSStyleSheet = class {
2219
2300
  serialize() {
2220
2301
  return safeSerializer.serializeMany(this.#rules);
2221
2302
  }
2222
- /** Drain opt-in mutation diagnostics without affecting CSSOM behavior. */
2303
+ /** Serialize current state only when its CSS semantics can be preserved exactly. */
2304
+ serializeStrict() {
2305
+ return strictSerializer.serializeMany(this.#rules);
2306
+ }
2307
+ /** Drain opt-in mutation and serialization diagnostics without affecting CSSOM behavior. */
2223
2308
  takeDiagnostics() {
2224
2309
  if (!this.#diagnostics) return [];
2225
2310
  return this.#diagnostics.splice(0);
@@ -2270,6 +2355,7 @@ exports.CSSStyleSheet = CSSStyleSheet;
2270
2355
  exports.CSSSupportsRule = CSSSupportsRule;
2271
2356
  exports.CSSViewTransitionRule = CSSViewTransitionRule;
2272
2357
  exports.MediaList = MediaList;
2358
+ exports.SheetOMSerializationError = SheetOMSerializationError;
2273
2359
  exports.parseStyleSheet = parseStyleSheet;
2274
2360
 
2275
2361
  //# sourceMappingURL=index.cjs.map