mnfst 0.5.274 → 0.5.275

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.274';
5
+ const MANIFEST_BUILD_VERSION = '0.5.275';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -1731,4 +1731,124 @@ window.ManifestDataPagination = {
1731
1731
  };
1732
1732
 
1733
1733
 
1734
+ /* Manifest Data Sources - Contract checks */
1735
+
1736
+ // Rows of Appwrite sources whose table the contract declares throw when app code
1737
+ // reads a column that is undeclared and never seen on a row. Dev hosts by default.
1738
+ (function () {
1739
+ const proxies = new WeakMap();
1740
+ const targets = new WeakMap();
1741
+ const warned = new Set();
1742
+ // Read by the framework on every row; storage columns are added per source
1743
+ const ALLOWED = new Set(['then', 'catch', 'finally', 'toJSON', 'length', 'contentType', 'fileIds']);
1744
+ let enabled;
1745
+ let tables = null;
1746
+ const tableBySource = new Map();
1747
+ const seenBySource = new Map();
1748
+ let appwriteSources = null;
1749
+
1750
+ function devHost(h) {
1751
+ return h === 'localhost' || h === '127.0.0.1' || h === '[::1]' || h === '::1' || h === '0.0.0.0' ||
1752
+ h.endsWith('.localhost') || h.endsWith('.local') || h.endsWith('.test');
1753
+ }
1754
+
1755
+ function hostMatches(pattern, host) {
1756
+ const re = new RegExp('^' + String(pattern).toLowerCase().replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^.]*') + '$');
1757
+ return re.test(host);
1758
+ }
1759
+
1760
+ function config(manifest) {
1761
+ const c = manifest && manifest.contract;
1762
+ if (typeof c === 'string') return { module: c, checks: 'dev' };
1763
+ if (c && typeof c === 'object' && typeof c.module === 'string') return { module: c.module, checks: c.checks === undefined ? 'dev' : c.checks };
1764
+ return null;
1765
+ }
1766
+
1767
+ function decide(manifest) {
1768
+ const cfg = config(manifest);
1769
+ if (!cfg || cfg.checks === false) return false;
1770
+ const host = String(location.hostname || '').toLowerCase();
1771
+ if (cfg.checks === true) return cfg;
1772
+ if (devHost(host)) return cfg;
1773
+ return Array.isArray(cfg.checks) && cfg.checks.some(p => hostMatches(p, host)) ? cfg : false;
1774
+ }
1775
+
1776
+ function load(manifest, cfg) {
1777
+ import(new URL(cfg.module, location.origin + '/').href).then(mod => {
1778
+ tables = mod && mod.tables && typeof mod.tables === 'object' ? mod.tables : {};
1779
+ const defaultDb = manifest.appwrite && (manifest.appwrite.databaseId || manifest.appwrite.database);
1780
+ for (const [source, src] of Object.entries(manifest.data || {})) {
1781
+ if (!src || typeof src.appwriteTableId !== 'string') continue;
1782
+ const db = src.appwriteDatabaseId || defaultDb;
1783
+ const hit = Object.entries(tables).find(([key, t]) => t && (t.id || key) === src.appwriteTableId && (!t.database || !db || t.database === db));
1784
+ if (!hit || !Array.isArray(hit[1].columns)) continue;
1785
+ const columns = new Set(hit[1].columns);
1786
+ for (const c of Object.values(src.storage || {})) columns.add(typeof c === 'string' ? c : (c && c.column) || 'fileIds');
1787
+ tableBySource.set(source, { key: hit[0], columns });
1788
+ }
1789
+ }).catch(e => {
1790
+ console.warn(`[Manifest] contract ${cfg.module} failed to load; column checks are off`, e);
1791
+ tables = {};
1792
+ });
1793
+ }
1794
+
1795
+ function isEnabled() {
1796
+ if (enabled !== undefined) return enabled;
1797
+ const manifest = window.__manifestLoaded;
1798
+ if (!manifest) return false;
1799
+ const cfg = decide(manifest);
1800
+ enabled = !!cfg;
1801
+ if (cfg) {
1802
+ appwriteSources = new Set(Object.entries(manifest.data || {}).filter(([, s]) => s && typeof s.appwriteTableId === 'string').map(([k]) => k));
1803
+ load(manifest, cfg);
1804
+ }
1805
+ return enabled;
1806
+ }
1807
+
1808
+ const known = (key, table) => key[0] === '$' || key[0] === '_' || ALLOWED.has(key) || table.columns.has(key);
1809
+
1810
+ function drift(source, table, row) {
1811
+ for (const key of Object.keys(row)) {
1812
+ if (known(key, table) || warned.has(source + '\0' + key)) continue;
1813
+ warned.add(source + '\0' + key);
1814
+ console.warn(`[Manifest] data source "${source}" has column "${key}", which the contract does not declare for "${table.key}"`);
1815
+ }
1816
+ }
1817
+
1818
+ function guard(source, row) {
1819
+ if (!row || typeof row !== 'object' || Array.isArray(row) || !isEnabled() || !appwriteSources.has(source)) return row;
1820
+ const existing = proxies.get(row);
1821
+ if (existing) return existing;
1822
+ let seen = seenBySource.get(source);
1823
+ if (!seen) seenBySource.set(source, seen = new Set());
1824
+ for (const key of Object.keys(row)) seen.add(key);
1825
+ let checked = false;
1826
+ const proxy = new Proxy(row, {
1827
+ // App reads arrive through Alpine's reactive proxy; its own old-value reads use this one
1828
+ get(target, key, receiver) {
1829
+ if (typeof key === 'string' && tables && receiver !== proxy) {
1830
+ const table = tableBySource.get(source);
1831
+ if (table) {
1832
+ if (!checked) { checked = true; drift(source, table, target); }
1833
+ if (!known(key, table) && !(key in target) && !seen.has(key)) {
1834
+ throw new Error(`[Manifest] "${key}" is not a column of "${table.key}" in the contract (data source "${source}")`);
1835
+ }
1836
+ }
1837
+ }
1838
+ return Reflect.get(target, key, receiver);
1839
+ }
1840
+ });
1841
+ proxies.set(row, proxy);
1842
+ proxies.set(proxy, proxy);
1843
+ targets.set(proxy, row);
1844
+ return proxy;
1845
+ }
1846
+
1847
+ // Framework reads go through the unchecked row
1848
+ const raw = (obj) => (obj && targets.get(obj)) || obj;
1849
+
1850
+ window.ManifestDataContract = { guard, raw };
1851
+ })();
1852
+
1853
+
1734
1854
  })();
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.274';
5
+ const MANIFEST_BUILD_VERSION = '0.5.275';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -397,7 +397,7 @@ function mergeRowFields(target, fields, dataSourceName) {
397
397
  if (key === '$files') continue;
398
398
  const value = fields[key];
399
399
  if (sameScalarOrList(raw[key], value)) continue;
400
- target[key] = createReactiveReferences(value, dataSourceName);
400
+ target[key] = createReactiveReferences(value, dataSourceName, true);
401
401
  }
