@almadar/core 9.10.1 → 9.10.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/core",
3
- "version": "9.10.1",
3
+ "version": "9.10.2",
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
+ }