tjs-lang 0.7.6 → 0.7.8

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 (61) hide show
  1. package/CLAUDE.md +101 -26
  2. package/bin/docs.js +4 -1
  3. package/demo/docs.json +46 -12
  4. package/demo/src/examples.test.ts +1 -0
  5. package/demo/src/imports.test.ts +16 -4
  6. package/demo/src/imports.ts +60 -15
  7. package/demo/src/playground-shared.ts +9 -8
  8. package/demo/src/tfs-worker.js +205 -147
  9. package/demo/src/tjs-playground.ts +34 -10
  10. package/demo/src/ts-playground.ts +24 -8
  11. package/dist/index.js +140 -119
  12. package/dist/index.js.map +4 -4
  13. package/dist/src/lang/bool-coercion.d.ts +50 -0
  14. package/dist/src/lang/docs.d.ts +31 -6
  15. package/dist/src/lang/linter.d.ts +8 -0
  16. package/dist/src/lang/parser-transforms.d.ts +18 -0
  17. package/dist/src/lang/parser-types.d.ts +2 -0
  18. package/dist/src/lang/parser.d.ts +9 -0
  19. package/dist/src/lang/runtime.d.ts +34 -0
  20. package/dist/src/lang/types.d.ts +9 -1
  21. package/dist/src/rbac/index.d.ts +1 -1
  22. package/dist/src/vm/runtime.d.ts +1 -1
  23. package/dist/tjs-eval.js +44 -39
  24. package/dist/tjs-eval.js.map +4 -4
  25. package/dist/tjs-from-ts.js +20 -20
  26. package/dist/tjs-from-ts.js.map +3 -3
  27. package/dist/tjs-lang.js +86 -80
  28. package/dist/tjs-lang.js.map +4 -4
  29. package/dist/tjs-vm.js +50 -45
  30. package/dist/tjs-vm.js.map +4 -4
  31. package/llms.txt +79 -0
  32. package/package.json +3 -2
  33. package/src/cli/commands/convert.test.ts +16 -21
  34. package/src/lang/bool-coercion.test.ts +203 -0
  35. package/src/lang/bool-coercion.ts +314 -0
  36. package/src/lang/codegen.test.ts +177 -0
  37. package/src/lang/docs.test.ts +328 -1
  38. package/src/lang/docs.ts +424 -24
  39. package/src/lang/emitters/ast.ts +11 -12
  40. package/src/lang/emitters/dts.test.ts +41 -0
  41. package/src/lang/emitters/dts.ts +9 -0
  42. package/src/lang/emitters/js-tests.ts +16 -4
  43. package/src/lang/emitters/js.ts +208 -2
  44. package/src/lang/features.test.ts +22 -0
  45. package/src/lang/inference.ts +54 -0
  46. package/src/lang/linter.test.ts +104 -1
  47. package/src/lang/linter.ts +124 -1
  48. package/src/lang/parser-params.ts +31 -0
  49. package/src/lang/parser-transforms.ts +539 -6
  50. package/src/lang/parser-types.ts +2 -0
  51. package/src/lang/parser.test.ts +73 -1
  52. package/src/lang/parser.ts +85 -1
  53. package/src/lang/runtime.ts +98 -0
  54. package/src/lang/tests.ts +21 -8
  55. package/src/lang/types.ts +6 -0
  56. package/src/rbac/index.ts +2 -2
  57. package/src/rbac/rules.tjs.d.ts +9 -0
  58. package/src/vm/atoms/batteries.ts +2 -2
  59. package/src/vm/runtime.ts +10 -3
  60. package/dist/src/rbac/rules.d.ts +0 -184
  61. package/src/rbac/rules.js +0 -338