402
402
  }
403
403
 
@@ -799,24 +799,25 @@ function getRawData(dataSourceName) {
799
799
  }
800
800
 
801
801
  // New object/array references (incl. nested arrays like fileIds) so Alpine tracks changes
802
- function createReactiveReferences(data, dataSourceName = null) {
802
+ function createReactiveReferences(data, dataSourceName = null, nested = false) {
803
803
  if (data === null || data === undefined) {
804
804
  return data;
805
805
  }
806
806
 
807
807
  if (Array.isArray(data)) {
808
808
  // Create new array with new references for each item
809
- return data.map(item => createReactiveReferences(item, dataSourceName));
809
+ return data.map(item => createReactiveReferences(item, dataSourceName, nested));
810
810
  }
811
811
 
812
812
  if (typeof data === 'object') {
813
813
  // Object.keys, not for…in + .hasOwnProperty (see deepSeal). Hot path for
814
814
  // every Appwrite mutation result and realtime event.
815
815
  const newObj = {};
816
+ let self = newObj;
816
817
  for (const key of Object.keys(data)) {
817
818
  const value = data[key];
818
819
  // Recursively create new references for nested objects/arrays
819
- newObj[key] = createReactiveReferences(value, dataSourceName);
820
+ newObj[key] = createReactiveReferences(value, dataSourceName, true);
820
821
  }
821
822
 
822
823
  // Detect file objects (have mimeType or sizeOriginal)
@@ -855,7 +856,7 @@ function createReactiveReferences(data, dataSourceName = null) {
855
856
  data.$id,
856
857
  bucketName,
857
858
  columnName,
858
- { owner: newObj }
859
+ { owner: self }
859
860
  );
860
861
 
861
862
  // Assign computed array to entry
@@ -1042,7 +1043,11 @@ function createReactiveReferences(data, dataSourceName = null) {
1042
1043
  });
1043
1044
  }
1044
1045
 
1045
- return newObj;
1046
+ // Contract checks wrap top-level Appwrite rows (dev and opted-in hosts)
1047
+ if (!nested && isDatabaseEntry && dataSourceName && window.ManifestDataContract) {
1048
+ self = window.ManifestDataContract.guard(dataSourceName, newObj);
1049
+ }
1050
+ return self;
1046
1051
  }
