@almadar/runtime 4.10.2 → 4.11.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.
@@ -1,6 +1,7 @@
1
1
  import { resolveBinding, evaluate, createMinimalContext, evaluateGuard } from '@almadar/evaluator';
2
2
  export { createMinimalContext } from '@almadar/evaluator';
3
3
  import { isKnownOperator } from '@almadar/std';
4
+ import { faker } from '@faker-js/faker';
4
5
  import { OrbitalSchemaSchema, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef } from '@almadar/core';
5
6
 
6
7
  // src/EventBus.ts
@@ -1476,6 +1477,286 @@ function createTestExecutor(overrides = {}) {
1476
1477
  debug: true
1477
1478
  });
1478
1479
  }
1480
+ var MockPersistenceAdapter = class {
1481
+ stores = /* @__PURE__ */ new Map();
1482
+ schemas = /* @__PURE__ */ new Map();
1483
+ idCounters = /* @__PURE__ */ new Map();
1484
+ config;
1485
+ constructor(config = {}) {
1486
+ this.config = {
1487
+ defaultSeedCount: 6,
1488
+ debug: false,
1489
+ ...config
1490
+ };
1491
+ if (config.seed !== void 0) {
1492
+ faker.seed(config.seed);
1493
+ if (this.config.debug) {
1494
+ console.log(`[MockPersistence] Using seed: ${config.seed}`);
1495
+ }
1496
+ }
1497
+ }
1498
+ // ============================================================================
1499
+ // Store Management
1500
+ // ============================================================================
1501
+ getStore(entityName) {
1502
+ const normalized = entityName.toLowerCase();
1503
+ if (!this.stores.has(normalized)) {
1504
+ this.stores.set(normalized, /* @__PURE__ */ new Map());
1505
+ this.idCounters.set(normalized, 0);
1506
+ }
1507
+ return this.stores.get(normalized);
1508
+ }
1509
+ nextId(entityName) {
1510
+ const normalized = entityName.toLowerCase();
1511
+ const counter = (this.idCounters.get(normalized) ?? 0) + 1;
1512
+ this.idCounters.set(normalized, counter);
1513
+ return `${this.capitalizeFirst(entityName)} Id ${counter}`;
1514
+ }
1515
+ // ============================================================================
1516
+ // Schema & Seeding
1517
+ // ============================================================================
1518
+ /**
1519
+ * Register an entity schema and seed mock data.
1520
+ * If the schema has seedData, those instances are used directly.
1521
+ * Otherwise, random mock data is generated with faker.
1522
+ */
1523
+ registerEntity(schema, seedCount) {
1524
+ const normalized = schema.name.toLowerCase();
1525
+ this.schemas.set(normalized, schema);
1526
+ if (schema.seedData && schema.seedData.length > 0) {
1527
+ this.seedFromInstances(schema.name, schema.seedData);
1528
+ } else {
1529
+ const count = seedCount ?? this.config.defaultSeedCount ?? 6;
1530
+ this.seed(schema.name, schema.fields, count);
1531
+ }
1532
+ }
1533
+ /**
1534
+ * Seed an entity with pre-authored instance data.
1535
+ */
1536
+ seedFromInstances(entityName, instances) {
1537
+ const store = this.getStore(entityName);
1538
+ if (this.config.debug) {
1539
+ console.log(`[MockPersistence] Seeding ${instances.length} ${entityName} from schema instances...`);
1540
+ }
1541
+ for (const instance of instances) {
1542
+ const id = instance.id || this.nextId(entityName);
1543
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1544
+ const item = {
1545
+ ...instance,
1546
+ id,
1547
+ createdAt: instance.createdAt || now,
1548
+ updatedAt: now
1549
+ };
1550
+ store.set(id, item);
1551
+ }
1552
+ }
1553
+ /**
1554
+ * Seed an entity with mock data.
1555
+ */
1556
+ seed(entityName, fields, count) {
1557
+ const store = this.getStore(entityName);
1558
+ const normalized = entityName.toLowerCase();
1559
+ if (this.config.debug) {
1560
+ console.log(`[MockPersistence] Seeding ${count} ${entityName}...`);
1561
+ }
1562
+ for (let i = 0; i < count; i++) {
1563
+ const item = this.generateMockItem(normalized, entityName, fields, i + 1);
1564
+ store.set(item.id, item);
1565
+ }
1566
+ }
1567
+ /**
1568
+ * Generate a single mock item based on field schemas.
1569
+ */
1570
+ generateMockItem(normalizedName, entityName, fields, index) {
1571
+ const id = this.nextId(entityName);
1572
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1573
+ const item = {
1574
+ id,
1575
+ createdAt: faker.date.past({ years: 1 }).toISOString(),
1576
+ updatedAt: now
1577
+ };
1578
+ for (const field of fields) {
1579
+ if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
1580
+ continue;
1581
+ }
1582
+ item[field.name] = this.generateFieldValue(entityName, field, index);
1583
+ }
1584
+ return item;
1585
+ }
1586
+ /**
1587
+ * Generate a mock value for a field based on its schema.
1588
+ */
1589
+ generateFieldValue(entityName, field, index) {
1590
+ if (field.default !== void 0) {
1591
+ if (field.default === "@now") {
1592
+ return (/* @__PURE__ */ new Date()).toISOString();
1593
+ }
1594
+ return field.default;
1595
+ }
1596
+ const fieldType = field.type.toLowerCase();
1597
+ switch (fieldType) {
1598
+ case "string":
1599
+ return this.generateStringValue(entityName, field, index);
1600
+ case "number":
1601
+ return faker.number.int({ min: 0, max: 100 });
1602
+ case "boolean":
1603
+ return faker.datatype.boolean();
1604
+ case "date":
1605
+ case "timestamp":
1606
+ case "datetime":
1607
+ return this.generateDateValue(field);
1608
+ case "enum":
1609
+ if (field.values && field.values.length > 0) {
1610
+ return faker.helpers.arrayElement(field.values);
1611
+ }
1612
+ return null;
1613
+ case "relation":
1614
+ return null;
1615
+ // Relations need special handling
1616
+ case "array":
1617
+ return [];
1618
+ case "object":
1619
+ return null;
1620
+ default:
1621
+ return this.generateStringValue(entityName, field, index);
1622
+ }
1623
+ }
1624
+ /**
1625
+ * Generate a string value based on the field's declared schema metadata.
1626
+ * Reads `values` (enum) first, then `format` (email/url/phone/uuid/date/
1627
+ * datetime), then falls back to faker.lorem.words. No field-name heuristics
1628
+ * — the schema is the source of truth. If a caller needs a real email, they
1629
+ * declare `format: "email"`; if they need an enum, they declare `values: [...]`.
1630
+ */
1631
+ generateStringValue(_entityName, field, _index) {
1632
+ if (field.values && field.values.length > 0) {
1633
+ return faker.helpers.arrayElement(field.values);
1634
+ }
1635
+ switch (field.format) {
1636
+ case "email":
1637
+ return faker.internet.email();
1638
+ case "url":
1639
+ return faker.internet.url();
1640
+ case "phone":
1641
+ return faker.phone.number();
1642
+ case "uuid":
1643
+ return faker.string.uuid();
1644
+ case "date":
1645
+ return faker.date.recent().toISOString().split("T")[0];
1646
+ case "datetime":
1647
+ return faker.date.recent().toISOString();
1648
+ default:
1649
+ return faker.lorem.words(2);
1650
+ }
1651
+ }
1652
+ /**
1653
+ * Generate a date value. Uses the field's `format` (date vs datetime) to
1654
+ * decide ISO shape; otherwise returns a recent ISO-8601 datetime. No
1655
+ * field-name heuristics.
1656
+ */
1657
+ generateDateValue(field) {
1658
+ const date = faker.date.recent({ days: 30 });
1659
+ if (field.format === "date") return date.toISOString().split("T")[0];
1660
+ return date.toISOString();
1661
+ }
1662
+ capitalizeFirst(str) {
1663
+ return str.charAt(0).toUpperCase() + str.slice(1);
1664
+ }
1665
+ // ============================================================================
1666
+ // PersistenceAdapter Implementation
1667
+ // ============================================================================
1668
+ async create(entityType, data) {
1669
+ const store = this.getStore(entityType);
1670
+ const id = this.nextId(entityType);
1671
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1672
+ const withDefaults = this.applyFieldDefaults(entityType, data);
1673
+ const item = {
1674
+ ...withDefaults,
1675
+ id,
1676
+ createdAt: now,
1677
+ updatedAt: now
1678
+ };
1679
+ store.set(id, item);
1680
+ return { id };
1681
+ }
1682
+ /**
1683
+ * Fill in any entity-declared field defaults that the caller omitted.
1684
+ * SAVE payloads coming from form-section only carry the fields the user
1685
+ * edited; persisted rows should still honor `field.default` so downstream
1686
+ * row-content probes (VG11f) see a row whose every declared-default field
1687
+ * is non-empty. `@now` resolves to the current ISO timestamp.
1688
+ */
1689
+ applyFieldDefaults(entityType, data) {
1690
+ const schema = this.schemas.get(entityType.toLowerCase());
1691
+ if (!schema) return data;
1692
+ const result = { ...data };
1693
+ for (const field of schema.fields) {
1694
+ if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") continue;
1695
+ if (result[field.name] !== void 0) continue;
1696
+ if (field.default === void 0) continue;
1697
+ result[field.name] = field.default === "@now" ? (/* @__PURE__ */ new Date()).toISOString() : field.default;
1698
+ }
1699
+ return result;
1700
+ }
1701
+ async update(entityType, id, data) {
1702
+ const store = this.getStore(entityType);
1703
+ const existing = store.get(id);
1704
+ if (!existing) {
1705
+ throw new Error(`Entity ${entityType} with id ${id} not found`);
1706
+ }
1707
+ const updated = {
1708
+ ...existing,
1709
+ ...data,
1710
+ id,
1711
+ // Preserve original ID
1712
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1713
+ };
1714
+ store.set(id, updated);
1715
+ }
1716
+ async delete(entityType, id) {
1717
+ const store = this.getStore(entityType);
1718
+ if (!store.has(id)) {
1719
+ throw new Error(`Entity ${entityType} with id ${id} not found`);
1720
+ }
1721
+ store.delete(id);
1722
+ }
1723
+ async getById(entityType, id) {
1724
+ const store = this.getStore(entityType);
1725
+ return store.get(id) ?? null;
1726
+ }
1727
+ async list(entityType) {
1728
+ const store = this.getStore(entityType);
1729
+ return Array.from(store.values());
1730
+ }
1731
+ // ============================================================================
1732
+ // Utilities
1733
+ // ============================================================================
1734
+ /**
1735
+ * Clear all data for an entity.
1736
+ */
1737
+ clear(entityName) {
1738
+ const normalized = entityName.toLowerCase();
1739
+ this.stores.delete(normalized);
1740
+ this.idCounters.delete(normalized);
1741
+ }
1742
+ /**
1743
+ * Clear all data.
1744
+ */
1745
+ clearAll() {
1746
+ this.stores.clear();
1747
+ this.idCounters.clear();
1748
+ }
1749
+ /**
1750
+ * Get count of items for an entity.
1751
+ */
1752
+ count(entityName) {
1753
+ const store = this.getStore(entityName);
1754
+ return store.size;
1755
+ }
1756
+ };
1757
+ function createMockPersistence(config) {
1758
+ return new MockPersistenceAdapter(config);
1759
+ }
1479
1760
 
