@lapis-notes/bases 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 (155) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE.md +8 -0
  3. package/README.md +20 -0
  4. package/dist/bases-plugin.d.ts +10 -0
  5. package/dist/bases-plugin.js +118 -0
  6. package/dist/bases-view/app-database-query-source.d.ts +30 -0
  7. package/dist/bases-view/app-database-query-source.js +248 -0
  8. package/dist/bases-view/bases.svelte.d.ts +569 -0
  9. package/dist/bases-view/bases.svelte.js +910 -0
  10. package/dist/bases-view/card-view/components/card-placeholder.svelte +68 -0
  11. package/dist/bases-view/card-view/components/card-placeholder.svelte.d.ts +7 -0
  12. package/dist/bases-view/card-view/components/card.svelte +289 -0
  13. package/dist/bases-view/card-view/components/card.svelte.d.ts +7 -0
  14. package/dist/bases-view/card-view/index.d.ts +11 -0
  15. package/dist/bases-view/card-view/index.js +55 -0
  16. package/dist/bases-view/cell-editor-options.d.ts +2 -0
  17. package/dist/bases-view/cell-editor-options.js +26 -0
  18. package/dist/bases-view/cell-editor.svelte +206 -0
  19. package/dist/bases-view/cell-editor.svelte.d.ts +12 -0
  20. package/dist/bases-view/cell.svelte +132 -0
  21. package/dist/bases-view/cell.svelte.d.ts +16 -0
  22. package/dist/bases-view/column-definition.d.ts +22 -0
  23. package/dist/bases-view/column-definition.js +149 -0
  24. package/dist/bases-view/columns.d.ts +15 -0
  25. package/dist/bases-view/columns.js +103 -0
  26. package/dist/bases-view/components/Tag.svelte +14 -0
  27. package/dist/bases-view/components/Tag.svelte.d.ts +8 -0
  28. package/dist/bases-view/components/autocomplete.svelte +45 -0
  29. package/dist/bases-view/components/autocomplete.svelte.d.ts +17 -0
  30. package/dist/bases-view/components/card-image.svelte +121 -0
  31. package/dist/bases-view/components/card-image.svelte.d.ts +11 -0
  32. package/dist/bases-view/components/column-visibility.svelte +388 -0
  33. package/dist/bases-view/components/column-visibility.svelte.d.ts +13 -0
  34. package/dist/bases-view/components/datetime.svelte +35 -0
  35. package/dist/bases-view/components/datetime.svelte.d.ts +7 -0
  36. package/dist/bases-view/components/file.svelte +31 -0
  37. package/dist/bases-view/components/file.svelte.d.ts +8 -0
  38. package/dist/bases-view/components/filters/filter-editor.svelte +102 -0
  39. package/dist/bases-view/components/filters/filter-editor.svelte.d.ts +10 -0
  40. package/dist/bases-view/components/filters/filter-group.svelte +142 -0
  41. package/dist/bases-view/components/filters/filter-group.svelte.d.ts +11 -0
  42. package/dist/bases-view/components/filters/filter-properties.svelte +77 -0
  43. package/dist/bases-view/components/filters/filter-properties.svelte.d.ts +8 -0
  44. package/dist/bases-view/components/filters/filter-root.svelte +112 -0
  45. package/dist/bases-view/components/filters/filter-root.svelte.d.ts +12 -0
  46. package/dist/bases-view/components/filters/filter-row.svelte +351 -0
  47. package/dist/bases-view/components/filters/filter-row.svelte.d.ts +11 -0
  48. package/dist/bases-view/components/group/group-root.svelte +120 -0
  49. package/dist/bases-view/components/group/group-root.svelte.d.ts +8 -0
  50. package/dist/bases-view/components/limit.svelte +100 -0
  51. package/dist/bases-view/components/limit.svelte.d.ts +12 -0
  52. package/dist/bases-view/components/query-editor.svelte +230 -0
  53. package/dist/bases-view/components/query-editor.svelte.d.ts +15 -0
  54. package/dist/bases-view/components/sort/sort-direction-labels.d.ts +9 -0
  55. package/dist/bases-view/components/sort/sort-direction-labels.js +25 -0
  56. package/dist/bases-view/components/sort/sort-root.svelte +239 -0
  57. package/dist/bases-view/components/sort/sort-root.svelte.d.ts +11 -0
  58. package/dist/bases-view/components/sort/sort-row.svelte +190 -0
  59. package/dist/bases-view/components/sort/sort-row.svelte.d.ts +15 -0
  60. package/dist/bases-view/components/summary-strip.svelte +56 -0
  61. package/dist/bases-view/components/summary-strip.svelte.d.ts +8 -0
  62. package/dist/bases-view/components/tag-chip.svelte +47 -0
  63. package/dist/bases-view/components/tag-chip.svelte.d.ts +12 -0
  64. package/dist/bases-view/components/views.svelte +178 -0
  65. package/dist/bases-view/components/views.svelte.d.ts +12 -0
  66. package/dist/bases-view/data-table-adapter/data-table.svelte.d.ts +45 -0
  67. package/dist/bases-view/data-table-adapter/data-table.svelte.js +112 -0
  68. package/dist/bases-view/data-table-adapter/flex-render.svelte +43 -0
  69. package/dist/bases-view/data-table-adapter/flex-render.svelte.d.ts +30 -0
  70. package/dist/bases-view/data-table-adapter/index.d.ts +3 -0
  71. package/dist/bases-view/data-table-adapter/index.js +3 -0
  72. package/dist/bases-view/data-table-adapter/render-helpers.d.ts +101 -0
  73. package/dist/bases-view/data-table-adapter/render-helpers.js +110 -0
  74. package/dist/bases-view/db.d.ts +31 -0
  75. package/dist/bases-view/db.js +174 -0
  76. package/dist/bases-view/document-core.d.ts +2 -0
  77. package/dist/bases-view/document-core.js +166 -0
  78. package/dist/bases-view/export-core.d.ts +4 -0
  79. package/dist/bases-view/export-core.js +31 -0
  80. package/dist/bases-view/file-fields-core.d.ts +31 -0
  81. package/dist/bases-view/file-fields-core.js +114 -0
  82. package/dist/bases-view/filter-parser.d.ts +27 -0
  83. package/dist/bases-view/filter-parser.js +315 -0
  84. package/dist/bases-view/functions-core.d.ts +66 -0
  85. package/dist/bases-view/functions-core.js +265 -0
  86. package/dist/bases-view/functions.d.ts +5 -0
  87. package/dist/bases-view/functions.js +107 -0
  88. package/dist/bases-view/fuzzy-search.d.ts +6 -0
  89. package/dist/bases-view/fuzzy-search.js +15 -0
  90. package/dist/bases-view/hooks/useResizeObserver.svelte.d.ts +7 -0
  91. package/dist/bases-view/hooks/useResizeObserver.svelte.js +26 -0
  92. package/dist/bases-view/index.d.ts +66 -0
  93. package/dist/bases-view/index.js +231 -0
  94. package/dist/bases-view/list-view/index.d.ts +10 -0
  95. package/dist/bases-view/list-view/index.js +17 -0
  96. package/dist/bases-view/list-view/list.svelte +156 -0
  97. package/dist/bases-view/list-view/list.svelte.d.ts +7 -0
  98. package/dist/bases-view/map-view/index.d.ts +10 -0
  99. package/dist/bases-view/map-view/index.js +17 -0
  100. package/dist/bases-view/map-view/view.svelte +28 -0
  101. package/dist/bases-view/map-view/view.svelte.d.ts +7 -0
  102. package/dist/bases-view/metadata-invalidation-core.d.ts +34 -0
  103. package/dist/bases-view/metadata-invalidation-core.js +162 -0
  104. package/dist/bases-view/metadata-type-info.d.ts +7 -0
  105. package/dist/bases-view/metadata-type-info.js +10 -0
  106. package/dist/bases-view/models.d.ts +90 -0
  107. package/dist/bases-view/models.js +133 -0
  108. package/dist/bases-view/query-result-core.d.ts +8 -0
  109. package/dist/bases-view/query-result-core.js +16 -0
  110. package/dist/bases-view/resource-url.d.ts +2 -0
  111. package/dist/bases-view/resource-url.js +21 -0
  112. package/dist/bases-view/search-core.d.ts +9 -0
  113. package/dist/bases-view/search-core.js +20 -0
  114. package/dist/bases-view/summary-core.d.ts +7 -0
  115. package/dist/bases-view/summary-core.js +17 -0
  116. package/dist/bases-view/table-view/components/div-table.svelte +464 -0
  117. package/dist/bases-view/table-view/components/div-table.svelte.d.ts +32 -0
  118. package/dist/bases-view/table-view/components/dnd-style.d.ts +3 -0
  119. package/dist/bases-view/table-view/components/dnd-style.js +6 -0
  120. package/dist/bases-view/table-view/components/sort-header.svelte +48 -0
  121. package/dist/bases-view/table-view/components/sort-header.svelte.d.ts +11 -0
  122. package/dist/bases-view/table-view/components/table-column-tracks.d.ts +14 -0
  123. package/dist/bases-view/table-view/components/table-column-tracks.js +27 -0
  124. package/dist/bases-view/table-view/components/table-header.svelte +146 -0
  125. package/dist/bases-view/table-view/components/table-header.svelte.d.ts +10 -0
  126. package/dist/bases-view/table-view/components/table-placeholder.svelte +46 -0
  127. package/dist/bases-view/table-view/components/table-placeholder.svelte.d.ts +30 -0
  128. package/dist/bases-view/table-view/components/table-virtualizer-core.d.ts +13 -0
  129. package/dist/bases-view/table-view/components/table-virtualizer-core.js +12 -0
  130. package/dist/bases-view/table-view/components/table.svelte +997 -0
  131. package/dist/bases-view/table-view/components/table.svelte.d.ts +32 -0
  132. package/dist/bases-view/table-view/index.svelte.d.ts +10 -0
  133. package/dist/bases-view/table-view/index.svelte.js +30 -0
  134. package/dist/bases-view/unknown-view/index.d.ts +10 -0
  135. package/dist/bases-view/unknown-view/index.js +19 -0
  136. package/dist/bases-view/unknown-view/view.svelte +11 -0
  137. package/dist/bases-view/unknown-view/view.svelte.d.ts +6 -0
  138. package/dist/bases-view/view-header.svelte +142 -0
  139. package/dist/bases-view/view-header.svelte.d.ts +28 -0
  140. package/dist/bases-view/view-registration-inventory.d.ts +22 -0
  141. package/dist/bases-view/view-registration-inventory.js +7 -0
  142. package/dist/bases-view/view-settings.svelte +350 -0
  143. package/dist/bases-view/view-settings.svelte.d.ts +9 -0
  144. package/dist/bases-view/view.svelte +192 -0
  145. package/dist/bases-view/view.svelte.d.ts +15 -0
  146. package/dist/bases-view-surface.svelte +31 -0
  147. package/dist/bases-view-surface.svelte.d.ts +14 -0
  148. package/dist/index.d.ts +10 -0
  149. package/dist/index.js +8 -0
  150. package/dist/serialized-write-queue.d.ts +10 -0
  151. package/dist/serialized-write-queue.js +36 -0
  152. package/dist/styles.css +2164 -0
  153. package/manifest.json +33 -0
  154. package/package.json +90 -0
  155. package/styles.css +1 -0
