ctx-core 2.3.0 → 2.4.0

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/all/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './Map__object/index.js'
5
5
  export * from './NumericRange/index.js'
6
6
  export * from './NumericUnit/index.js'
7
7
  export * from './PHI/index.js'
8
+ export * from './primitive/index.js'
8
9
  export * from './Request/index.js'
9
10
  export * from './Response/index.js'
10
11
  export * from './Timeout/index.js'
package/all/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './Map__object/index.js'
5
5
  export * from './NumericRange/index.js'
6
6
  export * from './NumericUnit/index.js'
7
7
  export * from './PHI/index.js'
8
+ export * from './primitive/index.js'
8
9
  export * from './Request/index.js'
9
10
  export * from './Response/index.js'
10
11
  export * from './Timeout/index.js'
package/all/noop/index.js CHANGED
@@ -2,5 +2,5 @@
2
2
  * Returns function that does nothing
3
3
  * @param {unknown}_
4
4
  */
5
- export function noop(..._) {
5
+ export function noop() {
6
6
  }
@@ -0,0 +1,2 @@
1
+ import type { nullish } from '../nullish/index.js'
2
+ export type primitive_T = boolean|number|string|nullish
@@ -0,0 +1 @@
1
+ export {}
@@ -1,2 +1,5 @@
1
1
  import type { nullish } from '../nullish/index.js'
2
+ import type { primitive_T } from '../primitive/index.js'
2
3
  export declare function prototype_(val:Exclude<any, nullish>):Object
4
+ export declare function prototype__set(obj:Exclude<any, primitive_T>, val:Object):Object
5
+ export declare const fn__proto:Object
@@ -1,8 +1,6 @@
1
- /**
2
- * @param {Object}val
3
- * @returns {Object}
4
- * @private
5
- */
6
- export function prototype_(val) {
7
- return Object.getPrototypeOf(val)
1
+ export const { getPrototypeOf, setPrototypeOf, defineProperty, defineProperties } = Object
2
+ export {
3
+ getPrototypeOf as prototype_,
4
+ setPrototypeOf as prototype__set,
8
5
  }
6
+ export const fn__proto = getPrototypeOf(getPrototypeOf)
@@ -1,8 +1,20 @@
1
- export declare function rememo_<val_T>(def:()=>val_T):rememo_T<val_T>
2
- export type rememo_T<val_T> =
3
- ((rememo:rememo_T<val>)=>val_T)
4
- &rememo_o_T<val_T>
1
+ export declare function rememo_<val_T>(
2
+ def:(rememo:rememo__T<val_T>)=>val_T,
3
+ ...subscriber_a:rememo_subscriber_T<val_T>[]
4
+ ):rememo_T<val_T>
5
+ export declare function signal_<val_T>(
6
+ init_val:val_T,
7
+ ...subscriber_a:rememo_subscriber_T<val_T>[]
8
+ ):rememo_T<val_T>
9
+ export type rememo__T<val_T> =
10
+ (def:rememo_def_T<val_T>, ...subscriber_a:rememo_subscriber_T<val_T>[])=>val_T
11
+ export type rememo_T<val_T> = ((val?:val_T)=>val_T)&rememo_o_T<val_T>
12
+ export type rememo_def_T<val_T> = (rememo:rememo__T<val>)=>val_T
13
+ export type rememo_subscriber_T<val_T> = (rememo:rememo_T<val_T>)=>unknown
5
14
  export type rememo_o_T<val_T> = {
6
- val:val_T
7
- listeners:Set<WeakRef<rememo_T<val_T>>>
15
+ _:val_T
16
+ readonly val:val_T
17
+ init():unknown
18
+ onset(val:val_T):unknown
19
+ refresh():val_T
8
20
  }
@@ -1,9 +1,82 @@
1
- /** @typedef {import('./index.d.ts').rememo_o_T} */
1
+ /** @typedef {import('./index.d.ts').rememo_T} */
2
+ /** @typedef {import('./index.d.ts').rememo_subscriber_T} */
3
+ /** @type {(()=>unknown)[]} */
4
+ let queue = []
5
+ /** @type {WeakRef<rememo_T>} */
6
+ let cur_ref
2
7
  /**
3
- * @param {(rememo:rememo_T<unknown>)=>unknown}fn
8
+ * @param {(rememo:rememo_T<unknown>)=>unknown}_f
9
+ * @param {rememo_subscriber_T<unknown>[]}subscriber_a
4
10
  * @returns {rememo_T}
5
11
  * @private
6
12
  */
7
- export function rememo_(fn) {
8
- // let
13
+ export function rememo_(_f, ...subscriber_a) {
14
+ let _a = []
15
+ let _rS = new Set
16
+ let rememo$ = (...arg_a)=>arg_a.length ? rememo$._ = arg_a[0] : rememo$._
17
+ rememo$._a = _a
18
+ rememo$._f = _f
19
+ rememo$._rS = _rS
20
+ rememo$._r = new WeakRef(()=>rememo$.refresh(_f(rememo$)))
21
+ rememo$.onset = ()=>0
22
+ rememo$._r.l = 0
23
+ rememo$.init = ()=>(rememo$(), rememo$)
24
+ Object.defineProperty(rememo$, '_', {
25
+ get() {
26
+ // allow self-referencing
27
+ if (!_a.length) {
28
+ let prev_ref = cur_ref
29
+ cur_ref = rememo$._r
30
+ try {
31
+ _a[0] = _f(rememo$)
32
+ } finally {
33
+ cur_ref = prev_ref
34
+ }
35
+ }
36
+ if (cur_ref && cur_ref !== rememo$._r) {
37
+ cur_ref.l = Math.max(rememo$._r.l + 1, cur_ref.l)
38
+ _rS.add(cur_ref)
39
+ }
40
+ return _a[0]
41
+ },
42
+ set(val) {
43
+ if (!_a.length || val !== _a[0]) {
44
+ rememo$.refresh(val)
45
+ }
46
+ return val
47
+ }
48
+ })
49
+ rememo$.refresh = val=>{
50
+ let { length } = _a
51
+ _a[0] = val
52
+ rememo$.onset(val)
53
+ if (length) {
54
+ let run_queue = !queue[0]
55
+ for (let ref of _rS) {
56
+ if (!~queue.indexOf(ref)) queue.push(ref)
57
+ }
58
+ if (run_queue) {
59
+ for (let ref; ref = queue.shift();) {
60
+ queue.some(_ref=>ref.l > _ref.l) ? queue.push(ref) : ref.deref()?.() ?? _rS.delete(ref)
61
+ }
62
+ }
63
+ }
64
+ return rememo$
65
+ }
66
+ rememo$._sa = subscriber_a.map(subscriber=>
67
+ rememo_(()=>subscriber(rememo$)).init())
68
+ return rememo$
69
+ }
70
+ /**
71
+ * @param {unknown}init_val
72
+ * @param {rememo_subscriber_T[]}subscriber_a
73
+ * @returns {rememo_T}
74
+ * @private
75
+ */
76
+ export function signal_(init_val, ...subscriber_a) {
77
+ let signal$ =
78
+ rememo_(signal$=>signal$._v, ...subscriber_a)
79
+ signal$.onset = val=>signal$._v = val
80
+ signal$._v = init_val
81
+ return signal$
9
82
  }
@@ -0,0 +1,241 @@
1
+ import { deepStrictEqual } from 'node:assert'
2
+ import { clock } from 'sinon'
3
+ import { test } from 'uvu'
4
+ import { equal } from 'uvu/assert'
5
+ import { sleep } from '../sleep/index.js'
6
+ import { rememo_, signal_ } from './index.js'
7
+ test('rememo_|static value', ()=>{
8
+ let count = 0
9
+ let rememo$ = rememo_(rememo=>{
10
+ count++
11
+ return 'rememo-value'
12
+ })
13
+ equal(count, 0)
14
+ equal(rememo$(), 'rememo-value')
15
+ equal(count, 1)
16
+ equal(rememo$(), 'rememo-value')
17
+ equal(count, 1)
18
+ })
19
+ test('signal_', ()=>{
20
+ let signal$ = signal_('val0')
21
+ equal(signal$(), 'val0')
22
+ signal$('val1')
23
+ equal(signal$(), 'val1')
24
+ })
25
+ test('rememo_|async subsubscriber', async ()=>{
26
+ let resolve:(user:{ id:string })=>void
27
+ let user0 = { id: 'id-0' }
28
+ let user1 = { id: 'id-1' }
29
+ let id$ = signal_('id-0')
30
+ let user$ = signal_<{ id:string }|null>(
31
+ null,
32
+ async (_user$)=>{
33
+ id$()
34
+ let user:{ id:string } = await new Promise(_resolve=>resolve = _resolve)
35
+ _user$(user)
36
+ })
37
+ equal(user$(), null)
38
+ resolve!(user0)
39
+ await sleep(0)
40
+ equal(user$(), user0)
41
+ id$('id-1')
42
+ equal(user$(), user0)
43
+ resolve!(user1)
44
+ await sleep(0)
45
+ })
46
+ test('rememo_+signal_|simple graph', ()=>{
47
+ let base$ = signal_('base0')
48
+ let dep0$ = rememo_(()=>base$() + '-dep0')
49
+ let dep1$ = rememo_(()=>dep0$() + '-dep1')
50
+ let dep2$ = rememo_(()=>dep1$() + '-dep2')
51
+ let dep3$ = rememo_(()=>dep2$() + '-dep3')
52
+ let dep4$ = rememo_(()=>dep3$() + '-dep4')
53
+ equal(dep4$(), 'base0-dep0-dep1-dep2-dep3-dep4')
54
+ equal(dep3$(), 'base0-dep0-dep1-dep2-dep3')
55
+ equal(dep2$(), 'base0-dep0-dep1-dep2')
56
+ equal(dep1$(), 'base0-dep0-dep1')
57
+ equal(dep0$(), 'base0-dep0')
58
+ equal(base$(), 'base0')
59
+ base$('base1')
60
+ equal(base$(), 'base1')
61
+ equal(dep0$(), 'base1-dep0')
62
+ equal(dep1$(), 'base1-dep0-dep1')
63
+ equal(dep2$(), 'base1-dep0-dep1-dep2')
64
+ equal(dep3$(), 'base1-dep0-dep1-dep2-dep3')
65
+ equal(dep4$(), 'base1-dep0-dep1-dep2-dep3-dep4')
66
+ })
67
+ test('prevents diamond dependency problem 1', ()=>{
68
+ let store$ = signal_(0)
69
+ let values:string[] = []
70
+ let a$ = rememo_(()=>`a${store$()}`)
71
+ let b$ = rememo_(()=>a$().replace('a', 'b'))
72
+ let c$ = rememo_(()=>a$().replace('a', 'c'))
73
+ let d$ = rememo_(()=>a$().replace('a', 'd'))
74
+ let combined$ = rememo_(()=>`${b$()}${c$()}${d$()}`,
75
+ combined$=>
76
+ values.push(combined$()))
77
+ deepStrictEqual(values, ['b0c0d0'])
78
+ store$(1)
79
+ store$(2)
80
+ deepStrictEqual(values, ['b0c0d0', 'b1c1d1', 'b2c2d2'])
81
+ })
82
+ test('prevents diamond dependency problem 2', ()=>{
83
+ let store$ = signal_(0)
84
+ let values:string[] = []
85
+ let a$ = rememo_(()=>`a${store$()}`)
86
+ let b$ = rememo_(()=>a$().replace('a', 'b'))
87
+ let c$ = rememo_(()=>b$().replace('b', 'c'))
88
+ let d$ = rememo_(()=>c$().replace('c', 'd'))
89
+ let e$ = rememo_(()=>d$().replace('d', 'e'))
90
+ let combined$ = rememo_<string>(
91
+ ()=>[a$(), e$()].join(''),
92
+ combined$=>values.push(combined$()))
93
+ deepStrictEqual(values, ['a0e0'])
94
+ store$(1)
95
+ deepStrictEqual(values, ['a0e0', 'a1e1'])
96
+ })
97
+ test('prevents diamond dependency problem 3', ()=>{
98
+ let store$ = signal_(0)
99
+ let values:string[] = []
100
+ let a$ = rememo_(()=>`a${store$()}`)
101
+ let b$ = rememo_(()=>a$().replace('a', 'b'))
102
+ let c$ = rememo_(()=>b$().replace('b', 'c'))
103
+ let d$ = rememo_(()=>c$().replace('c', 'd'))
104
+ rememo_<string>(
105
+ ()=>`${a$()}${b$()}${c$()}${d$()}`,
106
+ combined$=>values.push(combined$()))
107
+ deepStrictEqual(values, ['a0b0c0d0'])
108
+ store$(1)
109
+ deepStrictEqual(values, ['a0b0c0d0', 'a1b1c1d1'])
110
+ })
111
+ test('autosubscribe: prevents diamond dependency problem 4 (complex)', ()=>{
112
+ let store1$ = signal_(0)
113
+ let store2$ = signal_(0)
114
+ let values:string[] = []
115
+ let fn =
116
+ (name:string)=>
117
+ (...v:(number|string)[])=>
118
+ `${name}${v.join('')}`
119
+ let a$ = rememo_(()=>`a${store1$()}`)
120
+ let b$ = rememo_(()=>`b${store2$()}`)
121
+ let c$ = rememo_(()=>`c${a$()}${b$()}`)
122
+ let d$ = rememo_(()=>`d${a$()}`)
123
+ let e$ = rememo_(()=>`e${c$()}${d$()}`)
124
+ let f$ = rememo_(()=>`f${e$()}`)
125
+ let g$ = rememo_(()=>`g${f$()}`)
126
+ rememo_(
127
+ ()=>e$(),
128
+ combined1$=>values.push(combined1$()))
129
+ rememo_(
130
+ ()=>[e$(), g$()].join(''),
131
+ combined2$=>values.push(combined2$()))
132
+ deepStrictEqual(values, ['eca0b0da0', 'eca0b0da0gfeca0b0da0'])
133
+ store1$(1)
134
+ store2$(2)
135
+ deepStrictEqual(values, [
136
+ 'eca0b0da0',
137
+ 'eca0b0da0gfeca0b0da0',
138
+ 'eca1b0da1',
139
+ 'eca1b0da1gfeca1b0da1',
140
+ 'eca1b2da1',
141
+ 'eca1b2da1gfeca1b2da1'
142
+ ])
143
+ })
144
+ test('prevents diamond dependency problem 5', ()=>{
145
+ let events = ''
146
+ let firstName$ = signal_('John')
147
+ let lastName$ = signal_('Doe')
148
+ let fullName$ = rememo_(()=>{
149
+ events += 'full '
150
+ return `${firstName$()} ${lastName$()}`
151
+ })
152
+ let isFirstShort$ = rememo_(()=>{
153
+ events += 'short '
154
+ return firstName$().length < 10
155
+ })
156
+ let displayName$ = rememo_(
157
+ ()=>{
158
+ events += 'display '
159
+ return isFirstShort$() ? fullName$() : firstName$()
160
+ }
161
+ )
162
+ equal(events, '')
163
+ equal(displayName$(), 'John Doe')
164
+ equal(events, 'display short full ')
165
+ firstName$('Benedict')
166
+ equal(displayName$(), 'Benedict Doe')
167
+ equal(events, 'display short full short full display ')
168
+ firstName$('Montgomery')
169
+ equal(displayName$(), 'Montgomery')
170
+ equal(events, 'display short full short full display short full display ')
171
+ })
172
+ test('prevents diamond dependency problem 6', ()=>{
173
+ let store1$ = signal_(0)
174
+ let store2$ = signal_(0)
175
+ let values:string[] = []
176
+ let a$ = rememo_(()=>`a${store1$()}`)
177
+ let b$ = rememo_(()=>`b${store2$()}`)
178
+ let c$ = rememo_(()=>b$().replace('b', 'c'))
179
+ rememo_(
180
+ ()=>`${a$()}${c$()}`,
181
+ combined$=>values.push(combined$()))
182
+ deepStrictEqual(values, ['a0c0'])
183
+ store1$(1)
184
+ deepStrictEqual(values, ['a0c0', 'a1c0'])
185
+ })
186
+ test('prevents dependency listeners from being out of order', ()=>{
187
+ let base$ = signal_(0)
188
+ let a$ = rememo_(()=>{
189
+ return `${base$()}a`
190
+ })
191
+ let values:string[] = []
192
+ let b$ = rememo_(()=>{
193
+ return `${a$()}b`
194
+ }, b$=>values.push(b$()))
195
+ equal(b$(), '0ab')
196
+ deepStrictEqual(values, ['0ab'])
197
+ equal(a$(), '0a')
198
+ base$(1)
199
+ deepStrictEqual(values, ['0ab', '1ab'])
200
+ })
201
+ test('computes initial value when argument is undefined', ()=>{
202
+ let one$ = signal_<string|undefined>(undefined)
203
+ let two$ = rememo_(()=>!!one$())
204
+ equal(one$(), undefined)
205
+ equal(two$(), false)
206
+ })
207
+ test('async computed using task', async ()=>{
208
+ let a$ = signal_(1)
209
+ let b$ = signal_(2)
210
+ let sleepCycles = 5
211
+ let taskArgumentsCalls:number[][] = []
212
+ let sum$ = signal_<null|number>(null,
213
+ async sum$=>{
214
+ taskArgumentsCalls.push([a$(), b$()])
215
+ for (let i = 0; i < sleepCycles; i++) {
216
+ await Promise.resolve()
217
+ }
218
+ sum$(a$() + b$())
219
+ })
220
+ equal(sum$(), null)
221
+ deepStrictEqual(taskArgumentsCalls, [[1, 2]])
222
+ // sleepCycles = 0
223
+ a$(10)
224
+ b$(20)
225
+ for (let i = 0; i < sleepCycles; i++) {
226
+ equal(sum$(), null)
227
+ await Promise.resolve()
228
+ deepStrictEqual(taskArgumentsCalls, [
229
+ [1, 2],
230
+ [10, 2],
231
+ [10, 20]
232
+ ])
233
+ }
234
+ equal(sum$(), 30)
235
+ deepStrictEqual(taskArgumentsCalls, [
236
+ [1, 2],
237
+ [10, 2],
238
+ [10, 20]
239
+ ])
240
+ })
241
+ test.run()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctx-core",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "ctx-core core library",
5
5
  "keywords": [
6
6
  "ctx-core",
@@ -94,6 +94,7 @@
94
94
  "./queue": "./queue/index.js",
95
95
  "./random": "./random/index.js",
96
96
  "./regex": "./regex/index.js",
97
+ "./rememo": "./rememo/index.js",
97
98
  "./set": "./set/index.js",
98
99
  "./sleep": "./sleep/index.js",
99
100
  "./string": "./string/index.js",
@@ -105,11 +106,13 @@
105
106
  },
106
107
  "devDependencies": {
107
108
  "@arethetypeswrong/cli": "^0.13.1",
108
- "@types/node": "^20.9.0",
109
+ "@size-limit/preset-small-lib": "^11.0.0",
110
+ "@types/node": "^20.9.1",
109
111
  "@types/sinon": "^17.0.1",
110
112
  "c8": "^8.0.1",
111
113
  "check-dts": "^0.7.2",
112
114
  "sinon": "^17.0.1",
115
+ "size-limit": "^11.0.0",
113
116
  "ts-node": "^10.9.1",
114
117
  "tsx": "^4.1.2",
115
118
  "typescript": "next",
@@ -120,12 +123,37 @@
120
123
  "cache": "~/.npm"
121
124
  },
122
125
  "sideEffects": false,
126
+ "size-limit": [
127
+ {
128
+ "name": "be",
129
+ "import": {
130
+ "./be": "{ be_ }"
131
+ },
132
+ "limit": "600 B"
133
+ },
134
+ {
135
+ "name": "rememo",
136
+ "import": {
137
+ "./rememo": "{ rememo_ }"
138
+ },
139
+ "limit": "358 B"
140
+ },
141
+ {
142
+ "name": "rememo signal",
143
+ "import": {
144
+ "./rememo": "{ signal_ }"
145
+ },
146
+ "limit": "385 B"
147
+ }
148
+ ],
123
149
  "scripts": {
124
150
  "build": ":",
125
151
  "clean": ":",
126
152
  "exec": "$@",
127
- "test": "pnpm test-unit && check-dts",
128
- "test-unit": "tsx node_modules/uvu/bin.js . '\\.test\\.(ts|js)$'",
129
- "test-unit-coverage": "c8 pnpm test-unit"
153
+ "test": "pnpm run /^test:/",
154
+ "test:size": "size-limit",
155
+ "test:type": "check-dts",
156
+ "test:unit": "tsx node_modules/uvu/bin.js . '\\.test\\.(ts|js)$'",
157
+ "disable:test:coverage": "c8 pnpm test-unit"
130
158
  }
131
159
  }