sheetom 0.1.0-rc.1 → 0.1.0-rc.10

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
@@ -1,37 +1,36 @@
1
1
  # SheetOM
2
2
 
3
- SheetOM is a mutable, browser-shaped CSS authoring object model for Node.js,
4
- Bun, and Deno. It uses Lightning CSS for property parsing and canonicalization
5
- while keeping declaration state and CSSOM serialization under its own control.
3
+ **A browser-compatible, mutable CSSOM for JavaScript outside the browser.**
6
4
 
7
- It is intended for server-side stylesheet editing and serialization. It does
8
- not implement the DOM, cascade, selector matching, layout, or computed styles.
5
+ SheetOM lets build tools, publishers, and server applications parse, inspect,
6
+ mutate, and serialize stylesheets with familiar browser APIs. It preserves
7
+ browser recovery behavior for malformed-but-accepted CSS while still producing
8
+ deterministic, reparsable stylesheet output.
9
+
10
+ - Browser-shaped `CSSStyleSheet`, rules, and declarations
11
+ - Transactional `setProperty()` behavior and correct shorthand/longhand state
12
+ - Forgiving whole-sheet parsing without a DOM or global installation
13
+ - One repository-owned Rust engine for native and WebAssembly backends
14
+ - ESM, CommonJS, TypeScript, Node.js, Bun, Deno, browsers, and workers
15
+
16
+ SheetOM focuses on the **Authoring CSSOM**. It does not implement the cascade,
17
+ selector matching, layout, computed styles, fetching, or DOM attachment.
9
18
 
10
19
  ## Install
11
20
 
12
- Install the current release candidate from the `next` dist-tag:
21
+ For Node.js, Bun, or Deno:
13
22
 
14
23
  ```sh
15
- npm install sheetom@next
24
+ npm install sheetom
16
25
  ```
17
26
 
18
- `lightningcss`, `css-tree`, `cssstyle`, and the CSS Tools tokenizer are regular
19
- dependencies; consumers do not need to install peer dependencies or globals.
20
- SheetOM validates values before using `cssstyle` only as a static shorthand
21
- expander, and uses the tokenizer for lossless recovered-rule spans.
22
-
23
- Node.js 22 and 24 are tested on Linux x64, Windows x64, and macOS arm64. Bun
24
- 1.3.1 and Deno 2.9.5 are tested on Linux x64. Deno must use a local
25
- `node_modules` directory and grant the native Lightning CSS binding FFI and
26
- system-information permissions:
27
+ For browsers and compatible JavaScript isolates:
27
28
 
28
29
  ```sh
29
- deno run --node-modules-dir=manual --allow-ffi --allow-sys app.ts
30
+ npm install @sheetom/wasm
30
31
  ```
31
32
 
32
- SheetOM exports browser-named classes but never modifies `globalThis`.
33
-
34
- ## Use
33
+ ## Quick start
35
34
 
36
35
  ```ts
37
36
  import { CSSStyleRule, CSSStyleSheet, parseStyleSheet } from "sheetom";
@@ -41,34 +40,68 @@ sheet.replaceSync(".card { width: 12px; }");
41
40
 
42
41
  const rule = sheet.cssRules[0];
43
42
  if (rule instanceof CSSStyleRule) {
44
- rule.style.setProperty("padding", "8px 16px");
45
- Reflect.set(rule.style, "backgroundColor", "rebeccapurple");
43
+ rule.style.padding = "8px 16px";
44
+ rule.style.backgroundColor = "rebeccapurple";
45
+ rule.style.setProperty("--card-gap", "12px");
46
46
  }
47
47
 
48
48
  console.log(sheet.serialize());
49
49
 
50
- const existing = parseStyleSheet('@import "theme.css"; .card { color: red; }', {
51
- href: "https://example.com/assets/app.css",
52
- });
50
+ const existing = parseStyleSheet(
51
+ '@import "theme.css"; .card { color: red; }',
52
+ { href: "https://example.com/assets/app.css" },
53
+ );
54
+
53
55
  console.log(existing.cssRules.length); // 2
54
56
  ```
