@statezero/core 0.1.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.
Files changed (89) hide show
  1. package/dist/adaptors/react/composables.d.ts +1 -0
  2. package/dist/adaptors/react/composables.js +4 -0
  3. package/dist/adaptors/react/index.d.ts +1 -0
  4. package/dist/adaptors/react/index.js +1 -0
  5. package/dist/adaptors/vue/composables.d.ts +2 -0
  6. package/dist/adaptors/vue/composables.js +36 -0
  7. package/dist/adaptors/vue/index.d.ts +2 -0
  8. package/dist/adaptors/vue/index.js +2 -0
  9. package/dist/adaptors/vue/reactivity.d.ts +18 -0
  10. package/dist/adaptors/vue/reactivity.js +125 -0
  11. package/dist/cli/commands/syncModels.d.ts +132 -0
  12. package/dist/cli/commands/syncModels.js +1040 -0
  13. package/dist/cli/configFileLoader.d.ts +10 -0
  14. package/dist/cli/configFileLoader.js +85 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +14 -0
  17. package/dist/config.d.ts +52 -0
  18. package/dist/config.js +242 -0
  19. package/dist/core/eventReceivers.d.ts +179 -0
  20. package/dist/core/eventReceivers.js +210 -0
  21. package/dist/core/utils.d.ts +8 -0
  22. package/dist/core/utils.js +62 -0
  23. package/dist/filtering/localFiltering.d.ts +116 -0
  24. package/dist/filtering/localFiltering.js +834 -0
  25. package/dist/flavours/django/dates.d.ts +33 -0
  26. package/dist/flavours/django/dates.js +99 -0
  27. package/dist/flavours/django/errors.d.ts +138 -0
  28. package/dist/flavours/django/errors.js +187 -0
  29. package/dist/flavours/django/f.d.ts +6 -0
  30. package/dist/flavours/django/f.js +91 -0
  31. package/dist/flavours/django/files.d.ts +76 -0
  32. package/dist/flavours/django/files.js +338 -0
  33. package/dist/flavours/django/makeApiCall.d.ts +20 -0
  34. package/dist/flavours/django/makeApiCall.js +169 -0
  35. package/dist/flavours/django/manager.d.ts +197 -0
  36. package/dist/flavours/django/manager.js +222 -0
  37. package/dist/flavours/django/model.d.ts +112 -0
  38. package/dist/flavours/django/model.js +253 -0
  39. package/dist/flavours/django/operationFactory.d.ts +65 -0
  40. package/dist/flavours/django/operationFactory.js +216 -0
  41. package/dist/flavours/django/q.d.ts +70 -0
  42. package/dist/flavours/django/q.js +43 -0
  43. package/dist/flavours/django/queryExecutor.d.ts +131 -0
  44. package/dist/flavours/django/queryExecutor.js +468 -0
  45. package/dist/flavours/django/querySet.d.ts +412 -0
  46. package/dist/flavours/django/querySet.js +601 -0
  47. package/dist/flavours/django/tempPk.d.ts +19 -0
  48. package/dist/flavours/django/tempPk.js +48 -0
  49. package/dist/flavours/django/utils.d.ts +19 -0
  50. package/dist/flavours/django/utils.js +29 -0
  51. package/dist/index.d.ts +38 -0
  52. package/dist/index.js +38 -0
  53. package/dist/react-entry.d.ts +2 -0
  54. package/dist/react-entry.js +2 -0
  55. package/dist/reactiveAdaptor.d.ts +24 -0
  56. package/dist/reactiveAdaptor.js +38 -0
  57. package/dist/setup.d.ts +15 -0
  58. package/dist/setup.js +22 -0
  59. package/dist/syncEngine/cache/cache.d.ts +75 -0
  60. package/dist/syncEngine/cache/cache.js +341 -0
  61. package/dist/syncEngine/metrics/metricOptCalcs.d.ts +79 -0
  62. package/dist/syncEngine/metrics/metricOptCalcs.js +284 -0
  63. package/dist/syncEngine/registries/metricRegistry.d.ts +53 -0
  64. package/dist/syncEngine/registries/metricRegistry.js +162 -0
  65. package/dist/syncEngine/registries/modelStoreRegistry.d.ts +11 -0
  66. package/dist/syncEngine/registries/modelStoreRegistry.js +56 -0
  67. package/dist/syncEngine/registries/querysetStoreRegistry.d.ts +55 -0
  68. package/dist/syncEngine/registries/querysetStoreRegistry.js +244 -0
  69. package/dist/syncEngine/stores/metricStore.d.ts +55 -0
  70. package/dist/syncEngine/stores/metricStore.js +222 -0
  71. package/dist/syncEngine/stores/modelStore.d.ts +40 -0
  72. package/dist/syncEngine/stores/modelStore.js +405 -0
  73. package/dist/syncEngine/stores/operation.d.ts +99 -0
  74. package/dist/syncEngine/stores/operation.js +224 -0
  75. package/dist/syncEngine/stores/operationEventHandlers.d.ts +8 -0
  76. package/dist/syncEngine/stores/operationEventHandlers.js +239 -0
  77. package/dist/syncEngine/stores/querysetStore.d.ts +32 -0
  78. package/dist/syncEngine/stores/querysetStore.js +200 -0
  79. package/dist/syncEngine/stores/reactivity.d.ts +3 -0
  80. package/dist/syncEngine/stores/reactivity.js +4 -0
  81. package/dist/syncEngine/stores/utils.d.ts +14 -0
  82. package/dist/syncEngine/stores/utils.js +32 -0
  83. package/dist/syncEngine/sync.d.ts +32 -0
  84. package/dist/syncEngine/sync.js +169 -0
  85. package/dist/vue-entry.d.ts +6 -0
  86. package/dist/vue-entry.js +2 -0
  87. package/license.md +116 -0
  88. package/package.json +123 -0
  89. package/readme.md +222 -0
