mnfst 0.5.248 → 0.5.249
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 +207 -49
- package/lib/manifest.data.js +19 -3
- package/lib/manifest.integrity.json +3 -3
- package/lib/manifest.schema.json +15 -1
- package/lib/manifest.sw.js +1 -1
- package/package.json +1 -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.249';
|
|
6
6
|
|
|
7
7
|
/* Manifest Data Sources - Appwrite Integration */
|
|
8
8
|
|
|
@@ -683,17 +683,40 @@ window.ManifestDataAppwrite = {
|
|
|
683
683
|
|
|
684
684
|
|
|
685
685
|
/* Manifest Data Sources - Real-time Subscriptions
|
|
686
|
-
One shared Realtime client per tab;
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
686
|
+
One shared Realtime client per tab; subscriptions ref-counted by
|
|
687
|
+
(channel + canonical server-filter set) across sources.
|
|
688
|
+
|
|
689
|
+
Appwrite Realtime queries (server-side event filtering), verified against
|
|
690
|
+
the Web SDK and server source 2026-10:
|
|
691
|
+
- Realtime#subscribe(channels, callback, queries?) — queries are per
|
|
692
|
+
subscription (Query strings, ANDed; OR across subscriptions), supported
|
|
693
|
+
ops: equal/notEqual/lessThan(Equal)/greaterThan(Equal)/isNull/isNotNull/
|
|
694
|
+
and/or. SDK >=25.0.0 sends {type:'subscribe', data:[{subscriptionId,
|
|
695
|
+
channels, queries}]} socket messages and dispatches by the matched
|
|
696
|
+
subscription ids the server echoes; 22.4-24 carried per-slot queries in
|
|
697
|
+
the socket URL. subscribe() resolves to {unsubscribe, update, close}.
|
|
698
|
+
- Feature detection: a query-capable instance has pendingSubscribes (>=25)
|
|
699
|
+
or slotToSubscriptionId (22.4-24). Without either marker: channel-only
|
|
700
|
+
subscribe, exactly the 0.5.248 path.
|
|
701
|
+
- The server evaluates queries against the event payload only, so an
|
|
702
|
+
update that makes a held row STOP matching is not delivered. Therefore
|
|
703
|
+
serverFilter defaults to 'scope': only scope-column filters (permission-
|
|
704
|
+
mirrored, effectively immutable) ride the subscription; user queries stay
|
|
705
|
+
client-side so stop-matching rows still drop live. 'all' pushes every
|
|
706
|
+
expressible filter server-side (such rows then correct on the next load);
|
|
707
|
+
false sends none. The client matcher stays on in every mode. */
|
|
708
|
+
|
|
709
|
+
// Map<subKey, { channel, queries, refs, unsubscribe, handlers: Map<sourceName, handler> }>
|
|
710
|
+
const entries = new Map();
|
|
711
|
+
// Map<sourceName, { subKey, channel, token, handler, getFilters }> — token guards a stale unsubscribe closure
|
|
691
712
|
const sources = new Map();
|
|
692
713
|
// Map<sourceName, fn(eventType, row)> — pre-filters, run per event per source
|
|
693
714
|
const filters = new Map();
|
|
694
715
|
// Back-compat/debug surface: Map<sourceName, unsubscribeFunction>
|
|
695
716
|
const subscriptions = new Map();
|
|
696
|
-
//
|
|
717
|
+
// Map<sourceName, n> — explicit teardowns; aborts a subscribe in flight
|
|
718
|
+
const tombstones = new Map();
|
|
719
|
+
// Instance the current subscriptions live on
|
|
697
720
|
let activeRealtime = null;
|
|
698
721
|
|
|
699
722
|
function classify(event) {
|
|
@@ -704,10 +727,22 @@ function classify(event) {
|
|
|
704
727
|
return null;
|
|
705
728
|
}
|
|
706
729
|
|
|
730
|
+
// Query-capable Realtime service (see header): detect per instance, never assume
|
|
731
|
+
function supportsQueries(realtime) {
|
|
732
|
+
return !!(realtime && (realtime.pendingSubscribes || realtime.slotToSubscriptionId));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// (channel, resolved filter set) → stable subscription key; resolved.key is
|
|
736
|
+
// already canonical (sorted) so equivalent filter sets share one subscription
|
|
737
|
+
function subKeyFor(channel, resolved) {
|
|
738
|
+
if (!resolved || !resolved.key) return channel;
|
|
739
|
+
return `${channel}\u001f${resolved.key}`;
|
|
740
|
+
}
|
|
741
|
+
|
|
707
742
|
// Fan one SDK message out: each registered source handler gets each event once,
|
|
708
743
|
// after that source's pre-filter (setFilter) — before any merge/dedup work
|
|
709
|
-
function dispatch(
|
|
710
|
-
const entry =
|
|
744
|
+
function dispatch(subKey, response) {
|
|
745
|
+
const entry = entries.get(subKey);
|
|
711
746
|
if (!entry || !response || !response.events) return;
|
|
712
747
|
const events = Array.isArray(response.events) ? response.events : [response.events];
|
|
713
748
|
let payload = response.payload;
|
|
@@ -723,97 +758,183 @@ function dispatch(channel, response) {
|
|
|
723
758
|
}
|
|
724
759
|
}
|
|
725
760
|
|
|
726
|
-
function
|
|
727
|
-
|
|
761
|
+
// subscribe() returns an unsubscribe function (legacy client) or a
|
|
762
|
+
// Promise<RealtimeSubscription> (Realtime service): normalize teardown
|
|
763
|
+
function dropSub(sub) {
|
|
764
|
+
if (typeof sub === 'function') sub();
|
|
765
|
+
else if (sub && typeof sub.unsubscribe === 'function') sub.unsubscribe(); // per-subscription; never risks the shared socket
|
|
766
|
+
else if (sub && typeof sub.close === 'function') sub.close();
|
|
728
767
|
}
|
|
729
768
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
769
|
+
function makeUnsubscriber(result) {
|
|
770
|
+
if (!result || typeof result.then !== 'function') {
|
|
771
|
+
return () => { try { dropSub(result); } catch (e) { /* socket already gone */ } };
|
|
772
|
+
}
|
|
773
|
+
const settled = result.catch(() => null);
|
|
774
|
+
return () => { settled.then(dropSub).catch(() => { /* socket already gone */ }); };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function sdkSubscribe(realtime, subKey, channel, queries) {
|
|
778
|
+
const cb = response => dispatch(subKey, response);
|
|
779
|
+
const result = (queries && queries.length)
|
|
780
|
+
? realtime.subscribe(channel, cb, queries)
|
|
781
|
+
: realtime.subscribe(channel, cb);
|
|
782
|
+
return makeUnsubscriber(result);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Get or create the shared entry for (channel, filters); registered in the map
|
|
786
|
+
// before the SDK call so an event arriving mid-subscribe already dispatches
|
|
787
|
+
function ensureEntry(realtime, subKey, channel, queries) {
|
|
788
|
+
let entry = entries.get(subKey);
|
|
789
|
+
if (entry) return entry;
|
|
790
|
+
entry = { channel, queries: queries || [], refs: 0, unsubscribe: null, handlers: new Map() };
|
|
791
|
+
entries.set(subKey, entry);
|
|
792
|
+
try {
|
|
793
|
+
entry.unsubscribe = sdkSubscribe(realtime, subKey, channel, queries);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
entries.delete(subKey);
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
return entry;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function removeFromEntry(name, subKey) {
|
|
802
|
+
const entry = entries.get(subKey);
|
|
736
803
|
if (!entry) return;
|
|
737
804
|
entry.handlers.delete(name);
|
|
738
805
|
entry.refs = entry.handlers.size;
|
|
739
806
|
if (entry.refs === 0) {
|
|
740
|
-
|
|
807
|
+
entries.delete(subKey);
|
|
741
808
|
if (typeof entry.unsubscribe === 'function') {
|
|
742
809
|
try { entry.unsubscribe(); } catch (e) { /* socket already gone */ }
|
|
743
810
|
}
|
|
744
811
|
}
|
|
745
812
|
}
|
|
746
813
|
|
|
747
|
-
//
|
|
814
|
+
// Drop a source's registration; tear the subscription down when the last source leaves
|
|
815
|
+
function detach(name, token) {
|
|
816
|
+
const cur = sources.get(name);
|
|
817
|
+
if (!cur || (token && cur.token !== token)) return;
|
|
818
|
+
sources.delete(name);
|
|
819
|
+
removeFromEntry(name, cur.subKey);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Instance changed (client rebuilt on identity change): re-resolve each source's
|
|
823
|
+
// filters ($auth. values are stale) and regroup subscriptions on the new instance.
|
|
824
|
+
// Chained so overlapping rebuilds run in order; a superseded rebuild stops.
|
|
825
|
+
let syncChain = Promise.resolve();
|
|
748
826
|
function syncInstance(realtime) {
|
|
749
|
-
if (activeRealtime === realtime) return;
|
|
827
|
+
if (activeRealtime === realtime) return syncChain;
|
|
750
828
|
activeRealtime = realtime;
|
|
751
|
-
|
|
829
|
+
syncChain = syncChain
|
|
830
|
+
.then(() => rebindAll(realtime))
|
|
831
|
+
.catch(error => console.error('[Manifest Data] Realtime rebind failed:', error));
|
|
832
|
+
return syncChain;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function rebindAll(realtime) {
|
|
836
|
+
for (const [, entry] of entries) {
|
|
752
837
|
if (typeof entry.unsubscribe === 'function') {
|
|
753
838
|
try { entry.unsubscribe(); } catch (e) { /* old instance */ }
|
|
754
839
|
}
|
|
840
|
+
}
|
|
841
|
+
entries.clear();
|
|
842
|
+
const canQuery = supportsQueries(realtime);
|
|
843
|
+
for (const [name, src] of sources) {
|
|
844
|
+
if (activeRealtime !== realtime) return; // superseded by a newer instance
|
|
845
|
+
const resolved = (canQuery && src.getFilters) ? await src.getFilters() : null;
|
|
846
|
+
const subKey = subKeyFor(src.channel, resolved);
|
|
755
847
|
try {
|
|
756
|
-
entry
|
|
848
|
+
const entry = ensureEntry(realtime, subKey, src.channel, resolved?.queries);
|
|
849
|
+
entry.handlers.set(name, src.handler);
|
|
850
|
+
entry.refs = entry.handlers.size;
|
|
851
|
+
src.subKey = subKey;
|
|
757
852
|
} catch (error) {
|
|
758
|
-
console.error('[Manifest Data] Failed to re-subscribe channel:', channel, error);
|
|
853
|
+
console.error('[Manifest Data] Failed to re-subscribe channel:', src.channel, error);
|
|
759
854
|
}
|
|
760
855
|
}
|
|
761
856
|
}
|
|
762
857
|
|
|
763
|
-
// One SDK subscribe per distinct channel,
|
|
764
|
-
// isCurrent: false once the requesting load is superseded (logout) — skip, never touch a newer entry
|
|
765
|
-
async
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
858
|
+
// One SDK subscribe per distinct (channel, filter set); sources sharing both share it.
|
|
859
|
+
// isCurrent: false once the requesting load is superseded (logout) — skip, never touch a newer entry.
|
|
860
|
+
// getFilters: async () => ({ queries, key }) | null, re-run per (re)subscribe so
|
|
861
|
+
// $auth. values resolve at subscribe time; null → channel-only (0.5.248 path).
|
|
862
|
+
// A replaced registration detaches only after the new subscription is live (no
|
|
863
|
+
// event gap); overlap double-delivery is absorbed by the per-source event dedupe.
|
|
864
|
+
async function subscribeChannel(dataSourceName, channel, onEvent, isCurrent, getFilters) {
|
|
865
|
+
const epoch = tombstones.get(dataSourceName) || 0;
|
|
769
866
|
const services = await window.ManifestDataAppwrite.getAppwriteDataServices();
|
|
770
867
|
if (isCurrent && !isCurrent()) return null;
|
|
771
868
|
if (!services?.realtime) {
|
|
772
869
|
console.warn('[Manifest Data] Realtime service not available for', dataSourceName);
|
|
870
|
+
detach(dataSourceName);
|
|
871
|
+
subscriptions.delete(dataSourceName);
|
|
773
872
|
return null;
|
|
774
873
|
}
|
|
775
|
-
|
|
874
|
+
const realtime = services.realtime;
|
|
875
|
+
await syncInstance(realtime);
|
|
876
|
+
if (isCurrent && !isCurrent()) return null;
|
|
776
877
|
|
|
777
|
-
|
|
778
|
-
if (!
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
878
|
+
const resolved = (supportsQueries(realtime) && getFilters) ? await getFilters() : null;
|
|
879
|
+
if (isCurrent && !isCurrent()) return null;
|
|
880
|
+
// Torn down while resolving: land nothing
|
|
881
|
+
if ((tombstones.get(dataSourceName) || 0) !== epoch) return null;
|
|
882
|
+
// Instance replaced while resolving (identity rebuild): re-run against the live one
|
|
883
|
+
if (activeRealtime !== realtime) return subscribeChannel(dataSourceName, channel, onEvent, isCurrent, getFilters);
|
|
884
|
+
const subKey = subKeyFor(channel, resolved);
|
|
885
|
+
|
|
886
|
+
const prev = sources.get(dataSourceName);
|
|
887
|
+
let entry;
|
|
888
|
+
try {
|
|
889
|
+
entry = ensureEntry(realtime, subKey, channel, resolved?.queries);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
console.error('[Manifest Data] Failed to subscribe:', channel, error);
|
|
892
|
+
detach(dataSourceName);
|
|
893
|
+
subscriptions.delete(dataSourceName);
|
|
894
|
+
return null;
|
|
787
895
|
}
|
|
788
896
|
const token = Symbol(dataSourceName);
|
|
789
897
|
entry.handlers.set(dataSourceName, onEvent);
|
|
790
898
|
entry.refs = entry.handlers.size;
|
|
791
|
-
sources.set(dataSourceName, { channel, token });
|
|
899
|
+
sources.set(dataSourceName, { subKey, channel, token, handler: onEvent, getFilters });
|
|
900
|
+
if (prev && prev.subKey !== subKey) removeFromEntry(dataSourceName, prev.subKey);
|
|
792
901
|
|
|
793
902
|
const unsubscribe = () => detach(dataSourceName, token);
|
|
794
903
|
subscriptions.set(dataSourceName, unsubscribe);
|
|
795
904
|
return unsubscribe;
|
|
796
905
|
}
|
|
797
906
|
|
|
907
|
+
// Resolver re-runs per (re)subscribe so $auth. values are current at that moment
|
|
908
|
+
function filterResolver(filterSpec) {
|
|
909
|
+
if (!filterSpec) return null;
|
|
910
|
+
const build = window.ManifestDataQueries?.buildRealtimeQueries;
|
|
911
|
+
if (typeof build !== 'function') return null;
|
|
912
|
+
return () => build(filterSpec.queriesConfig, filterSpec.scope, filterSpec.scopeColumns, filterSpec.mode);
|
|
913
|
+
}
|
|
914
|
+
|
|
798
915
|
// Subscribe to storage bucket file changes
|
|
799
|
-
function subscribeToStorageBucket(dataSourceName, bucketId, scope, onEvent, isCurrent) {
|
|
800
|
-
return subscribeChannel(dataSourceName, `buckets.${bucketId}.files`, onEvent, isCurrent);
|
|
916
|
+
function subscribeToStorageBucket(dataSourceName, bucketId, scope, onEvent, isCurrent, filterSpec) {
|
|
917
|
+
return subscribeChannel(dataSourceName, `buckets.${bucketId}.files`, onEvent, isCurrent, filterResolver(filterSpec));
|
|
801
918
|
}
|
|
802
919
|
|
|
803
920
|
// Subscribe to database table row changes
|
|
804
|
-
function subscribeToTable(dataSourceName, databaseId, tableId, scope, onEvent, isCurrent) {
|
|
805
|
-
return subscribeChannel(dataSourceName, `databases.${databaseId}.tables.${tableId}.rows`, onEvent, isCurrent);
|
|
921
|
+
function subscribeToTable(dataSourceName, databaseId, tableId, scope, onEvent, isCurrent, filterSpec) {
|
|
922
|
+
return subscribeChannel(dataSourceName, `databases.${databaseId}.tables.${tableId}.rows`, onEvent, isCurrent, filterResolver(filterSpec));
|
|
806
923
|
}
|
|
807
924
|
|
|
808
925
|
// Unsubscribe from a data source
|
|
809
926
|
function unsubscribeFromDataSource(dataSourceName) {
|
|
927
|
+
tombstones.set(dataSourceName, (tombstones.get(dataSourceName) || 0) + 1);
|
|
810
928
|
detach(dataSourceName);
|
|
811
929
|
subscriptions.delete(dataSourceName);
|
|
812
930
|
}
|
|
813
931
|
|
|
814
932
|
// Unsubscribe from all data sources
|
|
815
933
|
function unsubscribeAll() {
|
|
816
|
-
for (const name of [...sources.keys()])
|
|
934
|
+
for (const name of [...sources.keys()]) {
|
|
935
|
+
tombstones.set(name, (tombstones.get(name) || 0) + 1);
|
|
936
|
+
detach(name);
|
|
937
|
+
}
|
|
817
938
|
subscriptions.clear();
|
|
818
939
|
}
|
|
819
940
|
|
|
@@ -825,11 +946,11 @@ function setFilter(sourceName, fn) {
|
|
|
825
946
|
}
|
|
826
947
|
|
|
827
948
|
// Identity events rebuild the shared Realtime (manifest.data.appwrite.js drops
|
|
828
|
-
// its memo on the same events); re-bind
|
|
949
|
+
// its memo on the same events); re-bind live subscriptions to the new instance
|
|
829
950
|
if (typeof window !== 'undefined') {
|
|
830
951
|
['manifest:auth:login', 'manifest:auth:logout', 'manifest:auth:anonymous', 'manifest:auth:session-cleared', 'manifest:auth:initialized']
|
|
831
952
|
.forEach(type => window.addEventListener(type, async () => {
|
|
832
|
-
if (!
|
|
953
|
+
if (!sources.size) return;
|
|
833
954
|
const services = await window.ManifestDataAppwrite?.getAppwriteDataServices?.();
|
|
834
955
|
if (services?.realtime) syncInstance(services.realtime);
|
|
835
956
|
}));
|
|
@@ -843,7 +964,7 @@ window.ManifestDataRealtime = {
|
|
|
843
964
|
unsubscribeAll,
|
|
844
965
|
setFilter,
|
|
845
966
|
subscriptions, // Expose for debugging
|
|
846
|
-
_channels:
|
|
967
|
+
_channels: entries // Expose for debugging (keyed by channel + filter set)
|
|
847
968
|
};
|
|
848
969
|
|
|
849
970
|
|
|
@@ -1334,6 +1455,42 @@ function toAppwriteQuery(queryArray) {
|
|
|
1334
1455
|
}
|
|
1335
1456
|
}
|
|
1336
1457
|
|
|
1458
|
+
// Realtime-expressible operators: the intersection of what the load serializes
|
|
1459
|
+
// (QUERY_MAP), what the matcher evaluates, and what Appwrite realtime filters
|
|
1460
|
+
// server-side. isNull/isNotNull are realtime-supported but the load drops them,
|
|
1461
|
+
// so filtering on them server-side would drop events the matcher keeps — excluded.
|
|
1462
|
+
const REALTIME_FILTER_OPS = new Set(['equal', 'notEqual', 'lessThan', 'lessThanEqual', 'greaterThan', 'greaterThanEqual']);
|
|
1463
|
+
|
|
1464
|
+
function isRealtimeExpressible(query) {
|
|
1465
|
+
if (!Array.isArray(query) || query.length === 0) return false;
|
|
1466
|
+
const [method, ...args] = query;
|
|
1467
|
+
if (method === 'or') return Array.isArray(args[0]) && args[0].length > 0 && args[0].every(isRealtimeExpressible);
|
|
1468
|
+
return REALTIME_FILTER_OPS.has(method);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Server-side realtime filter set, reduced to the expressible subset. Omitting
|
|
1472
|
+
// a query only WIDENS delivery — the client matcher still filters — so
|
|
1473
|
+
// unexpressible queries are dropped whole, never approximated.
|
|
1474
|
+
// mode 'scope' (default): only scope-column queries ride the subscription —
|
|
1475
|
+
// permission-mirrored, so a row leaving the filter still delivers its last
|
|
1476
|
+
// event and drops live. 'all': user queries too (a held row that stops
|
|
1477
|
+
// matching then corrects on the next load, not instantly).
|
|
1478
|
+
// Returns { queries, key } (key = canonical sorted serialization, for
|
|
1479
|
+
// subscription sharing) or null (not ready / nothing expressible → channel-only
|
|
1480
|
+
// subscribe, the 0.5.248 path).
|
|
1481
|
+
async function buildRealtimeQueries(queriesConfig, scope, scopeColumns, mode = 'scope') {
|
|
1482
|
+
const userConfig = (mode === 'all' && Array.isArray(queriesConfig)) ? queriesConfig : [];
|
|
1483
|
+
const resolved = await buildQueries(userConfig, scope, scopeColumns);
|
|
1484
|
+
if (resolved === null) return null;
|
|
1485
|
+
const queries = resolved
|
|
1486
|
+
.filter(isRealtimeExpressible)
|
|
1487
|
+
.map(query => toAppwriteQuery(query))
|
|
1488
|
+
.filter(query => query !== null);
|
|
1489
|
+
if (queries.length === 0) return null;
|
|
1490
|
+
const key = queries.map(q => typeof q === 'string' ? q : JSON.stringify(q)).sort().join('\u001e');
|
|
1491
|
+
return { queries, key };
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1337
1494
|
// Build Appwrite queries from configuration
|
|
1338
1495
|
// scopeColumns: { team, user } column names (default teamId/userId) — see manifest.data.config.js getScopeColumns
|
|
1339
1496
|
// Returns null ("not ready") straight through — see buildQueries.
|
|
@@ -1353,6 +1510,7 @@ window.ManifestDataQueries = {
|
|
|
1353
1510
|
getAuthValue,
|
|
1354
1511
|
buildQueries,
|
|
1355
1512
|
buildAppwriteQueries,
|
|
1513
|
+
buildRealtimeQueries,
|
|
1356
1514
|
rowMatchesQueries,
|
|
1357
1515
|
toAppwriteQuery,
|
|
1358
1516
|
ALLOWED_VARIABLES,
|
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.249';
|
|
6
6
|
|
|
7
7
|
/* Manifest Data Sources - Configuration */
|
|
8
8
|
|
|
@@ -11897,6 +11897,13 @@ function forgetRealtimeRow(dataSourceName, id) {
|
|
|
11897
11897
|
if (i !== -1) meta.appended.splice(i, 1);
|
|
11898
11898
|
}
|
|
11899
11899
|
|
|
11900
|
+
// realtime.serverFilter → mode: 'scope' (default) | 'all' (alias true) | null (false: no server filtering)
|
|
11901
|
+
function serverFilterMode(realtimeConfig) {
|
|
11902
|
+
const sf = (realtimeConfig && typeof realtimeConfig === 'object') ? realtimeConfig.serverFilter : undefined;
|
|
11903
|
+
if (sf === false) return null;
|
|
11904
|
+
return (sf === 'all' || sf === true) ? 'all' : 'scope';
|
|
11905
|
+
}
|
|
11906
|
+
|
|
11900
11907
|
// Query-aware event filter: the source's own queries, evaluated client-side
|
|
11901
11908
|
// (unevaluable → match; see rowMatchesQueries)
|
|
11902
11909
|
function eventMatchesQueries(row, queriesConfig) {
|
|
@@ -12340,6 +12347,10 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
12340
12347
|
const rtQueries = Array.isArray(queriesConfig?.default || queriesConfig) ? (queriesConfig.default || queriesConfig) : [];
|
|
12341
12348
|
const limitQ = rtQueries.find(q => Array.isArray(q) && q[0] === 'limit');
|
|
12342
12349
|
configureRealtimeMeta(dataSourceName, dataSource.realtime, Array.isArray(data) ? data.length : 0, limitQ ? limitQ[1] : null);
|
|
12350
|
+
// Events server-filtered where the SDK supports realtime queries; the client
|
|
12351
|
+
// matcher below still runs. serverFilter: 'scope' (default) | 'all' (alias true) | false
|
|
12352
|
+
const rtMode = serverFilterMode(dataSource.realtime);
|
|
12353
|
+
const rtFilterSpec = rtMode ? { queriesConfig: rtQueries, scope, scopeColumns, mode: rtMode } : null;
|
|
12343
12354
|
const unsubscribe = await window.ManifestDataRealtime.subscribeToTable(
|
|
12344
12355
|
dataSourceName,
|
|
12345
12356
|
appwriteConfig.databaseId,
|
|
@@ -12348,7 +12359,8 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
12348
12359
|
async (eventType, payload) => {
|
|
12349
12360
|
await handleTableRealtimeEvent(dataSourceName, appwriteConfig.databaseId, tableId, scope, scopeColumns, eventType, payload, generation, rtQueries);
|
|
12350
12361
|
},
|
|
12351
|
-
() => !superseded()
|
|
12362
|
+
() => !superseded(),
|
|
12363
|
+
rtFilterSpec
|
|
12352
12364
|
);
|
|
12353
12365
|
if (superseded()) { dropSubscription(dataSourceName, unsubscribe); landed = true; return null; }
|
|
12354
12366
|
}
|
|
@@ -12393,6 +12405,9 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
12393
12405
|
const rtQueries = Array.isArray(queriesConfig?.default || queriesConfig) ? (queriesConfig.default || queriesConfig) : [];
|
|
12394
12406
|
const limitQ = rtQueries.find(q => Array.isArray(q) && q[0] === 'limit');
|
|
12395
12407
|
configureRealtimeMeta(dataSourceName, dataSource.realtime, Array.isArray(data) ? data.length : 0, limitQ ? limitQ[1] : null);
|
|
12408
|
+
// Buckets have no scope columns: server filtering only under serverFilter 'all'
|
|
12409
|
+
const rtMode = serverFilterMode(dataSource.realtime);
|
|
12410
|
+
const rtFilterSpec = rtMode ? { queriesConfig: rtQueries, scope: null, mode: rtMode } : null;
|
|
12396
12411
|
const unsubscribe = await window.ManifestDataRealtime.subscribeToStorageBucket(
|
|
12397
12412
|
dataSourceName,
|
|
12398
12413
|
bucketId,
|
|
@@ -12401,7 +12416,8 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
12401
12416
|
if (superseded()) return;
|
|
12402
12417
|
await handleStorageRealtimeEvent(dataSourceName, bucketId, scope, eventType, payload, generation, rtQueries);
|
|
12403
12418
|
},
|
|
12404
|
-
() => !superseded()
|
|
12419
|
+
() => !superseded(),
|
|
12420
|
+
rtFilterSpec
|
|
12405
12421
|
);
|
|
12406
12422
|
if (superseded()) { dropSubscription(dataSourceName, unsubscribe); landed = true; return null; }
|
|
12407
12423
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-dsIWnH0qCAGSzAXMGrd8fvOrVl4tOIE6j5tC35JPTrQXZ56qLD36ARyvhR7vi3Id",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-DmpgWkBF02iuwOvFEq/0rpjHm5ts9F4Y961J8h6+gT2WhyGm3Ryp9Pgk0D/CWDki",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-G+huD7UvjF9zDovdVVlh4I5X1Mq4oNnJfoFg4NnGdP3R/4Skq9hy5cGuHNYLxDYm",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-BUFA8h+G5Plr82sV7bKzO42pPoiRZwRQcKj1lt2ROFiKjjlkayU/vmkAFqEsdBA+",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384
|
|
14
|
+
"manifest.data.js": "sha384-viViXTPQiJr7QNSx9Ln+uMXyNif1EK10JAmyPqeW4I6FrNtUu5MgKt04sa7LkABg",
|
|
15
15
|
"manifest.datepicker.js": "sha384-2XwFFnS4Bg5FDvG/v8x6KUvrjcBQEOYyTxynX2K/D/Qb3PI6+Y04lEwCnVDJLzwL",
|
|
16
16
|
"manifest.defer.js": "sha384-pMRGqQKiEoJhpUcD40QeM4jEHau7sz9Xn/YCpiUamT1T4xJBZDSiYX+xnVrSWoq+",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
29
|
"manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
|
|
30
30
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
31
|
+
"manifest.sw.js": "sha384-XGf/EbAhHXR7BCSlP6QFnCWPNxuwxyHgU/I8DdxEYw0UPQaZEGDlUILxExqx+ae+",
|
|
32
32
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
34
|
"manifest.toasts.js": "sha384-3zkWxzw693pxg3QDFsoYCA3CqYaLKmBaMArULXODD66Utolpewl0OAeTB92aWUVv",
|
package/lib/manifest.schema.json
CHANGED
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
}
|
|
247
247
|
},
|
|
248
248
|
"realtime": {
|
|
249
|
-
"description": "Realtime subscription config. false disables the subscription. { mode, maxRows } bounds growth: 'append' (default) stops appending at maxRows and counts skipped events in $realtimeOverflow; 'window' keeps the initial window size by evicting the oldest realtime-appended rows. maxRows defaults to max(500, 2x the initially loaded rows); 0 opts into legacy unbounded growth.",
|
|
249
|
+
"description": "Realtime subscription config. false disables the subscription. { mode, maxRows } bounds growth: 'append' (default) stops appending at maxRows and counts skipped events in $realtimeOverflow; 'window' keeps the initial window size by evicting the oldest realtime-appended rows. maxRows defaults to max(500, 2x the initially loaded rows); 0 opts into legacy unbounded growth. serverFilter picks which filters ride the Appwrite realtime subscription server-side: 'scope' (default) sends only scope-column filters; 'all' (alias true) sends every expressible filter — rows that stop matching then drop on the next load instead of instantly; false sends none.",
|
|
250
250
|
"oneOf": [
|
|
251
251
|
{
|
|
252
252
|
"type": "boolean"
|
|
@@ -264,6 +264,20 @@
|
|
|
264
264
|
"maxRows": {
|
|
265
265
|
"type": "number",
|
|
266
266
|
"minimum": 0
|
|
267
|
+
},
|
|
268
|
+
"serverFilter": {
|
|
269
|
+
"oneOf": [
|
|
270
|
+
{
|
|
271
|
+
"type": "boolean"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"type": "string",
|
|
275
|
+
"enum": [
|
|
276
|
+
"scope",
|
|
277
|
+
"all"
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
]
|
|
267
281
|
}
|
|
268
282
|
},
|
|
269
283
|
"additionalProperties": false
|
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.249';
|
|
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)
|