55
57
 
56
- Constructed sheets follow browser replacement behavior and strip `@import`.
57
- `parseStyleSheet` creates a regular authoring sheet and preserves valid imports
58
- without loading them.
58
+ Named CSS properties are typed and assignable directly. Use `setProperty()`
59
+ when a property name is dynamic, custom, or hyphenated. `CSSStyleSheet` is the
60
+ only public CSSOM constructor; rules and declarations come from a sheet, just
61
+ as they do in browsers.
59
62
 
60
- ## Recovered values and reparsable serialization
63
+ Constructed sheets strip `@import` during replacement. `parseStyleSheet()` is a
64
+ SheetOM extension for existing regular stylesheets and preserves valid imports
65
+ without fetching them.
61
66
 
62
- SheetOM distinguishes browser-facing CSSOM text from reparsable stylesheet
63
- output. Chromium
64
- accepts some values using CSS Syntax end-of-input recovery while preserving the
65
- unclosed input in `getPropertyValue()` and `cssText`:
67
+ Compilers can apply all known mutations for one rule with one engine crossing
68
+ and still receive one acceptance result per operation:
69
+
70
+ ```ts
71
+ function applyCompilerDeclarations(rule: CSSStyleRule, dynamicWidth: string) {
72
+ const results = rule.style.applyMutations([
73
+ { kind: "set", property: "display", value: "grid" },
74
+ { kind: "set", property: "width", value: dynamicWidth },
75
+ { kind: "remove", property: "margin-left" },
76
+ ]);
77
+
78
+ for (const result of results) {
79
+ if (result.kind === "set" && !result.accepted) {
80
+ console.warn(result.diagnostic);
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ Operations run in order through the same validation and shorthand/longhand
87
+ state machine as individual calls. Group one batch per declaration block;
88
+ never combine values into declaration text. Batch results already include
89
+ rejection diagnostics, so high-volume callers can leave the sheet diagnostic
90
+ queue disabled unless they also need diagnostics from other APIs.
91
+
92
+ ## Browser recovery, safe output
93
+
94
+ Browsers accept some CSS using end-of-input recovery and expose the incomplete
95
+ spelling through CSSOM. SheetOM keeps that observable behavior separate from
96
+ whole-sheet output:
66
97
 
67
98
  ```ts
68
99
  const sheet = new CSSStyleSheet();
69
100
  sheet.insertRule(".card {}");
70
101
 
71
- const rule = sheet.cssRules[0] as CSSStyleRule;
102
+ const rule = sheet.cssRules[0];
103
+ if (!(rule instanceof CSSStyleRule)) throw new TypeError("Expected a style rule");
104
+
72
105
  rule.style.setProperty("padding", "72px var(--space, var(--space,");
73
106
 
74
107
  rule.style.getPropertyValue("padding");
@@ -78,89 +111,105 @@ rule.style.cssText;
78
111
  // "padding: 72px var(--space, var(--space,;"
79
112
 
80
113
  sheet.serialize();
