@almadar/core 9.10.1 → 9.10.3
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/dist/builders.d.ts +2 -2
- package/dist/{compose-behaviors-DpYntG1r.d.ts → compose-behaviors-Dr2YbJmP.d.ts} +1 -1
- package/dist/factory/index.js +3 -0
- package/dist/factory/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -2
- package/src/types/bindings.ts +171 -0
- package/dist/{schema-kJUA2M6H.d.ts → schema-CMrW_N4k.d.ts} +120 -120
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almadar/core",
|
|
3
|
-
"version": "9.10.
|
|
3
|
+
"version": "9.10.3",
|
|
4
4
|
"description": "Core schema types and definitions for Almadar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
|
-
"dist"
|
|
37
|
+
"dist",
|
|
38
|
+
"src/types/bindings.ts"
|
|
38
39
|
],
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"registry": "https://registry.npmjs.org",
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-Expression Bindings
|
|
3
|
+
*
|
|
4
|
+
* Defines binding types and utilities for S-expression context.
|
|
5
|
+
* Bindings are references to values that are resolved at runtime.
|
|
6
|
+
*
|
|
7
|
+
* Core Bindings:
|
|
8
|
+
* - @entity - The linked entity for this trait (e.g., @entity.health)
|
|
9
|
+
* - @payload - Event payload data (e.g., @payload.amount)
|
|
10
|
+
* - @state - Current state machine state
|
|
11
|
+
* - @now - Current timestamp (Date.now())
|
|
12
|
+
*
|
|
13
|
+
* Entity Bindings:
|
|
14
|
+
* - @EntityName.field - Reference to singleton/runtime entity (e.g., @GameConfig.gravity)
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
|
|
21
|
+
// Re-export core binding utilities from expression.ts
|
|
22
|
+
export {
|
|
23
|
+
isBinding,
|
|
24
|
+
parseBinding,
|
|
25
|
+
isValidBinding,
|
|
26
|
+
CORE_BINDINGS,
|
|
27
|
+
type CoreBinding,
|
|
28
|
+
type ParsedBinding,
|
|
29
|
+
} from './expression.js';
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Binding Schema
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Schema for a binding string.
|
|
37
|
+
* Validates that the string starts with @ and has valid format.
|
|
38
|
+
*/
|
|
39
|
+
export const BindingSchema = z.string().refine(
|
|
40
|
+
(val) => {
|
|
41
|
+
if (!val.startsWith('@')) return false;
|
|
42
|
+
const parts = val.slice(1).split('.');
|
|
43
|
+
if (parts.length === 0 || parts[0] === '') return false;
|
|
44
|
+
// All parts must be valid identifiers (letters, numbers, underscore)
|
|
45
|
+
// supporting unicode characters for i18n
|
|
46
|
+
return parts.every((part) => /^[\p{L}_][\p{L}0-9_]*$/u.test(part));
|
|
47
|
+
},
|
|
48
|
+
{ message: 'Invalid binding format. Must be @name or @name.path.to.field' }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Binding Constants
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Binding documentation for LLM prompts and validation messages.
|
|
57
|
+
*
|
|
58
|
+
* IMPORTANT: Keep synchronized with CORE_BINDINGS in expression.ts
|
|
59
|
+
*/
|
|
60
|
+
export const BINDING_DOCS = {
|
|
61
|
+
entity: {
|
|
62
|
+
description: 'Reference to the linked entity for this trait',
|
|
63
|
+
examples: ['@entity.health', '@entity.x', '@entity.status'],
|
|
64
|
+
requiresPath: true,
|
|
65
|
+
},
|
|
66
|
+
payload: {
|
|
67
|
+
description: 'Reference to the event payload data',
|
|
68
|
+
examples: ['@payload.amount', '@payload.targetId', '@payload.action'],
|
|
69
|
+
requiresPath: true,
|
|
70
|
+
},
|
|
71
|
+
state: {
|
|
72
|
+
description: 'Current state machine state name',
|
|
73
|
+
examples: ['@state'],
|
|
74
|
+
requiresPath: false,
|
|
75
|
+
},
|
|
76
|
+
now: {
|
|
77
|
+
description: 'Current timestamp in milliseconds',
|
|
78
|
+
examples: ['@now'],
|
|
79
|
+
requiresPath: false,
|
|
80
|
+
},
|
|
81
|
+
config: {
|
|
82
|
+
description: 'Trait configuration values',
|
|
83
|
+
examples: ['@config.apiEndpoint', '@config.theme'],
|
|
84
|
+
requiresPath: true,
|
|
85
|
+
},
|
|
86
|
+
computed: {
|
|
87
|
+
description: 'Computed/calculated values',
|
|
88
|
+
examples: ['@computed.total', '@computed.isValid'],
|
|
89
|
+
requiresPath: true,
|
|
90
|
+
},
|
|
91
|
+
trait: {
|
|
92
|
+
description: 'Trait context data',
|
|
93
|
+
examples: ['@trait.name', '@trait.category'],
|
|
94
|
+
requiresPath: true,
|
|
95
|
+
},
|
|
96
|
+
user: {
|
|
97
|
+
description: 'Authenticated user / agent context for ownership and role-based gating',
|
|
98
|
+
examples: ['@user.id', '@user.role'],
|
|
99
|
+
requiresPath: true,
|
|
100
|
+
},
|
|
101
|
+
} as const;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validation rules for bindings in different contexts.
|
|
105
|
+
*/
|
|
106
|
+
export const BINDING_CONTEXT_RULES = {
|
|
107
|
+
guard: {
|
|
108
|
+
allowed: ['entity', 'payload', 'state', 'now', 'config', 'user'] as const,
|
|
109
|
+
description:
|
|
110
|
+
'Guards can access entity fields, event payload, current state, time, the call-site trait config (@config.X), and the authenticated user context (@user.id, @user.role) for ownership / role gates. Config access lets atoms write mode-aware guards — e.g. std-modal\'s OPEN can require @payload.row only when @config.mode equals "edit", letting create-mode legitimately fire OPEN with no row. Like effects, @config.X is substituted at molecule/organism inline time with the literal call-site value; at atom-scope validate, @config is allowed-but-unresolved.',
|
|
111
|
+
},
|
|
112
|
+
effect: {
|
|
113
|
+
allowed: ['entity', 'payload', 'state', 'now', 'trait', 'config', 'user'] as const,
|
|
114
|
+
description:
|
|
115
|
+
'Effects can access and modify entity fields, use payload data, embed another trait\'s live frame via @trait.X inside render-ui children, read trait config values (@config.X) for atoms parameterized by their call-site, and read the authenticated user context (@user.id, @user.role). At molecule/organism inline time, @config.X is substituted with the literal value from the call-site config block; at atom-scope validate, @config is allowed-but-unresolved.',
|
|
116
|
+
},
|
|
117
|
+
tick: {
|
|
118
|
+
allowed: ['entity', 'state', 'now', 'config', 'user'] as const,
|
|
119
|
+
description: 'Ticks can access entity fields, current state, time, trait config (@config.X) for parameterized atoms, and the authenticated user context (@user.id, @user.role). Same substitution semantics as guards/effects.',
|
|
120
|
+
},
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
export type BindingContext = keyof typeof BINDING_CONTEXT_RULES;
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Binding Validation Helpers
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if a binding is valid in a given context.
|
|
131
|
+
*
|
|
132
|
+
* @param binding - Parsed binding
|
|
133
|
+
* @param context - Context where binding is used
|
|
134
|
+
* @returns Error message if invalid, null if valid
|
|
135
|
+
*/
|
|
136
|
+
export function validateBindingInContext(
|
|
137
|
+
binding: { type: 'core' | 'entity'; root: string },
|
|
138
|
+
context: BindingContext
|
|
139
|
+
): string | null {
|
|
140
|
+
const rules = BINDING_CONTEXT_RULES[context];
|
|
141
|
+
|
|
142
|
+
if (binding.type === 'core') {
|
|
143
|
+
if (!(rules.allowed as readonly string[]).includes(binding.root)) {
|
|
144
|
+
return `Binding @${binding.root} is not allowed in ${context} context. Allowed: ${rules.allowed.join(', ')}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Entity bindings are always allowed (they reference singletons/runtime entities)
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get all valid binding examples for a context.
|
|
154
|
+
*
|
|
155
|
+
* @param context - Context to get examples for
|
|
156
|
+
* @returns Array of example binding strings
|
|
157
|
+
*/
|
|
158
|
+
export function getBindingExamples(context: BindingContext): string[] {
|
|
159
|
+
const rules = BINDING_CONTEXT_RULES[context];
|
|
160
|
+
const examples: string[] = [];
|
|
161
|
+
|
|
162
|
+
for (const binding of rules.allowed) {
|
|
163
|
+
const doc = BINDING_DOCS[binding];
|
|
164
|
+
examples.push(...doc.examples);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add entity binding example
|
|
168
|
+
examples.push('@GameConfig.gravity', '@Filter.searchTerm');
|
|
169
|
+
|
|
170
|
+
return examples;
|
|
171
|
+
}
|