@yuhere/js-jsonrpc2 2.1.9 → 2.1.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
@@ -105,7 +105,8 @@ s.string() // string
105
105
  s.string().min(1).max(64) // length-constrained string
106
106
  s.number() // finite number
107
107
  s.boolean() // boolean
108
- s.literal('2.0') // exact literal
108
+ s.literal('2.0') // exact literal (string | number | boolean only)
109
+ s.null() // exactly null
109
110
  s.array(s.string()) // string[]
110
111
  s.object({ id: s.number() }) // open object
111
112
  s.object({ id: s.number() }).strict() // all props required, no extras
@@ -113,7 +114,7 @@ s.record(s.number()) // Record<string, number>
113
114
  s.union([s.string(), s.number()])
114
115
  s.tuple([s.string(), s.number()])
115
116
  s.enum(['pending', 'success', 'failed'])
116
- s.unknown() // any JSON value
117
+ s.unknown() // any JSON value (already includes null)
117
118
  s.void() // RPC-only: no return value
118
119
  s.injectable('request') // RPC-only: server-injected parameter
119
120
  ```
@@ -123,6 +124,13 @@ Chainable modifiers: `.min()`, `.max()`, `.nullable()`, `.strict()`,
123
124
  `SchemaValidationError`) and `.safeParse(input)` (returns a result object).
124
125
  Introspection: `.toJSON()` returns a `SchemaDescription`.
125
126
 
127
+ Nullability notes:
128
+
129
+ - `.nullable()` / `.nullable(true)` allow `null`; `.nullable(false)` clears it. All calls are immutable.
130
+ - `s.unknown()` and `s.null()` do not expose `.nullable()`; they already accept / are `null`.
131
+ - `s.literal(v)` accepts `string | number | boolean`. Use `s.null()` for exact null, or `.nullable()` for "literal or null".
132
+ - `s.union([...])` strips member-level nullability; only the union's own `.nullable()` decides whether `null` is accepted.
133
+
126
134
  ## Transports
127
135
 
128
136
  ### Express
package/SKILL.md CHANGED
@@ -27,10 +27,16 @@ Hard constraints (by design, do not work around):
27
27
  5. Write `handler` so its positional args match the non-injectable slots in order;
28
28
  injectable slots are filled at their declared positions.
29
29
  6. Use `s.*` factories for schemas: `s.string()`, `s.number()`, `s.boolean()`,
30
- `s.literal(v)`, `s.array(item)`, `s.object(shape)`, `s.record(v)`,
31
- `s.union([...])`, `s.tuple([...])`, `s.enum([...])`, `s.unknown()`.
32
- 7. Chain modifiers before registering: `.min()`, `.max()`, `.nullable()`,
30
+ `s.literal(v)` (`v` is `string | number | boolean` only), `s.null()`,
31
+ `s.array(item)`, `s.object(shape)`, `s.record(v)`, `s.union([...])`,
32
+ `s.tuple([...])`, `s.enum([...])`, `s.unknown()`.
33
+ 7. Chain modifiers before registering: `.min()`, `.max()`, `.nullable(bool)`,
33
34
  `.strict()`, `.describe(text)`.
35
+ 8. Nullability rules:
36
+ - `.nullable()` / `.nullable(true)` allow `null`; `.nullable(false)` clears it. Calls are immutable.
37
+ - `s.unknown()` and `s.null()` already handle `null`; they do not expose `.nullable()`.
38
+ - `s.literal(v)` rejects `null`; use `s.null()` for exact null, `.nullable()` for "literal or null".
39
+ - `s.union([...])` strips member-level nullability at construction; use `s.union([...]).nullable()` to accept `null`.
34
40
 
35
41
  ```ts
36
42
  import { createRepository, s } from '@yuhere/js-jsonrpc2';
@@ -235,3 +241,15 @@ so `system.methodHelp` returns useful text.
235
241
  - **Treating a terminal `error` frame as a broken connection.** A mid-stream
236
242
  throw is delivered as `{ done: true, error }`; the HTTP/WS/stdio response
237
243
  itself ends cleanly. Only transport-level failures abort the connection.