81
- // reparsable CSS with the missing syntax repaired
114
+ // reparsable CSS with the missing syntax safely confined and repaired
82
115
  ```
83
116
 
84
- The three text surfaces have distinct contracts:
85
-
86
- | Surface | Contract |
117
+ | Surface | Purpose |
87
118
  | --- | --- |
88
- | `rule.style.cssText` | Browser-compatible observable declaration text, including accepted recovery quirks. |
89
- | `rule.cssText` | Browser-compatible observable text for one rule and its current descendants. |
90
- | `sheet.serialize()` | Deterministic, reparsable stylesheet output with recoverable syntax confined to its declaration or rule. |
91
-
92
- Reparsable serialization means that the output can be parsed again without one
93
- declaration leaking into the next declaration or rule, while preserving the
94
- valid semantic state owned by SheetOM. It does not sanitize URLs, enforce CSP,
95
- block remote resources, or make valid hostile CSS safe to render.
96
-
97
- Invalid `setProperty` values and priorities are atomic no-ops, matching browser
98
- behavior. Opt into mutation diagnostics with `{ diagnostics: true }` and drain
99
- them using `takeDiagnostics()`.
100
-
101
- ## API scope
102
-
103
- - `CSSStyleSheet`, live `CSSRuleList`, `CSSRule`, and `MediaList`
104
- - nested `CSSStyleRule`, `CSSGroupingRule`, and `CSSConditionRule`
105
- - media, supports, container, layer, scope, and starting-style rules
106
- - import, font-face, page/margin, nested-declaration, and position-try rules
107
- - keyframes and mutable keyframe rules
108
- - counter-style descriptors and font-feature-value maps
109
- - live indexed and named `CSSStyleDeclaration`
110
- - `insertRule`, `deleteRule`, `replace`, and `replaceSync`
111
- - `parseStyleSheet` for forgiving regular-sheet parsing
112
- - browser-facing `cssText` and reparsable `serialize()` output
113
- - generic retention of read-only metadata rules and experimental/future rules
114
-
115
- SheetOM targets standards and shared browser behavior first, with Chromium
116
- behavior used only as the final measured divergence fallback. Versioned JSON
117
- Operation Fixtures execute through SheetOM and native browser adapters;
118
- applicable WPT subtests retain their pinned source path, title, and blob SHA.
119
- Every release ships its machine-readable Compatibility Report.
120
-
121
- See [the behavioral API reference](./docs/api.md) for return, exception,
122
- identity, and detachment contracts. Maintainers use the separately reviewed
123
- [release procedure](./docs/releasing.md); Changesets never publishes by itself.
124
-
125
- The checked-in ordinary-property manifest was generated against Chromium
126
- 151.0.7922.34. Run `npm run generate:properties` only when intentionally
127
- advancing the Compatibility Baseline.
128
-
129
- ## Security and resource boundaries
130
-
131
- SheetOM does not fetch `@import` targets or URLs and does not execute CSS. It is
132
- also not a sanitizer: valid authored URLs, imports, and browser features remain
133
- in serialized output. Callers must sanitize untrusted output before attaching
134
- it to a document or another rendering environment.
135
-
136
- The browser-shaped interface has no implicit input-size, nesting-depth, or
137
- mutation-count limits. Isolate or bound untrusted workloads according to your
138
- application's resource policy. See [SECURITY.md](./SECURITY.md) for private
139
- vulnerability reporting and supported-release policy.
140
-
141
- ## Development
119
+ | `style.cssText` | Browser-compatible declaration text, including measured recovery quirks. |
120
+ | `rule.cssText` | Browser-compatible text for the current rule. |
121
+ | `sheet.serialize()` | Deterministic, reparsable CSS for a complete stylesheet. |
122
+
123
+ Invalid property values and priorities are atomic no-ops: the previous state
124
+ survives unchanged. Static shorthands are stored as canonical longhands, so
125
+ editing and then removing one longhand cannot accidentally reactivate an old
126
+ shorthand value.
127
+
128
+ Reparsable output prevents malformed input from leaking into a following
129
+ declaration or rule. It is not a CSS sanitizer, URL policy, CSP, or rendering
130
+ sandbox.
131
+
132
+ ## Native and WebAssembly runtimes
133
+
134
+ The `sheetom` package contains the JavaScript facade and selects one exact
135
+ `@sheetom/native-*` optional package for the current platform. It installs no
136
+ JavaScript parser dependency, downloads no binary at install time, and never
137
+ falls back to a different engine. Omitting optional dependencies is unsupported
138
+ and fails explicitly.
139
+
140
+ | Runtime | Entry point | Notes |
141
+ | --- | --- | --- |
142
+ | Node.js 22+ | `sheetom` | ESM and CommonJS; prebuilt native packages for macOS, Windows, GNU Linux, and musl Linux targets. |
143
+ | Bun | `sheetom` | Tested through the npm package on Linux x64. |
144
+ | Deno | `sheetom` | Uses npm resolution and requires native FFI/system permissions. |
145
+ | Browsers and workers | `@sheetom/wasm` | ESM-only asynchronous factory; no WASI or global installation. |
146
+
147
+ Deno example:
142
148
 
143
149
  ```sh