@@ -0,0 +1,210 @@
1
+ import Pusher from 'pusher-js';
2
+ /**
3
+ * Structure of events received from the server.
4
+ * @typedef {Object} ModelEvent
5
+ * @property {string} [type] - Support both frontend (type) and backend (event) naming conventions.
6
+ * @property {string} [event]
7
+ * @property {string} model
8
+ * @property {any} [data]
9
+ * @property {string} [operationId]
10
+ * @property {string} [namespace]
11
+ * @property {(string|number)[]} [instances] - For bulk events.
12
+ * @property {string} [pk_field_name]
13
+ * @property {string} [configKey] - The backend configuration key this event is associated with.
14
+ * @property {any} [key] - Additional open-ended keys.
15
+ */
16
+ /**
17
+ * Event types that can be received from the server.
18
+ * @readonly
19
+ * @enum {string}
20
+ */
21
+ export const EventType = {
22
+ CREATE: 'create',
23
+ UPDATE: 'update',
24
+ DELETE: 'delete',
25
+ BULK_UPDATE: 'bulk_update',
26
+ BULK_DELETE: 'bulk_delete'
27
+ };
28
+ /**
29
+ * Callback for handling model events.
30
+ * @callback EventHandler
31
+ * @param {ModelEvent} event - The event object.
32
+ */
33
+ /**
34
+ * A namespace resolver function.
35
+ * @callback NamespaceResolver
36
+ * @param {string} modelName - The model name.
37
+ * @returns {string} The namespace.
38
+ */
39
+ /**
40
+ * Options for instantiating a Pusher client.
41
+ * @typedef {Object} PusherClientOptions
42
+ * @property {string} appKey
43
+ * @property {string} cluster
44
+ * @property {boolean} [forceTLS]
45
+ * @property {string} authEndpoint
46
+ * @property {function(): Object<string, string>} [getAuthHeaders]
47
+ */
48
+ /**
49
+ * Configuration options for Pusher event receivers.
50
+ * @typedef {Object} PusherReceiverOptions
51
+ * @property {PusherClientOptions} clientOptions
52
+ * @property {function(string): string} [formatChannelName] - Optional channel name formatter. Default: (namespace) => `private-${namespace}`
53
+ * @property {NamespaceResolver} [namespaceResolver] - Optional namespace resolver. Default: (modelName) => modelName.
54
+ */
55
+ /**
56
+ * Implementation of EventReceiver that uses Pusher.
57
+ */
58
+ export class PusherEventReceiver {
59
+ /**
60
+ * @param {PusherReceiverOptions} options
61
+ * @param {string} configKey - The backend configuration key
62
+ */
63
+ constructor(options, configKey) {
64
+ const { clientOptions, formatChannelName, namespaceResolver } = options;
65
+ this.configKey = configKey;
66
+ this.pusherClient = new Pusher(clientOptions.appKey, {
67
+ cluster: clientOptions.cluster,
68
+ forceTLS: clientOptions.forceTLS ?? true,
69
+ authEndpoint: clientOptions.authEndpoint,
70
+ auth: { headers: clientOptions.getAuthHeaders?.() || {} }
71
+ });
72
+ this.formatChannelName = formatChannelName ?? (ns => `private-${ns}`);
73
+ this.namespaceResolver = namespaceResolver ?? (modelName => modelName);
74
+ this.channels = new Map();
75
+ this.eventHandlers = new Set();
76
+ }
77
+ /**
78
+ * Set the namespace resolver function.
79
+ * @param {NamespaceResolver} resolver
80
+ */
81
+ setNamespaceResolver(resolver) {
82
+ this.namespaceResolver = resolver;
83
+ }
84
+ /**
85
+ * Connect to Pusher (no-op since Pusher handles connection automatically).
86
+ */
87
+ connect() { }
88
+ /**
89
+ * Subscribe to events for a specific namespace.
90
+ * @param {string} namespace
91
+ */
92
+ subscribe(namespace) {
93
+ if (this.channels.has(namespace))
94
+ return;
95
+ const channelName = namespace.startsWith('private-')
96
+ ? namespace
97
+ : this.formatChannelName(namespace);
98
+ console.log(`Subscribing to channel: ${channelName} for backend: ${this.configKey}`);
99
+ const channel = this.pusherClient.subscribe(channelName);
100
+ channel.bind('pusher:subscription_succeeded', () => {
101
+ console.log(`Subscription succeeded for channel: ${channelName}`);
102
+ });
103
+ channel.bind('pusher:subscription_error', status => {
104
+ console.error(`Subscription error for channel: ${channelName}. Status:`, status);
105
+ });
106
+ // Listen for CRUD events
107
+ Object.values(EventType).forEach(eventType => {
108
+ channel.bind(eventType, data => {
109
+ const event = {
110
+ ...data,
111
+ type: data.event || eventType,
112
+ namespace,
113
+ configKey: this.configKey
114
+ };
115
+ this.eventHandlers.forEach(handler => handler(event));
116
+ });
117
+ });
118
+ this.channels.set(namespace, channel);
119
+ }
120
+ unsubscribe(namespace) {
121
+ const channel = this.channels.get(namespace);
122
+ if (!channel)
123
+ return;
124
+ Object.values(EventType).forEach(eventType => {
125
+ channel.unbind(eventType);
126
+ });
127
+ const channelName = namespace.startsWith('private-')
128
+ ? namespace
129
+ : this.formatChannelName(namespace);
130
+ this.pusherClient.unsubscribe(channelName);
131
+ this.channels.delete(namespace);
132
+ }
133
+ /**
134
+ * Disconnect from Pusher.
135
+ */
136
+ disconnect() {
137
+ [...this.channels.keys()].forEach(ns => this.unsubscribe(ns));
138
+ this.pusherClient.disconnect();
139
+ }
140
+ /**
141
+ * Add handler for model events
142
+ * @param {EventHandler} handler
143
+ */
144
+ addModelEventHandler(handler) {
145
+ this.eventHandlers.add(handler);
146
+ }
147
+ /**
148
+ * Legacy method - adds event handler for backwards compatibility
149
+ * @param {EventHandler} handler
150
+ */
151
+ addEventHandler(handler) {
152
+ this.eventHandlers.add(handler);
153
+ }
154
+ /**
155
+ * Remove an event handler callback.
156
+ * @param {EventHandler} handler
157
+ */
158
+ removeEventHandler(handler) {
159
+ this.eventHandlers.delete(handler);
160
+ }
161
+ /**
162
+ * Get namespace from model name using the resolver.
163
+ * @param {string} modelName
164
+ * @returns {string}
165
+ */
166
+ getNamespace(modelName) {
167
+ return this.namespaceResolver(modelName);
168
+ }
169
+ }
170
+ // Map of event receivers by backend key
171
+ const eventReceivers = new Map();
172
+ /**
173
+ * Set an event receiver for a specific backend.
174
+ * @param {string} configKey - The backend configuration key
175
+ * @param {EventReceiver} receiver - The event receiver instance
176
+ */
177
+ export function setEventReceiver(configKey, receiver) {
178
+ const currentReceiver = eventReceivers.get(configKey);
179
+ if (currentReceiver) {
180
+ currentReceiver.disconnect();
181
+ }
182
+ eventReceivers.set(configKey, receiver);
183
+ receiver.connect();
184
+ }
185
+ /**
186
+ * Get the event receiver for a specific backend.
187
+ * @param {string} configKey - The backend configuration key
188
+ * @returns {EventReceiver|null}
189
+ */
190
+ export function getEventReceiver(configKey = 'default') {
191
+ return eventReceivers.get(configKey);
192
+ }
193
+ /**
194
+ * Get all registered event receivers.
195
+ * @returns {Map<string, EventReceiver>}
196
+ */
197
+ export function getAllEventReceivers() {
198
+ return eventReceivers;
199
+ }
200
+ /**
201
+ * Set a custom namespace resolver function for a specific backend.
202
+ * @param {string} configKey - The backend configuration key
203
+ * @param {NamespaceResolver} resolver
204
+ */
205
+ export function setNamespaceResolver(configKey, resolver) {
206
+ const receiver = getEventReceiver(configKey);
207
+ if (receiver) {
208
+ receiver.setNamespaceResolver(resolver);
209
+ }
210
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Get a model class by name, loading it from the correct folder structure
3
+ *
4
+ * @param {string} modelName - The model name (e.g. 'app.model')
5
+ * @param {string} configKey - Optional config key override (defaults to Model.configKey)
6
+ * @returns {Function|null} - The model class or null if not found
7
+ */
8
+ export function getModelClass(modelName: string, configKey?: string): Function | null;
@@ -0,0 +1,62 @@
1
+ import { configInstance } from '../../config.js';
2
+ // Cache for model classes to avoid repeated imports
3
+ const modelClassCache = {};
4
+ /**
5
+ * Get a model class by name, loading it from the correct folder structure
6
+ *
7
+ * @param {string} modelName - The model name (e.g. 'app.model')
8
+ * @param {string} configKey - Optional config key override (defaults to Model.configKey)
9
+ * @returns {Function|null} - The model class or null if not found
10
+ */
11
+ export function getModelClass(modelName, configKey = this.configKey || 'default') {
12
+ // Check cache first
13
+ const cacheKey = `${configKey}:${modelName}`;
14
+ if (modelClassCache[cacheKey]) {
15
+ return modelClassCache[cacheKey];
16
+ }
17
+ try {
18
+ const config = configInstance.getConfig();
19
+ const backendConfig = config.backendConfigs[configKey];
20
+ if (!backendConfig) {
21
+ throw new Error(`No backend configuration found for key: ${configKey}`);
22
+ }
23
+ const baseDir = backendConfig.GENERATED_TYPES_DIR.replace(/\/$/, '');
24
+ // Split the model name into parts (e.g., 'app.model' -> ['app', 'model'])
25
+ const parts = modelName.split('.');
26
+ // The last part is the actual model name
27
+ const modelClassName = parts[parts.length - 1];
28
+ // The path is all parts except the last one, joined by '/'
29
+ const pathParts = parts.slice(0, parts.length - 1);
30
+ const path = pathParts.length > 0 ? `/${pathParts.join('/')}` : '';
31
+ // Construct the full path to the module
32
+ const modulePath = `${baseDir}${path}/${modelClassName}.js`;
33
+ // Load the module using require (in Node.js) or dynamic import (in browser)
34
+ // This implementation uses dynamic import as it's more common in modern JS environments
35
+ let ModelClass;
36
+ try {
37
+ // For Node.js environments, you might use require
38
+ // ModelClass = require(modulePath).default;
39
+ // For browser/ES modules using dynamic import
40
+ // Note: This is a synchronous approach to handle dynamic imports
41
+ // In a real implementation, this would need to be async or preloaded
42
+ // Since we can't do a true dynamic import synchronously, here we're assuming
43
+ // there's a global registry that was populated during build/load time
44
+ const allModels = window.__MODEL_REGISTRY__ || {};
45
+ ModelClass = allModels[modulePath];
46
+ if (!ModelClass) {
47
+ throw new Error(`Model not found in registry: ${modulePath}`);
48
+ }
49
+ }
50
+ catch (importError) {
51
+ console.error(`Error loading model class from ${modulePath}:`, importError);
52
+ return null;
53
+ }
54
+ // Cache the model class
55
+ modelClassCache[cacheKey] = ModelClass;
56
+ return ModelClass;
57
+ }
58
+ catch (error) {
59
+ console.error(`Error getting model class for ${modelName}:`, error);
60
+ return null;
61
+ }
62
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Inspect a QuerySet.build() and collect every field path
3
+ * you’ll need to fetch before running sift.
4
+ *
5
+ * @param {Object} queryBuild – result of QuerySet.build()
6
+ * @param {Class} ModelClass – the root model class
7
+ * @returns {string[]} Array of dot-notation paths, e.g. ['author.id','createdAt.year']
8
+ */
9
+ export function getRequiredFields(queryBuild: Object, ModelClass: Class): string[];
10
+ /**
11
+ * Pick out only the required fields from a (possibly nested) model object.
12
+ *
13
+ * @param {string[]} requiredPaths – e.g. ['id','related.name','related.age']
14
+ * @param {Object} instance – e.g. { id: 3, related: { name: 'bob', age: 12, foo: 'bar' } }
15
+ * @returns {Object} – e.g. { id: 3, related: { name: 'bob', age: 12 } }
16
+ */
17
+ export function pickRequiredFields(requiredPaths: string[], instance: Object): Object;
18
+ /**
19
+ * Filter and order a collection of data objects according to a QuerySet's AST.
20
+ * This combines getRequiredFields, pickRequiredFields, and processQuery in one function.
21
+ *
22
+ * @param {Array<Object>} data - Collection of objects to filter and order
23
+ * @param {Object} ast - Abstract Syntax Tree from QuerySet.build()
24
+ * @param {Class} ModelClass - The model class for schema traversal
25
+ * @param {boolean} [returnFullObjects=false] - If true, returns full objects instead of just primary keys
26
+ * @returns {Array} Filtered and ordered results (primary keys or full objects based on returnFullObjects)
27
+ */
28
+ export function filter(data: Array<Object>, ast: Object, ModelClass: Class, returnFullObjects?: boolean): any[];
29
+ /**
30
+ * Process a Django-style field path with relationships to match Django ORM behavior.
31
+ * This handles nested relationships by traversing the model schema and properly
32
+ * resolving relationship fields to their primary keys.
33
+ *
34
+ * @param {string} fieldPath - The Django-style field path (e.g., 'level2__level3__name')
35
+ * @param {any} value - The value to filter by
36
+ * @param {Class} ModelClass - The root model class to start traversal from
37
+ * @param {Object} options - Additional options
38
+ * @returns {Object} An object with processed field path and operator
39
+ */
40
+ export function processFieldPath(fieldPath: string, value: any, ModelClass: Class, options?: Object): Object;
41
+ /**
42
+ * Convert Django-style filter conditions to Sift-compatible criteria
43
+ * @param {Object} conditions - Filter conditions
44
+ * @param {Class} ModelClass - The model class for schema traversal
45
+ * @returns {Object} Sift-compatible criteria
46
+ */
47
+ export function convertToSiftCriteria(conditions: Object, ModelClass: Class): Object;
48
+ /**
49
+ * Processes a Q object array to match the backend AST structure
50
+ * @param {Array} qConditions - Array of Q objects or conditions
51
+ * @param {Class} ModelClass - The model class for schema traversal
52
+ * @returns {Object} Sift criteria for Q conditions
53
+ */
54
+ export function processQConditions(qConditions: any[], ModelClass: Class): Object;
55
+ /**
56
+ * Convert a filter node to sift criteria with proper relationship traversal
57
+ * @param {Object} filterNode - The filter node to convert
58
+ * @param {Class} ModelClass - The model class for schema traversal
59
+ * @returns {Object} Sift criteria object
60
+ */
61
+ export function convertFilterNodeToSiftCriteria(filterNode: Object, ModelClass: Class): Object;
62
+ /**
63
+ * Apply search criteria to a dataset
64
+ * @param {Array} data - Collection of objects to search
65
+ * @param {Object} searchNode - Search node from query
66
+ * @param {Class} ModelClass - The model class for schema traversal
67
+ * @returns {Array} Filtered results
68
+ */
69
+ export function applySearch(data: any[], searchNode: Object, ModelClass: Class): any[];
70
+ /**
71
+ * Applies ordering to a dataset based on a list of fields
72
+ * @param {Array} data - Collection of objects to order
73
+ * @param {Array<string>} orderBy - Fields to order by (prefix with - for descending)
74
+ * @param {Class} ModelClass - The model class for schema traversal
75
+ * @returns {Array} Ordered results
76
+ */
77
+ export function applyOrderBy(data: any[], orderBy: Array<string>, ModelClass: Class): any[];
78
+ /**
79
+ * Process an array of denormalized objects to filter & order them,
80
+ * then return just the matching primary-keys in order.
81
+ *
82
+ * @param {Array<Object>} data – denormalized rows, e.g. [{ id:1, name:"A", related:{ name:"B" } }, …]
83
+ * @param {Object} queryBuild – the result of QuerySet.build()
84
+ * @param {Class} ModelClass – your model class (for fieldPath resolution & date-ops)
85
+ * @returns {Array<*>} – the primary keys of matching rows, in order
86
+ */
87
+ export function processQuery(data: Array<Object>, queryBuild: Object, ModelClass: Class): Array<any>;
88
+ /**
89
+ * Creates custom operations for date parts to be used with Sift
90
+ * @param {string} timezone - The timezone to use for date operations
91
+ * @returns {Object} Object containing custom operations
92
+ */
93
+ export function createDateOperations(timezone?: string): Object;
94
+ /**
95
+ * Process a Django-style filter query to use with sift, including date part operations
96
+ * @param {Object} criteria - Sift criteria with possible date operations
97
+ * @param {Class} ModelClass - The model class for schema traversal
98
+ * @returns {Function} Sift filter function with date operations support
99
+ */
100
+ export function createFilterWithDateOperations(criteria: Object, ModelClass: Class): Function;
101
+ /**
102
+ * Creates a special operator for date part comparison (e.g., created_at__hour__gt: 12)
103
+ * @param {string} field - Processed field path
104
+ * @param {string} datePart - The date part to extract ('year', 'month', etc.)
105
+ * @param {string} comparisonOperator - The comparison operator ('gt', 'lt', etc.)
106
+ * @param {any} value - Value to filter by
107
+ * @param {boolean} isRelationship - Whether the field is a relationship
108
+ * @returns {Object} Object with field name and custom operator
109
+ */
110
+ export function createDatePartComparisonOperator(field: string, datePart: string, comparisonOperator: string, value: any, isRelationship: boolean): Object;
111
+ /**
112
+ * Gets the backend timezone for a model class
113
+ * @param {Class} ModelClass - The model class
114
+ * @returns {string} The backend timezone or 'UTC' as fallback
115
+ */
116
+ export function getBackendTimezone(ModelClass: Class): string;