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.
Files changed (150) hide show
  1. package/.env.sample +28 -0
  2. package/compose.yml +28 -0
  3. package/dist/client/index.ts +1 -0
  4. package/dist/client/stores/useMyProfileStore.ts +114 -0
  5. package/dist/client/stores/useOrgDashboardStore.ts +131 -0
  6. package/dist/client/stores/useVenueDetailStore.ts +117 -0
  7. package/dist/client/ws.ts +716 -0
  8. package/dist/db/migrations/000_core.sql +92 -0
  9. package/dist/db/migrations/20251229T212912022Z_schema.sql +3020 -0
  10. package/dist/db/migrations/20251229T212912022Z_subscribables.sql +371 -0
  11. package/dist/runtime/manifest.json +1562 -0
  12. package/docs/README.md +293 -36
  13. package/docs/feature-requests/applyPatch-bug-report.md +85 -0
  14. package/docs/feature-requests/connection-ready-profile.md +57 -0
  15. package/docs/feature-requests/hidden-bug-report.md +111 -0
  16. package/docs/feature-requests/hidden-fields-subscribables.md +34 -0
  17. package/docs/feature-requests/subscribable-param-key-bug.md +38 -0
  18. package/docs/feature-requests/todo.md +146 -0
  19. package/docs/for_ai.md +641 -0
  20. package/docs/project-setup.md +432 -0
  21. package/examples/blog.ts +50 -0
  22. package/examples/invalid.ts +18 -0
  23. package/examples/venues.js +485 -0
  24. package/package.json +23 -60
  25. package/src/cli/codegen/client.ts +99 -0
  26. package/src/cli/codegen/manifest.ts +95 -0
  27. package/src/cli/codegen/pinia.ts +174 -0
  28. package/src/cli/codegen/realtime.ts +58 -0
  29. package/src/cli/codegen/sql.ts +698 -0
  30. package/src/cli/codegen/subscribable_sql.ts +547 -0
  31. package/src/cli/codegen/subscribable_store.ts +184 -0
  32. package/src/cli/codegen/types.ts +142 -0
  33. package/src/cli/compiler/analyzer.ts +52 -0
  34. package/src/cli/compiler/graph_rules.ts +251 -0
  35. package/src/cli/compiler/ir.ts +233 -0
  36. package/src/cli/compiler/loader.ts +132 -0
  37. package/src/cli/compiler/permissions.ts +227 -0
  38. package/src/cli/index.ts +164 -0
  39. package/src/client/index.ts +1 -0
  40. package/src/client/ws.ts +286 -0
  41. package/src/create/.env.example +8 -0
  42. package/src/create/README.md +101 -0
  43. package/src/create/compose.yml +14 -0
  44. package/src/create/domain.ts +153 -0
  45. package/src/create/package.json +24 -0
  46. package/src/create/server.ts +18 -0
  47. package/src/create/setup.sh +11 -0
  48. package/src/create/tsconfig.json +15 -0
  49. package/src/runtime/auth.ts +39 -0
  50. package/src/runtime/db.ts +33 -0
  51. package/src/runtime/errors.ts +51 -0
  52. package/src/runtime/index.ts +98 -0
  53. package/src/runtime/js_functions.ts +63 -0
  54. package/src/runtime/manifest_loader.ts +29 -0
  55. package/src/runtime/namespace.ts +483 -0
  56. package/src/runtime/server.ts +87 -0
  57. package/src/runtime/ws.ts +197 -0
  58. package/src/shared/ir.ts +197 -0
  59. package/tests/client.test.ts +38 -0
  60. package/tests/codegen.test.ts +71 -0
  61. package/tests/compiler.test.ts +45 -0
  62. package/tests/graph_rules.test.ts +173 -0
  63. package/tests/integration/db.test.ts +174 -0
  64. package/tests/integration/e2e.test.ts +65 -0
  65. package/tests/integration/features.test.ts +922 -0
  66. package/tests/integration/full_stack.test.ts +262 -0
  67. package/tests/integration/setup.ts +45 -0
  68. package/tests/ir.test.ts +32 -0
  69. package/tests/namespace.test.ts +395 -0
  70. package/tests/permissions.test.ts +55 -0
  71. package/tests/pinia.test.ts +48 -0
  72. package/tests/realtime.test.ts +22 -0
  73. package/tests/runtime.test.ts +80 -0
  74. package/tests/subscribable_gen.test.ts +72 -0
  75. package/tests/subscribable_reactivity.test.ts +258 -0
  76. package/tests/venues_gen.test.ts +25 -0
  77. package/tsconfig.json +20 -0
  78. package/tsconfig.tsbuildinfo +1 -0
  79. package/README.md +0 -90
  80. package/bin/cli.js +0 -727
  81. package/docs/compiler/ADVANCED_FILTERS.md +0 -183
  82. package/docs/compiler/CODING_STANDARDS.md +0 -415
  83. package/docs/compiler/COMPARISON.md +0 -673
  84. package/docs/compiler/QUICKSTART.md +0 -326
  85. package/docs/compiler/README.md +0 -134
  86. package/docs/examples/README.md +0 -38
  87. package/docs/examples/blog.sql +0 -160
  88. package/docs/examples/venue-detail-simple.sql +0 -8
  89. package/docs/examples/venue-detail-subscribable.sql +0 -45
  90. package/docs/for-ai/claude-guide.md +0 -1210
  91. package/docs/getting-started/quickstart.md +0 -125
  92. package/docs/getting-started/subscriptions-quick-start.md +0 -203
  93. package/docs/getting-started/tutorial.md +0 -1104
  94. package/docs/guides/atomic-updates.md +0 -299
  95. package/docs/guides/client-stores.md +0 -730
  96. package/docs/guides/composite-primary-keys.md +0 -158
  97. package/docs/guides/custom-functions.md +0 -362
  98. package/docs/guides/drop-semantics.md +0 -554
  99. package/docs/guides/field-defaults.md +0 -240
  100. package/docs/guides/interpreter-vs-compiler.md +0 -237
  101. package/docs/guides/many-to-many.md +0 -929
  102. package/docs/guides/subscriptions.md +0 -537
  103. package/docs/reference/api.md +0 -1373
  104. package/docs/reference/client.md +0 -224
  105. package/src/client/stores/index.js +0 -8
  106. package/src/client/stores/useAppStore.js +0 -285
  107. package/src/client/stores/useWsStore.js +0 -289
  108. package/src/client/ws.js +0 -762
  109. package/src/compiler/cli/compile-example.js +0 -33
  110. package/src/compiler/cli/compile-subscribable.js +0 -43
  111. package/src/compiler/cli/debug-compile.js +0 -44
  112. package/src/compiler/cli/debug-parse.js +0 -26
  113. package/src/compiler/cli/debug-path-parser.js +0 -18
  114. package/src/compiler/cli/debug-subscribable-parser.js +0 -21
  115. package/src/compiler/cli/index.js +0 -174
  116. package/src/compiler/codegen/auth-codegen.js +0 -153
  117. package/src/compiler/codegen/drop-semantics-codegen.js +0 -553
  118. package/src/compiler/codegen/graph-rules-codegen.js +0 -450
  119. package/src/compiler/codegen/notification-codegen.js +0 -232
  120. package/src/compiler/codegen/operation-codegen.js +0 -1382
  121. package/src/compiler/codegen/permission-codegen.js +0 -318
  122. package/src/compiler/codegen/subscribable-codegen.js +0 -827
  123. package/src/compiler/compiler.js +0 -371
  124. package/src/compiler/index.js +0 -11
  125. package/src/compiler/parser/entity-parser.js +0 -440
  126. package/src/compiler/parser/path-parser.js +0 -290
  127. package/src/compiler/parser/subscribable-parser.js +0 -244
  128. package/src/database/dzql-core.sql +0 -161
  129. package/src/database/migrations/001_schema.sql +0 -60
  130. package/src/database/migrations/002_functions.sql +0 -890
  131. package/src/database/migrations/003_operations.sql +0 -1135
  132. package/src/database/migrations/004_search.sql +0 -581
  133. package/src/database/migrations/005_entities.sql +0 -730
  134. package/src/database/migrations/006_auth.sql +0 -94
  135. package/src/database/migrations/007_events.sql +0 -133
  136. package/src/database/migrations/008_hello.sql +0 -18
  137. package/src/database/migrations/008a_meta.sql +0 -172
  138. package/src/database/migrations/009_subscriptions.sql +0 -240
  139. package/src/database/migrations/010_atomic_updates.sql +0 -157
  140. package/src/database/migrations/010_fix_m2m_events.sql +0 -94
  141. package/src/index.js +0 -40
  142. package/src/server/api.js +0 -9
  143. package/src/server/db.js +0 -442
  144. package/src/server/index.js +0 -317
  145. package/src/server/logger.js +0 -259
  146. package/src/server/mcp.js +0 -594
  147. package/src/server/meta-route.js +0 -251
  148. package/src/server/namespace.js +0 -292
  149. package/src/server/subscriptions.js +0 -351
  150. package/src/server/ws.js +0 -573