@@ -0,0 +1,166 @@
1
+ function emptyFilter() {
2
+ return { and: [] };
3
+ }
4
+ function defaultTableView() {
5
+ return {
6
+ type: "table",
7
+ name: "Table",
8
+ layout: "table",
9
+ order: [],
10
+ sort: [],
11
+ filter: emptyFilter(),
12
+ limit: 0,
13
+ columnSize: {},
14
+ imageAspectRatio: 1,
15
+ };
16
+ }
17
+ function defaultCardsView() {
18
+ return {
19
+ type: "cards",
20
+ name: "Cards",
21
+ order: [],
22
+ sort: [],
23
+ filter: emptyFilter(),
24
+ limit: 0,
25
+ image: "",
26
+ imageFit: "contain",
27
+ imageAspectRatio: 1,
28
+ cardSize: 200,
29
+ };
30
+ }
31
+ function defaultCustomView(type) {
32
+ return {
33
+ type,
34
+ name: type,
35
+ order: [],
36
+ sort: [],
37
+ filter: emptyFilter(),
38
+ limit: 0,
39
+ };
40
+ }
41
+ function normalizeView(view, fallbackName) {
42
+ if (!view || typeof view !== "object")
43
+ return null;
44
+ const candidate = view;
45
+ if (candidate.type === "table") {
46
+ return {
47
+ ...defaultTableView(),
48
+ ...candidate,
49
+ name: typeof candidate.name === "string" ? candidate.name : fallbackName,
50
+ order: Array.isArray(candidate.order)
51
+ ? candidate.order.filter((value) => typeof value === "string")
52
+ : [],
53
+ sort: Array.isArray(candidate.sort) ? candidate.sort : [],
54
+ filter: candidate.filter && typeof candidate.filter === "object"
55
+ ? candidate.filter
56
+ : emptyFilter(),
57
+ limit: typeof candidate.limit === "number" ? candidate.limit : 0,
58
+ columnSize: candidate.columnSize && typeof candidate.columnSize === "object"
59
+ ? candidate.columnSize
60
+ : {},
61
+ imageAspectRatio: typeof candidate.imageAspectRatio === "number"
62
+ ? candidate.imageAspectRatio
63
+ : 1,
64
+ };
65
+ }
66
+ if (candidate.type === "cards") {
67
+ return {
68
+ ...defaultCardsView(),
69
+ ...candidate,
70
+ name: typeof candidate.name === "string" ? candidate.name : fallbackName,
71
+ order: Array.isArray(candidate.order)
72
+ ? candidate.order.filter((value) => typeof value === "string")
73
+ : [],
74
+ sort: Array.isArray(candidate.sort) ? candidate.sort : [],
75
+ filter: candidate.filter && typeof candidate.filter === "object"
76
+ ? candidate.filter
77
+ : emptyFilter(),
78
+ limit: typeof candidate.limit === "number" ? candidate.limit : 0,
79
+ summaries: candidate.summaries && typeof candidate.summaries === "object"
80
+ ? candidate.summaries
81
+ : undefined,
82
+ groupBy: candidate.groupBy && typeof candidate.groupBy === "object"
83
+ ? candidate.groupBy
84
+ : undefined,
85
+ image: typeof candidate.image === "string" ? candidate.image : "",
86
+ imageFit: candidate.imageFit === "cover" || candidate.imageFit === "contain"
87
+ ? candidate.imageFit
88
+ : "contain",
89
+ imageAspectRatio: typeof candidate.imageAspectRatio === "number"
90
+ ? candidate.imageAspectRatio
91
+ : 1,
92
+ cardSize: typeof candidate.cardSize === "number" ? candidate.cardSize : 200,
93
+ };
94
+ }
95
+ if (candidate.type === "list" || candidate.type === "map") {
96
+ return {
97
+ type: candidate.type,
98
+ name: typeof candidate.name === "string" ? candidate.name : fallbackName,
99
+ order: Array.isArray(candidate.order)
100
+ ? candidate.order.filter((value) => typeof value === "string")
101
+ : [],
102
+ sort: Array.isArray(candidate.sort) ? candidate.sort : [],
103
+ filter: candidate.filter && typeof candidate.filter === "object"
104
+ ? candidate.filter
105
+ : emptyFilter(),
106
+ limit: typeof candidate.limit === "number" ? candidate.limit : 0,
107
+ summaries: candidate.summaries && typeof candidate.summaries === "object"
108
+ ? candidate.summaries
109
+ : undefined,
110
+ groupBy: candidate.groupBy && typeof candidate.groupBy === "object"
111
+ ? candidate.groupBy
112
+ : undefined,
113
+ };
114
+ }
115
+ if (typeof candidate.type === "string" && candidate.type.length > 0) {
116
+ return {
117
+ ...defaultCustomView(candidate.type),
118
+ ...candidate,
119
+ name: typeof candidate.name === "string" ? candidate.name : fallbackName,
120
+ order: Array.isArray(candidate.order)
121
+ ? candidate.order.filter((value) => typeof value === "string")
122
+ : [],
123
+ sort: Array.isArray(candidate.sort) ? candidate.sort : [],
124
+ filter: candidate.filter && typeof candidate.filter === "object"
125
+ ? candidate.filter
126
+ : emptyFilter(),
127
+ limit: typeof candidate.limit === "number" ? candidate.limit : 0,
128
+ summaries: candidate.summaries && typeof candidate.summaries === "object"
129
+ ? candidate.summaries
130
+ : undefined,
131
+ groupBy: candidate.groupBy && typeof candidate.groupBy === "object"
132
+ ? candidate.groupBy
133
+ : undefined,
134
+ };
135
+ }
136
+ return null;
137
+ }
138
+ export function normalizeBasesDocument(input) {
139
+ const candidate = input && typeof input === "object" ? input : {};
140
+ const views = Array.isArray(candidate.views)
141
+ ? candidate.views
142
+ .map((view, index) => normalizeView(view, `View ${index + 1}`))
143
+ .filter((view) => !!view)
144
+ : [];
145
+ const normalizedViews = views.length ? views : [defaultTableView()];
146
+ const activeView = typeof candidate.activeView === "string" &&
147
+ normalizedViews.some((view) => view.name === candidate.activeView)
148
+ ? candidate.activeView
149
+ : normalizedViews[0].name;
150
+ return {
151
+ filters: candidate.filters && typeof candidate.filters === "object"
152
+ ? candidate.filters
153
+ : emptyFilter(),
154
+ properties: candidate.properties && typeof candidate.properties === "object"
155
+ ? candidate.properties
156
+ : {},
157
+ formulas: candidate.formulas && typeof candidate.formulas === "object"
158
+ ? candidate.formulas
159
+ : {},
160
+ summaries: candidate.summaries && typeof candidate.summaries === "object"
161
+ ? candidate.summaries
162
+ : {},
163
+ activeView,
164
+ views: normalizedViews,
165
+ };
166
+ }
@@ -0,0 +1,4 @@
1
+ export type ExportableEntry<TPropertyId extends string> = {
2
+ getValue(propertyId: TPropertyId): unknown;
3
+ };
4
+ export declare function serializeResultsToCsv<TPropertyId extends string>(entries: Array<ExportableEntry<TPropertyId>>, properties: TPropertyId[], displayName: (propertyId: TPropertyId) => string): string;
@@ -0,0 +1,31 @@
1
+ function stringifyValue(value) {
2
+ if (value === null || value === undefined) {
3
+ return "";
4
+ }
5
+ if (typeof value === "object" &&
6
+ value &&
7
+ "value" in value) {
8
+ const inner = value.value;
9
+ return inner === null || inner === undefined ? "" : String(inner);
10
+ }
11
+ return String(value);
12
+ }
13
+ function escapeCsv(value) {
14
+ if (!/[",\n]/.test(value)) {
15
+ return value;
16
+ }
17
+ return `"${value.replaceAll('"', '""')}"`;
18
+ }
19
+ export function serializeResultsToCsv(entries, properties, displayName) {
20
+ const lines = [
21
+ properties
22
+ .map((propertyId) => escapeCsv(displayName(propertyId)))
23
+ .join(","),
24
+ ];
25
+ for (const entry of entries) {
26
+ lines.push(properties
27
+ .map((propertyId) => escapeCsv(stringifyValue(entry.getValue(propertyId))))
28
+ .join(","));
29
+ }
30
+ return lines.join("\n");
31
+ }
@@ -0,0 +1,31 @@
1
+ import type { MetadataType } from "@lapis-notes/api";
2
+ export type FileColumnDefinition = {
3
+ id: string;
4
+ displayName: string;
5
+ icon: string;
6
+ type: MetadataType;
7
+ };
8
+ export type FileMetadataCache = {
9
+ frontmatter?: Record<string, unknown>;
10
+ links?: Array<{
11
+ link: string;
12
+ }>;
13
+ embeds?: Array<{
14
+ link: string;
15
+ }>;
16
+ tags?: Array<{
17
+ tag: string;
18
+ }>;
19
+ } | null | undefined;
20
+ export declare function buildFileColumns(properties?: Record<string, {
21
+ displayName: string;
22
+ }>): FileColumnDefinition[];
23
+ export declare function deriveFileMetadata(entryPath: string, cache: FileMetadataCache, backlinkInput?: string[] | Record<string, Record<string, number>>): {
24
+ tags: string[];
25
+ links: string[];
26
+ embeds: string[];
27
+ backlinks: string[];
28
+ properties: {
29
+ [x: string]: unknown;
30
+ };
31
+ };
@@ -0,0 +1,114 @@
1
+ export function buildFileColumns(properties = {}) {
2
+ return [
3
+ {
4
+ id: "file",
5
+ displayName: properties["file"]?.displayName || "file",
6
+ icon: "lucide-file",
7
+ type: "file",
8
+ },
9
+ {
10
+ id: "file.name",
11
+ displayName: properties["file.name"]?.displayName || "file name",
12
+ icon: "lucide-text",
13
+ type: "text",
14
+ },
15
+ {
16
+ id: "file.file",
17
+ displayName: properties["file.file"]?.displayName || "file reference",
18
+ icon: "lucide-file",
19
+ type: "file",
20
+ },
21
+ {
22
+ id: "file.ext",
23
+ displayName: properties["file.ext"]?.displayName || "file extension",
24
+ icon: "lucide-text",
25
+ type: "text",
26
+ },
27
+ {
28
+ id: "file.basename",
29
+ displayName: properties["file.basename"]?.displayName || "file base name",
30
+ icon: "lucide-text",
31
+ type: "text",
32
+ },
33
+ {
34
+ id: "file.folder",
35
+ displayName: properties["file.folder"]?.displayName || "folder",
36
+ icon: "lucide-text",
37
+ type: "text",
38
+ },
39
+ {
40
+ id: "file.fullname",
41
+ displayName: properties["file.fullname"]?.displayName || "file full name",
42
+ icon: "lucide-text",
43
+ type: "text",
44
+ },
45
+ {
46
+ id: "file.path",
47
+ displayName: properties["file.path"]?.displayName || "file path",
48
+ icon: "lucide-text",
49
+ type: "text",
50
+ },
51
+ {
52
+ id: "file.ctime",
53
+ displayName: properties["file.ctime"]?.displayName || "created time",
54
+ icon: "lucide-clock",
55
+ type: "datetime",
56
+ },
57
+ {
58
+ id: "file.mtime",
59
+ displayName: properties["file.mtime"]?.displayName || "modified time",
60
+ icon: "lucide-clock",
61
+ type: "datetime",
62
+ },
63
+ {
64
+ id: "file.size",
65
+ displayName: properties["file.size"]?.displayName || "file size",
66
+ icon: "lucide-binary",
67
+ type: "number",
68
+ },
69
+ {
70
+ id: "file.tags",
71
+ displayName: properties["file.tags"]?.displayName || "file tags",
72
+ icon: "lucide-tags",
73
+ type: "tags",
74
+ },
75
+ {
76
+ id: "file.links",
77
+ displayName: properties["file.links"]?.displayName || "file links",
78
+ icon: "lucide-link",
79
+ type: "multitext",
80
+ },
81
+ {
82
+ id: "file.embeds",
83
+ displayName: properties["file.embeds"]?.displayName || "file embeds",
84
+ icon: "lucide-image",
85
+ type: "multitext",
86
+ },
87
+ {
88
+ id: "file.backlinks",
89
+ displayName: properties["file.backlinks"]?.displayName || "file backlinks",
90
+ icon: "lucide-waypoints",
91
+ type: "multitext",
92
+ },
93
+ {
94
+ id: "file.properties",
95
+ displayName: properties["file.properties"]?.displayName || "file properties",
96
+ icon: "lucide-braces",
97
+ type: "unknown",
98
+ },
99
+ ];
100
+ }
101
+ export function deriveFileMetadata(entryPath, cache, backlinkInput = []) {
102
+ const backlinks = Array.isArray(backlinkInput)
103
+ ? backlinkInput
104
+ : Object.entries(backlinkInput)
105
+ .filter(([sourcePath, targets]) => sourcePath !== entryPath && Boolean(targets[entryPath]))
106
+ .map(([sourcePath]) => sourcePath);
107
+ return {
108
+ tags: (cache?.tags || []).map((it) => it.tag),
109
+ links: (cache?.links || []).map((it) => it.link),
110
+ embeds: (cache?.embeds || []).map((it) => it.link),
111
+ backlinks: backlinks.filter((sourcePath) => sourcePath !== entryPath),
112
+ properties: { ...(cache?.frontmatter || {}) },
113
+ };
114
+ }
@@ -0,0 +1,27 @@
1
+ import { Expression } from "peaql";
2
+ import { type BasesViewBase, type Filters } from "./models";
3
+ import type { BasesPropertyId } from "@lapis-notes/api";
4
+ export declare function isOp(op: string): boolean;
5
+ export declare function parsePredicate(query: string): unknown;
6
+ export declare const INVALID_FILTER_FORMULA_MESSAGE = "Invalid filter formula. Check the expression syntax.";
7
+ export type FilterPredicateValidation = {
8
+ valid: true;
9
+ predicate: unknown;
10
+ error: null;
11
+ } | {
12
+ valid: false;
13
+ predicate: null;
14
+ error: string;
15
+ };
16
+ export declare function validateFilterPredicate(query: string): FilterPredicateValidation;
17
+ export declare function resolveFilterDraft(applied: string, draft: string): {
18
+ applied: string;
19
+ error: string | null;
20
+ valid: boolean;
21
+ };
22
+ export declare function normalizeFilter(filter: Filters): Filters;
23
+ export declare function toSQL(filter: unknown, formulas?: Record<string, string>): string;
24
+ export declare function resolveQuery(expr: Expression): unknown;
25
+ export declare function isFilterColumn(column: string): [boolean, string, string];
26
+ export declare function formulaColumn(name: string): BasesPropertyId;
27
+ export declare function generateQuery(view: BasesViewBase, formulas: Record<string, string>, globalFilter?: Filters): string;
@@ -0,0 +1,315 @@
1
+ import { AttributeExpression, BooleanExpression, ColumnExpression, Decimal, Expression, FunctionExpression, isNull, isValidNumber, LiteralExpression, parseQuery, Query, readString, SubscriptExpression, typeName, } from "peaql";
2
+ import { filterGroupType, filterGroupValues, isFilterGroup, isFilterLine, } from "./models";
3
+ import { isNumber } from "lodash-es";
4
+ import { DateTime } from "luxon";
5
+ const OPS = new Set([
6
+ ">",
7
+ "=",
8
+ "<",
9
+ "!=",
10
+ ">=",
11
+ "<=",
12
+ "~",
13
+ "~*",
14
+ "!~",
15
+ "!~*",
16
+ "?~",
17
+ "?~*",
18
+ "+",
19
+ "-",
20
+ "*",
21
+ "%",
22
+ "/",
23
+ ]);
24
+ export function isOp(op) {
25
+ return OPS.has(op);
26
+ }
27
+ export function parsePredicate(query) {
28
+ if (!query)
29
+ return null;
30
+ const [expr] = parseQuery(`SELECT 1 WHERE ${query}`);
31
+ if (expr instanceof Query && expr.where) {
32
+ return resolveQuery(expr.where);
33
+ }
34
+ }
35
+ export const INVALID_FILTER_FORMULA_MESSAGE = "Invalid filter formula. Check the expression syntax.";
36
+ export function validateFilterPredicate(query) {
37
+ if (!query.trim()) {
38
+ return { valid: true, predicate: null, error: null };
39
+ }
40
+ try {
41
+ const predicate = parsePredicate(query);
42
+ if (predicate) {
43
+ return { valid: true, predicate, error: null };
44
+ }
45
+ }
46
+ catch {
47
+ // The editor keeps the draft available for correction and reports one
48
+ // stable, user-facing message instead of leaking parser internals.
49
+ }
50
+ return {
51
+ valid: false,
52
+ predicate: null,
53
+ error: INVALID_FILTER_FORMULA_MESSAGE,
54
+ };
55
+ }
56
+ export function resolveFilterDraft(applied, draft) {
57
+ const validation = validateFilterPredicate(draft);
58
+ return {
59
+ applied: validation.valid ? draft : applied,
60
+ error: validation.error,
61
+ valid: validation.valid,
62
+ };
63
+ }
64
+ export function normalizeFilter(filter) {
65
+ const values = filterGroupValues(filter).slice();
66
+ values.forEach((value, i) => {
67
+ if (isFilterGroup(value)) {
68
+ normalizeFilter(value);
69
+ }
70
+ });
71
+ const type = filterGroupType(filter);
72
+ return {
73
+ [type]: values.filter((f) => {
74
+ if (typeof f === "string") {
75
+ return f.trim().length > 0;
76
+ }
77
+ return f;
78
+ }),
79
+ };
80
+ }
81
+ function toSQLValue(value) {
82
+ if (value === null || value === undefined)
83
+ return "null";
84
+ if (Array.isArray(value)) {
85
+ return value.map(toSQLValue).join(",");
86
+ }
87
+ if (typeof value === "string") {
88
+ value = readString(value);
89
+ }
90
+ else if (typeof value === "number" || value instanceof Decimal) {
91
+ if (!isNull(value) && isValidNumber(value.toString())) {
92
+ return new Decimal(value.toString()).number;
93
+ }
94
+ }
95
+ else if (value instanceof DateTime) {
96
+ value = value.toISO();
97
+ }
98
+ return JSON.stringify(value);
99
+ }
100
+ function replaceFormulas(query, formulas = {}) {
101
+ while (query.match(/formula\["([^"]*)"\]|filter\.[a-zA-Z_][a-zA-Z0-9_]*/g)) {
102
+ query = query.replaceAll(/formula\["([^"]*)"\]|filter\.[a-zA-Z_][a-zA-Z0-9_]*/g, (match, captured) => {
103
+ return formulas[captured] || "null";
104
+ });
105
+ }
106
+ return query;
107
+ }
108
+ export function toSQL(filter, formulas = {}) {
109
+ if (isFilterGroup(filter)) {
110
+ const type = filterGroupType(filter);
111
+ const values = filterGroupValues(filter)
112
+ .map((it) => toSQL(it, formulas))
113
+ .filter((it) => it.trim());
114
+ if (!values.length)
115
+ return "";
116
+ if (type === "not") {
117
+ return `NOT (${values.join(` OR `)})`;
118
+ }
119
+ return "(" + values.join(` ${type.toUpperCase()} `).trim() + ")";
120
+ }
121
+ else if (isFilterLine(filter)) {
122
+ const filt = filter;
123
+ if (filt.custom) {
124
+ return filt.custom;
125
+ }
126
+ if (filt.op &&
127
+ filt.column &&
128
+ (filter.value !== null || filt.value !== undefined)) {
129
+ let op = filter.op;
130
+ let column = filter.column;
131
+ const [isFilter, filterKey] = isFilterColumn(column);
132
+ if (isFilter) {
133
+ if (formulas && filterKey in formulas && formulas[filterKey]) {
134
+ column = `(${formulas[filterKey]})`;
135
+ }
136
+ }
137
+ else if (column.match(/^[a-zA-Z0-9_.-]+\[[^\]]+\]$/)) {
138
+ column = column;
139
+ }
140
+ else if (!column.match(/^[a-zA-Z0-9_.-]+$/)) {
141
+ column = `{${column.replaceAll("}", "}}")}}`;
142
+ }
143
+ if (!OPS.has(op) && op.startsWith("!")) {
144
+ op = op.substring(1);
145
+ column = "!" + column;
146
+ }
147
+ if (OPS.has(op)) {
148
+ return `${column} ${op} ${toSQLValue(filt.value)}`;
149
+ }
150
+ const values = Array.isArray(filt.value) ? filt.value : [filt.value];
151
+ return `${column}.${op}(${toSQLValue(values)})`;
152
+ }
153
+ }
154
+ else if (typeof filter === "string") {
155
+ return replaceFormulas(filter, formulas);
156
+ }
157
+ return "";
158
+ }
159
+ function flattenBooleanExpression(expr, targetOp) {
160
+ const conditions = [];
161
+ for (const arg of expr.args) {
162
+ if (arg instanceof BooleanExpression && arg.op === targetOp) {
163
+ // Same operator - flatten recursively
164
+ conditions.push(...flattenBooleanExpression(arg, targetOp));
165
+ }
166
+ else {
167
+ // Different operator or non-boolean - resolve normally
168
+ conditions.push(resolveQuery(arg));
169
+ }
170
+ }
171
+ return conditions;
172
+ }
173
+ export function resolveQuery(expr) {
174
+ if (expr instanceof BooleanExpression) {
175
+ if (expr.args.length == 2) {
176
+ const [left, right] = expr.args;
177
+ const l = resolveQuery(left), r = resolveQuery(right);
178
+ if (typeof l === "string" &&
179
+ (right instanceof LiteralExpression || isScalar(r))) {
180
+ return { column: l, op: expr.op, value: r };
181
+ }
182
+ }
183
+ if (["AND", "OR", "NOT"].includes(expr.op)) {
184
+ const flattened = flattenBooleanExpression(expr, expr.op);
185
+ if (expr.op === "NOT" && flattened.length == 1) {
186
+ const value = flattened[0];
187
+ if (isFilterLine(value) && !value.custom) {
188
+ if (!OPS.has(value.op)) {
189
+ value.op = "!" + value.op;
190
+ return value;
191
+ }
192
+ }
193
+ }
194
+ return { [expr.op.toLowerCase()]: flattened };
195
+ }
196
+ return { column: "", op: expr.op, value: "", custom: expr.toString() };
197
+ }
198
+ else if (expr instanceof AttributeExpression) {
199
+ const op = resolveQuery(expr.operand);
200
+ if (typeof expr.name === "string" && typeof op == "string") {
201
+ return `${op}.${expr.name}`;
202
+ }
203
+ else if (typeof op === "string" && expr.name instanceof Expression) {
204
+ const value = resolveQuery(expr.name);
205
+ if (typeof value === "object") {
206
+ if (value && "custom" in value && value.custom) {
207
+ return { column: op, value: "", ...value, custom: expr.toString() };
208
+ }
209
+ return { column: op, ...value };
210
+ }
211
+ }
212
+ else {
213
+ return { column: "", op: "", custom: expr.toString(), value: "" };
214
+ }
215
+ }
216
+ else if (expr instanceof ColumnExpression) {
217
+ return expr.name;
218
+ }
219
+ else if (expr instanceof LiteralExpression) {
220
+ return expr.value;
221
+ }
222
+ else if (expr instanceof FunctionExpression) {
223
+ if (expr.args.every((it) => {
224
+ return (it instanceof LiteralExpression || it instanceof ColumnExpression);
225
+ })) {
226
+ let values = expr.args.map((it) => resolveQuery(it));
227
+ if (values.length == 1) {
228
+ return { op: expr.name, value: values[0] };
229
+ }
230
+ return { op: expr.name, value: values };
231
+ }
232
+ return { op: expr.name, custom: expr.toString(), value: "" };
233
+ }
234
+ else if (expr instanceof SubscriptExpression) {
235
+ if (expr.operand instanceof ColumnExpression) {
236
+ return `${expr.operand.name}["${expr.key}"]`;
237
+ }
238
+ }
239
+ throw new Error(`Unsupported expression(${typeName(expr)}): ${expr}`);
240
+ }
241
+ function isScalar(value) {
242
+ const type = typeof value;
243
+ return ["string", "boolean", "number"].includes(type);
244
+ }
245
+ export function isFilterColumn(column) {
246
+ if (!column)
247
+ return [false, "", ""];
248
+ column = column.trim();
249
+ if (column.startsWith("formula.")) {
250
+ return [true, column.split(".", 2)[1], `{${column}}`];
251
+ }
252
+ else if (column.startsWith("formula[") && column.endsWith("]")) {
253
+ const id = readString(column.substring(8, column.length - 1));
254
+ return [true, id, `{formula.${id}}`];
255
+ }
256
+ return [false, "", ""];
257
+ }
258
+ export function formulaColumn(name) {
259
+ if (name.match(/^[a-zA-Z0-9_]+$/)) {
260
+ return `formula.${name}`;
261
+ }
262
+ return `formula[${JSON.stringify(name)}]`;
263
+ }
264
+ export function generateQuery(view, formulas, globalFilter) {
265
+ let query = "SET identifier_quoting = bracket;\nSELECT\n";
266
+ const columns = [
267
+ ...new Set([
268
+ ...view.order,
269
+ ...(view.groupBy?.property ? [view.groupBy.property] : []),
270
+ ...(typeof view.image === "string" && view.image.length > 0
271
+ ? [view.image]
272
+ : []),
273
+ ]),
274
+ ];
275
+ query += columns
276
+ .map((column) => {
277
+ const [isFilter, filterKey, filterColumn] = isFilterColumn(column);
278
+ if (isFilter) {
279
+ if (filterKey in formulas) {
280
+ if (formulas[filterKey]) {
281
+ return ` (${replaceFormulas(formulas[filterKey], formulas)}) as ${filterColumn}`;
282
+ }
283
+ }
284
+ return ` null as ${filterColumn}`;
285
+ }
286
+ else if (column.match(/^[a-zA-Z0-9_.-]+$/)) {
287
+ return ` ${column} as {${column}}`;
288
+ }
289
+ return ` {${column}} as {${column}}`;
290
+ })
291
+ .join(",\n");
292
+ query += columns.length ? ",\n rowId as {$rowId}" : " rowId as {$rowId}";
293
+ const where = [toSQL(globalFilter, formulas), toSQL(view.filter, formulas)]
294
+ .filter((it) => it.length > 0)
295
+ .map((q) => `(${q})`)
296
+ .join(" and ");
297
+ if (where) {
298
+ query += "\nWHERE " + where;
299
+ }
300
+ const sort = view.sort
301
+ .map((s) => {
302
+ const [isFormula] = isFilterColumn(s.property);
303
+ return isFormula
304
+ ? `(${replaceFormulas(formulaColumn(s.property), formulas)}) ${s.direction}`
305
+ : `{${s.property}} ${s.direction}`;
306
+ })
307
+ .join(",");
308
+ if (sort.length) {
309
+ query += "\nORDER BY " + sort;
310
+ }
311
+ if (view.limit && view.limit > 0) {
312
+ query += "\nLIMIT " + view.limit;
313
+ }
314
+ return query;
315
+ }