dzql 0.5.32 → 0.6.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.
- package/.env.sample +28 -0
- package/compose.yml +28 -0
- package/dist/client/index.ts +1 -0
- package/dist/client/stores/useMyProfileStore.ts +114 -0
- package/dist/client/stores/useOrgDashboardStore.ts +131 -0
- package/dist/client/stores/useVenueDetailStore.ts +117 -0
- package/dist/client/ws.ts +716 -0
- package/dist/db/migrations/000_core.sql +92 -0
- package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
- package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
- package/dist/runtime/manifest.json +1562 -0
- package/docs/README.md +293 -36
- package/docs/feature-requests/applyPatch-bug-report.md +85 -0
- package/docs/feature-requests/connection-ready-profile.md +57 -0
- package/docs/feature-requests/hidden-bug-report.md +111 -0
- package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
- package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
- package/docs/feature-requests/todo.md +146 -0
- package/docs/for_ai.md +641 -0
- package/docs/project-setup.md +432 -0
- package/examples/blog.ts +50 -0
- package/examples/invalid.ts +18 -0
- package/examples/venues.js +485 -0
- package/package.json +23 -60
- package/src/cli/codegen/client.ts +99 -0
- package/src/cli/codegen/manifest.ts +95 -0
- package/src/cli/codegen/pinia.ts +174 -0
- package/src/cli/codegen/realtime.ts +58 -0
- package/src/cli/codegen/sql.ts +698 -0
- package/src/cli/codegen/subscribable_sql.ts +547 -0
- package/src/cli/codegen/subscribable_store.ts +184 -0
- package/src/cli/codegen/types.ts +142 -0
- package/src/cli/compiler/analyzer.ts +52 -0
- package/src/cli/compiler/graph_rules.ts +251 -0
- package/src/cli/compiler/ir.ts +233 -0
- package/src/cli/compiler/loader.ts +132 -0
- package/src/cli/compiler/permissions.ts +227 -0
- package/src/cli/index.ts +164 -0
- package/src/client/index.ts +1 -0
- package/src/client/ws.ts +286 -0
- package/src/create/.env.example +8 -0
- package/src/create/README.md +101 -0
- package/src/create/compose.yml +14 -0
- package/src/create/domain.ts +153 -0
- package/src/create/package.json +24 -0
- package/src/create/server.ts +18 -0
- package/src/create/setup.sh +11 -0
- package/src/create/tsconfig.json +15 -0
- package/src/runtime/auth.ts +39 -0
- package/src/runtime/db.ts +33 -0
- package/src/runtime/errors.ts +51 -0
- package/src/runtime/index.ts +98 -0
- package/src/runtime/js_functions.ts +63 -0
- package/src/runtime/manifest_loader.ts +29 -0
- package/src/runtime/namespace.ts +483 -0
- package/src/runtime/server.ts +87 -0
- package/src/runtime/ws.ts +197 -0
- package/src/shared/ir.ts +197 -0
- package/tests/client.test.ts +38 -0
- package/tests/codegen.test.ts +71 -0
- package/tests/compiler.test.ts +45 -0
- package/tests/graph_rules.test.ts +173 -0
- package/tests/integration/db.test.ts +174 -0
- package/tests/integration/e2e.test.ts +65 -0
- package/tests/integration/features.test.ts +922 -0
- package/tests/integration/full_stack.test.ts +262 -0
- package/tests/integration/setup.ts +45 -0
- package/tests/ir.test.ts +32 -0
- package/tests/namespace.test.ts +395 -0
- package/tests/permissions.test.ts +55 -0
- package/tests/pinia.test.ts +48 -0
- package/tests/realtime.test.ts +22 -0
- package/tests/runtime.test.ts +80 -0
- package/tests/subscribable_gen.test.ts +72 -0
- package/tests/subscribable_reactivity.test.ts +258 -0
- package/tests/venues_gen.test.ts +25 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/README.md +0 -90
- package/bin/cli.js +0 -727
- package/docs/compiler/ADVANCED_FILTERS.md +0 -183
- package/docs/compiler/CODING_STANDARDS.md +0 -415
- package/docs/compiler/COMPARISON.md +0 -673
- package/docs/compiler/QUICKSTART.md +0 -326
- package/docs/compiler/README.md +0 -134
- package/docs/examples/README.md +0 -38
- package/docs/examples/blog.sql +0 -160
- package/docs/examples/venue-detail-simple.sql +0 -8
- package/docs/examples/venue-detail-subscribable.sql +0 -45
- package/docs/for-ai/claude-guide.md +0 -1210
- package/docs/getting-started/quickstart.md +0 -125
- package/docs/getting-started/subscriptions-quick-start.md +0 -203
- package/docs/getting-started/tutorial.md +0 -1104
- package/docs/guides/atomic-updates.md +0 -299
- package/docs/guides/client-stores.md +0 -730
- package/docs/guides/composite-primary-keys.md +0 -158
- package/docs/guides/custom-functions.md +0 -362
- package/docs/guides/drop-semantics.md +0 -554
- package/docs/guides/field-defaults.md +0 -240
- package/docs/guides/interpreter-vs-compiler.md +0 -237
- package/docs/guides/many-to-many.md +0 -929
- package/docs/guides/subscriptions.md +0 -537
- package/docs/reference/api.md +0 -1373
- package/docs/reference/client.md +0 -224
- package/src/client/stores/index.js +0 -8
- package/src/client/stores/useAppStore.js +0 -285
- package/src/client/stores/useWsStore.js +0 -289
- package/src/client/ws.js +0 -762
- package/src/compiler/cli/compile-example.js +0 -33
- package/src/compiler/cli/compile-subscribable.js +0 -43
- package/src/compiler/cli/debug-compile.js +0 -44
- package/src/compiler/cli/debug-parse.js +0 -26
- package/src/compiler/cli/debug-path-parser.js +0 -18
- package/src/compiler/cli/debug-subscribable-parser.js +0 -21
- package/src/compiler/cli/index.js +0 -174
- package/src/compiler/codegen/auth-codegen.js +0 -153
- package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
- package/src/compiler/codegen/graph-rules-codegen.js +0 -450
- package/src/compiler/codegen/notification-codegen.js +0 -232
- package/src/compiler/codegen/operation-codegen.js +0 -1382
- package/src/compiler/codegen/permission-codegen.js +0 -318
- package/src/compiler/codegen/subscribable-codegen.js +0 -827
- package/src/compiler/compiler.js +0 -371
- package/src/compiler/index.js +0 -11
- package/src/compiler/parser/entity-parser.js +0 -440
- package/src/compiler/parser/path-parser.js +0 -290
- package/src/compiler/parser/subscribable-parser.js +0 -244
- package/src/database/dzql-core.sql +0 -161
- package/src/database/migrations/001_schema.sql +0 -60
- package/src/database/migrations/002_functions.sql +0 -890
- package/src/database/migrations/003_operations.sql +0 -1135
- package/src/database/migrations/004_search.sql +0 -581
- package/src/database/migrations/005_entities.sql +0 -730
- package/src/database/migrations/006_auth.sql +0 -94
- package/src/database/migrations/007_events.sql +0 -133
- package/src/database/migrations/008_hello.sql +0 -18
- package/src/database/migrations/008a_meta.sql +0 -172
- package/src/database/migrations/009_subscriptions.sql +0 -240
- package/src/database/migrations/010_atomic_updates.sql +0 -157
- package/src/database/migrations/010_fix_m2m_events.sql +0 -94
- package/src/index.js +0 -40
- package/src/server/api.js +0 -9
- package/src/server/db.js +0 -442
- package/src/server/index.js +0 -317
- package/src/server/logger.js +0 -259
- package/src/server/mcp.js +0 -594
- package/src/server/meta-route.js +0 -251
- package/src/server/namespace.js +0 -292
- package/src/server/subscriptions.js +0 -351
- package/src/server/ws.js +0 -573
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { ref, isRef, isReactive, nextTick, watch } from "vue";
|
|
3
|
+
import { createPinia, setActivePinia, defineStore } from "pinia";
|
|
4
|
+
import { generateSubscribableStore } from "../src/cli/codegen/subscribable_store.js";
|
|
5
|
+
import { generateManifest } from "../src/cli/codegen/manifest.js";
|
|
6
|
+
import { generateIR } from "../src/cli/compiler/ir.js";
|
|
7
|
+
import { entities, subscribables } from "../examples/venues.js";
|
|
8
|
+
|
|
9
|
+
// Mock WebSocket manager
|
|
10
|
+
const mockWs = {
|
|
11
|
+
api: {
|
|
12
|
+
subscribe_venue_detail: (params: any, callback: (data: any) => void) => {
|
|
13
|
+
// Simulate async subscription response
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
callback({
|
|
16
|
+
id: 1,
|
|
17
|
+
name: "Test Venue",
|
|
18
|
+
sites: [
|
|
19
|
+
{ id: 1, name: "Site A", allocations: [] },
|
|
20
|
+
{ id: 2, name: "Site B", allocations: [] }
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
}, 10);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
registerStore: (listener: (event: any) => void) => {
|
|
27
|
+
// Store the listener so tests can dispatch events
|
|
28
|
+
mockWs._storeListener = listener;
|
|
29
|
+
return () => {};
|
|
30
|
+
},
|
|
31
|
+
_storeListener: null as ((event: any) => void) | null,
|
|
32
|
+
dispatchEvent: (event: any) => {
|
|
33
|
+
if (mockWs._storeListener) {
|
|
34
|
+
mockWs._storeListener(event);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("Subscribable Store Reactivity", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
setActivePinia(createPinia());
|
|
42
|
+
mockWs._storeListener = null;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("generated store code structure is correct", () => {
|
|
46
|
+
const ir = generateIR({ entities, subscribables });
|
|
47
|
+
const manifest = generateManifest(ir);
|
|
48
|
+
const code = generateSubscribableStore(manifest, "venue_detail");
|
|
49
|
+
|
|
50
|
+
// Should use typed ref for documents
|
|
51
|
+
expect(code).toContain("const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});");
|
|
52
|
+
|
|
53
|
+
// Should add plain object with empty data object to documents.value (preserves reactivity)
|
|
54
|
+
expect(code).toContain("documents.value[key] = { data: {}, loading: true, ready };");
|
|
55
|
+
|
|
56
|
+
// Should merge initial data into existing object via Object.assign (preserves reactivity)
|
|
57
|
+
expect(code).toContain("Object.assign(documents.value[key].data, eventData");
|
|
58
|
+
expect(code).toContain("documents.value[key].loading = false;");
|
|
59
|
+
|
|
60
|
+
// Should have typed unbind function
|
|
61
|
+
expect(code).toContain("function unbind(params: VenueDetailParams)");
|
|
62
|
+
|
|
63
|
+
// Should NOT pre-wrap in ref()
|
|
64
|
+
expect(code).not.toContain("const docState = ref(");
|
|
65
|
+
expect(code).not.toContain("const loading = ref(");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("documents.value entries become reactive when added as plain objects", async () => {
|
|
69
|
+
// Simulate what the generated store does
|
|
70
|
+
const documents = ref<Record<string, any>>({});
|
|
71
|
+
|
|
72
|
+
// Add a plain object - Vue should make it reactive
|
|
73
|
+
documents.value["test-key"] = { data: null, loading: true };
|
|
74
|
+
|
|
75
|
+
// The entry should be reactive
|
|
76
|
+
expect(isReactive(documents.value["test-key"])).toBe(true);
|
|
77
|
+
|
|
78
|
+
// Assign data
|
|
79
|
+
documents.value["test-key"].data = { id: 1, name: "Test" };
|
|
80
|
+
documents.value["test-key"].loading = false;
|
|
81
|
+
|
|
82
|
+
// Data should also be reactive
|
|
83
|
+
expect(isReactive(documents.value["test-key"].data)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("mutations to document data trigger reactivity", async () => {
|
|
87
|
+
const documents = ref<Record<string, any>>({});
|
|
88
|
+
|
|
89
|
+
// Setup
|
|
90
|
+
documents.value["key1"] = {
|
|
91
|
+
data: { id: 1, name: "Original", items: [{ id: 1, value: "a" }] },
|
|
92
|
+
loading: false
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
let changeCount = 0;
|
|
96
|
+
|
|
97
|
+
// Watch for changes
|
|
98
|
+
watch(
|
|
99
|
+
() => documents.value["key1"]?.data?.name,
|
|
100
|
+
() => { changeCount++; },
|
|
101
|
+
{ flush: 'sync' }
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Mutate via Object.assign (like applyPatch does)
|
|
105
|
+
Object.assign(documents.value["key1"].data, { name: "Updated" });
|
|
106
|
+
|
|
107
|
+
expect(changeCount).toBe(1);
|
|
108
|
+
expect(documents.value["key1"].data.name).toBe("Updated");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("array mutations trigger reactivity", async () => {
|
|
112
|
+
const documents = ref<Record<string, any>>({});
|
|
113
|
+
|
|
114
|
+
documents.value["key1"] = {
|
|
115
|
+
data: {
|
|
116
|
+
id: 1,
|
|
117
|
+
sites: [
|
|
118
|
+
{ id: 1, name: "Site A" },
|
|
119
|
+
{ id: 2, name: "Site B" }
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
loading: false
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let changeCount = 0;
|
|
126
|
+
|
|
127
|
+
watch(
|
|
128
|
+
() => documents.value["key1"]?.data?.sites?.length,
|
|
129
|
+
() => { changeCount++; },
|
|
130
|
+
{ flush: 'sync' }
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Insert (push)
|
|
134
|
+
documents.value["key1"].data.sites.push({ id: 3, name: "Site C" });
|
|
135
|
+
expect(changeCount).toBe(1);
|
|
136
|
+
expect(documents.value["key1"].data.sites.length).toBe(3);
|
|
137
|
+
|
|
138
|
+
// Delete (splice)
|
|
139
|
+
documents.value["key1"].data.sites.splice(0, 1);
|
|
140
|
+
expect(changeCount).toBe(2);
|
|
141
|
+
expect(documents.value["key1"].data.sites.length).toBe(2);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("Object.assign on array items triggers reactivity", async () => {
|
|
145
|
+
const documents = ref<Record<string, any>>({});
|
|
146
|
+
|
|
147
|
+
documents.value["key1"] = {
|
|
148
|
+
data: {
|
|
149
|
+
sites: [{ id: 1, name: "Original" }]
|
|
150
|
+
},
|
|
151
|
+
loading: false
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
let changeCount = 0;
|
|
155
|
+
|
|
156
|
+
watch(
|
|
157
|
+
() => documents.value["key1"]?.data?.sites?.[0]?.name,
|
|
158
|
+
() => { changeCount++; },
|
|
159
|
+
{ flush: 'sync' }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Update via Object.assign (like handleArrayPatch does)
|
|
163
|
+
Object.assign(documents.value["key1"].data.sites[0], { name: "Updated" });
|
|
164
|
+
|
|
165
|
+
expect(changeCount).toBe(1);
|
|
166
|
+
expect(documents.value["key1"].data.sites[0].name).toBe("Updated");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("applyPatch simulation works with reactivity", async () => {
|
|
170
|
+
const documents = ref<Record<string, any>>({});
|
|
171
|
+
|
|
172
|
+
// Simulate bind() adding the document
|
|
173
|
+
documents.value["venue-1"] = {
|
|
174
|
+
data: {
|
|
175
|
+
id: 1,
|
|
176
|
+
name: "Test Venue",
|
|
177
|
+
sites: [
|
|
178
|
+
{ id: 1, name: "Site A", allocations: [] }
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
loading: false
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Simulate applyPatch for root entity update
|
|
185
|
+
function applyPatch(doc: any, event: any) {
|
|
186
|
+
if (!doc) return;
|
|
187
|
+
switch (event.table) {
|
|
188
|
+
case 'venues':
|
|
189
|
+
if (event.op === 'update') Object.assign(doc, event.data);
|
|
190
|
+
break;
|
|
191
|
+
case 'sites':
|
|
192
|
+
handleArrayPatch(doc.sites, event);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function handleArrayPatch(arr: any[], event: any) {
|
|
198
|
+
if (!arr) return;
|
|
199
|
+
const pkValue = event.pk?.id;
|
|
200
|
+
const idx = arr.findIndex(i => i.id === pkValue);
|
|
201
|
+
if (event.op === 'insert') {
|
|
202
|
+
if (idx === -1) arr.push(event.data);
|
|
203
|
+
} else if (event.op === 'update') {
|
|
204
|
+
if (idx !== -1) Object.assign(arr[idx], event.data);
|
|
205
|
+
} else if (event.op === 'delete') {
|
|
206
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let venueNameChanges = 0;
|
|
211
|
+
let sitesLengthChanges = 0;
|
|
212
|
+
|
|
213
|
+
watch(
|
|
214
|
+
() => documents.value["venue-1"]?.data?.name,
|
|
215
|
+
() => { venueNameChanges++; },
|
|
216
|
+
{ flush: 'sync' }
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
watch(
|
|
220
|
+
() => documents.value["venue-1"]?.data?.sites?.length,
|
|
221
|
+
() => { sitesLengthChanges++; },
|
|
222
|
+
{ flush: 'sync' }
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Test root entity update
|
|
226
|
+
applyPatch(documents.value["venue-1"].data, {
|
|
227
|
+
table: 'venues',
|
|
228
|
+
op: 'update',
|
|
229
|
+
data: { name: "Updated Venue" },
|
|
230
|
+
pk: { id: 1 }
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(venueNameChanges).toBe(1);
|
|
234
|
+
expect(documents.value["venue-1"].data.name).toBe("Updated Venue");
|
|
235
|
+
|
|
236
|
+
// Test insert into nested array
|
|
237
|
+
applyPatch(documents.value["venue-1"].data, {
|
|
238
|
+
table: 'sites',
|
|
239
|
+
op: 'insert',
|
|
240
|
+
data: { id: 2, name: "Site B", allocations: [] },
|
|
241
|
+
pk: { id: 2 }
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(sitesLengthChanges).toBe(1);
|
|
245
|
+
expect(documents.value["venue-1"].data.sites.length).toBe(2);
|
|
246
|
+
|
|
247
|
+
// Test delete from nested array
|
|
248
|
+
applyPatch(documents.value["venue-1"].data, {
|
|
249
|
+
table: 'sites',
|
|
250
|
+
op: 'delete',
|
|
251
|
+
data: { id: 1 },
|
|
252
|
+
pk: { id: 1 }
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(sitesLengthChanges).toBe(2);
|
|
256
|
+
expect(documents.value["venue-1"].data.sites.length).toBe(1);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { generatePiniaStore } from "../src/cli/codegen/pinia.js";
|
|
3
|
+
import { generateManifest } from "../src/cli/codegen/manifest.js";
|
|
4
|
+
import { generateIR } from "../src/cli/compiler/ir.js";
|
|
5
|
+
import { entities, subscribables } from "../examples/venues.js";
|
|
6
|
+
|
|
7
|
+
const venuesDomain = { entities, subscribables };
|
|
8
|
+
|
|
9
|
+
describe("Venues Store Generation", () => {
|
|
10
|
+
test("should generate venues store", () => {
|
|
11
|
+
const ir = generateIR(venuesDomain);
|
|
12
|
+
const manifest = generateManifest(ir);
|
|
13
|
+
|
|
14
|
+
// Generate the store for the 'venues' entity
|
|
15
|
+
const code = generatePiniaStore(manifest, "venues");
|
|
16
|
+
|
|
17
|
+
console.log("--- GENERATED VENUES STORE ---");
|
|
18
|
+
console.log(code);
|
|
19
|
+
console.log("------------------------------");
|
|
20
|
+
|
|
21
|
+
expect(code).toContain("useVenuesStore");
|
|
22
|
+
expect(code).toContain("ws.api.save_venues");
|
|
23
|
+
expect(code).toContain("table_changed");
|
|
24
|
+
});
|
|
25
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext"],
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"target": "esnext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"composite": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"downlevelIteration": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"allowSyntheticDefaultImports": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
"allowJs": true,
|
|
18
|
+
"types": ["bun-types"]
|
|
19
|
+
}
|
|
20
|
+
}
|