package/src/rbac/rules.js DELETED
@@ -1,338 +0,0 @@
1
- /*#
2
- # RBAC Security Rules (Pure Logic)
3
-
4
- Pure security rule evaluation logic, independent of storage backend.
5
- This module contains no I/O - it only evaluates rules against contexts.
6
-
7
- ## Rule Context
8
- - `_uid` - authenticated user ID (null if public)
9
- - `_roles` - array of user roles
10
- - `_method` - 'read' | 'write' | 'delete'
11
- - `_collection` - collection name
12
- - `_docId` - document ID
13
- - `doc` - existing document data (for read/write/delete)
14
- - `newData` - incoming data (for write only)
15
-
16
- ## Rule Response
17
- - Return `true` to allow
18
- - Return `false` to deny
19
- - Return `{ allow: true/false, reason: string }` for detailed response
20
- */
21
-
22
- /*#
23
- ## Access Rule Shortcuts
24
-
25
- Evaluates simple access rule strings without AJS overhead.
26
- Returns { allowed: boolean, reason?: string } or null if not a shortcut.
27
-
28
- Shortcuts:
29
- - 'none' - deny all
30
- - 'all' - allow all
31
- - 'authenticated' - must be logged in
32
- - 'admin' - must have admin role
33
- - 'author' - must have author role
34
- - 'owner:fieldName' - doc[fieldName] === _uid
35
- - 'role:roleName' - _roles.includes(roleName)
36
- */
37
- export function evaluateAccessShortcut(accessRule, context) {
38
- if (typeof accessRule !== 'string') return null
39
-
40
- const { _uid, _roles, doc, newData } = context
41
-
42
- switch (accessRule) {
43
- case 'none':
44
- return { allowed: false, reason: 'Access denied' }
45
-
46
- case 'all':
47
- return { allowed: true }
48
-
49
- case 'authenticated':
50
- return _uid
51
- ? { allowed: true }
52
- : { allowed: false, reason: 'Authentication required' }
53
-
54
- case 'admin':
55
- return _roles?.includes('admin')
56
- ? { allowed: true }
57
- : { allowed: false, reason: 'Admin role required' }
58
-
59
- case 'author':
60
- return _roles?.includes('author')
61
- ? { allowed: true }
62
- : { allowed: false, reason: 'Author role required' }
63
-
64
- default:
65
- // owner:fieldName pattern
66
- if (accessRule.startsWith('owner:')) {
67
- const field = accessRule.slice(6)
68
- const checkDoc = doc || newData
69
- if (!_uid) {
70
- return { allowed: false, reason: 'Authentication required' }
71
- }
72
- if (checkDoc && checkDoc[field] === _uid) {
73
- return { allowed: true }
74
- }
75
- if (!doc && newData && newData[field] === _uid) {
76
- return { allowed: true }
77
- }
78
- return { allowed: false, reason: `Must be owner (${field})` }
79
- }
80
-
81
- // role:roleName pattern
82
- if (accessRule.startsWith('role:')) {
83
- const role = accessRule.slice(5)
84
- return _roles?.includes(role)
85
- ? { allowed: true }
86
- : { allowed: false, reason: `Role '${role}' required` }
87
- }
88
-
89
- return null // Not a recognized shortcut
90
- }
91
- }
92
- evaluateAccessShortcut.__tjs = {
93
- params: {
94
- accessRule: {
95
- type: {
96
- kind: 'any',
97
- },
98
- required: false,
99
- },
100
- context: {
101
- type: {
102
- kind: 'any',
103
- },
104
- required: false,
105
- },
106
- },
107
- unsafe: true,
108
- source: 'rules.tjs:37',
109
- }
110
-
111
- /*#
112
- ## Select Access Rule
113
-
114
- Determines which access rule to use based on the operation method.
115
- Supports granular rules (read/create/update/delete) with fallbacks.
116
- */
117
- export function selectAccessRule(rule, context) {
118
- const { _method, doc } = context
119
-
120
- // Method-specific rules with fallbacks
121
- if (_method === 'read' && rule.read !== undefined) {
122
- return rule.read
123
- }
124
-
125
- if (_method === 'write') {
126
- // Distinguish create vs update
127
- if (!doc && rule.create !== undefined) {
128
- return rule.create
129
- }
130
- if (doc && rule.update !== undefined) {
131
- return rule.update
132
- }
133
- if (rule.write !== undefined) {
134
- return rule.write
135
- }
136
- }
137
-
138
- if (_method === 'delete' && rule.delete !== undefined) {
139
- return rule.delete
140
- }
141
-
142
- // Fall back to code for backwards compatibility
143
- return rule.code
144
- }
145
- selectAccessRule.__tjs = {
146
- params: {
147
- rule: {
148
- type: {
149
- kind: 'any',
150
- },
151
- required: false,
152
- },
153
- context: {
154
- type: {
155
- kind: 'any',
156
- },
157
- required: false,
158
- },
159
- },
160
- unsafe: true,
161
- source: 'rules.tjs:99',
162
- }
163
-
164
- /*#
165
- ## Validate Schema
166
-
167
- Simple schema validation for write operations.
168
- Checks required fields and basic types.
169
- */
170
- export function validateSchema(schema, data) {
171
- const errors = []
172
-
173
- if (!schema || typeof schema !== 'object') {
174
- return { valid: true, errors: [] }
175
- }
176
-
177
- // Check required fields
178
- if (schema.required && Array.isArray(schema.required)) {
179
- for (const field of schema.required) {
180
- if (data[field] === undefined || data[field] === null) {
181
- errors.push(`Missing required field: ${field}`)
182
- }
183
- }
184
- }
185
-
186
- // Check field types
187
- if (schema.properties && typeof schema.properties === 'object') {
188
- for (const [field, spec] of Object.entries(schema.properties)) {
189
- const value = data[field]
190
- if (value === undefined) continue // Skip missing optional fields
191
-
192
- const expectedType = spec.type
193
- if (expectedType) {
194
- const actualType = Array.isArray(value) ? 'array' : typeof value
195
- if (actualType !== expectedType) {
196
- errors.push(
197
- `Field '${field}' expected ${expectedType}, got ${actualType}`
198
- )
199
- }
200
- }
201
- }
202
- }
203
-
204
- return {
205
- valid: errors.length === 0,
206
- errors,
207
- }
208
- }
209
- validateSchema.__tjs = {
210
- params: {
211
- schema: {
212
- type: {
213
- kind: 'any',
214
- },
215
- required: false,
216
- },
217
- data: {
218
- type: {
219
- kind: 'any',
220
- },
221
- required: false,
222
- },
223
- },
224
- unsafe: true,
225
- source: 'rules.tjs:134',
226
- }
227
-
228
- /*#
229
- ## Evaluate Rule Result
230
-
231
- Interprets the result of a rule evaluation (boolean, object, etc.)
232
- into a standardized { allowed, reason } format.
233
- */
234
- export function interpretRuleResult(result) {
235
- if (typeof result === 'boolean') {
236
- return { allowed: result, reason: result ? null : 'Access denied' }
237
- }
238
-
239
- if (typeof result === 'object' && result !== null) {
240
- return {
241
- allowed: !!result.allow,
242
- reason: result.reason || (result.allow ? null : 'Access denied'),
243
- }
244
- }
245
-
246
- // Truthy/falsy fallback
247
- return { allowed: !!result, reason: result ? null : 'Access denied' }
248
- }
249
- interpretRuleResult.__tjs = {
250
- params: {
251
- result: {
252
- type: {
253
- kind: 'any',
254
- },
255
- required: false,
256
- },
257
- },
258
- unsafe: true,
259
- source: 'rules.tjs:178',
260
- }
261
-
262
- /*#
263
- ## Check Role Hierarchy
264
-
265
- Evaluates if a user has sufficient role level.
266
- Role hierarchy: admin > author > user > guest
267
- */
268
- const ROLE_LEVELS = {
269
- admin: 100,
270
- author: 50,
271
- editor: 40,
272
- user: 10,
273
- guest: 0,
274
- }
275
-
276
- export function hasRoleLevel(userRoles, requiredRole) {
277
- const requiredLevel = ROLE_LEVELS[requiredRole] ?? 0
278
-
279
- for (const role of userRoles || []) {
280
- const level = ROLE_LEVELS[role] ?? 0
281
- if (level >= requiredLevel) {
282
- return true
283
- }
284
- }
285
-
286
- return false
287
- }
288
- hasRoleLevel.__tjs = {
289
- params: {
290
- userRoles: {
291
- type: {
292
- kind: 'any',
293
- },
294
- required: false,
295
- },
296
- requiredRole: {
297
- type: {
298
- kind: 'any',
299
- },
300
- required: false,
301
- },
302
- },
303
- unsafe: true,
304
- source: 'rules.tjs:208',
305
- }
306
-
307
- /*#
308
- ## Build Rule Context
309
-
310
- Creates the evaluation context for a security rule.
311
- */
312
- export function buildRuleContext(options) {
313
- const { uid, roles, method, collection, docId, doc, newData } = options
314
-
315
- return {
316
- _uid: uid || null,
317
- _roles: roles || [],
318
- _isAdmin: roles?.includes('admin') || false,
319
- _isAuthor: roles?.includes('author') || false,
320
- _method: method,
321
- _collection: collection,
322
- _docId: docId,
323
- doc: doc || null,
324
- newData: newData || null,
325
- }
326
- }
327
- buildRuleContext.__tjs = {
328
- params: {
329
- options: {
330
- type: {
331
- kind: 'any',
332
- },
333
- required: false,
334
- },
335
- },
336
- unsafe: true,
337
- source: 'rules.tjs:226',
338
- }