1480
1761
  // src/loader/schema-loader.ts
1481
1762
  function isElectron() {
@@ -2843,6 +3124,70 @@ function parseNamespacedEvent(eventName) {
2843
3124
  return { event: eventName };
2844
3125
  }
2845
3126
 
2846
- export { EffectExecutor, EventBus, HANDLER_MANIFEST, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent };
2847
- //# sourceMappingURL=chunk-K36736GF.js.map
2848
- //# sourceMappingURL=chunk-K36736GF.js.map
3127
+ // src/PersistenceAdapter.ts
3128
+ var InMemoryPersistence = class {
3129
+ data = /* @__PURE__ */ new Map();
3130
+ idCounter = 0;
3131
+ /**
3132
+ * Seed the store with pre-existing rows.
3133
+ *
3134
+ * Accepts either a plain `Record<entityType, EntityRow[]>` or an iterable
3135
+ * of `[entityType, EntityRow[]]` entries. Rows without an `id` get one
3136
+ * generated at insert time; rows with an `id` keep it (so re-seeding
3137
+ * after a schema rebuild preserves identities used in render bindings).
3138
+ */
3139
+ seed(seedData) {
3140
+ const entries = Symbol.iterator in Object(seedData) ? seedData : Object.entries(seedData);
3141
+ for (const [entityType, rows] of entries) {
3142
+ if (!this.data.has(entityType)) {
3143
+ this.data.set(entityType, /* @__PURE__ */ new Map());
3144
+ }
3145
+ const collection = this.data.get(entityType);
3146
+ for (const row of rows) {
3147
+ const id = row.id || `${entityType}-${++this.idCounter}`;
3148
+ collection.set(id, { ...row, id });
3149
+ }
3150
+ }
3151
+ }
3152
+ async create(entityType, data) {
3153
+ const id = data.id || `${entityType}-${++this.idCounter}`;
3154
+ if (!this.data.has(entityType)) {
3155
+ this.data.set(entityType, /* @__PURE__ */ new Map());
3156
+ }
3157
+ this.data.get(entityType).set(id, { ...data, id });
3158
+ return { id };
3159
+ }
3160
+ async update(entityType, id, data) {
3161
+ const collection = this.data.get(entityType);
3162
+ if (collection?.has(id)) {
3163
+ const existing = collection.get(id);
3164
+ collection.set(id, { ...existing, ...data });
3165
+ }
3166
+ }
3167
+ async delete(entityType, id) {
3168
+ this.data.get(entityType)?.delete(id);
3169
+ }
3170
+ async getById(entityType, id) {
3171
+ return this.data.get(entityType)?.get(id) || null;
3172
+ }
3173
+ async list(entityType) {
3174
+ const collection = this.data.get(entityType);
3175
+ return collection ? Array.from(collection.values()) : [];
3176
+ }
3177
+ /**
3178
+ * Snapshot the entire store as a plain object (entityType → rows).
3179
+ * Useful for feeding a fresh render-time binding layer with the
3180
+ * current persistence view.
3181
+ */
3182
+ snapshot() {
3183
+ const out = {};
3184
+ for (const [entityType, collection] of this.data) {
3185
+ out[entityType] = Array.from(collection.values());
3186
+ }
3187
+ return out;
3188
+ }
3189
+ };
3190
+
3191
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent };
3192
+ //# sourceMappingURL=chunk-RCWMQH7Y.js.map
3193
+ //# sourceMappingURL=chunk-RCWMQH7Y.js.map