api-ape 0.0.0 → 1.0.1

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.
Files changed (63) hide show
  1. package/README.md +261 -0
  2. package/client/README.md +69 -0
  3. package/client/browser.js +17 -0
  4. package/client/connectSocket.js +260 -0
  5. package/dist/ape.js +454 -0
  6. package/example/ExpressJs/README.md +97 -0
  7. package/example/ExpressJs/api/message.js +11 -0
  8. package/example/ExpressJs/backend.js +37 -0
  9. package/example/ExpressJs/index.html +88 -0
  10. package/example/ExpressJs/package-lock.json +834 -0
  11. package/example/ExpressJs/package.json +10 -0
  12. package/example/ExpressJs/styles.css +128 -0
  13. package/example/NextJs/.dockerignore +29 -0
  14. package/example/NextJs/Dockerfile +52 -0
  15. package/example/NextJs/Dockerfile.dev +27 -0
  16. package/example/NextJs/README.md +113 -0
  17. package/example/NextJs/ape/client.js +66 -0
  18. package/example/NextJs/ape/embed.js +12 -0
  19. package/example/NextJs/ape/index.js +23 -0
  20. package/example/NextJs/ape/logic/chat.js +62 -0
  21. package/example/NextJs/ape/onConnect.js +69 -0
  22. package/example/NextJs/ape/onDisconnect.js +13 -0
  23. package/example/NextJs/ape/onError.js +9 -0
  24. package/example/NextJs/ape/onReceive.js +15 -0
  25. package/example/NextJs/ape/onSend.js +15 -0
  26. package/example/NextJs/api/message.js +44 -0
  27. package/example/NextJs/docker-compose.yml +22 -0
  28. package/example/NextJs/next-env.d.ts +5 -0
  29. package/example/NextJs/next.config.js +8 -0
  30. package/example/NextJs/package-lock.json +5107 -0
  31. package/example/NextJs/package.json +25 -0
  32. package/example/NextJs/pages/_app.tsx +6 -0
  33. package/example/NextJs/pages/index.tsx +182 -0
  34. package/example/NextJs/public/favicon.ico +0 -0
  35. package/example/NextJs/public/vercel.svg +4 -0
  36. package/example/NextJs/server.js +40 -0
  37. package/example/NextJs/styles/Chat.module.css +194 -0
  38. package/example/NextJs/styles/Home.module.css +129 -0
  39. package/example/NextJs/styles/globals.css +26 -0
  40. package/example/NextJs/tsconfig.json +20 -0
  41. package/example/README.md +66 -0
  42. package/index.d.ts +179 -0
  43. package/index.js +11 -0
  44. package/package.json +11 -4
  45. package/server/README.md +93 -0
  46. package/server/index.js +6 -0
  47. package/server/lib/broadcast.js +63 -0
  48. package/server/lib/loader.js +10 -0
  49. package/server/lib/main.js +23 -0
  50. package/server/lib/wiring.js +94 -0
  51. package/server/security/extractRootDomain.js +21 -0
  52. package/server/security/origin.js +13 -0
  53. package/server/security/reply.js +21 -0
  54. package/server/socket/open.js +10 -0
  55. package/server/socket/receive.js +66 -0
  56. package/server/socket/send.js +55 -0
  57. package/server/utils/deepRequire.js +45 -0
  58. package/server/utils/genId.js +24 -0
  59. package/todo.md +85 -0
  60. package/utils/jss.js +273 -0
  61. package/utils/jss.test.js +261 -0
  62. package/utils/messageHash.js +43 -0
  63. package/utils/messageHash.test.js +56 -0
