tjs-lang 0.7.7 → 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.
- package/CLAUDE.md +90 -33
- package/bin/docs.js +4 -1
- package/demo/docs.json +45 -11
- package/demo/src/examples.test.ts +1 -0
- package/demo/src/imports.test.ts +16 -4
- package/demo/src/imports.ts +60 -15
- package/demo/src/playground-shared.ts +9 -8
- package/demo/src/tfs-worker.js +205 -147
- package/demo/src/tjs-playground.ts +34 -10
- package/demo/src/ts-playground.ts +24 -8
- package/dist/index.js +118 -101
- package/dist/index.js.map +4 -4
- package/dist/src/lang/bool-coercion.d.ts +50 -0
- package/dist/src/lang/docs.d.ts +31 -6
- package/dist/src/lang/linter.d.ts +8 -0
- package/dist/src/lang/parser-transforms.d.ts +18 -0
- package/dist/src/lang/parser-types.d.ts +2 -0
- package/dist/src/lang/parser.d.ts +3 -0
- package/dist/src/lang/runtime.d.ts +34 -0
- package/dist/src/lang/types.d.ts +9 -1
- package/dist/src/rbac/index.d.ts +1 -1
- package/dist/src/vm/runtime.d.ts +1 -1
- package/dist/tjs-eval.js +38 -36
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +20 -20
- package/dist/tjs-from-ts.js.map +3 -3
- package/dist/tjs-lang.js +85 -83
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-vm.js +47 -45
- package/dist/tjs-vm.js.map +4 -4
- package/llms.txt +79 -0
- package/package.json +3 -2
- package/src/cli/commands/convert.test.ts +16 -21
- package/src/lang/bool-coercion.test.ts +203 -0
- package/src/lang/bool-coercion.ts +314 -0
- package/src/lang/codegen.test.ts +137 -0
- package/src/lang/docs.test.ts +328 -1
- package/src/lang/docs.ts +424 -24
- package/src/lang/emitters/ast.ts +11 -12
- package/src/lang/emitters/dts.test.ts +41 -0
- package/src/lang/emitters/dts.ts +9 -0
- package/src/lang/emitters/js-tests.ts +9 -4
- package/src/lang/emitters/js.ts +182 -2
- package/src/lang/inference.ts +54 -0
- package/src/lang/linter.test.ts +104 -1
- package/src/lang/linter.ts +124 -1
- package/src/lang/parser-params.ts +31 -0
- package/src/lang/parser-transforms.ts +304 -0
- package/src/lang/parser-types.ts +2 -0
- package/src/lang/parser.test.ts +73 -1
- package/src/lang/parser.ts +34 -1
- package/src/lang/runtime.ts +98 -0
- package/src/lang/types.ts +6 -0
- package/src/rbac/index.ts +2 -2
- package/src/rbac/rules.tjs.d.ts +9 -0
- package/src/vm/atoms/batteries.ts +2 -2
- package/src/vm/runtime.ts +10 -3
- package/dist/src/rbac/rules.d.ts +0 -184
- package/src/rbac/rules.js +0 -338
package/src/rbac/index.ts
CHANGED
|
@@ -46,7 +46,7 @@ export {
|
|
|
46
46
|
interpretRuleResult,
|
|
47
47
|
hasRoleLevel,
|
|
48
48
|
buildRuleContext,
|
|
49
|
-
} from './rules.
|
|
49
|
+
} from './rules.tjs'
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Security rule definition
|
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
validateSchema,
|
|
126
126
|
buildRuleContext,
|
|
127
127
|
interpretRuleResult,
|
|
128
|
-
} from './rules.
|
|
128
|
+
} from './rules.tjs'
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* Create an RBAC instance with a store backend
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Ambient declarations for rules.tjs. The bun plugin (bunfig.toml)
|
|
2
|
+
// transpiles .tjs at runtime; tsc needs explicit names to resolve the
|
|
3
|
+
// `from './rules.tjs'` imports in index.ts.
|
|
4
|
+
export function evaluateAccessShortcut(accessRule: any, context: any): any
|
|
5
|
+
export function selectAccessRule(rule: any, context: any): any
|
|
6
|
+
export function validateSchema(schema: any, data: any): any
|
|
7
|
+
export function interpretRuleResult(result: any): any
|
|
8
|
+
export function hasRoleLevel(userRoles: any, requiredRole: any): any
|
|
9
|
+
export function buildRuleContext(options: any): any
|
|
@@ -25,7 +25,7 @@ interface StoreBattery {
|
|
|
25
25
|
interface LLMBattery {
|
|
26
26
|
predict(
|
|
27
27
|
system: string,
|
|
28
|
-
user: string,
|
|
28
|
+
user: string | any[], // string for single-turn, message array for multi-turn
|
|
29
29
|
tools?: any[],
|
|
30
30
|
responseFormat?: any
|
|
31
31
|
): Promise<any>
|
|
@@ -140,7 +140,7 @@ export const llmPredictBattery = defineAtom(
|
|
|
140
140
|
'llmPredictBattery',
|
|
141
141
|
s.object({
|
|
142
142
|
system: s.string.optional,
|
|
143
|
-
user: s.string,
|
|
143
|
+
user: s.union([s.string, s.array(s.any)]), // string or message array for multi-turn
|
|
144
144
|
tools: s.array(s.any).optional,
|
|
145
145
|
responseFormat: s.any.optional,
|
|
146
146
|
}),
|
package/src/vm/runtime.ts
CHANGED
|
@@ -451,7 +451,9 @@ export type ExprNode =
|
|
|
451
451
|
| {
|
|
452
452
|
$expr: 'member'
|
|
453
453
|
object: ExprNode
|
|
454
|
-
|
|
454
|
+
// string for static `obj.foo` and literal-indexed `arr[0]`;
|
|
455
|
+
// ExprNode for variable-indexed `arr[i]` (evaluated at runtime).
|
|
456
|
+
property: string | ExprNode
|
|
455
457
|
computed?: boolean
|
|
456
458
|
optional?: boolean
|
|
457
459
|
}
|
|
@@ -1132,8 +1134,12 @@ export function evaluateExpr(node: ExprNode, ctx: RuntimeContext): any {
|
|
|
1132
1134
|
return undefined
|
|
1133
1135
|
}
|
|
1134
1136
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
+
// Property is either a static string or a computed expression node (e.g. arr[i])
|
|
1138
|
+
const prop =
|
|
1139
|
+
typeof node.property === 'object' && node.property !== null
|
|
1140
|
+
? evaluateExpr(node.property, ctx)
|
|
1141
|
+
: node.property
|
|
1142
|
+
assertSafeProperty(String(prop))
|
|
1137
1143
|
|
|
1138
1144
|
return obj?.[prop]
|
|
1139
1145
|
}
|
|
@@ -1524,6 +1530,7 @@ export const whileLoop = defineAtom(
|
|
|
1524
1530
|
if ((ctx.fuel.current -= 0.1) <= 0) throw new Error('Out of Fuel')
|
|
1525
1531
|
await seq.exec({ op: 'seq', steps: step.body } as any, ctx)
|
|
1526
1532
|
if (ctx.output !== undefined) return
|
|
1533
|
+
if (ctx.error) return // Propagate monadic errors out of the loop
|
|
1527
1534
|
}
|
|
1528
1535
|
},
|
|
1529
1536
|
{ docs: 'While Loop', timeoutMs: 0, cost: 0.1 }
|
package/dist/src/rbac/rules.d.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
export function evaluateAccessShortcut(accessRule: any, context: any): {
|
|
2
|
-
allowed: boolean;
|
|
3
|
-
reason: string;
|
|
4
|
-
} | {
|
|
5
|
-
allowed: boolean;
|
|
6
|
-
reason?: undefined;
|
|
7
|
-
} | null;
|
|
8
|
-
export namespace evaluateAccessShortcut {
|
|
9
|
-
namespace __tjs {
|
|
10
|
-
namespace params {
|
|
11
|
-
namespace accessRule {
|
|
12
|
-
namespace type {
|
|
13
|
-
let kind: string;
|
|
14
|
-
}
|
|
15
|
-
let required: boolean;
|
|
16
|
-
}
|
|
17
|
-
namespace context {
|
|
18
|
-
export namespace type_1 {
|
|
19
|
-
let kind_1: string;
|
|
20
|
-
export { kind_1 as kind };
|
|
21
|
-
}
|
|
22
|
-
export { type_1 as type };
|
|
23
|
-
let required_1: boolean;
|
|
24
|
-
export { required_1 as required };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
let unsafe: boolean;
|
|
28
|
-
let source: string;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
export function selectAccessRule(rule: any, context: any): any;
|
|
32
|
-
export namespace selectAccessRule {
|
|
33
|
-
export namespace __tjs_1 {
|
|
34
|
-
export namespace params_1 {
|
|
35
|
-
export namespace rule {
|
|
36
|
-
export namespace type_2 {
|
|
37
|
-
let kind_2: string;
|
|
38
|
-
export { kind_2 as kind };
|
|
39
|
-
}
|
|
40
|
-
export { type_2 as type };
|
|
41
|
-
let required_2: boolean;
|
|
42
|
-
export { required_2 as required };
|
|
43
|
-
}
|
|
44
|
-
export namespace context_1 {
|
|
45
|
-
export namespace type_3 {
|
|
46
|
-
let kind_3: string;
|
|
47
|
-
export { kind_3 as kind };
|
|
48
|
-
}
|
|
49
|
-
export { type_3 as type };
|
|
50
|
-
let required_3: boolean;
|
|
51
|
-
export { required_3 as required };
|
|
52
|
-
}
|
|
53
|
-
export { context_1 as context };
|
|
54
|
-
}
|
|
55
|
-
export { params_1 as params };
|
|
56
|
-
let unsafe_1: boolean;
|
|
57
|
-
export { unsafe_1 as unsafe };
|
|
58
|
-
let source_1: string;
|
|
59
|
-
export { source_1 as source };
|
|
60
|
-
}
|
|
61
|
-
export { __tjs_1 as __tjs };
|
|
62
|
-
}
|
|
63
|
-
export function validateSchema(schema: any, data: any): {
|
|
64
|
-
valid: boolean;
|
|
65
|
-
errors: string[];
|
|
66
|
-
};
|
|
67
|
-
export namespace validateSchema {
|
|
68
|
-
export namespace __tjs_2 {
|
|
69
|
-
export namespace params_2 {
|
|
70
|
-
namespace schema {
|
|
71
|
-
export namespace type_4 {
|
|
72
|
-
let kind_4: string;
|
|
73
|
-
export { kind_4 as kind };
|
|
74
|
-
}
|
|
75
|
-
export { type_4 as type };
|
|
76
|
-
let required_4: boolean;
|
|
77
|
-
export { required_4 as required };
|
|
78
|
-
}
|
|
79
|
-
namespace data {
|
|
80
|
-
export namespace type_5 {
|
|
81
|
-
let kind_5: string;
|
|
82
|
-
export { kind_5 as kind };
|
|
83
|
-
}
|
|
84
|
-
export { type_5 as type };
|
|
85
|
-
let required_5: boolean;
|
|
86
|
-
export { required_5 as required };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
export { params_2 as params };
|
|
90
|
-
let unsafe_2: boolean;
|
|
91
|
-
export { unsafe_2 as unsafe };
|
|
92
|
-
let source_2: string;
|
|
93
|
-
export { source_2 as source };
|
|
94
|
-
}
|
|
95
|
-
export { __tjs_2 as __tjs };
|
|
96
|
-
}
|
|
97
|
-
export function interpretRuleResult(result: any): {
|
|
98
|
-
allowed: boolean;
|
|
99
|
-
reason: any;
|
|
100
|
-
};
|
|
101
|
-
export namespace interpretRuleResult {
|
|
102
|
-
export namespace __tjs_3 {
|
|
103
|
-
export namespace params_3 {
|
|
104
|
-
namespace result {
|
|
105
|
-
export namespace type_6 {
|
|
106
|
-
let kind_6: string;
|
|
107
|
-
export { kind_6 as kind };
|
|
108
|
-
}
|
|
109
|
-
export { type_6 as type };
|
|
110
|
-
let required_6: boolean;
|
|
111
|
-
export { required_6 as required };
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
export { params_3 as params };
|
|
115
|
-
let unsafe_3: boolean;
|
|
116
|
-
export { unsafe_3 as unsafe };
|
|
117
|
-
let source_3: string;
|
|
118
|
-
export { source_3 as source };
|
|
119
|
-
}
|
|
120
|
-
export { __tjs_3 as __tjs };
|
|
121
|
-
}
|
|
122
|
-
export function hasRoleLevel(userRoles: any, requiredRole: any): boolean;
|
|
123
|
-
export namespace hasRoleLevel {
|
|
124
|
-
export namespace __tjs_4 {
|
|
125
|
-
export namespace params_4 {
|
|
126
|
-
namespace userRoles {
|
|
127
|
-
export namespace type_7 {
|
|
128
|
-
let kind_7: string;
|
|
129
|
-
export { kind_7 as kind };
|
|
130
|
-
}
|
|
131
|
-
export { type_7 as type };
|
|
132
|
-
let required_7: boolean;
|
|
133
|
-
export { required_7 as required };
|
|
134
|
-
}
|
|
135
|
-
namespace requiredRole {
|
|
136
|
-
export namespace type_8 {
|
|
137
|
-
let kind_8: string;
|
|
138
|
-
export { kind_8 as kind };
|
|
139
|
-
}
|
|
140
|
-
export { type_8 as type };
|
|
141
|
-
let required_8: boolean;
|
|
142
|
-
export { required_8 as required };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
export { params_4 as params };
|
|
146
|
-
let unsafe_4: boolean;
|
|
147
|
-
export { unsafe_4 as unsafe };
|
|
148
|
-
let source_4: string;
|
|
149
|
-
export { source_4 as source };
|
|
150
|
-
}
|
|
151
|
-
export { __tjs_4 as __tjs };
|
|
152
|
-
}
|
|
153
|
-
export function buildRuleContext(options: any): {
|
|
154
|
-
_uid: any;
|
|
155
|
-
_roles: any;
|
|
156
|
-
_isAdmin: any;
|
|
157
|
-
_isAuthor: any;
|
|
158
|
-
_method: any;
|
|
159
|
-
_collection: any;
|
|
160
|
-
_docId: any;
|
|
161
|
-
doc: any;
|
|
162
|
-
newData: any;
|
|
163
|
-
};
|
|
164
|
-
export namespace buildRuleContext {
|
|
165
|
-
export namespace __tjs_5 {
|
|
166
|
-
export namespace params_5 {
|
|
167
|
-
namespace options {
|
|
168
|
-
export namespace type_9 {
|
|
169
|
-
let kind_9: string;
|
|
170
|
-
export { kind_9 as kind };
|
|
171
|
-
}
|
|
172
|
-
export { type_9 as type };
|
|
173
|
-
let required_9: boolean;
|
|
174
|
-
export { required_9 as required };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
export { params_5 as params };
|
|
178
|
-
let unsafe_5: boolean;
|
|
179
|
-
export { unsafe_5 as unsafe };
|
|
180
|
-
let source_5: string;
|
|
181
|
-
export { source_5 as source };
|
|
182
|
-
}
|
|
183
|
-
export { __tjs_5 as __tjs };
|
|
184
|
-
}
|
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
|
-
}
|