1047
1052
 
1048
1053
  // Primitives can be returned as-is
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest.appwrite.auth.js": "sha384-gWLSeVPqCLMiUZB8MU7GqMg3x2yULFUsp8nTTZrPp/9Dlkix42rBn9yIimvwrHqZ",
3
- "manifest.appwrite.data.js": "sha384-7gDP0QYT/sjPnPxKB0DahCW7wGTjVeKzqRHJSkqv4R4J8CtcmB7b6qTEsyK4iGoh",
3
+ "manifest.appwrite.data.js": "sha384-DL3DcEUoVXDqLwODzd/CBtnTyBPqeFl6RVx01oBv1lCwgMkNEGQeeKdA9l132m2m",
4
4
  "manifest.appwrite.presences.js": "sha384-LINPhIPTCK+rA8zd+HPUzMSEgH6Sl2vQMEyItT97bnEE2FYGYKqjXX+CWdt6nl9z",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
6
  "manifest.charts.js": "sha384-AFbP7mQnigy7DU1s+YE30yKTeQAKfgNazhbfSNbfd9ZpLgeulZKaPi/fAq3gJHDO",
@@ -11,7 +11,7 @@
11
11
  "manifest.combobox.js": "sha384-/lJo6vaxKjgnBSRs/dzkhK1dzGk3GBw8FISoNJZB70mwAVwu1k5veZyBXjOb4keS",
12
12
  "manifest.components.js": "sha384-1el5WMdgmzu/2ocaYN21OjLi/lfjllvA8aju1J3knYuOizw+XFfJU1+rvGqYW7qk",
13
13
  "manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
14
- "manifest.data.js": "sha384-mV2B+tijlZgcD/MFDLzw6w+6fGd0rZpGTTOJEyv6LJYwRydD3XtekFaMvZCWzudH",
14
+ "manifest.data.js": "sha384-pOXyFP7tUdQG4c52usKJkWYgGxMN5g2a6dWAKYGAJEHrJL4gZDYRNgOsSq/+jnTm",
15
15
  "manifest.datepicker.js": "sha384-EzlMwvIxlla/0BuyhAEqxN0M8TGcWIWgEIg840V0akKQQK1uY648wPWMkobvtq/o",
16
16
  "manifest.defer.js": "sha384-PRp7+dCZHnnl55rX2xrHutLrQnszHo7o5o9Tq28M/yjLZPuXDzcwQNxCdvbtCYSz",
17
17
  "manifest.device.js": "sha384-YeQaS56L8IILqrQNBSeAOLwIh5DuAOrE60cYLK1+46qJtUrXlAvqGe2j5mbV4Vq7",
@@ -29,7 +29,7 @@
29
29
  "manifest.status.js": "sha384-ofu7nBnVIGZ1U1o2R9d2H3V8aEMPUCVbl5FO3gIToXqi0oa36fTviEdh5UU7Kitw",