package/.env.sample ADDED
@@ -0,0 +1,28 @@
1
+ # TZQL Environment Variables
2
+ # Copy this file to .env and modify as needed
3
+
4
+ # ===================
5
+ # Server (Runtime)
6
+ # ===================
7
+
8
+ # PostgreSQL connection string
9
+ DATABASE_URL=postgres://user:password@localhost:5432/database
10
+
11
+ # Server port
12
+ PORT=3000
13
+
14
+ # Path to compiled manifest (relative to working directory)
15
+ MANIFEST_PATH=./dist/runtime/manifest.json
16
+
17
+ # JWT secret for authentication (CHANGE IN PRODUCTION!)
18
+ JWT_SECRET=your-secure-secret-key-here
19
+
20
+ # ===================
21
+ # Client (Browser)
22
+ # ===================
23
+ # These are injected at build time by your bundler (Vite, Webpack, etc.)
24
+
25
+ # Token name for localStorage (Vite: prefix with VITE_)
26
+ # VITE_TZQL_TOKEN_NAME=myapp_token
27
+ # Or for other bundlers:
28
+ # TZQL_TOKEN_NAME=myapp_token
package/compose.yml ADDED
@@ -0,0 +1,28 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16-alpine
4
+ container_name: tzql-test-db
5
+ environment:
6
+ POSTGRES_USER: dzql_test
7
+ POSTGRES_PASSWORD: dzql_test
8
+ POSTGRES_DB: dzql_test
9
+ ports:
10
+ - "5433:5432" # Map container's 5432 to host's 5433
11
+ volumes:
12
+ - tzql-test-data:/var/lib/postgresql/data
13
+ healthcheck:
14
+ test: ["CMD-SHELL", "pg_isready -U dzql_test -d dzql_test"]
15
+ interval: 5s
16
+ timeout: 5s
17
+ retries: 5
18
+ command:
19
+ - "postgres"
20
+ - "-c"
21
+ - "log_statement=all"
22
+ - "-c"
23
+ - "log_destination=stderr"
24
+ - "-c"
25
+ - "logging_collector=off"
26
+
27
+ volumes:
28
+ tzql-test-data:
@@ -0,0 +1 @@
1
+ export * from './ws.js';
@@ -0,0 +1,114 @@
1
+ // Generated by TZQL Compiler v2.0.0
2
+ // Do not edit this file directly.
3
+
4
+ import { defineStore } from 'pinia';
5
+ import { ref, type Ref } from 'vue';
6
+ import { ws } from '../index.js';
7
+
8
+ /** Parameters for my_profile subscription */
9
+ export interface MyProfileParams {
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ /** Event from server for patching */
14
+ export interface PatchEvent {
15
+ table: string;
16
+ op: 'insert' | 'update' | 'delete';
17
+ pk: { id: number };
18
+ data: Record<string, unknown> | null;
19
+ }
20
+
21
+ /** Document wrapper with loading state */
22
+ export interface DocumentWrapper<T> {
23
+ data: T;
24
+ loading: boolean;
25
+ ready: Promise<void>;
26
+ }
27
+
28
+ export const useMyProfileStore = defineStore('sub-my_profile', () => {
29
+ const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});
30
+ const unsubscribers = new Map<string, () => void>();
31
+
32
+ async function bind(params: MyProfileParams): Promise<DocumentWrapper<Record<string, unknown>>> {
33
+ const key = JSON.stringify(params);
34
+
35
+ if (documents.value[key]) {
36
+ const existing = documents.value[key];
37
+ if (existing.loading) {
38
+ await existing.ready;
39
+ }
40
+ return existing;
41
+ }
42
+
43
+ let resolveReady!: () => void;
44
+ const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
45
+
46
+ documents.value[key] = { data: {}, loading: true, ready };
47
+ let isFirst = true;
48
+
49
+ const unsubscribe = await ws.api.subscribe_my_profile(params, (eventData: unknown) => {
50
+ if (isFirst) {
51
+ // Initial data - merge into existing object to preserve reactivity
52
+ Object.assign(documents.value[key].data, eventData as Record<string, unknown>);
53
+ documents.value[key].loading = false;
54
+ isFirst = false;
55
+ resolveReady();
56
+ } else {
57
+ // Patch event
58
+ applyPatch(documents.value[key].data, eventData as PatchEvent);
59
+ }
60
+ });
61
+
62
+ unsubscribers.set(key, unsubscribe as () => void);
63
+ await ready;
64
+ return documents.value[key];
65
+ }
66
+
67
+ function unbind(params: MyProfileParams): void {
68
+ const key = JSON.stringify(params);
69
+ const unsubscribe = unsubscribers.get(key);
70
+ if (unsubscribe) {
71
+ unsubscribe();
72
+ unsubscribers.delete(key);
73
+ delete documents.value[key];
74
+ }
75
+ }
76
+
77
+ function applyPatch(doc: Record<string, unknown>, event: PatchEvent): void {
78
+ if (!doc) return;
79
+ switch (event.table) {
80
+ case 'users':
81
+ if (event.op === 'update' && doc.users) {
82
+ Object.assign(doc.users, event.data);
83
+ }
84
+ break;
85
+ case 'acts_for':
86
+ handleArrayPatch(doc.memberships, event);
87
+ break;
88
+ case 'organisations':
89
+ if (event.data && event.data.membership_id) {
90
+ const parent = doc.memberships?.find((p: { id: number }) => p.id === event.data.membership_id);
91
+ if (parent && parent.org) {
92
+ handleArrayPatch(parent.org, event);
93
+ }
94
+ }
95
+ break;
96
+ }
97
+ }
98
+
99
+ function handleArrayPatch(arr: unknown[] | undefined, event: PatchEvent): void {
100
+ if (!arr || !Array.isArray(arr)) return;
101
+ const pkValue = event.pk?.id;
102
+ const idx = arr.findIndex((i: unknown) => (i as { id: number }).id === pkValue);
103
+
104
+ if (event.op === 'insert') {
105
+ if (idx === -1 && event.data) arr.push(event.data);
106
+ } else if (event.op === 'update') {
107
+ if (idx !== -1 && event.data) Object.assign(arr[idx] as object, event.data);
108
+ } else if (event.op === 'delete') {
109
+ if (idx !== -1) arr.splice(idx, 1);
110
+ }
111
+ }
112
+
113
+ return { bind, unbind, documents };
114
+ });
@@ -0,0 +1,131 @@
1
+ // Generated by TZQL Compiler v2.0.0
2
+ // Do not edit this file directly.
3
+
4
+ import { defineStore } from 'pinia';
5
+ import { ref, type Ref } from 'vue';
6
+ import { ws } from '../index.js';
7
+
8
+ /** Parameters for org_dashboard subscription */
9
+ export interface OrgDashboardParams {
10
+ org_id: number;
11
+ }
12
+
13
+ /** Event from server for patching */
14
+ export interface PatchEvent {
15
+ table: string;
16
+ op: 'insert' | 'update' | 'delete';
17
+ pk: { id: number };
18
+ data: Record<string, unknown> | null;
19
+ }
20
+
21
+ /** Document wrapper with loading state */
22
+ export interface DocumentWrapper<T> {
23
+ data: T;
24
+ loading: boolean;
25
+ ready: Promise<void>;
26
+ }
27
+
28
+ export const useOrgDashboardStore = defineStore('sub-org_dashboard', () => {
29
+ const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});
30
+ const unsubscribers = new Map<string, () => void>();
31
+
32
+ async function bind(params: OrgDashboardParams): Promise<DocumentWrapper<Record<string, unknown>>> {
33
+ const key = JSON.stringify(params);
34
+
35
+ if (documents.value[key]) {
36
+ const existing = documents.value[key];
37
+ if (existing.loading) {
38
+ await existing.ready;
39
+ }
40
+ return existing;
41
+ }
42
+
43
+ let resolveReady!: () => void;
44
+ const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
45
+
46
+ documents.value[key] = { data: {}, loading: true, ready };
47
+ let isFirst = true;
48
+
49
+ const unsubscribe = await ws.api.subscribe_org_dashboard(params, (eventData: unknown) => {
50
+ if (isFirst) {
51
+ // Initial data - merge into existing object to preserve reactivity
52
+ Object.assign(documents.value[key].data, eventData as Record<string, unknown>);
53
+ documents.value[key].loading = false;
54
+ isFirst = false;
55
+ resolveReady();
56
+ } else {
57
+ // Patch event
58
+ applyPatch(documents.value[key].data, eventData as PatchEvent);
59
+ }
60
+ });
61
+
62
+ unsubscribers.set(key, unsubscribe as () => void);
63
+ await ready;
64
+ return documents.value[key];
65
+ }
66
+
67
+ function unbind(params: OrgDashboardParams): void {
68
+ const key = JSON.stringify(params);
69
+ const unsubscribe = unsubscribers.get(key);
70
+ if (unsubscribe) {
71
+ unsubscribe();
72
+ unsubscribers.delete(key);
73
+ delete documents.value[key];
74
+ }
75
+ }
76
+
77
+ function applyPatch(doc: Record<string, unknown>, event: PatchEvent): void {
78
+ if (!doc) return;
79
+ switch (event.table) {
80
+ case 'organisations':
81
+ if (event.op === 'update' && doc.organisations) {
82
+ Object.assign(doc.organisations, event.data);
83
+ }
84
+ break;
85
+ case 'venues':
86
+ handleArrayPatch(doc.venues, event);
87
+ break;
88
+ case 'sites':
89
+ if (event.data && event.data.venue_id) {
90
+ const parent = doc.venues?.find((p: { id: number }) => p.id === event.data.venue_id);
91
+ if (parent && parent.sites) {
92
+ handleArrayPatch(parent.sites, event);
93
+ }
94
+ }
95
+ break;
96
+ case 'products':
97
+ handleArrayPatch(doc.products, event);
98
+ break;
99
+ case 'packages':
100
+ handleArrayPatch(doc.packages, event);
101
+ break;
102
+ case 'brands':
103
+ handleArrayPatch(doc.brands, event);
104
+ break;
105
+ case 'artwork':
106
+ if (event.data && event.data.brand_id) {
107
+ const parent = doc.brands?.find((p: { id: number }) => p.id === event.data.brand_id);
108
+ if (parent && parent.artwork) {
109
+ handleArrayPatch(parent.artwork, event);
110
+ }
111
+ }
112
+ break;
113
+ }
114
+ }
115
+
116
+ function handleArrayPatch(arr: unknown[] | undefined, event: PatchEvent): void {
117
+ if (!arr || !Array.isArray(arr)) return;
118
+ const pkValue = event.pk?.id;
119
+ const idx = arr.findIndex((i: unknown) => (i as { id: number }).id === pkValue);
120
+
121
+ if (event.op === 'insert') {
122
+ if (idx === -1 && event.data) arr.push(event.data);
123
+ } else if (event.op === 'update') {
124
+ if (idx !== -1 && event.data) Object.assign(arr[idx] as object, event.data);
125
+ } else if (event.op === 'delete') {
126
+ if (idx !== -1) arr.splice(idx, 1);
127
+ }
128
+ }
129
+
130
+ return { bind, unbind, documents };
131
+ });
@@ -0,0 +1,117 @@
1
+ // Generated by TZQL Compiler v2.0.0
2
+ // Do not edit this file directly.
3
+
4
+ import { defineStore } from 'pinia';
5
+ import { ref, type Ref } from 'vue';
6
+ import { ws } from '../index.js';
7
+
8
+ /** Parameters for venue_detail subscription */
9
+ export interface VenueDetailParams {
10
+ venue_id: number;
11
+ }
12
+
13
+ /** Event from server for patching */
14
+ export interface PatchEvent {
15
+ table: string;
16
+ op: 'insert' | 'update' | 'delete';
17
+ pk: { id: number };
18
+ data: Record<string, unknown> | null;
19
+ }
20
+
21
+ /** Document wrapper with loading state */
22
+ export interface DocumentWrapper<T> {
23
+ data: T;
24
+ loading: boolean;
25
+ ready: Promise<void>;
26
+ }
27
+
28
+ export const useVenueDetailStore = defineStore('sub-venue_detail', () => {
29
+ const documents: Ref<Record<string, DocumentWrapper<Record<string, unknown>>>> = ref({});
30
+ const unsubscribers = new Map<string, () => void>();
31
+
32
+ async function bind(params: VenueDetailParams): Promise<DocumentWrapper<Record<string, unknown>>> {
33
+ const key = JSON.stringify(params);
34
+
35
+ if (documents.value[key]) {
36
+ const existing = documents.value[key];
37
+ if (existing.loading) {
38
+ await existing.ready;
39
+ }
40
+ return existing;
41
+ }
42
+
43
+ let resolveReady!: () => void;
44
+ const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
45
+
46
+ documents.value[key] = { data: {}, loading: true, ready };
47
+ let isFirst = true;
48
+
49
+ const unsubscribe = await ws.api.subscribe_venue_detail(params, (eventData: unknown) => {
50
+ if (isFirst) {
51
+ // Initial data - merge into existing object to preserve reactivity
52
+ Object.assign(documents.value[key].data, eventData as Record<string, unknown>);
53
+ documents.value[key].loading = false;
54
+ isFirst = false;
55
+ resolveReady();
56
+ } else {
57
+ // Patch event
58
+ applyPatch(documents.value[key].data, eventData as PatchEvent);
59
+ }
60
+ });
61
+
62
+ unsubscribers.set(key, unsubscribe as () => void);
63
+ await ready;
64
+ return documents.value[key];
65
+ }
66
+
67
+ function unbind(params: VenueDetailParams): void {
68
+ const key = JSON.stringify(params);
69
+ const unsubscribe = unsubscribers.get(key);
70
+ if (unsubscribe) {
71
+ unsubscribe();
72
+ unsubscribers.delete(key);
73
+ delete documents.value[key];
74
+ }
75
+ }
76
+
77
+ function applyPatch(doc: Record<string, unknown>, event: PatchEvent): void {
78
+ if (!doc) return;
79
+ switch (event.table) {
80
+ case 'venues':
81
+ if (event.op === 'update' && doc.venues) {
82
+ Object.assign(doc.venues, event.data);
83
+ }
84
+ break;
85
+ case 'organisations':
86
+ handleArrayPatch(doc.org, event);
87
+ break;
88
+ case 'sites':
89
+ handleArrayPatch(doc.sites, event);
90
+ break;
91
+ case 'allocations':
92
+ if (event.data && event.data.site_id) {
93
+ const parent = doc.sites?.find((p: { id: number }) => p.id === event.data.site_id);
94
+ if (parent && parent.allocations) {
95
+ handleArrayPatch(parent.allocations, event);
96
+ }
97
+ }
98
+ break;
99
+ }
100
+ }
101
+
102
+ function handleArrayPatch(arr: unknown[] | undefined, event: PatchEvent): void {
103
+ if (!arr || !Array.isArray(arr)) return;
104
+ const pkValue = event.pk?.id;
105
+ const idx = arr.findIndex((i: unknown) => (i as { id: number }).id === pkValue);
106
+
107
+ if (event.op === 'insert') {
108
+ if (idx === -1 && event.data) arr.push(event.data);
109
+ } else if (event.op === 'update') {
110
+ if (idx !== -1 && event.data) Object.assign(arr[idx] as object, event.data);
111
+ } else if (event.op === 'delete') {
112
+ if (idx !== -1) arr.splice(idx, 1);
113
+ }
114
+ }
115
+
116
+ return { bind, unbind, documents };
117
+ });