@anteros/core 0.0.1-alpha.1
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/README.md +143 -0
- package/database/collection.ts +160 -0
- package/database/decorator.ts +172 -0
- package/database/file.ts +93 -0
- package/database/mongodbadapter.ts +1128 -0
- package/database/rest.ts +14 -0
- package/database/schema.ts +160 -0
- package/database/tenant.ts +37 -0
- package/database/workflow.ts +384 -0
- package/index.ts +28 -0
- package/lib/asyncContextStorage.ts +68 -0
- package/lib/define.ts +114 -0
- package/lib/error.ts +21 -0
- package/lib/files.ts +459 -0
- package/lib/middleware.ts +66 -0
- package/lib/routes.ts +44 -0
- package/lib/scripts.ts +47 -0
- package/lib/services.ts +45 -0
- package/lib/sockets.ts +44 -0
- package/lib/workflow.ts +60 -0
- package/package.json +31 -0
- package/server/api.ts +789 -0
- package/server/boot.ts +101 -0
- package/server/config.ts +107 -0
- package/server/env.ts +16 -0
- package/server/hono.ts +176 -0
- package/server/io.ts +15 -0
- package/server/routes.ts +48 -0
- package/server/security.ts +138 -0
- package/tests/api.test.ts +281 -0
- package/tsconfig.json +36 -0
- package/types/activity.d.ts +45 -0
- package/types/api.d.ts +85 -0
- package/types/collection.d.ts +82 -0
- package/types/config.d.ts +55 -0
- package/types/field.d.ts +72 -0
- package/types/file.d.ts +120 -0
- package/types/hook.d.ts +30 -0
- package/types/middleware.d.ts +18 -0
- package/types/mongo.d.ts +61 -0
- package/types/options.d.ts +7 -0
- package/types/rest.d.ts +18 -0
- package/types/route.d.ts +19 -0
- package/types/schema.d.ts +0 -0
- package/types/scripts.d.ts +10 -0
- package/types/service.d.ts +37 -0
- package/types/task.d.ts +12 -0
- package/types/tenant.d.ts +16 -0
- package/types/token.d.ts +14 -0
- package/types/websocket.d.ts +15 -0
- package/types/workflow.d.ts +91 -0
- package/utils/cache.ts +96 -0
- package/utils/crypto.ts +226 -0
- package/utils/func.ts +1037 -0
- package/utils/index.ts +17 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { useRest } from "../database/rest";
|
|
3
|
+
import { formatConfig, cfg } from "../server/config";
|
|
4
|
+
import { syncTenants } from "../database/tenant";
|
|
5
|
+
import { syncCollections } from "../database/collection";
|
|
6
|
+
|
|
7
|
+
const TEST_TENANT = "test";
|
|
8
|
+
const TEST_DB = "mongodb://localhost:27017/_DB_TEST";
|
|
9
|
+
|
|
10
|
+
let rest: InstanceType<typeof useRest>;
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
formatConfig({
|
|
14
|
+
tenants: [{ id: TEST_TENANT, dir: "src", database: { uri: TEST_DB } }],
|
|
15
|
+
});
|
|
16
|
+
await syncTenants();
|
|
17
|
+
await syncCollections();
|
|
18
|
+
|
|
19
|
+
(cfg as any).collections = [
|
|
20
|
+
{
|
|
21
|
+
_tenant_: TEST_TENANT,
|
|
22
|
+
slug: "items",
|
|
23
|
+
fields: [
|
|
24
|
+
{ name: "title", type: "string", required: true },
|
|
25
|
+
{ name: "count", type: "number" },
|
|
26
|
+
],
|
|
27
|
+
api: { access: { "*": true } },
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
rest = new useRest({ internal: false, tenant_id: TEST_TENANT });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterAll(async () => {
|
|
35
|
+
try { await rest.db.collection("items").drop(); } catch (_) {}
|
|
36
|
+
try { await rest.db.collection("_locks_").drop(); } catch (_) {}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ─── CRUD ────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe("insertOne", () => {
|
|
42
|
+
it("returns _id as 24-char hex string", async () => {
|
|
43
|
+
const doc = await rest.insertOne("items", { title: "hello", count: 1 });
|
|
44
|
+
expect(doc._id).toBeString();
|
|
45
|
+
expect(doc._id.length).toBe(24);
|
|
46
|
+
});
|
|
47
|
+
it("sets createdAt and updatedAt", async () => {
|
|
48
|
+
const doc = await rest.insertOne("items", { title: "ts" });
|
|
49
|
+
expect(doc.createdAt).toBeString();
|
|
50
|
+
expect(doc.updatedAt).toBeString();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("find", () => {
|
|
55
|
+
it("returns an array", async () => {
|
|
56
|
+
expect(Array.isArray(await rest.find("items", {}))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it("filters with $match", async () => {
|
|
59
|
+
await rest.insertOne("items", { title: "match-me", count: 42 });
|
|
60
|
+
const docs = await rest.find("items", { $match: { count: 42 } });
|
|
61
|
+
expect(docs.length).toBeGreaterThanOrEqual(1);
|
|
62
|
+
});
|
|
63
|
+
it("matches _id string", async () => {
|
|
64
|
+
const doc = await rest.insertOne("items", { title: "by-id" });
|
|
65
|
+
const docs = await rest.find("items", { $match: { _id: doc._id } });
|
|
66
|
+
expect(docs.length).toBe(1);
|
|
67
|
+
});
|
|
68
|
+
it("respects $limit", async () => {
|
|
69
|
+
await rest.insertOne("items", { title: "a1" });
|
|
70
|
+
await rest.insertOne("items", { title: "a2" });
|
|
71
|
+
expect((await rest.find("items", { $limit: 1 })).length).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("findOne", () => {
|
|
76
|
+
it("returns single doc by _id", async () => {
|
|
77
|
+
const doc = await rest.insertOne("items", { title: "single" });
|
|
78
|
+
expect((await rest.findOne("items", doc._id)).title).toBe("single");
|
|
79
|
+
});
|
|
80
|
+
it("returns null for unknown id", async () => {
|
|
81
|
+
expect(await rest.findOne("items", "aaaaaaaaaaaaaaaaaaaaaaaa")).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("updateOne", () => {
|
|
86
|
+
it("$set title and changes updatedAt", async () => {
|
|
87
|
+
const doc = await rest.insertOne("items", { title: "old" });
|
|
88
|
+
const upd = await rest.updateOne("items", doc._id, { $set: { title: "new" } });
|
|
89
|
+
expect(upd.title).toBe("new");
|
|
90
|
+
expect(upd.updatedAt).not.toBe(doc.updatedAt);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("deleteOne", () => {
|
|
95
|
+
it("deletes and returns _id", async () => {
|
|
96
|
+
const doc = await rest.insertOne("items", { title: "del-me" });
|
|
97
|
+
expect((await rest.deleteOne("items", doc._id))._id).toBe(doc._id);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("insertMany", () => {
|
|
102
|
+
it("inserts and returns array with _id", async () => {
|
|
103
|
+
const docs = await rest.insertMany("items", [{ title: "m1" }, { title: "m2" }]);
|
|
104
|
+
expect(docs.length).toBe(2);
|
|
105
|
+
expect(docs[0]._id).toBeString();
|
|
106
|
+
expect(docs[1]._id).toBeString();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("updateMany", () => {
|
|
111
|
+
it("updates multiple by ids", async () => {
|
|
112
|
+
const a = await rest.insertOne("items", { title: "u1", count: 1 });
|
|
113
|
+
const b = await rest.insertOne("items", { title: "u2", count: 1 });
|
|
114
|
+
const r = await rest.updateMany("items", [a._id, b._id], { $set: { count: 99 } });
|
|
115
|
+
expect(r).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("findOneAndUpdate", () => {
|
|
120
|
+
it("finds and updates atomically", async () => {
|
|
121
|
+
await rest.insertOne("items", { title: "fau", count: 0 });
|
|
122
|
+
const doc = await rest.findOneAndUpdate("items", { title: "fau" }, { $set: { count: 10 } });
|
|
123
|
+
expect(doc.count).toBe(10);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("deleteMany", () => {
|
|
128
|
+
it("deletes multiple by ids", async () => {
|
|
129
|
+
const a = await rest.insertOne("items", { title: "d1" });
|
|
130
|
+
const b = await rest.insertOne("items", { title: "d2" });
|
|
131
|
+
const r = await rest.deleteMany("items", [a._id, b._id]);
|
|
132
|
+
expect(r).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("aggregate", () => {
|
|
137
|
+
it("returns array from pipeline", async () => {
|
|
138
|
+
await rest.insertOne("items", { title: "agg", count: 5 });
|
|
139
|
+
const docs = await rest.aggregate("items", [{ $match: { title: "agg" } }, { $limit: 1 }]);
|
|
140
|
+
expect(docs.length).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
it("rejects $where", async () => {
|
|
143
|
+
await expect(rest.aggregate("items", [{ $where: "1" }])).rejects.toThrow();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ─── UTILS ───────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
describe("countDocuments", () => {
|
|
150
|
+
it("returns a number", async () => {
|
|
151
|
+
const n = await rest.countDocuments("items", {});
|
|
152
|
+
expect(typeof n).toBe("number");
|
|
153
|
+
});
|
|
154
|
+
it("counts with filter", async () => {
|
|
155
|
+
await rest.insertOne("items", { title: "count-me" });
|
|
156
|
+
const n = await rest.countDocuments("items", { title: "count-me" });
|
|
157
|
+
expect(n).toBeGreaterThanOrEqual(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("bulkWrite", () => {
|
|
162
|
+
it("executes mixed operations", async () => {
|
|
163
|
+
const result = await rest.bulkWrite("items", [
|
|
164
|
+
{ insertOne: { document: { title: "bw1" } } },
|
|
165
|
+
{ insertOne: { document: { title: "bw2" } } },
|
|
166
|
+
]);
|
|
167
|
+
expect(result.insertedCount).toBe(2);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("bulkUpdate", () => {
|
|
172
|
+
it("updates many documents matching filters", async () => {
|
|
173
|
+
await rest.insertOne("items", { title: "bu1", count: 1 });
|
|
174
|
+
await rest.insertOne("items", { title: "bu2", count: 1 });
|
|
175
|
+
const r = await rest.bulkUpdate("items", [
|
|
176
|
+
{ updateMany: { filter: { count: 1 }, update: { $set: { count: 100 } } } },
|
|
177
|
+
]);
|
|
178
|
+
expect(r.modifiedCount).toBeGreaterThanOrEqual(1);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("stats", () => {
|
|
183
|
+
it("returns collection stats", async () => {
|
|
184
|
+
const s = await rest.stats("items");
|
|
185
|
+
expect(s).toBeDefined();
|
|
186
|
+
expect(typeof s).toBe("object");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("audit", () => {
|
|
191
|
+
it("adds and retrieves activities", async () => {
|
|
192
|
+
await rest.audit.addActivities([{
|
|
193
|
+
internal: false,
|
|
194
|
+
trace: { id: crypto.randomUUID() },
|
|
195
|
+
request: null,
|
|
196
|
+
meta: {},
|
|
197
|
+
operation: {
|
|
198
|
+
tenant: TEST_TENANT,
|
|
199
|
+
action: "test",
|
|
200
|
+
collection: "items",
|
|
201
|
+
status: "success",
|
|
202
|
+
input: null,
|
|
203
|
+
result: null,
|
|
204
|
+
error: null,
|
|
205
|
+
duration: 0,
|
|
206
|
+
transaction: false,
|
|
207
|
+
},
|
|
208
|
+
ts: new Date(),
|
|
209
|
+
}]);
|
|
210
|
+
const activities = await rest.audit.getActivities({ $limit: 10 });
|
|
211
|
+
expect(Array.isArray(activities)).toBe(true);
|
|
212
|
+
expect(activities.length).toBeGreaterThanOrEqual(1);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("workflows", () => {
|
|
217
|
+
it("returns workflows array", async () => {
|
|
218
|
+
const w = await rest.workflows({});
|
|
219
|
+
expect(Array.isArray(w)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("lock / unlock", () => {
|
|
224
|
+
it("acquires a lock (throws if held)", async () => {
|
|
225
|
+
await rest.unlock("test-lock").catch(() => {});
|
|
226
|
+
await expect(rest.lock("test-lock", 5000)).resolves.toBeUndefined();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("fails to acquire same lock twice", async () => {
|
|
230
|
+
await rest.unlock("test-lock-2").catch(() => {});
|
|
231
|
+
await rest.lock("test-lock-2", 10000);
|
|
232
|
+
await expect(rest.lock("test-lock-2", 1000)).rejects.toThrow();
|
|
233
|
+
await rest.unlock("test-lock-2");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("transactions", () => {
|
|
238
|
+
it("commits a transaction (requires replica set)", async () => {
|
|
239
|
+
try {
|
|
240
|
+
rest.startSession();
|
|
241
|
+
rest.startTransaction();
|
|
242
|
+
await rest.insertOne("items", { title: "tx-commit" });
|
|
243
|
+
await rest.commitTransaction();
|
|
244
|
+
await rest.endSession();
|
|
245
|
+
const docs = await rest.find("items", { $match: { title: "tx-commit" } });
|
|
246
|
+
expect(docs.length).toBe(1);
|
|
247
|
+
} catch (err: any) {
|
|
248
|
+
if (err?.message?.includes("retryable writes") || err?.message?.includes("replica set")) {
|
|
249
|
+
console.warn("Skipping transaction test: MongoDB standalone");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
throw err;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("aborts a transaction (requires replica set)", async () => {
|
|
257
|
+
try {
|
|
258
|
+
rest.startSession();
|
|
259
|
+
rest.startTransaction();
|
|
260
|
+
await rest.insertOne("items", { title: "tx-abort" });
|
|
261
|
+
await rest.abortTransaction();
|
|
262
|
+
await rest.endSession();
|
|
263
|
+
const docs = await rest.find("items", { $match: { title: "tx-abort" } });
|
|
264
|
+
expect(docs.length).toBe(0);
|
|
265
|
+
} catch (err: any) {
|
|
266
|
+
if (err?.message?.includes("retryable writes") || err?.message?.includes("replica set")) {
|
|
267
|
+
console.warn("Skipping transaction test: MongoDB standalone");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
throw err;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("watch", () => {
|
|
276
|
+
it("returns a change stream", async () => {
|
|
277
|
+
const stream = await rest.watch("items", [{ $match: { operationType: "insert" } }]);
|
|
278
|
+
expect(stream).toBeDefined();
|
|
279
|
+
await stream.close();
|
|
280
|
+
});
|
|
281
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": [
|
|
5
|
+
"ESNext"
|
|
6
|
+
],
|
|
7
|
+
"target": "ESNext",
|
|
8
|
+
"module": "Preserve",
|
|
9
|
+
"moduleDetection": "force",
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
// Bundler mode
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
// Some stricter flags (disabled by default)
|
|
24
|
+
"noUnusedLocals": false,
|
|
25
|
+
"noUnusedParameters": false,
|
|
26
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
27
|
+
"experimentalDecorators": true,
|
|
28
|
+
},
|
|
29
|
+
"include": [
|
|
30
|
+
"types/**/*.d.ts",
|
|
31
|
+
"**/*.ts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": [
|
|
34
|
+
"dist"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type ActivityInput = {
|
|
2
|
+
internal: boolean;
|
|
3
|
+
trace: {
|
|
4
|
+
id: string;
|
|
5
|
+
comment?: string;
|
|
6
|
+
tag?: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
};
|
|
9
|
+
request?: {
|
|
10
|
+
ip?: string;
|
|
11
|
+
user_agent?: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
method?: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
query?: Record<string, any>;
|
|
16
|
+
};
|
|
17
|
+
meta: {
|
|
18
|
+
environment?: string;
|
|
19
|
+
hostname?: string;
|
|
20
|
+
core_version?: string;
|
|
21
|
+
platform?: string;
|
|
22
|
+
};
|
|
23
|
+
operation: {
|
|
24
|
+
tenant: string;
|
|
25
|
+
action: string;
|
|
26
|
+
collection: string;
|
|
27
|
+
collectionType?: "document" | "file";
|
|
28
|
+
status: "success" | "error";
|
|
29
|
+
input: any;
|
|
30
|
+
result: any;
|
|
31
|
+
error: {
|
|
32
|
+
message: string;
|
|
33
|
+
code: string;
|
|
34
|
+
} | null;
|
|
35
|
+
duration: number;
|
|
36
|
+
transaction: boolean;
|
|
37
|
+
token?: {
|
|
38
|
+
decoded: Record<string, unknown> | null;
|
|
39
|
+
value: string | null;
|
|
40
|
+
provided: boolean;
|
|
41
|
+
expired: boolean;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
ts: Date;
|
|
45
|
+
}
|
package/types/api.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export declare enum ApiActions {
|
|
2
|
+
insertOne = "insertOne",
|
|
3
|
+
insertMany = "insertMany",
|
|
4
|
+
updateOne = "updateOne",
|
|
5
|
+
updateMany = "updateMany",
|
|
6
|
+
deleteOne = "deleteOne",
|
|
7
|
+
deleteMany = "deleteMany",
|
|
8
|
+
findOne = "findOne",
|
|
9
|
+
find = "find",
|
|
10
|
+
runAction = "runAction",
|
|
11
|
+
runService = "runService",
|
|
12
|
+
upload = "upload",
|
|
13
|
+
auth = "auth",
|
|
14
|
+
login = "login",
|
|
15
|
+
aggregate = "aggregate",
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ActionsApiList =
|
|
20
|
+
| "findOneAndUpdate"
|
|
21
|
+
| "insertOne"
|
|
22
|
+
| "insertMany"
|
|
23
|
+
| "updateOne"
|
|
24
|
+
| "updateMany"
|
|
25
|
+
| "deleteOne"
|
|
26
|
+
| "deleteMany"
|
|
27
|
+
| "findOne"
|
|
28
|
+
| "find"
|
|
29
|
+
| "runAction"
|
|
30
|
+
| "runService"
|
|
31
|
+
| "upload"
|
|
32
|
+
| "auth"
|
|
33
|
+
| "login"
|
|
34
|
+
| "logout"
|
|
35
|
+
| "aggregate"
|
|
36
|
+
| "*"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export type ApiOptions = {
|
|
40
|
+
cleanDeep?: boolean;
|
|
41
|
+
useCache?: boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ApiParams = {
|
|
45
|
+
action: ApiActions;
|
|
46
|
+
collection: string;
|
|
47
|
+
tenant: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export type ApiAccess = {
|
|
52
|
+
[key in ActionsApiList | (string & {})]?:
|
|
53
|
+
| boolean
|
|
54
|
+
| ((ctx: {
|
|
55
|
+
rest: InstanceType<typeof useRest>;
|
|
56
|
+
error: typeof fn.error;
|
|
57
|
+
jwt: typeof jwt;
|
|
58
|
+
/** `value`: raw JWT string; `decoded`: verified claims (or null if unauthenticated). */
|
|
59
|
+
token: { value: string | null; decoded: Record<string, unknown> | null; provided: boolean; expired: boolean };
|
|
60
|
+
}) => boolean);
|
|
61
|
+
};
|
|
62
|
+
export type ApiActions = {
|
|
63
|
+
[key: string]:
|
|
64
|
+
| boolean
|
|
65
|
+
| ((ctx: {
|
|
66
|
+
rest: InstanceType<typeof useRest>;
|
|
67
|
+
error: typeof fn.error;
|
|
68
|
+
jwt: typeof jwt;
|
|
69
|
+
}) => boolean);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ApiResponse = {
|
|
73
|
+
data: any;
|
|
74
|
+
error: any;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export type updateApiOptions = {
|
|
79
|
+
cleanDeep?: boolean;
|
|
80
|
+
upsert?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type findApiOptions = {
|
|
84
|
+
cleanDeep?: boolean;
|
|
85
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AppError, fn } from "../lib/error";
|
|
2
|
+
import type { Field } from "./field";
|
|
3
|
+
import type { HooksCollection } from "./hook";
|
|
4
|
+
import type Joi from "joi";
|
|
5
|
+
import type { Cookies } from "hono/types";
|
|
6
|
+
import type { Context } from "hono";
|
|
7
|
+
import type { useRest } from "../database/rest";
|
|
8
|
+
import type { jwt } from "../utils/func";
|
|
9
|
+
import type { ActionsApiList, ApiAccess, ApiActions } from "./api";
|
|
10
|
+
export type Collection = {
|
|
11
|
+
type?: "document" | "file";
|
|
12
|
+
slug: string;
|
|
13
|
+
studio?: {
|
|
14
|
+
label?: string;
|
|
15
|
+
info?: string;
|
|
16
|
+
};
|
|
17
|
+
hooks?: {
|
|
18
|
+
beforeOperation?: HooksCollection['beforeOperation'];
|
|
19
|
+
afterOperation?: HooksCollection['afterOperation'];
|
|
20
|
+
},
|
|
21
|
+
fields: Field[];
|
|
22
|
+
api?: {
|
|
23
|
+
access?: ApiAccess,
|
|
24
|
+
auth?: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
onLogin: (ctx: {
|
|
27
|
+
rest: InstanceType<typeof useRest>;
|
|
28
|
+
payload: any;
|
|
29
|
+
error: typeof fn.error;
|
|
30
|
+
jwt: typeof jwt;
|
|
31
|
+
req: {
|
|
32
|
+
cookies: {
|
|
33
|
+
set: (name: string, value: string, options?: {
|
|
34
|
+
httpOnly?: boolean;
|
|
35
|
+
secure?: boolean;
|
|
36
|
+
maxAge?: number;
|
|
37
|
+
path?: string;
|
|
38
|
+
domain?: string;
|
|
39
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
40
|
+
}) => any;
|
|
41
|
+
get: (name: string) => string | undefined;
|
|
42
|
+
delete: (name: string) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
}) => Promise<{
|
|
47
|
+
token: string;
|
|
48
|
+
data?: any;
|
|
49
|
+
} | typeof fn.error>;
|
|
50
|
+
onLogout?: (ctx: {
|
|
51
|
+
rest: InstanceType<typeof useRest>;
|
|
52
|
+
payload: any;
|
|
53
|
+
error: typeof fn.error;
|
|
54
|
+
jwt: typeof jwt;
|
|
55
|
+
req: {
|
|
56
|
+
cookies: {
|
|
57
|
+
delete: (name: string) => void;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}) => Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
privateFields?: (string | RegExp)[];
|
|
63
|
+
readOnlyFields?: (string | RegExp)[];
|
|
64
|
+
};
|
|
65
|
+
actions?: {
|
|
66
|
+
[key: string]: (ctx: {
|
|
67
|
+
rest: InstanceType<typeof useRest>;
|
|
68
|
+
data: any;
|
|
69
|
+
error: typeof fn.error;
|
|
70
|
+
}) => Promise<any>;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The tenant id of the collection
|
|
74
|
+
* @type {string}
|
|
75
|
+
*/
|
|
76
|
+
_tenant_?: string;
|
|
77
|
+
_isTimeSerie_?: boolean;
|
|
78
|
+
_isCollection_?: boolean;
|
|
79
|
+
_schema_?: Joi.Schema;
|
|
80
|
+
_schemaPartial_?: Joi.Schema;
|
|
81
|
+
|
|
82
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { Tenant } from "./tenant";
|
|
3
|
+
import type { IPRestrictionRule } from "hono/ip-restriction";
|
|
4
|
+
import type { Collection } from "./collection";
|
|
5
|
+
import type { Route } from "./route";
|
|
6
|
+
import type { Service } from "./service";
|
|
7
|
+
import type { Script } from "./scripts";
|
|
8
|
+
import type { FileCollection } from "./file";
|
|
9
|
+
export type ServerConfig = {
|
|
10
|
+
version?: string;
|
|
11
|
+
clusterMode?: boolean;
|
|
12
|
+
server: {
|
|
13
|
+
name?: string;
|
|
14
|
+
port: number;
|
|
15
|
+
body?: {
|
|
16
|
+
maxSize?: number;
|
|
17
|
+
};
|
|
18
|
+
cors?: {
|
|
19
|
+
origin: string | string[] | ((ctx: { origin: string, c: Context }) => string | string[]);
|
|
20
|
+
credentials?: boolean;
|
|
21
|
+
allowHeaders?: string[];
|
|
22
|
+
allowMethods?: string[];
|
|
23
|
+
}
|
|
24
|
+
ipRestriction?: {
|
|
25
|
+
denyList?: IPRestrictionRule[];
|
|
26
|
+
allowList?: IPRestrictionRule[];
|
|
27
|
+
}
|
|
28
|
+
jwt?: {
|
|
29
|
+
secret?: string;
|
|
30
|
+
expiresIn?: string;
|
|
31
|
+
}
|
|
32
|
+
rateLimit?: {
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
windowMs?: number;
|
|
35
|
+
max?: number;
|
|
36
|
+
/** If true, uses Redis store (requires ioredis client available) */
|
|
37
|
+
useRedis?: boolean;
|
|
38
|
+
/** Stricter limits for login endpoints */
|
|
39
|
+
login?: {
|
|
40
|
+
windowMs?: number;
|
|
41
|
+
max?: number;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
tenants: Tenant[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type Config = ServerConfig & {
|
|
49
|
+
version?: string;
|
|
50
|
+
collections?: Collection[]
|
|
51
|
+
routes?: Route[]
|
|
52
|
+
services?: Service[]
|
|
53
|
+
scripts?: Script[]
|
|
54
|
+
fileCollections?: FileCollection[]
|
|
55
|
+
}
|
package/types/field.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Type } from "arktype";
|
|
2
|
+
import type { GenerateRandomOptions } from "../utils/func";
|
|
3
|
+
import type Joi from "joi";
|
|
4
|
+
export type FieldType = "boolean"
|
|
5
|
+
| "ipv4"
|
|
6
|
+
| "ipv6"
|
|
7
|
+
| "url"
|
|
8
|
+
| "date"
|
|
9
|
+
| "datetime-local"
|
|
10
|
+
| "email"
|
|
11
|
+
| "array"
|
|
12
|
+
| "number"
|
|
13
|
+
| "integer"
|
|
14
|
+
| "password"
|
|
15
|
+
| "random"
|
|
16
|
+
| "relationship"
|
|
17
|
+
| "string"
|
|
18
|
+
| "enum"
|
|
19
|
+
| "json"
|
|
20
|
+
| "geojson.Point"
|
|
21
|
+
| "geojson.LineString"
|
|
22
|
+
| "geojson.Polygon"
|
|
23
|
+
| "uuid"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export type enumOptions = {
|
|
29
|
+
multiple?: boolean;
|
|
30
|
+
items?: Array<string | number | boolean | object>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export type Field = {
|
|
35
|
+
name: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
type: FieldType;
|
|
38
|
+
studio?: {
|
|
39
|
+
label?: string;
|
|
40
|
+
info?: string;
|
|
41
|
+
display?: string;
|
|
42
|
+
};
|
|
43
|
+
validate?: {
|
|
44
|
+
schema: Joi.Schema
|
|
45
|
+
},
|
|
46
|
+
required?: boolean;
|
|
47
|
+
enumOptions?: enumOptions;
|
|
48
|
+
unique?: boolean;
|
|
49
|
+
nullable?: boolean;
|
|
50
|
+
empty?: boolean;
|
|
51
|
+
defaultValue?: number | string | boolean | object | array<any>;
|
|
52
|
+
index?: boolean;
|
|
53
|
+
indexType?: "text"
|
|
54
|
+
| "hashed"
|
|
55
|
+
| "2dsphere"
|
|
56
|
+
| "2d"
|
|
57
|
+
indexOptions?: {
|
|
58
|
+
expireAfterSeconds?: number;
|
|
59
|
+
sparse?: boolean;
|
|
60
|
+
version?: number;
|
|
61
|
+
unique?: boolean;
|
|
62
|
+
};
|
|
63
|
+
relation?: {
|
|
64
|
+
to: string;
|
|
65
|
+
hasMany?: boolean;
|
|
66
|
+
pipeline?: Array<any>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Required if type === "random"
|
|
70
|
+
*/
|
|
71
|
+
randomOptions?: GenerateRandomOptions;
|
|
72
|
+
}
|