firstly 0.10.4 → 0.10.5
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/CHANGELOG.md +10 -0
- package/esm/carbone/CarboneController.js +117 -103
- package/esm/feedback/FeedbackController.js +310 -289
- package/esm/sqlAdmin/SqlAdminController.d.ts +1 -0
- package/esm/sqlAdmin/SqlAdminController.js +33 -25
- package/esm/sqlAdmin/SqlDriftController.js +25 -13
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# firstly
|
|
2
2
|
|
|
3
|
+
## 0.10.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#380](https://github.com/jycouet/firstly/pull/380) [`84d2acf`](https://github.com/jycouet/firstly/commit/84d2acfb2898a83e9334442b72e7ff130b2f3991) Thanks [@renovate](https://github.com/apps/renovate)! - mail: nodemailer 10 (requires Node 20+; remote content fetched over HTTPS now validates TLS certificates).
|
|
8
|
+
|
|
9
|
+
- [#377](https://github.com/jycouet/firstly/pull/377) [`d200b62`](https://github.com/jycouet/firstly/commit/d200b62a027eda358ea4ead9be73bce5974b0e8d) Thanks [@jycouet](https://github.com/jycouet)! - sqlAdmin: server-only code is wrapped in `if (import.meta.env.SSR) { ... }`, so Vite 7 (Rollup) client builds no longer fail on `node:crypto` and the server helpers stay out of the client bundle.
|
|
10
|
+
|
|
11
|
+
- [#378](https://github.com/jycouet/firstly/pull/378) [`525ce42`](https://github.com/jycouet/firstly/commit/525ce424ba0affaec73e08e0c8cb906d20d21859) Thanks [@jycouet](https://github.com/jycouet)! - feedback & carbone: BackendMethod bodies are wrapped in `if (import.meta.env.SSR) { ... }`, so their server code (GitHub GraphQL queries, Carbone calls) no longer ships in the client bundle.
|
|
12
|
+
|
|
3
13
|
## 0.10.4
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -11,123 +11,137 @@ import { Roles_Carbon } from './Roles_Carbon';
|
|
|
11
11
|
export class CarboneController {
|
|
12
12
|
static server;
|
|
13
13
|
static async uploadTemplate(config) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const j = await res.json();
|
|
25
|
-
const templateId = j.data.templateId;
|
|
26
|
-
const newTemplate = await repo(CarboneTemplate).upsert({
|
|
27
|
-
where: { id: templateId, name, extension: j.data.templateExtension },
|
|
28
|
-
});
|
|
29
|
-
await repo(CarboneLog).insert({
|
|
30
|
-
templateId,
|
|
31
|
-
action: CarbonLogAction.template_upload,
|
|
32
|
-
});
|
|
33
|
-
// Delete all other templates with the same name
|
|
34
|
-
if (deleteOlderTemplatesWithSameName) {
|
|
35
|
-
const older = await repo(CarboneTemplate).find({
|
|
36
|
-
where: { name: newTemplate.name, id: { $ne: newTemplate.id } },
|
|
14
|
+
if (import.meta.env.SSR) {
|
|
15
|
+
const { name, base64, nameForCarbone, deleteOlderTemplatesWithSameName } = config;
|
|
16
|
+
// 30 max 1 13
|
|
17
|
+
const nameToUse = strTrimMid(nameForCarbone ?? name, { len: 16, midStr: '-' }) + '_' + dateCompact();
|
|
18
|
+
const res = await CarboneController.server.fetch({
|
|
19
|
+
api: `/template`,
|
|
20
|
+
body: JSON.stringify({ name, template: base64 }),
|
|
21
|
+
headers: {
|
|
22
|
+
'carbone-template-name': nameToUse,
|
|
23
|
+
},
|
|
37
24
|
});
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
const j = await res.json();
|
|
26
|
+
const templateId = j.data.templateId;
|
|
27
|
+
const newTemplate = await repo(CarboneTemplate).upsert({
|
|
28
|
+
where: { id: templateId, name, extension: j.data.templateExtension },
|
|
29
|
+
});
|
|
30
|
+
await repo(CarboneLog).insert({
|
|
31
|
+
templateId,
|
|
32
|
+
action: CarbonLogAction.template_upload,
|
|
33
|
+
});
|
|
34
|
+
// Delete all other templates with the same name
|
|
35
|
+
if (deleteOlderTemplatesWithSameName) {
|
|
36
|
+
const older = await repo(CarboneTemplate).find({
|
|
37
|
+
where: { name: newTemplate.name, id: { $ne: newTemplate.id } },
|
|
38
|
+
});
|
|
39
|
+
for (const t of older) {
|
|
40
|
+
await CarboneController.deleteTemplate({ templateId: t.id });
|
|
41
|
+
}
|
|
40
42
|
}
|
|
43
|
+
return;
|
|
41
44
|
}
|
|
45
|
+
throw new Error('carbone.uploadTemplate: server-only');
|
|
42
46
|
}
|
|
43
47
|
static async downloadTemplate(config) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
if (import.meta.env.SSR) {
|
|
49
|
+
const { templateName, filename } = config;
|
|
50
|
+
let { templateId, templateExtension } = config;
|
|
51
|
+
if (templateName) {
|
|
52
|
+
const t = await repo(CarboneTemplate).findFirst({ name: templateName });
|
|
53
|
+
if (!t) {
|
|
54
|
+
throw new Error('Template not found');
|
|
55
|
+
}
|
|
56
|
+
templateId = t.id;
|
|
57
|
+
templateExtension = t.extension;
|
|
50
58
|
}
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
const response = await CarboneController.server.fetch({
|
|
60
|
+
api: `/template/${templateId}`,
|
|
61
|
+
method: 'GET',
|
|
62
|
+
});
|
|
63
|
+
await repo(CarboneLog).insert({
|
|
64
|
+
templateId,
|
|
65
|
+
action: CarbonLogAction.template_download,
|
|
66
|
+
});
|
|
67
|
+
const contentType = response.headers.get('content-type');
|
|
68
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
69
|
+
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
|
70
|
+
const filenameToUse = filename ?? templateName ?? `template_${templateId}`;
|
|
71
|
+
return {
|
|
72
|
+
data: base64,
|
|
73
|
+
contentType: contentType || 'application/octet-stream',
|
|
74
|
+
filename: `${filenameToUse}.${templateExtension}`,
|
|
75
|
+
};
|
|
53
76
|
}
|
|
54
|
-
|
|
55
|
-
api: `/template/${templateId}`,
|
|
56
|
-
method: 'GET',
|
|
57
|
-
});
|
|
58
|
-
await repo(CarboneLog).insert({
|
|
59
|
-
templateId,
|
|
60
|
-
action: CarbonLogAction.template_download,
|
|
61
|
-
});
|
|
62
|
-
const contentType = response.headers.get('content-type');
|
|
63
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
64
|
-
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
|
65
|
-
const filenameToUse = filename ?? templateName ?? `template_${templateId}`;
|
|
66
|
-
return {
|
|
67
|
-
data: base64,
|
|
68
|
-
contentType: contentType || 'application/octet-stream',
|
|
69
|
-
filename: `${filenameToUse}.${templateExtension}`,
|
|
70
|
-
};
|
|
77
|
+
throw new Error('carbone.downloadTemplate: server-only');
|
|
71
78
|
}
|
|
72
79
|
static async deleteTemplate(config) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if (import.meta.env.SSR) {
|
|
81
|
+
const { templateId } = config;
|
|
82
|
+
await CarboneController.server.fetch({
|
|
83
|
+
api: `/template/${templateId}`,
|
|
84
|
+
method: 'DELETE',
|
|
85
|
+
});
|
|
86
|
+
await repo(CarboneLog).insert({
|
|
87
|
+
templateId,
|
|
88
|
+
action: CarbonLogAction.template_delete,
|
|
89
|
+
});
|
|
90
|
+
await repo(CarboneTemplate).delete({ id: templateId });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
throw new Error('carbone.deleteTemplate: server-only');
|
|
83
94
|
}
|
|
84
95
|
static async render(config) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
else if (templateName) {
|
|
93
|
-
mode = 'templateName';
|
|
94
|
-
const t = await repo(CarboneTemplate).findFirst({ name: templateName });
|
|
95
|
-
if (!t) {
|
|
96
|
-
throw new Error('Template not found');
|
|
96
|
+
if (import.meta.env.SSR) {
|
|
97
|
+
const { templateName, templateBase64, data, filename } = config;
|
|
98
|
+
let { templateId, convertTo } = config;
|
|
99
|
+
// eslint-disable-next-line
|
|
100
|
+
let mode = '';
|
|
101
|
+
if (templateBase64) {
|
|
102
|
+
mode = 'templateBase64';
|
|
97
103
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
else if (templateName) {
|
|
105
|
+
mode = 'templateName';
|
|
106
|
+
const t = await repo(CarboneTemplate).findFirst({ name: templateName });
|
|
107
|
+
if (!t) {
|
|
108
|
+
throw new Error('Template not found');
|
|
109
|
+
}
|
|
110
|
+
templateId = t.id;
|
|
111
|
+
if (!convertTo) {
|
|
112
|
+
convertTo = t.extension;
|
|
113
|
+
}
|
|
101
114
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
else {
|
|
116
|
+
mode = 'templateId';
|
|
117
|
+
}
|
|
118
|
+
if (CarboneController.server.test) {
|
|
119
|
+
convertTo = 'pdf';
|
|
120
|
+
}
|
|
121
|
+
const response = templateBase64
|
|
122
|
+
? await CarboneController.server.fetch({
|
|
123
|
+
api: `/render/template?download=true`,
|
|
124
|
+
body: JSON.stringify({ data, template: templateBase64, convertTo: convertTo ?? 'pdf' }),
|
|
125
|
+
})
|
|
126
|
+
: await CarboneController.server.fetch({
|
|
127
|
+
api: `/render/${templateId}?download=true`,
|
|
128
|
+
body: JSON.stringify({ data, convertTo: convertTo ?? 'pdf' }),
|
|
129
|
+
});
|
|
130
|
+
await repo(CarboneLog).insert({
|
|
131
|
+
templateId: mode === 'templateBase64' ? 'templateBase64' : templateId,
|
|
132
|
+
action: CarbonLogAction.render,
|
|
117
133
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
filename: `${filenameToUse}.${convertTo}`,
|
|
130
|
-
};
|
|
134
|
+
const contentType = response.headers.get('content-type');
|
|
135
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
136
|
+
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
|
137
|
+
const filenameToUse = filename ?? `template_${templateId}`;
|
|
138
|
+
return {
|
|
139
|
+
data: base64,
|
|
140
|
+
contentType: contentType || 'application/octet-stream',
|
|
141
|
+
filename: `${filenameToUse}.${convertTo}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
throw new Error('carbone.render: server-only');
|
|
131
145
|
}
|
|
132
146
|
}
|
|
133
147
|
__decorate([
|
|
@@ -66,319 +66,340 @@ async function addMetaData(issueId, obj) {
|
|
|
66
66
|
}
|
|
67
67
|
export class FeedbackController {
|
|
68
68
|
static async getMilestones() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
if (import.meta.env.SSR) {
|
|
70
|
+
const data = await getGitHub(`query Milestones(
|
|
71
|
+
$repository: String!
|
|
72
|
+
$owner: String!
|
|
73
|
+
$filter: String
|
|
74
|
+
$take: Int = 25
|
|
75
|
+
$cursor: String
|
|
76
|
+
) {
|
|
77
|
+
repository(name: $repository, owner: $owner) {
|
|
78
|
+
milestones(query: $filter, last: $take, after: $cursor, states: OPEN) {
|
|
79
|
+
pageInfo {
|
|
80
|
+
endCursor
|
|
81
|
+
}
|
|
82
|
+
nodes {
|
|
83
|
+
id
|
|
84
|
+
number
|
|
85
|
+
title
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`, {
|
|
91
|
+
repository: remult.context.feedbackOptions.repo.name,
|
|
92
|
+
owner: remult.context.feedbackOptions.repo.owner,
|
|
93
|
+
filter: remult.context.feedbackOptions.milestones?.title_filter ?? '',
|
|
94
|
+
});
|
|
95
|
+
return data.repository.milestones.nodes.map((c) => {
|
|
96
|
+
return {
|
|
97
|
+
...c,
|
|
98
|
+
title: c.title
|
|
99
|
+
.replaceAll(remult.context.feedbackOptions.milestones?.title_filter ?? '', '')
|
|
100
|
+
.trim(),
|
|
101
|
+
};
|
|
102
|
+
});
|
|
86
103
|
}
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
`, {
|
|
90
|
-
repository: remult.context.feedbackOptions.repo.name,
|
|
91
|
-
owner: remult.context.feedbackOptions.repo.owner,
|
|
92
|
-
filter: remult.context.feedbackOptions.milestones?.title_filter ?? '',
|
|
93
|
-
});
|
|
94
|
-
return data.repository.milestones.nodes.map((c) => {
|
|
95
|
-
return {
|
|
96
|
-
...c,
|
|
97
|
-
title: c.title
|
|
98
|
-
.replaceAll(remult.context.feedbackOptions.milestones?.title_filter ?? '', '')
|
|
99
|
-
.trim(),
|
|
100
|
-
};
|
|
101
|
-
});
|
|
104
|
+
throw new Error('feedback.getMilestones: server-only');
|
|
102
105
|
}
|
|
103
106
|
static async getIssues(milestoneNumber, issueState) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
107
|
+
if (import.meta.env.SSR) {
|
|
108
|
+
const issueOrder = issueState === 'CLOSED'
|
|
109
|
+
? { field: 'UPDATED_AT', direction: 'DESC' } // When close, the last issue updated.
|
|
110
|
+
: null; // When open take milestone order
|
|
111
|
+
const data = await getGitHub(`query Issues(
|
|
112
|
+
$repository: String!
|
|
113
|
+
$owner: String!
|
|
114
|
+
$filters: IssueFilters
|
|
115
|
+
$milestoneNumber: Int!
|
|
116
|
+
$take: Int = 25
|
|
117
|
+
$cursor: String
|
|
118
|
+
$issueOrder: IssueOrder
|
|
119
|
+
) {
|
|
120
|
+
repository(name: $repository, owner: $owner) {
|
|
121
|
+
milestone(number: $milestoneNumber) {
|
|
122
|
+
issues(first: $take, after: $cursor, filterBy: $filters, orderBy: $issueOrder) {
|
|
123
|
+
nodes {
|
|
124
|
+
id
|
|
125
|
+
number
|
|
126
|
+
titleHTML
|
|
127
|
+
state
|
|
128
|
+
createdAt
|
|
129
|
+
updatedAt
|
|
130
|
+
labels(first:10){
|
|
131
|
+
nodes {
|
|
132
|
+
name
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`, {
|
|
141
|
+
repository: remult.context.feedbackOptions.repo.name,
|
|
142
|
+
owner: remult.context.feedbackOptions.repo.owner,
|
|
143
|
+
milestoneNumber,
|
|
144
|
+
filters: {
|
|
145
|
+
// GitHub treats `labels: []` as "match nothing", so omit the key when unset.
|
|
146
|
+
labels: remult.context.feedbackOptions.milestones?.labels_filters?.length
|
|
147
|
+
? remult.context.feedbackOptions.milestones.labels_filters
|
|
148
|
+
: undefined,
|
|
149
|
+
states: [issueState],
|
|
150
|
+
},
|
|
151
|
+
issueOrder,
|
|
152
|
+
});
|
|
153
|
+
return data.repository.milestone.issues.nodes.map((issue) => {
|
|
154
|
+
const labels = issue.labels.nodes.map((label) => label.name);
|
|
155
|
+
const hasWaitingForAnswerLabel = remult.context.feedbackOptions.highlight_label
|
|
156
|
+
? labels.some((name) => name.includes(remult.context.feedbackOptions.highlight_label))
|
|
157
|
+
: false;
|
|
158
|
+
return {
|
|
159
|
+
id: issue.id,
|
|
160
|
+
number: issue.number,
|
|
161
|
+
titleHTML: issue.titleHTML,
|
|
162
|
+
state: issue.state,
|
|
163
|
+
createdAt: issue.createdAt,
|
|
164
|
+
updatedAt: issue.updatedAt,
|
|
165
|
+
labels,
|
|
166
|
+
highlight: hasWaitingForAnswerLabel,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
throw new Error('feedback.getIssues: server-only');
|
|
171
|
+
}
|
|
172
|
+
static async getIssue(issueNumber) {
|
|
173
|
+
if (import.meta.env.SSR) {
|
|
174
|
+
const data = await getGitHub(`query Issue($repository: String!, $owner: String!, $issueNumber: Int!) {
|
|
175
|
+
repository(name: $repository, owner: $owner) {
|
|
176
|
+
issue(number: $issueNumber) {
|
|
177
|
+
id
|
|
178
|
+
createdAt
|
|
179
|
+
bodyHTML
|
|
180
|
+
state
|
|
181
|
+
title
|
|
182
|
+
labels(first: 25){
|
|
183
|
+
nodes{
|
|
184
|
+
id
|
|
185
|
+
name
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
comments(first: 100) {
|
|
189
|
+
nodes {
|
|
190
|
+
id
|
|
191
|
+
isMinimized
|
|
192
|
+
createdAt
|
|
193
|
+
body
|
|
194
|
+
bodyHTML
|
|
195
|
+
reactionGroups {
|
|
196
|
+
content
|
|
197
|
+
reactors(first: 1) {
|
|
198
|
+
totalCount
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
`, {
|
|
207
|
+
repository: remult.context.feedbackOptions.repo.name,
|
|
208
|
+
owner: remult.context.feedbackOptions.repo.owner,
|
|
209
|
+
issueNumber,
|
|
210
|
+
});
|
|
211
|
+
const items = [];
|
|
212
|
+
const firstItem = {
|
|
213
|
+
bodyHTML: data.repository.issue.bodyHTML,
|
|
214
|
+
createdAt: data.repository.issue.createdAt,
|
|
215
|
+
public: true,
|
|
216
|
+
title: data.repository.issue.title,
|
|
217
|
+
};
|
|
218
|
+
items.push(firstItem);
|
|
219
|
+
const comments = data.repository.issue.comments.nodes;
|
|
220
|
+
for (let i = 0; i < comments.length; i++) {
|
|
221
|
+
if (comments[i].isMinimized) {
|
|
222
|
+
const parsed = JSON.parse(comments[i].body.replaceAll('<pre>\n', '').replaceAll('\n</pre>', ''));
|
|
223
|
+
const last = items.at(-1);
|
|
224
|
+
last.who = parsed?.author ?? '???';
|
|
225
|
+
last.public = true;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const nbEye = comments[i].reactionGroups.find((c) => c.content === 'EYES')?.reactors.totalCount;
|
|
229
|
+
items.push({
|
|
230
|
+
bodyHTML: comments[i].bodyHTML,
|
|
231
|
+
createdAt: new Date(comments[i].createdAt),
|
|
232
|
+
public: nbEye && nbEye > 0 ? true : false,
|
|
233
|
+
title: data.repository.issue.title,
|
|
234
|
+
});
|
|
130
235
|
}
|
|
131
|
-
}
|
|
132
236
|
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
`, {
|
|
137
|
-
repository: remult.context.feedbackOptions.repo.name,
|
|
138
|
-
owner: remult.context.feedbackOptions.repo.owner,
|
|
139
|
-
milestoneNumber,
|
|
140
|
-
filters: {
|
|
141
|
-
// GitHub treats `labels: []` as "match nothing", so omit the key when unset.
|
|
142
|
-
labels: remult.context.feedbackOptions.milestones?.labels_filters?.length
|
|
143
|
-
? remult.context.feedbackOptions.milestones.labels_filters
|
|
144
|
-
: undefined,
|
|
145
|
-
states: [issueState],
|
|
146
|
-
},
|
|
147
|
-
issueOrder,
|
|
148
|
-
});
|
|
149
|
-
return data.repository.milestone.issues.nodes.map((issue) => {
|
|
150
|
-
const labels = issue.labels.nodes.map((label) => label.name);
|
|
151
237
|
const hasWaitingForAnswerLabel = remult.context.feedbackOptions.highlight_label
|
|
152
|
-
? labels.some((
|
|
238
|
+
? data.repository.issue.labels.nodes.some((label) => label.name.includes(remult.context.feedbackOptions.highlight_label))
|
|
153
239
|
: false;
|
|
154
|
-
|
|
155
|
-
id: issue.id,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
createdAt: issue.createdAt,
|
|
160
|
-
updatedAt: issue.updatedAt,
|
|
161
|
-
labels,
|
|
240
|
+
const toRet = {
|
|
241
|
+
id: data.repository.issue.id,
|
|
242
|
+
state: data.repository.issue.state,
|
|
243
|
+
items: items.filter((c) => c.public),
|
|
244
|
+
labels: data.repository.issue.labels.nodes,
|
|
162
245
|
highlight: hasWaitingForAnswerLabel,
|
|
246
|
+
title: data.repository.issue.title,
|
|
163
247
|
};
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
static async getIssue(issueNumber) {
|
|
167
|
-
const data = await getGitHub(`query Issue($repository: String!, $owner: String!, $issueNumber: Int!) {
|
|
168
|
-
repository(name: $repository, owner: $owner) {
|
|
169
|
-
issue(number: $issueNumber) {
|
|
170
|
-
id
|
|
171
|
-
createdAt
|
|
172
|
-
bodyHTML
|
|
173
|
-
state
|
|
174
|
-
title
|
|
175
|
-
labels(first: 25){
|
|
176
|
-
nodes{
|
|
177
|
-
id
|
|
178
|
-
name
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
comments(first: 100) {
|
|
182
|
-
nodes {
|
|
183
|
-
id
|
|
184
|
-
isMinimized
|
|
185
|
-
createdAt
|
|
186
|
-
body
|
|
187
|
-
bodyHTML
|
|
188
|
-
reactionGroups {
|
|
189
|
-
content
|
|
190
|
-
reactors(first: 1) {
|
|
191
|
-
totalCount
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
`, {
|
|
200
|
-
repository: remult.context.feedbackOptions.repo.name,
|
|
201
|
-
owner: remult.context.feedbackOptions.repo.owner,
|
|
202
|
-
issueNumber,
|
|
203
|
-
});
|
|
204
|
-
const items = [];
|
|
205
|
-
const firstItem = {
|
|
206
|
-
bodyHTML: data.repository.issue.bodyHTML,
|
|
207
|
-
createdAt: data.repository.issue.createdAt,
|
|
208
|
-
public: true,
|
|
209
|
-
title: data.repository.issue.title,
|
|
210
|
-
};
|
|
211
|
-
items.push(firstItem);
|
|
212
|
-
const comments = data.repository.issue.comments.nodes;
|
|
213
|
-
for (let i = 0; i < comments.length; i++) {
|
|
214
|
-
if (comments[i].isMinimized) {
|
|
215
|
-
const parsed = JSON.parse(comments[i].body.replaceAll('<pre>\n', '').replaceAll('\n</pre>', ''));
|
|
216
|
-
const last = items.at(-1);
|
|
217
|
-
last.who = parsed?.author ?? '???';
|
|
218
|
-
last.public = true;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
const nbEye = comments[i].reactionGroups.find((c) => c.content === 'EYES')?.reactors.totalCount;
|
|
222
|
-
items.push({
|
|
223
|
-
bodyHTML: comments[i].bodyHTML,
|
|
224
|
-
createdAt: new Date(comments[i].createdAt),
|
|
225
|
-
public: nbEye && nbEye > 0 ? true : false,
|
|
226
|
-
title: data.repository.issue.title,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
248
|
+
return toRet;
|
|
229
249
|
}
|
|
230
|
-
|
|
231
|
-
? data.repository.issue.labels.nodes.some((label) => label.name.includes(remult.context.feedbackOptions.highlight_label))
|
|
232
|
-
: false;
|
|
233
|
-
const toRet = {
|
|
234
|
-
id: data.repository.issue.id,
|
|
235
|
-
state: data.repository.issue.state,
|
|
236
|
-
items: items.filter((c) => c.public),
|
|
237
|
-
labels: data.repository.issue.labels.nodes,
|
|
238
|
-
highlight: hasWaitingForAnswerLabel,
|
|
239
|
-
title: data.repository.issue.title,
|
|
240
|
-
};
|
|
241
|
-
return toRet;
|
|
250
|
+
throw new Error('feedback.getIssue: server-only');
|
|
242
251
|
}
|
|
243
252
|
static async createIssue(milestoneId, title, body, metadata,
|
|
244
253
|
/** Extra label names applied on top of `create_label` (unknown names are ignored). */
|
|
245
254
|
labels = []) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
if (import.meta.env.SSR) {
|
|
256
|
+
const repoInfo = await getGitHub(`query RepoInfo(
|
|
257
|
+
$repository: String!
|
|
258
|
+
$owner: String!
|
|
259
|
+
) {
|
|
260
|
+
repository(name: $repository, owner: $owner) {
|
|
261
|
+
id
|
|
262
|
+
labels(first: 25){
|
|
263
|
+
nodes{
|
|
264
|
+
id
|
|
265
|
+
name
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}`, {
|
|
270
|
+
repository: remult.context.feedbackOptions.repo.name,
|
|
271
|
+
owner: remult.context.feedbackOptions.repo.owner,
|
|
272
|
+
});
|
|
273
|
+
const repoInfoData = repoInfo.repository;
|
|
274
|
+
const wanted = new Set([remult.context.feedbackOptions.create_label, ...labels]);
|
|
275
|
+
const labelIds = repoInfoData.labels.nodes.filter((c) => wanted.has(c.name)).map((c) => c.id);
|
|
276
|
+
const newIssue = await getGitHub(`mutation CreateIssue($input: CreateIssueInput!) {
|
|
277
|
+
createIssue(input: $input) {
|
|
278
|
+
issue {
|
|
279
|
+
id
|
|
280
|
+
number
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
`, {
|
|
285
|
+
input: {
|
|
286
|
+
repositoryId: repoInfoData.id,
|
|
287
|
+
milestoneId: milestoneId,
|
|
288
|
+
labelIds,
|
|
289
|
+
title: title ?? 'New Feedback (wo title...)',
|
|
290
|
+
body: body,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
const toRet = newIssue.createIssue.issue;
|
|
294
|
+
const feedbackMetadata = remult.context.feedbackOptions.transformMetadata?.({
|
|
295
|
+
user: remult.user,
|
|
296
|
+
metadata,
|
|
297
|
+
}) ?? metadata;
|
|
298
|
+
await addMetaData(toRet.id, feedbackMetadata);
|
|
299
|
+
remult.context.feedbackOptions.saved?.({
|
|
300
|
+
number: toRet.number,
|
|
301
|
+
title: title,
|
|
302
|
+
body,
|
|
303
|
+
metadata: feedbackMetadata,
|
|
304
|
+
});
|
|
305
|
+
return toRet;
|
|
271
306
|
}
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
`, {
|
|
275
|
-
input: {
|
|
276
|
-
repositoryId: repoInfoData.id,
|
|
277
|
-
milestoneId: milestoneId,
|
|
278
|
-
labelIds,
|
|
279
|
-
title: title ?? 'New Feedback (wo title...)',
|
|
280
|
-
body: body,
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
const toRet = newIssue.createIssue.issue;
|
|
284
|
-
const feedbackMetadata = remult.context.feedbackOptions.transformMetadata?.({
|
|
285
|
-
user: remult.user,
|
|
286
|
-
metadata,
|
|
287
|
-
}) ?? metadata;
|
|
288
|
-
await addMetaData(toRet.id, feedbackMetadata);
|
|
289
|
-
remult.context.feedbackOptions.saved?.({
|
|
290
|
-
number: toRet.number,
|
|
291
|
-
title: title,
|
|
292
|
-
body,
|
|
293
|
-
metadata: feedbackMetadata,
|
|
294
|
-
});
|
|
295
|
-
return toRet;
|
|
307
|
+
throw new Error('feedback.createIssue: server-only');
|
|
296
308
|
}
|
|
297
309
|
static async addCommentOnIssue(issueId, issueNumber, title, body, metadata, labels) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
310
|
+
if (import.meta.env.SSR) {
|
|
311
|
+
const inputComment = {
|
|
312
|
+
subjectId: issueId,
|
|
313
|
+
body,
|
|
314
|
+
};
|
|
315
|
+
const inputIssue = {
|
|
316
|
+
id: issueId,
|
|
317
|
+
labelIds: (remult.context.feedbackOptions.highlight_label
|
|
318
|
+
? labels.filter((c) => c.name !== remult.context.feedbackOptions.highlight_label)
|
|
319
|
+
: labels).map((c) => c.id),
|
|
320
|
+
};
|
|
321
|
+
await getGitHub(`mutation AddComment($inputComment: AddCommentInput!, $inputIssue: UpdateIssueInput!) {
|
|
322
|
+
addComment(input: $inputComment) {
|
|
323
|
+
commentEdge {
|
|
324
|
+
node {
|
|
325
|
+
id
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
updateIssue(input: $inputIssue) {
|
|
330
|
+
issue {
|
|
331
|
+
id
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
`, {
|
|
336
|
+
inputComment,
|
|
337
|
+
inputIssue,
|
|
338
|
+
});
|
|
339
|
+
const feedbackMetadata = remult.context.feedbackOptions.transformMetadata?.({
|
|
340
|
+
user: remult.user,
|
|
341
|
+
metadata,
|
|
342
|
+
}) ?? metadata;
|
|
343
|
+
await addMetaData(issueId, feedbackMetadata);
|
|
344
|
+
remult.context.feedbackOptions.saved?.({
|
|
345
|
+
number: issueNumber,
|
|
346
|
+
title,
|
|
347
|
+
body,
|
|
348
|
+
metadata: feedbackMetadata,
|
|
349
|
+
});
|
|
350
|
+
return 'done';
|
|
320
351
|
}
|
|
321
|
-
|
|
322
|
-
`, {
|
|
323
|
-
inputComment,
|
|
324
|
-
inputIssue,
|
|
325
|
-
});
|
|
326
|
-
const feedbackMetadata = remult.context.feedbackOptions.transformMetadata?.({
|
|
327
|
-
user: remult.user,
|
|
328
|
-
metadata,
|
|
329
|
-
}) ?? metadata;
|
|
330
|
-
await addMetaData(issueId, feedbackMetadata);
|
|
331
|
-
remult.context.feedbackOptions.saved?.({
|
|
332
|
-
number: issueNumber,
|
|
333
|
-
title,
|
|
334
|
-
body,
|
|
335
|
-
metadata: feedbackMetadata,
|
|
336
|
-
});
|
|
337
|
-
return 'done';
|
|
352
|
+
throw new Error('feedback.addCommentOnIssue: server-only');
|
|
338
353
|
}
|
|
339
354
|
static async close(issueId, labels) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
355
|
+
if (import.meta.env.SSR) {
|
|
356
|
+
const inputClose = {
|
|
357
|
+
issueId,
|
|
358
|
+
};
|
|
359
|
+
const inputIssue = {
|
|
360
|
+
id: issueId,
|
|
361
|
+
labelIds: (remult.context.feedbackOptions.highlight_label
|
|
362
|
+
? labels.filter((c) => c.name !== remult.context.feedbackOptions.highlight_label)
|
|
363
|
+
: labels).map((c) => c.id),
|
|
364
|
+
};
|
|
365
|
+
await getGitHub(`mutation CloseIssue($inputIssue: UpdateIssueInput!, $inputClose: CloseIssueInput!) {
|
|
366
|
+
updateIssue(input: $inputIssue) {
|
|
367
|
+
issue {
|
|
368
|
+
id
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
closeIssue(input: $inputClose) {
|
|
372
|
+
issue {
|
|
373
|
+
id
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
`, {
|
|
378
|
+
inputIssue,
|
|
379
|
+
inputClose,
|
|
380
|
+
});
|
|
381
|
+
return 'done';
|
|
359
382
|
}
|
|
360
|
-
|
|
361
|
-
`, {
|
|
362
|
-
inputIssue,
|
|
363
|
-
inputClose,
|
|
364
|
-
});
|
|
365
|
-
return 'done';
|
|
383
|
+
throw new Error('feedback.close: server-only');
|
|
366
384
|
}
|
|
367
385
|
static async reOpen(issueId) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
386
|
+
if (import.meta.env.SSR) {
|
|
387
|
+
const input = {
|
|
388
|
+
issueId,
|
|
389
|
+
};
|
|
390
|
+
await getGitHub(`mutation ReOpenIssue($input: ReopenIssueInput!) {
|
|
391
|
+
reopenIssue(input: $input) {
|
|
392
|
+
issue {
|
|
393
|
+
id
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
`, {
|
|
398
|
+
input,
|
|
399
|
+
});
|
|
400
|
+
return 'done';
|
|
376
401
|
}
|
|
377
|
-
|
|
378
|
-
`, {
|
|
379
|
-
input,
|
|
380
|
-
});
|
|
381
|
-
return 'done';
|
|
402
|
+
throw new Error('feedback.reOpen: server-only');
|
|
382
403
|
}
|
|
383
404
|
}
|
|
384
405
|
__decorate([
|
|
@@ -50,6 +50,7 @@ export type SqlTokensOptions = {
|
|
|
50
50
|
/** Override the pool used by `read` tokens. Defaults to the Postgres pool behind the data provider. */
|
|
51
51
|
pool?: SqlTokenPool;
|
|
52
52
|
};
|
|
53
|
+
export declare const SERVER_ONLY = "sqlAdmin: server-only";
|
|
53
54
|
export declare const SQL_ADMINS: ("FF_Role.Admin" | "SqlAdmin.Admin")[];
|
|
54
55
|
export declare class SqlAdminController {
|
|
55
56
|
/** Optional override set by the `sqlAdmin()` module's `initApi`. Falls back to `SqlDatabase.getDb()`. */
|
|
@@ -8,6 +8,8 @@ import { BackendMethod, remult, repo, SqlDatabase } from 'remult';
|
|
|
8
8
|
import { FF_Role } from '../core/common';
|
|
9
9
|
import { Roles_SqlAdmin } from './Roles_SqlAdmin';
|
|
10
10
|
import { SQL_CAPABILITIES, SQL_TOKEN_TTLS, SqlToken, SqlTokenCall, } from './sqlTokenEntities';
|
|
11
|
+
// Thrown when a server-only body runs on the client (its `import.meta.env.SSR` block is gone there).
|
|
12
|
+
export const SERVER_ONLY = 'sqlAdmin: server-only';
|
|
11
13
|
export const SQL_ADMINS = [Roles_SqlAdmin.SqlAdmin_Admin, FF_Role.FF_Role_Admin];
|
|
12
14
|
const CALL_LOG_CMD_MAX = 4000;
|
|
13
15
|
/** Retention is a housekeeping sweep, not something to pay for on every query. */
|
|
@@ -42,21 +44,24 @@ export class SqlAdminController {
|
|
|
42
44
|
return SqlAdminController.run(cmd, capabilities);
|
|
43
45
|
}
|
|
44
46
|
static async run(cmd, capabilities) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
if (import.meta.env.SSR) {
|
|
48
|
+
const db = getDb();
|
|
49
|
+
try {
|
|
50
|
+
// Anything that is not an explicit list (null, a stray boolean) is a read.
|
|
51
|
+
if (Array.isArray(capabilities) && capabilities.includes('write')) {
|
|
52
|
+
const start = performance.now();
|
|
53
|
+
const rows = (await db.execute(cmd)).rows;
|
|
54
|
+
return { rows, rowCount: rows.length, took: performance.now() - start };
|
|
55
|
+
}
|
|
56
|
+
const { readOnlySql } = await import('./server/readOnlySql');
|
|
57
|
+
return await readOnlySql(db, SqlAdminController.options.tokens?.pool ?? poolFrom(db), cmd);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const { enrichSqlError } = await import('./server/sqlError');
|
|
61
|
+
throw await enrichSqlError(db, err, cmd);
|
|
52
62
|
}
|
|
53
|
-
const { readOnlySql } = await import('./server/readOnlySql');
|
|
54
|
-
return await readOnlySql(db, SqlAdminController.options.tokens?.pool ?? poolFrom(db), cmd);
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
const { enrichSqlError } = await import('./server/sqlError');
|
|
58
|
-
throw await enrichSqlError(db, err, cmd);
|
|
59
63
|
}
|
|
64
|
+
throw new Error(SERVER_ONLY);
|
|
60
65
|
}
|
|
61
66
|
static async execAsToken(token, cmd) {
|
|
62
67
|
const o = SqlAdminController.options.tokens;
|
|
@@ -115,18 +120,21 @@ export class SqlAdminController {
|
|
|
115
120
|
if (!o.capabilities.includes(c))
|
|
116
121
|
throw new Error(`${c} is not enabled`);
|
|
117
122
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
if (import.meta.env.SSR) {
|
|
124
|
+
const { newRawToken, hashToken, tokenHint, randomTokenName } = await import('./server/token');
|
|
125
|
+
const prefix = o.prefix ?? 'ffsql_';
|
|
126
|
+
const raw = newRawToken(prefix);
|
|
127
|
+
await repo(SqlToken).insert({
|
|
128
|
+
name: name.trim() || randomTokenName(),
|
|
129
|
+
hint: tokenHint(raw, prefix),
|
|
130
|
+
tokenHash: hashToken(raw),
|
|
131
|
+
userId,
|
|
132
|
+
capabilities: unique,
|
|
133
|
+
expiresAt: new Date(Date.now() + ttlMs),
|
|
134
|
+
});
|
|
135
|
+
return raw;
|
|
136
|
+
}
|
|
137
|
+
throw new Error(SERVER_ONLY);
|
|
130
138
|
}
|
|
131
139
|
/**
|
|
132
140
|
* Deletes every token that can no longer be used (expired or revoked), and
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { BackendMethod, remult, SqlDatabase } from 'remult';
|
|
8
|
-
import { SQL_ADMINS } from './SqlAdminController';
|
|
8
|
+
import { SERVER_ONLY, SQL_ADMINS } from './SqlAdminController';
|
|
9
9
|
export class SqlDriftController {
|
|
10
10
|
/** Set by the `sqlAdmin()` module's `initApi`. */
|
|
11
11
|
static options;
|
|
@@ -27,24 +27,36 @@ export class SqlDriftController {
|
|
|
27
27
|
}
|
|
28
28
|
// Each apply shares the BackendMethod transaction: any failure rolls every statement back.
|
|
29
29
|
static async relationIndexes(opts) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
if (import.meta.env.SSR) {
|
|
31
|
+
const { db, entities } = SqlDriftController.ctx('relationIndexes');
|
|
32
|
+
const { createRelationIndexes } = await import('./server/drift/relationIndexes');
|
|
33
|
+
return createRelationIndexes(db, entities, opts);
|
|
34
|
+
}
|
|
35
|
+
throw new Error(SERVER_ONLY);
|
|
33
36
|
}
|
|
34
37
|
static async primaryKeys(opts) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
if (import.meta.env.SSR) {
|
|
39
|
+
const { db, entities } = SqlDriftController.ctx('primaryKeys');
|
|
40
|
+
const { reindexPrimaryKeys } = await import('./server/drift/primaryKeys');
|
|
41
|
+
return reindexPrimaryKeys(db, entities, opts);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(SERVER_ONLY);
|
|
38
44
|
}
|
|
39
45
|
static async nullable(opts) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
if (import.meta.env.SSR) {
|
|
47
|
+
const { db, entities } = SqlDriftController.ctx('nullable');
|
|
48
|
+
const { syncNullable } = await import('./server/drift/nullable');
|
|
49
|
+
return syncNullable(db, entities, opts);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(SERVER_ONLY);
|
|
43
52
|
}
|
|
44
53
|
static async orphanColumns(opts) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if (import.meta.env.SSR) {
|
|
55
|
+
const { db, entities } = SqlDriftController.ctx('orphanColumns');
|
|
56
|
+
const { dropColumns } = await import('./server/drift/orphanColumns');
|
|
57
|
+
return dropColumns(db, entities, opts);
|
|
58
|
+
}
|
|
59
|
+
throw new Error(SERVER_ONLY);
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
__decorate([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firstly",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Firstly, an opinionated Remult setup!",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"clsx": "2.1.1",
|
|
42
42
|
"cron": "4.4.0",
|
|
43
43
|
"esm-env": "1.2.2",
|
|
44
|
-
"nodemailer": "
|
|
44
|
+
"nodemailer": "10.0.10",
|
|
45
45
|
"svelte-sonner": "1.2.1",
|
|
46
46
|
"tailwind-merge": "3.7.0",
|
|
47
47
|
"tailwindcss": "4.3.0",
|