144
- npm test
145
- npm run typecheck
146
- npm run build
147
- npm run conformance:validate
148
- npm run conformance:drift
149
- npm run fuzz
150
- npm run benchmark
151
- npm run pack:test
150
+ deno run --node-modules-dir=manual --allow-ffi --allow-sys app.ts
152
151
  ```
153
152
 
154
- `npm test` runs unit, deterministic fuzz, and local Chromium projects. Use
155
- `npm run test:browser:matrix` on a machine with all Playwright dependencies to
156
- run Chromium, Firefox, and WebKit. `npm run docs:build` generates the TypeDoc
157
- reference under `site/api`.
153
+ WebAssembly example:
154
+
155
+ ```ts
156
+ import { createSheetOM } from "@sheetom/wasm";
157
+
158
+ const { CSSStyleSheet } = await createSheetOM();
159
+ const sheet = new CSSStyleSheet();
160
+ sheet.replaceSync(".card { color: red; }");
161
+ ```
162
+
163
+ The native and WebAssembly packages execute the same Rust engine and verify an
164
+ exact engine ABI identity before exposing CSSOM objects. `createSheetOM()`
165
+ shares default initialization within one JavaScript realm; passing an explicit
166
+ `URL`, `Response`, `ArrayBuffer`, or `WebAssembly.Module` creates an isolated
167
+ facade with its own class identity.
168
+
169
+ ## Compatibility contract
170
+
171
+ SheetOM tests complete observable state—not only whether serialized CSS looks
172
+ similar. Its versioned evidence covers getters, `cssText`, indexed property
173
+ order, priorities, invalid-mutation atomicity, live identity, shorthand
174
+ sequences, recovery, native-browser reparsing, subprocess crash safety,
175
+ grammar-oriented fuzzing, and Publisher-shaped performance workloads.
176
+
177
+ Standards and shared browser behavior take precedence. When browser engines
178
+ genuinely differ, SheetOM records the result and follows the pinned Chromium
179
+ baseline as its final fallback. Browser observations and WPT mappings remain
180
+ test evidence; they are never imported as runtime authority.
181
+
182
+ See [Compatibility](./docs/compatibility.md) for the exact promise, evidence,
183
+ and exclusions.
184
+
185
+ ## Resource limits and diagnostics
186
+
187
+ Each sheet has configurable limits for stylesheet bytes, declaration-value
188
+ bytes, syntax depth, rule count, and declarations per block. A limit violation
189
+ throws `RangeError` before mutation. These budgets protect the host process;
190
+ they are not content policy.
191
+
192
+ Mutation diagnostics are opt-in:
193
+
194
+ ```ts
195
+ const sheet = new CSSStyleSheet({ diagnostics: true });
196
+ // ...mutate the sheet...
197
+ const diagnostics = sheet.takeDiagnostics();
198
+ ```
199
+
200
+ Diagnostics retain rejected inputs until drained. Leave them disabled or drain
201
+ them promptly when handling untrusted or unusually large source.
202
+
203
+ ## Documentation
158
204
 
159
- Only the latest published `0.x` minor and its active prereleases receive fixes
160
- before 1.0. During `0.x`, observable compatibility corrections and interface
161
- breaks require a new minor version and migration note; patches remain backward
162
- compatible.
205
+ - [API behavior](./docs/api.md)
206
+ - [Architecture](https://github.com/leo91000/sheetom/blob/main/docs/architecture.md)
207
+ - [Compatibility](https://github.com/leo91000/sheetom/blob/main/docs/compatibility.md)
208
+ - [Contributing](https://github.com/leo91000/sheetom/blob/main/CONTRIBUTING.md)
209
+ - [Release process](https://github.com/leo91000/sheetom/blob/main/docs/releasing.md)
210
+ - [Architecture decisions](https://github.com/leo91000/sheetom/blob/main/docs/adr/README.md)
211
+ - [Security policy](./SECURITY.md)
163
212
 
164
213
  ## License
165
214
 
166
- MIT
215
+ MIT. Vendored parser sources retain their upstream licenses and provenance.