@@ -0,0 +1,261 @@
1
+ const jss = require('./jss')
2
+
3
+ describe('JJS - JSON SuperSet', () => {
4
+
5
+ describe('Primitives', () => {
6
+ test('handles strings', () => {
7
+ const input = { str: 'hello world' }
8
+ const result = jss.parse(jss.stringify(input))
9
+ expect(result.str).toBe('hello world')
10
+ })
11
+
12
+ test('handles numbers', () => {
13
+ const input = { int: 42, float: 3.14, neg: -100 }
14
+ const result = jss.parse(jss.stringify(input))
15
+ expect(result.int).toBe(42)
16
+ expect(result.float).toBe(3.14)
17
+ expect(result.neg).toBe(-100)
18
+ })
19
+
20
+ test('handles booleans', () => {
21
+ const input = { t: true, f: false }
22
+ const result = jss.parse(jss.stringify(input))
23
+ expect(result.t).toBe(true)
24
+ expect(result.f).toBe(false)
25
+ })
26
+
27
+ test('handles null', () => {
28
+ const input = { n: null }
29
+ const result = jss.parse(jss.stringify(input))
30
+ expect(result.n).toBe(null)
31
+ })
32
+ })
33
+
34
+ describe('Special Types', () => {
35
+ test('preserves Date objects', () => {
36
+ const date = new Date('2025-01-01T12:00:00Z')
37
+ const input = { created: date }
38
+ const result = jss.parse(jss.stringify(input))
39
+ expect(result.created).toBeInstanceOf(Date)
40
+ expect(result.created.getTime()).toBe(date.getTime())
41
+ })
42
+
43
+ test('encodes RegExp objects', () => {
44
+ const regex = /hello/
45
+ const input = { pattern: regex }
46
+ const result = jss.parse(jss.stringify(input))
47
+ // RegExp is encoded/decoded - verify it's a RegExp
48
+ expect(result.pattern).toBeInstanceOf(RegExp)
49
+ })
50
+
51
+ test('preserves Error objects', () => {
52
+ const error = new Error('Something went wrong')
53
+ error.name = 'CustomError'
54
+ const input = { err: error }
55
+ const result = jss.parse(jss.stringify(input))
56
+ expect(result.err).toBeInstanceOf(Error)
57
+ expect(result.err.message).toBe('Something went wrong')
58
+ expect(result.err.name).toBe('CustomError')
59
+ })
60
+
61
+ test('handles undefined in objects', () => {
62
+ const input = { defined: 'yes', notDefined: undefined }
63
+ const result = jss.parse(jss.stringify(input))
64
+ // undefined properties should round-trip
65
+ expect(result.notDefined).toBe(undefined)
66
+ })
67
+
68
+ test('preserves Set objects', () => {
69
+ const set = new Set([1, 2, 3, 'a', 'b'])
70
+ const input = { items: set }
71
+ const result = jss.parse(jss.stringify(input))
72
+ expect(result.items).toBeInstanceOf(Set)
73
+ expect(result.items.has(1)).toBe(true)
74
+ expect(result.items.has('a')).toBe(true)
75
+ expect(result.items.size).toBe(5)
76
+ })
77
+
78
+ test('preserves Map objects', () => {
79
+ const map = new Map([['key1', 'value1'], ['key2', 42]])
80
+ const input = { data: map }
81
+ const result = jss.parse(jss.stringify(input))
82
+ expect(result.data).toBeInstanceOf(Map)
83
+ expect(result.data.get('key1')).toBe('value1')
84
+ expect(result.data.get('key2')).toBe(42)
85
+ })
86
+ })
87
+
88
+ describe('Objects and Arrays', () => {
89
+ test('handles nested objects', () => {
90
+ const input = {
91
+ user: {
92
+ name: 'Alice',
93
+ profile: {
94
+ age: 30
95
+ }
96
+ }
97
+ }
98
+ const result = jss.parse(jss.stringify(input))
99
+ expect(result.user.name).toBe('Alice')
100
+ expect(result.user.profile.age).toBe(30)
101
+ })
102
+
103
+ test('handles arrays', () => {
104
+ const input = { items: [1, 2, 3, 'four', 'five'] }
105
+ const result = jss.parse(jss.stringify(input))
106
+ expect(result.items).toEqual([1, 2, 3, 'four', 'five'])
107
+ })
108
+
109
+ test('handles arrays with Dates', () => {
110
+ const date = new Date('2025-06-15')
111
+ const input = { mixed: ['text', 42, date, null] }
112
+ const result = jss.parse(jss.stringify(input))
113
+ expect(result.mixed[0]).toBe('text')
114
+ expect(result.mixed[1]).toBe(42)
115
+ expect(result.mixed[2]).toBeInstanceOf(Date)
116
+ expect(result.mixed[3]).toBe(null)
117
+ })
118
+ })
119
+
120
+ describe('encode/decode', () => {
121
+ test('encode returns tagged object for Date', () => {
122
+ const input = { d: new Date('2025-01-01') }
123
+ const encoded = jss.encode(input)
124
+ expect(encoded['d<!D>']).toBeDefined()
125
+ })
126
+
127
+ test('decode restores Date from tagged object', () => {
128
+ const encoded = { 'd<!D>': 1735689600000 }
129
+ const decoded = jss.decode(encoded)
130
+ expect(decoded.d).toBeInstanceOf(Date)
131
+ })
132
+
133
+ test('encode handles Error type', () => {
134
+ const input = { e: new Error('test') }
135
+ const encoded = jss.encode(input)
136
+ expect(encoded['e<!E>']).toBeDefined()
137
+ })
138
+
139
+ test('encode handles Set type', () => {
140
+ const input = { s: new Set([1, 2]) }
141
+ const encoded = jss.encode(input)
142
+ expect(encoded['s<!S>']).toBeDefined()
143
+ })
144
+
145
+ test('encode handles Map type', () => {
146
+ const input = { m: new Map([['a', 1]]) }
147
+ const encoded = jss.encode(input)
148
+ expect(encoded['m<!M>']).toBeDefined()
149
+ })
150
+ })
151
+
152
+ describe('Circular References', () => {
153
+ test('handles self-referencing object', () => {
154
+ const original = {
155
+ id: 123,
156
+ name: 'Test'
157
+ }
158
+ original.foo = original
159
+
160
+ const encoded = jss.encode(original)
161
+ const result = jss.decode(encoded)
162
+
163
+ expect(result.id).toBe(123)
164
+ expect(result.name).toBe('Test')
165
+ expect(result.foo).toBe(result)
166
+ })
167
+
168
+ test('handles self-referencing object', () => {
169
+ const original = {
170
+ name: 'Test',
171
+ cat: {
172
+ cars: true
173
+ },
174
+ bar: {
175
+ baz: true
176
+ }
177
+ }
178
+ original.cat.foo = original.bar.baz
179
+
180
+ const encoded = jss.encode(original)
181
+ const result = jss.decode(encoded)
182
+
183
+ expect(result.cat.foo).toBe(result.bar.baz)
184
+ })
185
+
186
+ test('handles multiple self-references', () => {
187
+ const original = { id: 1 }
188
+ original.refA = original
189
+ original.refB = original
190
+
191
+ const result = jss.decode(jss.encode(original))
192
+
193
+ expect(result.refA).toBe(result)
194
+ expect(result.refB).toBe(result)
195
+ })
196
+ })
197
+
198
+ describe('Shared References', () => {
199
+ test('shared object referenced twice', () => {
200
+ const shared = { value: 42 }
201
+ const original = {
202
+ first: shared,
203
+ second: shared
204
+ }
205
+
206
+ const result = jss.decode(jss.encode(original))
207
+
208
+ expect(result.first.value).toBe(42)
209
+ expect(result.second.value).toBe(42)
210
+ expect(result.first).toBe(result.second) // same object reference
211
+ })
212
+
213
+ test('shared object in array', () => {
214
+ const shared = { id: 'shared' }
215
+ const original = {
216
+ items: [shared, shared, shared]
217
+ }
218
+
219
+ const result = jss.decode(jss.encode(original))
220
+
221
+ expect(result.items[0]).toBe(result.items[1])
222
+ expect(result.items[1]).toBe(result.items[2])
223
+ })
224
+
225
+ test('deeply nested shared reference', () => {
226
+ const shared = { data: 'test' }
227
+ const original = {
228
+ level1: {
229
+ level2: {
230
+ ref: shared
231
+ }
232
+ },
233
+ otherRef: shared
234
+ }
235
+
236
+ const result = jss.decode(jss.encode(original))
237
+
238
+ expect(result.level1.level2.ref.data).toBe('test')
239
+ expect(result.level1.level2.ref).toBe(result.otherRef)
240
+ })
241
+ })
242
+
243
+ describe('Round-trip', () => {
244
+ test('object with multiple special types survives round-trip', () => {
245
+ const original = {
246
+ id: 123,
247
+ name: 'Test',
248
+ createdAt: new Date(),
249
+ tags: new Set(['a', 'b']),
250
+ meta: new Map([['x', 1]])
251
+ }
252
+ const result = jss.parse(jss.stringify(original))
253
+
254
+ expect(result.id).toBe(original.id)
255
+ expect(result.name).toBe(original.name)
256
+ expect(result.createdAt.getTime()).toBe(original.createdAt.getTime())
257
+ expect(result.tags).toBeInstanceOf(Set)
258
+ expect(result.meta).toBeInstanceOf(Map)
259
+ })
260
+ })
261
+ })
@@ -0,0 +1,43 @@
1
+ const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
2
+ /*
3
+ function charValue(char){
4
+ return alphabet.indexOf(char.toUpperCase())
5
+ } // END charValue
6
+
7
+ function fromBase32(b32){
8
+ if (0 === b32.length) {
9
+ return 0
10
+ }
11
+ return charValue(b32.slice(-1)) + fromBase32(b32.slice(0,-1)) * 32
12
+ } // END fromBase32
13
+ */
14
+ function toBase32 (n){
15
+ const remainder = Math.floor(n/32)
16
+ const current = n % 32
17
+ if (0 === remainder) {
18
+ return alphabet[current]
19
+ }
20
+ return toBase32(remainder)+alphabet[current]
21
+ } // END toBase32
22
+
23
+ function jenkinsOneAtATimeHash(keyString){
24
+
25
+ var hash = 0
26
+
27
+ for (var charIndex = 0; charIndex < keyString.length; ++charIndex)
28
+ {
29
+ hash += keyString.charCodeAt(charIndex);
30
+ hash += hash << 10;
31
+ hash ^= hash >> 6;
32
+ }
33
+ hash += hash << 3;
34
+ hash ^= hash >> 11;
35
+ //4,294,967,295 is FFFFFFFF, the maximum 32 bit unsigned integer value, used here as a mask.
36
+ return (((hash + (hash << 15)) & 4294967295) >>> 0)
37
+ } // END jenkinsOneAtATimeHash
38
+
39
+ function messageHash(messageSt){
40
+ return toBase32(jenkinsOneAtATimeHash(messageSt))
41
+ } // END messageHash
42
+
43
+ module.exports = messageHash
@@ -0,0 +1,56 @@
1
+ const messageHash = require('./messageHash')
2
+
3
+ describe('messageHash', () => {
4
+
5
+ test('returns consistent hash for same input', () => {
6
+ const input = 'hello world'
7
+ const hash1 = messageHash(input)
8
+ const hash2 = messageHash(input)
9
+ expect(hash1).toBe(hash2)
10
+ })
11
+
12
+ test('returns different hashes for different inputs', () => {
13
+ const hash1 = messageHash('message one')
14
+ const hash2 = messageHash('message two')
15
+ expect(hash1).not.toBe(hash2)
16
+ })
17
+
18
+ test('returns base32-encoded string', () => {
19
+ const hash = messageHash('test message')
20
+ // Base32 alphabet used: 0-9, A-Z excluding I, L, O, U
21
+ expect(hash).toMatch(/^[0-9A-HJKMNP-TV-Z]+$/)
22
+ })
23
+
24
+ test('handles empty string', () => {
25
+ const hash = messageHash('')
26
+ expect(typeof hash).toBe('string')
27
+ expect(hash.length).toBeGreaterThan(0)
28
+ })
29
+
30
+ test('handles special characters', () => {
31
+ const hash = messageHash('特殊文字 🎉 emoji!')
32
+ expect(typeof hash).toBe('string')
33
+ expect(hash.length).toBeGreaterThan(0)
34
+ })
35
+
36
+ test('handles long strings', () => {
37
+ const longString = 'x'.repeat(10000)
38
+ const hash = messageHash(longString)
39
+ expect(typeof hash).toBe('string')
40
+ // Hash should be compact regardless of input length
41
+ expect(hash.length).toBeLessThan(20)
42
+ })
43
+
44
+ test('handles JSON strings', () => {
45
+ const json = JSON.stringify({ type: '/message', data: { user: 'test' } })
46
+ const hash = messageHash(json)
47
+ expect(typeof hash).toBe('string')
48
+ expect(hash.length).toBeGreaterThan(0)
49
+ })
50
+
51
+ test('different order produces different hash', () => {
52
+ const hash1 = messageHash('abc')
53
+ const hash2 = messageHash('bca')
54
+ expect(hash1).not.toBe(hash2)
55
+ })
56
+ })