akanjs 2.1.0-rc.6 → 2.1.0-rc.8
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/capacitor.base.config.ts +88 -0
- package/client/device.ts +6 -1
- package/document/filterMeta.ts +5 -0
- package/package.json +10 -1
- package/server/di/predefinedAdaptor.ts +1 -1
- package/server/resolver/database.resolver.ts +8 -5
- package/server/resolver/service.resolver.ts +6 -3
- package/service/predefinedAdaptor/database.adaptor.ts +2 -1
- package/types/capacitor.base.config.d.ts +3 -0
- package/types/document/filterMeta.d.ts +1 -0
- package/types/server/di/predefinedAdaptor.d.ts +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import type { CapacitorConfig } from "@capacitor/cli";
|
|
3
|
+
import type { AkanMobileTargetConfig, AppScanResult } from "akanjs";
|
|
4
|
+
|
|
5
|
+
const getLocalIP = () => {
|
|
6
|
+
const interfaces = os.networkInterfaces();
|
|
7
|
+
for (const interfaceName in interfaces) {
|
|
8
|
+
const iface = interfaces[interfaceName];
|
|
9
|
+
if (!iface) continue;
|
|
10
|
+
for (const alias of iface) {
|
|
11
|
+
if (alias.family === "IPv4" && !alias.internal) return alias.address;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return "127.0.0.1";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const normalizeBasePath = (basePath: string | undefined) => basePath?.replace(/^\/+|\/+$/g, "");
|
|
18
|
+
|
|
19
|
+
const routeBasePaths = (appInfo: AppScanResult) =>
|
|
20
|
+
new Set(
|
|
21
|
+
appInfo.routes
|
|
22
|
+
.map((route) => route.replace(/^\.\//, "").split("/")[0])
|
|
23
|
+
.filter((segment): segment is string => !!segment && !segment.startsWith("_") && !segment.startsWith("(")),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const resolveTarget = (appInfo: AppScanResult, targetName = process.env.AKAN_MOBILE_TARGET) => {
|
|
27
|
+
const targets = appInfo.akanConfig.mobile.targets;
|
|
28
|
+
if (!targets || Object.keys(targets).length === 0) throw new Error("Akan mobile target metadata is missing.");
|
|
29
|
+
if (targetName) {
|
|
30
|
+
const target = targets[targetName];
|
|
31
|
+
if (!target) {
|
|
32
|
+
const basePath = normalizeBasePath(targetName);
|
|
33
|
+
const [template] = Object.values(targets);
|
|
34
|
+
if (basePath && template && routeBasePaths(appInfo).has(basePath))
|
|
35
|
+
return { ...template, name: basePath, basePath };
|
|
36
|
+
throw new Error(`Akan mobile target '${targetName}' was not found.`);
|
|
37
|
+
}
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
const entries = Object.entries(targets);
|
|
41
|
+
if (entries.length !== 1) throw new Error("AKAN_MOBILE_TARGET is required when multiple mobile targets exist.");
|
|
42
|
+
return entries[0]?.[1] as AkanMobileTargetConfig;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const localCsrUrl = (ip: string, target: AkanMobileTargetConfig) => {
|
|
46
|
+
const basePath = normalizeBasePath(target.basePath);
|
|
47
|
+
const port = process.env.AKAN_PUBLIC_CLIENT_PORT ?? process.env.PORT ?? "8282";
|
|
48
|
+
return `http://${ip}:${port}/${basePath ? `${basePath}` : ""}?csr=true`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const withBase = (
|
|
52
|
+
configImp: (config: CapacitorConfig, target: AkanMobileTargetConfig) => CapacitorConfig = (config) => config,
|
|
53
|
+
appData?: AppScanResult,
|
|
54
|
+
targetName?: string,
|
|
55
|
+
) => {
|
|
56
|
+
const ip = getLocalIP();
|
|
57
|
+
const appInfo = appData;
|
|
58
|
+
if (!appInfo) throw new Error("withBase requires apps/<app>/akan.app.json metadata.");
|
|
59
|
+
const target = resolveTarget(appInfo, targetName);
|
|
60
|
+
const baseConfig: CapacitorConfig = {
|
|
61
|
+
...target,
|
|
62
|
+
appId: target.appId,
|
|
63
|
+
appName: target.appName,
|
|
64
|
+
webDir: "dist",
|
|
65
|
+
server:
|
|
66
|
+
process.env.APP_OPERATION_MODE !== "release"
|
|
67
|
+
? {
|
|
68
|
+
androidScheme: "http",
|
|
69
|
+
url: localCsrUrl(ip, target),
|
|
70
|
+
cleartext: true,
|
|
71
|
+
allowNavigation: [ip, "localhost"],
|
|
72
|
+
}
|
|
73
|
+
: {
|
|
74
|
+
allowNavigation: ["*"],
|
|
75
|
+
},
|
|
76
|
+
plugins: {
|
|
77
|
+
CapacitorCookies: { enabled: true },
|
|
78
|
+
...target.plugins,
|
|
79
|
+
},
|
|
80
|
+
android: {
|
|
81
|
+
...target.android,
|
|
82
|
+
},
|
|
83
|
+
ios: {
|
|
84
|
+
...target.ios,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
return configImp(baseConfig, target);
|
|
88
|
+
};
|
package/client/device.ts
CHANGED
|
@@ -50,6 +50,11 @@ const getRenderMode = () => globalWithProcess.process?.env?.AKAN_PUBLIC_RENDER_E
|
|
|
50
50
|
|
|
51
51
|
const getBrowserLanguage = () => globalThis.navigator?.language?.split("-")[0] ?? "en";
|
|
52
52
|
|
|
53
|
+
const isNativeTarget = () => {
|
|
54
|
+
if (typeof window === "undefined") return false;
|
|
55
|
+
return Boolean((window as typeof window & { __AKAN_MOBILE_TARGET__?: unknown }).__AKAN_MOBILE_TARGET__);
|
|
56
|
+
};
|
|
57
|
+
|
|
53
58
|
export const isMobileDevice = () => {
|
|
54
59
|
if (typeof navigator === "undefined") return false;
|
|
55
60
|
if (typeof window !== "undefined" && window.matchMedia?.("(hover: none) and (pointer: coarse)")?.matches) return true;
|
|
@@ -88,7 +93,7 @@ export class Device {
|
|
|
88
93
|
supportLanguages?: string[] | readonly string[];
|
|
89
94
|
}) {
|
|
90
95
|
if (Device.instance) return Device.instance;
|
|
91
|
-
if (getRenderMode() !== "csr") {
|
|
96
|
+
if (getRenderMode() !== "csr" || !isNativeTarget()) {
|
|
92
97
|
const device = createWebDevice({ lang, supportLanguages });
|
|
93
98
|
Device.instance = device;
|
|
94
99
|
return device;
|
package/document/filterMeta.ts
CHANGED
|
@@ -93,6 +93,11 @@ export const getFilterSortByKey = (modelRef: FilterCls, key: string) => {
|
|
|
93
93
|
return filterMeta.sort[key];
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
export const fillMissingFilterArgs = (filterInfo: FilterInfo, args: unknown[]) => {
|
|
97
|
+
if (args.length >= filterInfo.args.length) return args;
|
|
98
|
+
return [...args, ...Array(filterInfo.args.length - args.length).fill(undefined)];
|
|
99
|
+
};
|
|
100
|
+
|
|
96
101
|
export type BaseFilterSortKey = "latest" | "oldest";
|
|
97
102
|
export type BaseFilterQueryKey = "any";
|
|
98
103
|
export type BaseFilterKey = BaseFilterSortKey | BaseFilterQueryKey;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akanjs",
|
|
3
|
-
"version": "2.1.0-rc.
|
|
3
|
+
"version": "2.1.0-rc.8",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -95,6 +95,11 @@
|
|
|
95
95
|
"import": "./client/capacitor.ts",
|
|
96
96
|
"default": "./client/capacitor.ts"
|
|
97
97
|
},
|
|
98
|
+
"./capacitor.base.config": {
|
|
99
|
+
"types": "./types/capacitor.base.config.d.ts",
|
|
100
|
+
"import": "./capacitor.base.config.ts",
|
|
101
|
+
"default": "./capacitor.base.config.ts"
|
|
102
|
+
},
|
|
98
103
|
"./webkit": {
|
|
99
104
|
"types": "./types/webkit/index.d.ts",
|
|
100
105
|
"import": "./webkit/index.ts",
|
|
@@ -159,6 +164,7 @@
|
|
|
159
164
|
"@capacitor/app": "^8.1.0",
|
|
160
165
|
"@capacitor/browser": "^8.0.3",
|
|
161
166
|
"@capacitor/camera": "^8.2.0",
|
|
167
|
+
"@capacitor/cli": "^8.3.4",
|
|
162
168
|
"@capacitor/core": "^8.3.4",
|
|
163
169
|
"@capacitor/device": "^8.0.2",
|
|
164
170
|
"@capacitor/geolocation": "^8.2.0",
|
|
@@ -211,6 +217,9 @@
|
|
|
211
217
|
"@capacitor/camera": {
|
|
212
218
|
"optional": true
|
|
213
219
|
},
|
|
220
|
+
"@capacitor/cli": {
|
|
221
|
+
"optional": true
|
|
222
|
+
},
|
|
214
223
|
"@capacitor/core": {
|
|
215
224
|
"optional": true
|
|
216
225
|
},
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
ConsoleLogger,
|
|
11
11
|
type DatabaseAdaptor,
|
|
12
12
|
DatabaseAdaptorRole,
|
|
13
|
+
JsonCompressor,
|
|
13
14
|
LibsqlDatabase,
|
|
14
15
|
type LoggingAdaptor,
|
|
15
16
|
LoggingAdaptorRole,
|
|
16
17
|
PostgresDatabase,
|
|
17
|
-
JsonCompressor,
|
|
18
18
|
type QueueAdaptor,
|
|
19
19
|
QueueAdaptorRole,
|
|
20
20
|
RedisCache,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
DocumentSchema,
|
|
12
12
|
documentQueryHelper,
|
|
13
13
|
type FindQueryOption,
|
|
14
|
+
fillMissingFilterArgs,
|
|
14
15
|
getFilterInfoByKey,
|
|
15
16
|
getFilterMeta,
|
|
16
17
|
getFilterSortByKey,
|
|
@@ -338,7 +339,8 @@ export class DatabaseResolver {
|
|
|
338
339
|
const filterInfo = getFilterInfoByKey(database.filter, queryKey);
|
|
339
340
|
const queryFn = filterInfo.queryFn;
|
|
340
341
|
if (!queryFn) throw new Error(`No query function for key: ${queryKey}`);
|
|
341
|
-
const
|
|
342
|
+
const queryArgs = fillMissingFilterArgs(filterInfo, hasQueryOption ? args.slice(0, -1) : args);
|
|
343
|
+
const query = queryFn(...queryArgs, documentQueryHelper);
|
|
342
344
|
const queryOption = hasQueryOption ? lastArg : {};
|
|
343
345
|
return { query, queryOption };
|
|
344
346
|
};
|
|
@@ -371,18 +373,19 @@ export class DatabaseResolver {
|
|
|
371
373
|
return (this as unknown as DatabaseInstance).__pickId(query, queryOption);
|
|
372
374
|
},
|
|
373
375
|
[`exists${capitalize(queryKey)}`]: async function (...args: any) {
|
|
374
|
-
const query = queryFn(...args, documentQueryHelper);
|
|
376
|
+
const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
|
|
375
377
|
return (this as unknown as DatabaseInstance).__exists(query);
|
|
376
378
|
},
|
|
377
379
|
[`count${capitalize(queryKey)}`]: async function (...args: any) {
|
|
378
|
-
const query = queryFn(...args, documentQueryHelper);
|
|
380
|
+
const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
|
|
379
381
|
return (this as unknown as DatabaseInstance).__count(query);
|
|
380
382
|
},
|
|
381
383
|
[`insight${capitalize(queryKey)}`]: async function (...args: any) {
|
|
382
|
-
const query = queryFn(...args, documentQueryHelper);
|
|
384
|
+
const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
|
|
383
385
|
return (this as unknown as DatabaseInstance).__insight(query);
|
|
384
386
|
},
|
|
385
|
-
[`query${capitalize(queryKey)}`]: (...args: any) =>
|
|
387
|
+
[`query${capitalize(queryKey)}`]: (...args: any) =>
|
|
388
|
+
queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper),
|
|
386
389
|
});
|
|
387
390
|
});
|
|
388
391
|
applyMixins(DatabaseModelInstance, [database.model]);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type Doc,
|
|
9
9
|
documentQueryHelper,
|
|
10
10
|
type FindQueryOption,
|
|
11
|
+
fillMissingFilterArgs,
|
|
11
12
|
getFilterInfoByKey,
|
|
12
13
|
getFilterMeta,
|
|
13
14
|
type ListQueryOption,
|
|
@@ -116,9 +117,11 @@ export class ServiceResolver {
|
|
|
116
117
|
typeof lastArg.skip === "number" ||
|
|
117
118
|
typeof lastArg.limit === "number" ||
|
|
118
119
|
typeof lastArg.sort === "string");
|
|
119
|
-
const
|
|
120
|
+
const filterInfo = getFilterInfoByKey(database.filter, queryKey);
|
|
121
|
+
const queryFn = filterInfo.queryFn;
|
|
120
122
|
if (!queryFn) throw new Error(`No query function for key: ${queryKey}`);
|
|
121
|
-
const
|
|
123
|
+
const queryArgs = fillMissingFilterArgs(filterInfo, hasQueryOption ? args.slice(0, -1) : args);
|
|
124
|
+
const query = queryFn(...queryArgs, documentQueryHelper);
|
|
122
125
|
const queryOption = hasQueryOption ? lastArg : {};
|
|
123
126
|
return { query, queryOption };
|
|
124
127
|
};
|
|
@@ -167,7 +170,7 @@ export class ServiceResolver {
|
|
|
167
170
|
return this.__insight(query);
|
|
168
171
|
},
|
|
169
172
|
[`query${capitalize(queryKey)}`]: function (this: DatabaseService, ...args: any) {
|
|
170
|
-
return queryFn(...args, documentQueryHelper);
|
|
173
|
+
return queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
|
|
171
174
|
},
|
|
172
175
|
});
|
|
173
176
|
});
|
|
@@ -1003,7 +1003,8 @@ export class SqliteDocumentStore {
|
|
|
1003
1003
|
const store = this;
|
|
1004
1004
|
const original = JSON.parse(JSON.stringify(sanitizeJson(originalData) ?? {})) as Record<string, unknown>;
|
|
1005
1005
|
const isNew = !originalData.id;
|
|
1006
|
-
const
|
|
1006
|
+
const hydratedData = isNew ? this.prepareDocument(data) : data;
|
|
1007
|
+
const doc = Object.assign(Object.create(this.database.doc.prototype), hydratedData);
|
|
1007
1008
|
Object.defineProperties(doc, {
|
|
1008
1009
|
set: {
|
|
1009
1010
|
value(patch: DocumentRecord) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CapacitorConfig } from "@capacitor/cli";
|
|
2
|
+
import type { AkanMobileTargetConfig, AppScanResult } from "akanjs";
|
|
3
|
+
export declare const withBase: (configImp?: (config: CapacitorConfig, target: AkanMobileTargetConfig) => CapacitorConfig, appData?: AppScanResult, targetName?: string) => CapacitorConfig;
|
|
@@ -18,6 +18,7 @@ export declare const setFilterMeta: (filterRef: Cls<unknown, {
|
|
|
18
18
|
export declare const getFilterInfoByKey: <ArgNames extends string[] = [], Args extends any[] = any[], Model = any>(modelRef: FilterCls, key: string) => FilterInfo<ArgNames, Args, Model>;
|
|
19
19
|
export declare const setFilterInfoByKey: <ArgNames extends string[] = [], Args extends any[] = any[], Model = any>(modelRef: Cls<Model>, key: string, filterInfo: FilterInfo<ArgNames, Args, Model>) => void;
|
|
20
20
|
export declare const getFilterSortByKey: (modelRef: FilterCls, key: string) => unknown;
|
|
21
|
+
export declare const fillMissingFilterArgs: (filterInfo: FilterInfo, args: unknown[]) => any[];
|
|
21
22
|
export type BaseFilterSortKey = "latest" | "oldest";
|
|
22
23
|
export type BaseFilterQueryKey = "any";
|
|
23
24
|
export type BaseFilterKey = BaseFilterSortKey | BaseFilterQueryKey;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DatabaseMode } from "akanjs";
|
|
2
|
-
import { type AdaptorCls, BlobStorage, type CacheAdaptor, type CompressAdaptor, ConsoleLogger, type DatabaseAdaptor, type LoggingAdaptor,
|
|
2
|
+
import { type AdaptorCls, BlobStorage, type CacheAdaptor, type CompressAdaptor, ConsoleLogger, type DatabaseAdaptor, JsonCompressor, type LoggingAdaptor, type QueueAdaptor, type ScheduleAdaptor, Scheduler, SolidCache, SolidPubSub, SolidQueue, SqliteDatabase, type StorageAdaptor, type WebsocketAdaptor } from "akanjs/service";
|
|
3
3
|
export interface PredefinedAdaptor {
|
|
4
4
|
database: AdaptorCls<DatabaseAdaptor>;
|
|
5
5
|
cache: AdaptorCls<CacheAdaptor>;
|