30
30
  "manifest.submissions.js": "sha384-v9eS47ytAfTGIKAyVnbd0QWegucVS7yduvigqfsxxoqXmbaEoy0M2y+BiI4xeZBd",
31
31
  "manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
32
- "manifest.sw.js": "sha384-tJoDnCGd8KNuPQSC2U45K3sFnVP+FKP/Qddzg4BoFsBjwhwQCk/FDo2i54aS/l+s",
32
+ "manifest.sw.js": "sha384-hyn8eZtfGN2VxD1+L4AxEyI7CQlPPguEkG5AgjB19A2Ca/W4HhOnTiF3QfoAMnLq",
33
33
  "manifest.tabs.js": "sha384-otU09H7jnawVKQdLAYpMCd3jx00tgyPVr42D2hLIBzN5dG4se505YesavdnMqEi1",
34
34
  "manifest.tailwind.js": "sha384-yuP3LEvWU+QVjxjkZ0YhErtC9RrGmhOmHjjFjhS9obqZbp5/KTIEnSR0CXDvHE0B",
35
35
  "manifest.toasts.js": "sha384-UpOz9fAdGeGUhL0NamBg5iPepEfIy73p9HYdcxfOQC5b7CVsl7Our0skdZzaLNcC",
@@ -137,6 +137,28 @@
137
137
  "type": "string"
138
138
  }
139
139
  },
140
+ "contract": {
141
+ "description": "Shared contract module, a path from the project root (e.g. /shared/contract.js), whose `tables` export declares Appwrite tables and columns. mnfst-test checks data sources against it; on dev hosts (or hosts listed in `checks`) app code reading a column of an Appwrite row that is undeclared and absent from every row of the source throws.",
142
+ "oneOf": [
143
+ { "type": "string" },
144
+ {
145
+ "type": "object",
146
+ "properties": {
147
+ "module": { "type": "string" },
148
+ "checks": {
149
+ "description": "Where column checks run: 'dev' (default, local hosts), true (everywhere), false (nowhere), or host patterns such as '*-staging.example.com' (dev hosts included).",
150
+ "oneOf": [
151
+ { "type": "boolean" },
152
+ { "type": "string", "enum": ["dev"] },
153
+ { "type": "array", "items": { "type": "string" } }
154
+ ]
155
+ }
156
+ },
157
+ "required": ["module"],
158
+ "additionalProperties": false
159
+ }
160
+ ]
161
+ },
140
162
  "data": {
141
163
  "type": "object",
142
164
  "description": "Named data sources made available via $x.<sourceName>. Each value is either a string path/URL or a config object.",
@@ -16,7 +16,7 @@
16
16
 
17
17
  // Build stamps the published version here; unstamped (dev) falls back to the
18
18
  // registration URL's `v`, then 'dev'.
19
- const BUILD_VERSION = '0.5.274';
19
+ const BUILD_VERSION = '0.5.275';
20
20
  const CACHE_PREFIX = 'mnfst-sw:';
21
21
  const META_CACHE = CACHE_PREFIX + 'meta';
22
22
  // Sized to hold a full precache.json warm-up (PRECACHE_MAX_ENTRIES server-side)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.274",
3
+ "version": "0.5.275",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",
@@ -53,6 +53,7 @@
53
53
  "test:e2e:test-kit": "node tests/e2e/test-kit-journeys.e2e.mjs",
54
54
  "test:e2e:submissions": "node tests/e2e/submissions.e2e.mjs",
55
55
  "test:e2e:modules": "node tests/e2e/modules.e2e.mjs",
56
+ "test:e2e:contract": "node tests/e2e/contract.e2e.mjs && node tests/e2e/contract-checks.e2e.mjs",
56
57
  "lint": "echo 'No linting configured'",
57
58
  "release:docs": "node scripts/release-bump.mjs packages/docs && cd packages/docs && npm publish --auth-type=web",
58
59
  "release:check": "node scripts/release-check.mjs"