244
+ - **Calling `.nullable()` on `s.unknown()` or `s.null()`.** Those schemas do not
245
+ extend `NullableSchema`; the call is a TypeScript error. Use `.nullable()` on
246
+ the concrete schema instead (e.g. `s.string().nullable()`), or `s.null()`
247
+ when the value must be exactly `null`.
248
+ - **Expecting member-level `.nullable()` inside a union to matter.**
249
+ `s.union([s.string().nullable(), s.number()])` is equivalent to
250
+ `s.union([s.string(), s.number()])` — members are cloned and set to
251
+ `nullable(false)` at construction. Put `.nullable()` on the union itself:
252
+ `s.union([s.string(), s.number()]).nullable()`.
253
+ - **Calling `s.literal(null)`.** `literal` only accepts `string | number |
254
+ boolean`; construction throws `TypeError`. Use `s.null()` for exact null, or
255
+ `.nullable()` for "literal or null".
@@ -25,6 +25,7 @@
25
25
  --syn-container: #7ee787;
26
26
  --syn-object: #79c0ff;
27
27
  --syn-union: #ffa657;
28
+ --syn-nullable: #ff79c6;
28
29
  --syn-muted: #8b949e;
29
30
 
30
31
  --mono: "JetBrains Mono", "Fira Code", Menlo, Consolas, "Courier New", monospace;
@@ -52,6 +53,7 @@
52
53
  --syn-container: #116329;
53
54
  --syn-object: #0969da;
54
55
  --syn-union: #bc4c00;
56
+ --syn-nullable: #a626a4;
55
57
  --syn-muted: #57606a;
56
58
  }
57
59
 
@@ -391,6 +393,15 @@ textarea:focus {
391
393
  color: var(--fg-dim);
392
394
  }
393
395
 
