rip-lang 3.13.136 → 3.14.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/README.md +46 -4
- package/docs/RIP-LANG.md +116 -11
- package/docs/RIP-SCHEMA.md +2390 -0
- package/docs/RIP-TYPES.md +21 -14
- package/docs/assets/rip-schema-logo-960w.png +0 -0
- package/docs/assets/rip-schema-social.png +0 -0
- package/docs/dist/rip.js +6817 -3670
- package/docs/dist/rip.min.js +1454 -211
- package/docs/dist/rip.min.js.br +0 -0
- package/package.json +10 -4
- package/src/AGENTS.md +130 -0
- package/src/compiler.js +65 -2
- package/src/components.js +19 -5
- package/src/grammar/grammar.rip +20 -1
- package/src/lexer.js +42 -0
- package/src/parser.js +222 -220
- package/src/schema.js +3298 -0
- package/src/sourcemap-utils.js +155 -0
- package/src/typecheck.js +395 -23
- package/src/types.js +25 -0
- package/src/ui.rip +203 -45
- package/CHANGELOG.md +0 -1500
package/src/ui.rip
CHANGED
|
@@ -30,32 +30,35 @@ export { setContext, getContext, hasContext }
|
|
|
30
30
|
#
|
|
31
31
|
# Wraps a plain object in a Proxy that lazily creates fine-grained signals
|
|
32
32
|
# for each property. Nested objects are wrapped recursively. Supports path-
|
|
33
|
-
# based access
|
|
33
|
+
# based bracket access (e.g., app['data/theme/title']) and 7 reserved methods
|
|
34
|
+
# for atomic/structural operations: inc, dec, flip, join, keys, has, del.
|
|
34
35
|
# ==============================================================================
|
|
35
36
|
|
|
36
|
-
STASH = Symbol('stash')
|
|
37
|
-
SIGNALS = Symbol('signals')
|
|
38
|
-
RAW = Symbol('raw')
|
|
39
|
-
PERSISTED = Symbol('persisted')
|
|
40
37
|
PROXIES = new WeakMap()
|
|
38
|
+
METHODS = new WeakMap()
|
|
41
39
|
|
|
42
40
|
_keysVersion = 0
|
|
43
41
|
_writeVersion = __state(0)
|
|
42
|
+
_depth = 0
|
|
43
|
+
|
|
44
|
+
isPathKey = (prop) -> typeof prop is 'string' and (prop.indexOf('.') isnt -1 or prop.indexOf('/') isnt -1 or prop.indexOf('[') isnt -1)
|
|
45
|
+
|
|
46
|
+
isNum = (val) -> /^-?\d+$/.test val
|
|
44
47
|
|
|
45
48
|
getSignal = (target, prop) ->
|
|
46
|
-
unless target[
|
|
47
|
-
Object.defineProperty target,
|
|
48
|
-
sig = target[
|
|
49
|
+
unless target[:signals]
|
|
50
|
+
Object.defineProperty target, :signals, { value: new Map(), enumerable: false }
|
|
51
|
+
sig = target[:signals].get(prop)
|
|
49
52
|
unless sig
|
|
50
53
|
sig = __state(target[prop])
|
|
51
|
-
target[
|
|
54
|
+
target[:signals].set(prop, sig)
|
|
52
55
|
sig
|
|
53
56
|
|
|
54
|
-
keysSignal = (target) -> getSignal(target,
|
|
57
|
+
keysSignal = (target) -> getSignal(target, :keys)
|
|
55
58
|
|
|
56
59
|
wrapDeep = (value) ->
|
|
57
60
|
return value unless value? and typeof value is 'object'
|
|
58
|
-
return value if value[
|
|
61
|
+
return value if value[:stash]
|
|
59
62
|
return value if value instanceof Date or value instanceof RegExp or value instanceof Map or value instanceof Set or value instanceof Promise
|
|
60
63
|
existing = PROXIES.get(value)
|
|
61
64
|
return existing if existing
|
|
@@ -65,17 +68,19 @@ makeProxy = (target) ->
|
|
|
65
68
|
proxy = null
|
|
66
69
|
handler =
|
|
67
70
|
get: (target, prop) ->
|
|
68
|
-
return true if prop is
|
|
69
|
-
return target if prop is
|
|
71
|
+
return true if prop is :stash
|
|
72
|
+
return target if prop is :raw
|
|
70
73
|
return Reflect.get(target, prop) if typeof prop is 'symbol'
|
|
71
74
|
|
|
72
75
|
if prop is 'length' and Array.isArray(target)
|
|
73
76
|
keysSignal(target).value
|
|
74
77
|
return target.length
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
if not _depth and isPathKey(prop)
|
|
80
|
+
return stashGet(proxy, prop)
|
|
81
|
+
|
|
82
|
+
fn = stashMethodFn(proxy, prop)
|
|
83
|
+
return fn if fn
|
|
79
84
|
|
|
80
85
|
sig = getSignal(target, prop)
|
|
81
86
|
val = sig.value
|
|
@@ -84,13 +89,17 @@ makeProxy = (target) ->
|
|
|
84
89
|
val
|
|
85
90
|
|
|
86
91
|
set: (target, prop, value) ->
|
|
92
|
+
if not _depth and isPathKey(prop)
|
|
93
|
+
stashSet(proxy, prop, value)
|
|
94
|
+
return true
|
|
95
|
+
|
|
87
96
|
old = target[prop]
|
|
88
|
-
r = if value?[
|
|
97
|
+
r = if value?[:raw] then value[:raw] else value
|
|
89
98
|
return true if r is old
|
|
90
99
|
target[prop] = r
|
|
91
100
|
|
|
92
|
-
if target[
|
|
93
|
-
target[
|
|
101
|
+
if target[:signals]?.has(prop)
|
|
102
|
+
target[:signals].get(prop).value = r
|
|
94
103
|
if old is undefined and r isnt undefined
|
|
95
104
|
keysSignal(target).value = ++_keysVersion
|
|
96
105
|
_writeVersion.value++
|
|
@@ -99,9 +108,10 @@ makeProxy = (target) ->
|
|
|
99
108
|
|
|
100
109
|
deleteProperty: (target, prop) ->
|
|
101
110
|
delete target[prop]
|
|
102
|
-
sig = target[
|
|
111
|
+
sig = target[:signals]?.get(prop)
|
|
103
112
|
sig?.value = undefined
|
|
104
113
|
keysSignal(target).value = ++_keysVersion
|
|
114
|
+
_writeVersion.value++
|
|
105
115
|
true
|
|
106
116
|
|
|
107
117
|
ownKeys: (target) ->
|
|
@@ -133,35 +143,126 @@ walk = (path) ->
|
|
|
133
143
|
i += 2
|
|
134
144
|
result
|
|
135
145
|
|
|
146
|
+
resolveIndex = (seg, obj) ->
|
|
147
|
+
if typeof seg is 'number' and seg < 0
|
|
148
|
+
t = raw(obj)
|
|
149
|
+
return t.length + seg if Array.isArray(t)
|
|
150
|
+
seg
|
|
151
|
+
|
|
136
152
|
stashGet = (proxy, path) ->
|
|
137
153
|
segs = walk(path)
|
|
138
154
|
obj = proxy
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
_depth++
|
|
156
|
+
try
|
|
157
|
+
for seg in segs
|
|
158
|
+
return undefined unless obj?
|
|
159
|
+
obj = obj[resolveIndex(seg, obj)]
|
|
160
|
+
obj
|
|
161
|
+
finally
|
|
162
|
+
_depth--
|
|
143
163
|
|
|
144
164
|
stashSet = (proxy, path, value) ->
|
|
145
165
|
segs = walk(path)
|
|
146
166
|
obj = proxy
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
167
|
+
_depth++
|
|
168
|
+
try
|
|
169
|
+
for seg, i in segs
|
|
170
|
+
key = resolveIndex(seg, obj)
|
|
171
|
+
if i is segs.length - 1
|
|
172
|
+
obj[key] = value
|
|
173
|
+
else
|
|
174
|
+
unless obj[key]?
|
|
175
|
+
nextSeg = segs[i + 1]
|
|
176
|
+
obj[key] = if typeof nextSeg is 'number' or isNum(nextSeg) then [] else {}
|
|
177
|
+
obj = obj[key]
|
|
178
|
+
value
|
|
179
|
+
finally
|
|
180
|
+
_depth--
|
|
181
|
+
|
|
182
|
+
# Stash methods — reserved names for atomic/structural operations
|
|
183
|
+
STASH_METHOD_NAMES = { inc: true, dec: true, flip: true, join: true, keys: true, has: true, del: true }
|
|
184
|
+
|
|
185
|
+
stashMethodFn = (proxy, prop) ->
|
|
186
|
+
return undefined unless STASH_METHOD_NAMES[prop]
|
|
187
|
+
cache = METHODS.get(proxy)
|
|
188
|
+
unless cache
|
|
189
|
+
cache = {}
|
|
190
|
+
METHODS.set(proxy, cache)
|
|
191
|
+
return cache[prop] if cache[prop]
|
|
192
|
+
fn = switch prop
|
|
193
|
+
when 'inc' then (path, step = 1) -> cur = stashGet(proxy, path) ?? 0; stashSet(proxy, path, cur + step)
|
|
194
|
+
when 'dec' then (path, step = 1) -> cur = stashGet(proxy, path) ?? 0; stashSet(proxy, path, cur - step)
|
|
195
|
+
when 'flip' then (path) -> cur = stashGet(proxy, path) ?? false; stashSet(proxy, path, not cur)
|
|
196
|
+
when 'join' then (path, obj) ->
|
|
197
|
+
__batch ->
|
|
198
|
+
target = stashGet(proxy, path)
|
|
199
|
+
unless target? and typeof target is 'object'
|
|
200
|
+
stashSet(proxy, path, {})
|
|
201
|
+
target = stashGet(proxy, path)
|
|
202
|
+
_depth++
|
|
203
|
+
try
|
|
204
|
+
target[k] = v for k, v of obj
|
|
205
|
+
finally
|
|
206
|
+
_depth--
|
|
207
|
+
when 'keys' then (path) ->
|
|
208
|
+
_depth++
|
|
209
|
+
try
|
|
210
|
+
segs = walk(path)
|
|
211
|
+
obj = proxy
|
|
212
|
+
for seg in segs
|
|
213
|
+
return [] unless obj?
|
|
214
|
+
obj = obj[resolveIndex(seg, obj)]
|
|
215
|
+
return [] unless obj? and typeof obj is 'object'
|
|
216
|
+
t = raw(obj)
|
|
217
|
+
keysSignal(t).value
|
|
218
|
+
Object.keys(t)
|
|
219
|
+
finally
|
|
220
|
+
_depth--
|
|
221
|
+
when 'has' then (path) ->
|
|
222
|
+
_depth++
|
|
223
|
+
try
|
|
224
|
+
segs = walk(path)
|
|
225
|
+
return false unless segs.length > 0
|
|
226
|
+
obj = proxy
|
|
227
|
+
for seg, i in segs
|
|
228
|
+
key = resolveIndex(seg, obj)
|
|
229
|
+
if i is segs.length - 1
|
|
230
|
+
t = raw(obj)
|
|
231
|
+
keysSignal(t).value
|
|
232
|
+
return Object.prototype.hasOwnProperty.call(t, key)
|
|
233
|
+
return false unless obj?
|
|
234
|
+
obj = obj[key]
|
|
235
|
+
false
|
|
236
|
+
finally
|
|
237
|
+
_depth--
|
|
238
|
+
when 'del' then (path) ->
|
|
239
|
+
_depth++
|
|
240
|
+
try
|
|
241
|
+
segs = walk(path)
|
|
242
|
+
return unless segs.length > 0
|
|
243
|
+
obj = proxy
|
|
244
|
+
for seg, i in segs
|
|
245
|
+
key = resolveIndex(seg, obj)
|
|
246
|
+
if i is segs.length - 1
|
|
247
|
+
delete obj[key]
|
|
248
|
+
return
|
|
249
|
+
return unless obj?
|
|
250
|
+
obj = obj[key]
|
|
251
|
+
finally
|
|
252
|
+
_depth--
|
|
253
|
+
cache[prop] = fn
|
|
254
|
+
fn
|
|
154
255
|
|
|
155
256
|
export stash = (data = {}) -> makeProxy(data)
|
|
156
257
|
|
|
157
|
-
export raw = (proxy) -> if proxy?[
|
|
258
|
+
export raw = (proxy) -> if proxy?[:raw] then proxy[:raw] else proxy
|
|
158
259
|
|
|
159
|
-
export isStash = (obj) -> obj?[
|
|
260
|
+
export isStash = (obj) -> obj?[:stash] is true
|
|
160
261
|
|
|
161
262
|
export persistStash = (app, opts = {}) ->
|
|
162
263
|
target = raw(app) or app
|
|
163
|
-
return if target[
|
|
164
|
-
target[
|
|
264
|
+
return if target[:persisted]
|
|
265
|
+
target[:persisted] = true
|
|
165
266
|
storage = if opts.local then localStorage else sessionStorage
|
|
166
267
|
storageKey = opts.key or '__rip_app'
|
|
167
268
|
try
|
|
@@ -626,18 +727,77 @@ buildComponentMap = (components, root = 'components') ->
|
|
|
626
727
|
map[name] = path
|
|
627
728
|
map
|
|
628
729
|
|
|
730
|
+
# Resolve a .rip import specifier to a path in the component store
|
|
731
|
+
resolveStorePath = (specifier, currentPath, components) ->
|
|
732
|
+
clean = specifier.replace(/^(\.\.\/|\.\/)+/, '')
|
|
733
|
+
basename = clean.split('/').pop()
|
|
734
|
+
# Direct resolution relative to current file in the store
|
|
735
|
+
if currentPath
|
|
736
|
+
parts = currentPath.split('/')
|
|
737
|
+
parts.pop()
|
|
738
|
+
for seg in specifier.split('/')
|
|
739
|
+
if seg is '..'
|
|
740
|
+
parts.pop()
|
|
741
|
+
else unless seg is '.'
|
|
742
|
+
parts.push seg
|
|
743
|
+
candidate = parts.join('/')
|
|
744
|
+
return candidate if components.exists(candidate)
|
|
745
|
+
# Try common store prefixes
|
|
746
|
+
return "components/_lib/#{clean}" if components.exists("components/_lib/#{clean}")
|
|
747
|
+
return "components/#{clean}" if components.exists("components/#{clean}")
|
|
748
|
+
# Basename search as last resort
|
|
749
|
+
for p in components.listAll('components')
|
|
750
|
+
return p if p.endsWith("/#{basename}")
|
|
751
|
+
null
|
|
752
|
+
|
|
629
753
|
compileAndImport = (source, compile, components = null, path = null, resolver = null) ->
|
|
630
754
|
# Check compilation cache
|
|
631
755
|
if components and path
|
|
632
756
|
cached = components.getCompiled(path)
|
|
633
757
|
return cached if cached
|
|
634
758
|
|
|
759
|
+
# Mark as in-progress to prevent circular dependency loops
|
|
760
|
+
if resolver and path
|
|
761
|
+
resolver.compiling ?= {}
|
|
762
|
+
resolver.compiling[path] = true
|
|
763
|
+
|
|
635
764
|
js = compile(source)
|
|
636
765
|
|
|
637
|
-
# Resolve component dependencies — scan for PascalCase references in compiled JS
|
|
638
766
|
if resolver
|
|
767
|
+
importedNames = new Set()
|
|
768
|
+
|
|
769
|
+
# Step 1: Rewrite .rip imports to blob URLs
|
|
770
|
+
if components
|
|
771
|
+
ripImportRe = /^(\s*import\s+(?:\{([^}]+)\}\s+from\s+|.*?\s+from\s+)?['"])([^'"]*\.rip)(['"];?\s*)$/gm
|
|
772
|
+
matches = Array.from(js.matchAll(ripImportRe))
|
|
773
|
+
# Walk matches in reverse so string indices stay valid after each splice
|
|
774
|
+
for m in matches by -1
|
|
775
|
+
[full, pre, namedImports, specifier, post] = m
|
|
776
|
+
storePath = resolveStorePath(specifier, path, components)
|
|
777
|
+
continue if storePath is path
|
|
778
|
+
unless storePath
|
|
779
|
+
msg = "[Rip] Could not resolve import: #{specifier}"
|
|
780
|
+
msg += " (from #{path})" if path
|
|
781
|
+
console.warn msg
|
|
782
|
+
continue
|
|
783
|
+
# Guard against circular imports — skip if already being compiled
|
|
784
|
+
unless resolver.blobUrls?[storePath]
|
|
785
|
+
continue if resolver.compiling?[storePath]
|
|
786
|
+
depSource = components.read(storePath)
|
|
787
|
+
if depSource
|
|
788
|
+
compileAndImport! depSource, compile, components, storePath, resolver
|
|
789
|
+
blobUrl = resolver.blobUrls?[storePath]
|
|
790
|
+
if blobUrl
|
|
791
|
+
replacement = "#{pre}#{blobUrl}#{post}"
|
|
792
|
+
js = js.slice(0, m.index) + replacement + js.slice(m.index + full.length)
|
|
793
|
+
if namedImports
|
|
794
|
+
for n in namedImports.split(',')
|
|
795
|
+
importedNames.add(n.trim().split(/\s+as\s+/).pop().trim())
|
|
796
|
+
|
|
797
|
+
# Step 2: Implicit component resolution — for template-used components not explicitly imported
|
|
639
798
|
needed = {}
|
|
640
799
|
for name, depPath of resolver.map
|
|
800
|
+
continue if importedNames.has(name)
|
|
641
801
|
if depPath isnt path and js.includes("new #{name}(")
|
|
642
802
|
unless resolver.classes[name]
|
|
643
803
|
depSource = components.read(depPath)
|
|
@@ -656,6 +816,13 @@ compileAndImport = (source, compile, components = null, path = null, resolver =
|
|
|
656
816
|
header = if path then "// #{path}\n" else ''
|
|
657
817
|
blob = new Blob([header + js], { type: 'application/javascript' })
|
|
658
818
|
url = URL.createObjectURL(blob)
|
|
819
|
+
|
|
820
|
+
# Cache blob URL so other files can rewrite imports to point here
|
|
821
|
+
if resolver and path
|
|
822
|
+
resolver.blobUrls ?= {}
|
|
823
|
+
resolver.blobUrls[path] = url
|
|
824
|
+
delete resolver.compiling[path] if resolver.compiling
|
|
825
|
+
|
|
659
826
|
mod = await import(url)
|
|
660
827
|
|
|
661
828
|
# Register any components from this module
|
|
@@ -680,9 +847,6 @@ export createRenderer = (opts = {}) ->
|
|
|
680
847
|
container.id = 'app'
|
|
681
848
|
document.body.appendChild container
|
|
682
849
|
|
|
683
|
-
# Fade in after first mount (prevents layout-before-content flicker)
|
|
684
|
-
container.style.opacity = '0'
|
|
685
|
-
|
|
686
850
|
currentComponent = null
|
|
687
851
|
currentRoute = null
|
|
688
852
|
currentParams = null
|
|
@@ -816,15 +980,9 @@ export createRenderer = (opts = {}) ->
|
|
|
816
980
|
instance.load!(params, query) if instance.load
|
|
817
981
|
oldTarget?.remove()
|
|
818
982
|
router.navigating = false
|
|
819
|
-
if container.style.opacity is '0'
|
|
820
|
-
document.fonts.ready.then ->
|
|
821
|
-
requestAnimationFrame ->
|
|
822
|
-
container.style.transition = 'opacity 150ms ease-in'
|
|
823
|
-
container.style.opacity = '1'
|
|
824
983
|
|
|
825
984
|
catch err
|
|
826
985
|
router.navigating = false
|
|
827
|
-
container.style.opacity = '1'
|
|
828
986
|
console.error "Renderer: error mounting #{route.file}:", err
|
|
829
987
|
onError({ status: 500, message: err.message, error: err }) if onError
|
|
830
988
|
|