fix: one ** per match pattern and one * per segment (#3736)

`compileMatch` turned every `*` into an unbounded `[^/]*`, so a subject
like `fs:~/x/*a*a*a*a*a*a*a*a*a*a*z` tested against a 24-char filename
with no `z` — both named by the same subscriber — cost the regex engine
C(34,10) splits before failing: 1.0 s per event at ten stars, 3.7 s at
eleven, on the fs-write dispatch path, inside the 256-character cap.

With one `*` per segment the delimiters pin each star and a wrong split
dies in one step; the single `**` is the only choice point left, so the
worst allowed shape is O(depth × length): 0.4 ms at depth 60 of 200-char
segments. Every documented pattern (`*.png`, `**/build.log`, `**/*.png`,
`dir/**`, `report-?.csv`) stays valid; `*a*`, `a**b` and `**/x/**` are
refused with `invalid_subject_pattern`. The rule is stated where the
syntax is introduced, on the limits page, and in onLocal's error table.
This commit is contained in:
Daniel Salazar
2026-09-03 15:08:49 -07:00
committed by GitHub
parent 1b777cd760
commit 32a838d2e2
5 changed files with 65 additions and 14 deletions
+2
View File
@@ -38,6 +38,8 @@ await puter.events.onLocal('fs:~/Pictures/*.png', handler); // one segme
await puter.events.onLocal('fs:~/Projects/**/build.log', handler); // across directories
```
`*` matches within one path segment, `**` crosses directories, and `?` matches one character. A subject may use `*` once per segment and `**` once in total; anything more is rejected with `invalid_subject_pattern`.
`fs:` and `kv:` subjects can be subscribed to; `notif:` is reserved and still rejected.
### Key-value subjects
+1 -1
View File
@@ -50,7 +50,7 @@ The promise rejects with `{ message, code }`:
| `invalid_subject` | The subject is not a non-empty string, or the server could not parse it. |
| `invalid_handler` | `handler` is not a function. |
| `invalid_subject_op` | The `:op` suffix is not one of the five operations. |
| `invalid_subject_pattern` | The match pattern is past the compile-cost bounds (256 characters, 16 segments). |
| `invalid_subject_pattern` | The match pattern is past its bounds: 256 characters, 16 segments, one `*` per segment, one `**` in total. |
| `invalid_kv_pattern` | A `kv:` subject has a `*` somewhere other than the end, or a `?`. |
| `events_cross_app_disabled` | The subject names another app's key-value data and that is not enabled here. |
| `forbidden` | The target app does not share its data, or this app has not been granted `app-data:<appId>:kv:read` on it. |
+1 -1
View File
@@ -213,7 +213,7 @@ A suspended subscription stops delivering and stops being metered, so it cannot
Deleting the node a subscription is anchored on ends it too, unless the subject named a path or a pattern, in which case it follows that path up to the nearest folder that still exists and keeps watching, so recreating the path resumes delivery.
Match patterns are compiled once when you subscribe and are capped at **256 characters** and **16 segments**; anything larger is rejected with `invalid_subject_pattern`. `**` crosses directories and costs no more than `*`.
Match patterns are compiled once when you subscribe and are capped at **256 characters** and **16 segments**, with **one `*` per segment** and **one `**` per pattern**; anything past that is rejected with `invalid_subject_pattern`. `**` crosses directories and costs no more than `*`.
A `kv:` subject is indexed on the first **6** `:`-segments, or **160 bytes**, of its key — whichever comes first; past that the remainder becomes a match pattern, which is subject to the caps above. A key-value subject matches its key exactly unless it ends in `*`, and a `*` anywhere else — or a `?` — is rejected with `invalid_kv_pattern`. Watching another app's key-value data is refused with `events_cross_app_disabled` where that is not enabled, and otherwise takes the same consent as reading it.