396
+ .schema-nullable-badge {
397
+ color: var(--syn-nullable);
398
+ font-size: 90%;
399
+ font-weight: bold;
400
+ letter-spacing: .5px;
401
+ text-transform: lowercase;
402
+ opacity: .9;
403
+ }
404
+
394
405
  .schema-constraints {
395
406
  color: var(--syn-muted);
396
407
  font-size: 90%;
@@ -458,6 +469,15 @@ textarea:focus {
458
469
  color: var(--fg-dim);
459
470
  }
460
471
 
472
+ .sf-nullable-badge {
473
+ color: var(--syn-nullable);
474
+ font-size: 90%;
475
+ font-weight: bold;
476
+ letter-spacing: .5px;
477
+ text-transform: lowercase;
478
+ opacity: .9;
479
+ }
480
+
461
481
  .sf-desc {
462
482
  color: var(--fg-dim);
463
483
  font-style: italic;
@@ -739,4 +759,205 @@ textarea:focus {
739
759
 
740
760
  .elapsed-badge .hint {
741
761
  font-size: 11px;
762
+ }
763
+
764
+
765
+
766
+ /* ================================================================
767
+ * Metrics 面板
768
+ * ================================================================ */
769
+ .metrics {
770
+ display: flex;
771
+ flex-direction: column;
772
+ gap: 16px;
773
+ }
774
+
775
+ .metrics-header {
776
+ display: flex;
777
+ align-items: center;
778
+ justify-content: space-between;
779
+ gap: 12px;
780
+ flex-wrap: wrap;
781
+ }
782
+
783
+ .metrics-header h2 {
784
+ margin: 0;
785
+ color: var(--accent);
786
+ font-size: 16px;
787
+ }
788
+
789
+ .metrics-controls {
790
+ display: flex;
791
+ align-items: center;
792
+ gap: 10px;
793
+ flex-wrap: wrap;
794
+ }
795
+
796
+ .metrics-auto {
797
+ display: inline-flex;
798
+ align-items: center;
799
+ gap: 4px;
800
+ font-size: 12px;
801
+ color: var(--fg-dim);
802
+ cursor: pointer;
803
+ user-select: none;
804
+ }
805
+
806
+ .metrics-cards {
807
+ display: grid;
808
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
809
+ gap: 10px;
810
+ }
811
+
812
+ .metric-card {
813
+ background: var(--bg-elev);
814
+ border: 1px solid var(--border);
815
+ border-radius: 4px;
816
+ padding: 10px 12px;
817
+ display: flex;
818
+ flex-direction: column;
819
+ gap: 4px;
820
+ }
821
+
822
+ .metric-card-label {
823
+ font-size: 11px;
824
+ color: var(--fg-dim);
825
+ text-transform: uppercase;
826
+ letter-spacing: 1px;
827
+ }
828
+
829
+ .metric-card-value {
830
+ font-size: 22px;
831
+ font-weight: bold;
832
+ color: var(--fg);
833
+ font-variant-numeric: tabular-nums;
834
+ }
835
+
836
+ .metric-card-danger .metric-card-value {
837
+ color: var(--danger);
838
+ }
839
+
840
+ .metric-card-warn .metric-card-value {
841
+ color: var(--syn-number);
842
+ }
843
+
844
+ .metrics-table {
845
+ width: 100%;
846
+ border-collapse: collapse;
847
+ font-size: 12px;
848
+ font-variant-numeric: tabular-nums;
849
+ }
850
+
851
+ .metrics-table th,
852
+ .metrics-table td {
853
+ padding: 6px 8px;
854
+ border-bottom: 1px solid var(--border);
855
+ text-align: left;
856
+ white-space: nowrap;
857
+ }
858
+
859
+ .metrics-table th {
860
+ color: var(--fg-dim);
861
+ font-weight: normal;
862
+ text-transform: uppercase;
863
+ letter-spacing: .5px;
864
+ font-size: 11px;
865
+ position: sticky;
866
+ top: 0;
867
+ background: var(--bg);
868
+ z-index: 1;
869
+ }
870
+
871
+ .metrics-table td.num,
872
+ .metrics-table th.num {
873
+ text-align: right;
874
+ }
875
+
876
+ .metrics-table tr:hover td {
877
+ background: var(--hover-bg);
878
+ }
879
+
880
+ .metrics-table .method-name {
881
+ color: var(--accent);
882
+ font-family: var(--mono);
883
+ max-width: 260px;
884
+ overflow: hidden;
885
+ text-overflow: ellipsis;
886
+ }
887
+
888
+ .metrics-table .error {
889
+ color: var(--danger);
890
+ }
891
+
892
+ .metrics-table .warn {
893
+ color: var(--syn-number);
894
+ }
895
+
896
+ .metrics-table .rate-hot {
897
+ color: var(--syn-boolean);
898
+ font-weight: bold;
899
+ }
900
+
901
+ .metrics-table .rate-warm {
902
+ color: var(--syn-number);
903
+ }
904
+
905
+ .metrics-table .rate-cold {
906
+ color: var(--fg-dim);
907
+ }
908
+
909
+ .metrics-table.compact th,
910
+ .metrics-table.compact td {
911
+ padding: 3px 6px;
912
+ font-size: 11px;
913
+ }
914
+
915
+ .view-switch {
916
+ margin-bottom: 8px;
917
+ width: 100%;
918
+ }
919
+
920
+ .view-switch button {
921
+ flex: 1;
922
+ }
923
+
924
+ /* ---- Metrics 侧边栏 ---- */
925
+ .metrics-sidebar {
926
+ display: flex;
927
+ flex-direction: column;
928
+ gap: 8px;
929
+ }
930
+
931
+ .metrics-filter {
932
+ display: flex;
933
+ flex-direction: column;
934
+ gap: 4px;
935
+ }
936
+
937
+ .metrics-filter-label {
938
+ font-size: 11px;
939
+ color: var(--fg-dim);
940
+ text-transform: uppercase;
941
+ letter-spacing: 1px;
942
+ }
943
+
944
+ .metrics-filter-help {
945
+ font-size: 11px;
946
+ line-height: 1.5;
947
+ margin: 0;
948
+ }
949
+
950
+ .metrics-filter-help code {
951
+ background: var(--bg-deep);
952
+ border: 1px solid var(--border);
953
+ border-radius: 3px;
954
+ padding: 0 4px;
955
+ font-size: 11px;
956
+ color: var(--syn-literal);
957
+ }
958
+
959
+ .metrics-filter-stat {
960
+ font-size: 11px;
961
+ margin: 4px 0 0;
962
+ color: var(--accent);
742
963
  }