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.
- package/lib/manifest.appwrite.data.js +121 -1
- package/lib/manifest.data.js +12 -7
- package/lib/manifest.integrity.json +3 -3
- package/lib/manifest.schema.json +22 -0
- package/lib/manifest.sw.js +1 -1
- package/package.json +2 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.
|
|
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
|
})();
|
package/lib/manifest.data.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.
|
|
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:
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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",
|
package/lib/manifest.schema.json
CHANGED
|
@@ -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.",
|
package/lib/manifest.sw.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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"
|