appflare 0.0.28 → 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.
- package/cli/commands/index.ts +140 -0
- package/cli/generate.ts +149 -0
- package/cli/index.ts +56 -447
- package/cli/load-config.ts +182 -0
- package/cli/schema-compiler.ts +657 -0
- package/cli/templates/auth/README.md +156 -0
- package/cli/templates/auth/config.ts +61 -0
- package/cli/templates/auth/route-config.ts +18 -0
- package/cli/templates/auth/route-handler.ts +18 -0
- package/cli/templates/auth/route-request-utils.ts +55 -0
- package/cli/templates/auth/route.ts +14 -0
- package/cli/templates/core/README.md +266 -0
- package/cli/templates/core/app-creation.ts +19 -0
- package/cli/templates/core/client/appflare.ts +37 -0
- package/cli/templates/core/client/index.ts +6 -0
- package/cli/templates/core/client/storage.ts +100 -0
- package/cli/templates/core/client/types.ts +54 -0
- package/cli/templates/core/client-modules/appflare.ts +112 -0
- package/cli/templates/core/client-modules/handlers/index.ts +740 -0
- package/cli/templates/core/client-modules/handlers.ts +1 -0
- package/cli/templates/core/client-modules/index.ts +7 -0
- package/cli/templates/core/client-modules/storage.ts +180 -0
- package/cli/templates/core/client-modules/types.ts +145 -0
- package/cli/templates/core/client.ts +39 -0
- package/cli/templates/core/drizzle.ts +15 -0
- package/cli/templates/core/export.ts +14 -0
- package/cli/templates/core/handlers-route.ts +23 -0
- package/cli/templates/core/handlers.ts +1 -0
- package/cli/templates/core/imports.ts +8 -0
- package/cli/templates/core/server.ts +38 -0
- package/cli/templates/core/types.ts +6 -0
- package/cli/templates/core/wrangler.ts +109 -0
- package/cli/templates/handlers/README.md +265 -0
- package/cli/templates/handlers/auth.ts +36 -0
- package/cli/templates/handlers/execution.ts +39 -0
- package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
- package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
- package/cli/templates/handlers/generators/context/types.ts +18 -0
- package/cli/templates/handlers/generators/context.ts +43 -0
- package/cli/templates/handlers/generators/execution.ts +15 -0
- package/cli/templates/handlers/generators/handlers.ts +13 -0
- package/cli/templates/handlers/index.ts +43 -0
- package/cli/templates/handlers/operations.ts +116 -0
- package/cli/templates/handlers/registration.ts +1114 -0
- package/cli/templates/handlers/types.ts +960 -0
- package/cli/templates/handlers/utils.ts +48 -0
- package/cli/types.ts +108 -0
- package/cli/utils/handler-discovery.ts +366 -0
- package/cli/utils/json-utils.ts +24 -0
- package/cli/utils/path-utils.ts +19 -0
- package/cli/utils/schema-discovery.ts +390 -0
- package/index.ts +27 -4
- package/package.json +23 -20
- package/react/index.ts +5 -3
- package/react/use-infinite-query.ts +190 -0
- package/react/use-mutation.ts +54 -0
- package/react/use-query.ts +158 -0
- package/schema.ts +262 -0
- package/tsconfig.json +2 -4
- package/cli/README.md +0 -108
- package/cli/core/build.ts +0 -187
- package/cli/core/config.ts +0 -92
- package/cli/core/discover-handlers.ts +0 -143
- package/cli/core/handlers.ts +0 -7
- package/cli/core/index.ts +0 -205
- package/cli/generators/generate-api-client/client.ts +0 -163
- package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
- package/cli/generators/generate-api-client/index.ts +0 -973
- package/cli/generators/generate-api-client/types.ts +0 -164
- package/cli/generators/generate-api-client/utils.ts +0 -22
- package/cli/generators/generate-api-client.ts +0 -1
- package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
- package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
- package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
- package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
- package/cli/generators/generate-cloudflare-worker.ts +0 -4
- package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
- package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
- package/cli/generators/generate-cron-handlers/index.ts +0 -61
- package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
- package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
- package/cli/generators/generate-db-handlers/index.ts +0 -33
- package/cli/generators/generate-db-handlers/prepare.ts +0 -24
- package/cli/generators/generate-db-handlers/templates.ts +0 -189
- package/cli/generators/generate-db-handlers.ts +0 -1
- package/cli/generators/generate-hono-server/auth.ts +0 -97
- package/cli/generators/generate-hono-server/imports.ts +0 -55
- package/cli/generators/generate-hono-server/index.ts +0 -52
- package/cli/generators/generate-hono-server/routes.ts +0 -115
- package/cli/generators/generate-hono-server/template.ts +0 -371
- package/cli/generators/generate-hono-server.ts +0 -1
- package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
- package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
- package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
- package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
- package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers.ts +0 -1
- package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
- package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
- package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
- package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
- package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
- package/cli/generators/generate-websocket-durable-object.ts +0 -1
- package/cli/schema/schema-static-types.ts +0 -702
- package/cli/schema/schema.ts +0 -151
- package/cli/utils/tsc.ts +0 -54
- package/cli/utils/utils.ts +0 -190
- package/cli/utils/zod-utils.ts +0 -121
- package/lib/README.md +0 -50
- package/lib/db.ts +0 -19
- package/lib/location.ts +0 -110
- package/lib/values.ts +0 -27
- package/react/README.md +0 -67
- package/react/hooks/useMutation.ts +0 -89
- package/react/hooks/usePaginatedQuery.ts +0 -213
- package/react/hooks/useQuery.ts +0 -106
- package/react/shared/queryShared.ts +0 -174
- package/server/README.md +0 -218
- package/server/auth.ts +0 -107
- package/server/database/builders.ts +0 -83
- package/server/database/context.ts +0 -327
- package/server/database/populate.ts +0 -234
- package/server/database/query-builder.ts +0 -161
- package/server/database/query-utils.ts +0 -25
- package/server/db.ts +0 -2
- package/server/storage/auth.ts +0 -16
- package/server/storage/bucket.ts +0 -22
- package/server/storage/context.ts +0 -34
- package/server/storage/index.ts +0 -38
- package/server/storage/operations.ts +0 -149
- package/server/storage/route-handler.ts +0 -60
- package/server/storage/types.ts +0 -55
- package/server/storage/utils.ts +0 -47
- package/server/storage.ts +0 -6
- package/server/types/schema-refs.ts +0 -66
- package/server/types/types.ts +0 -633
- package/server/utils/id-utils.ts +0 -230
package/cli/schema/schema.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { pathToFileURL } from "node:url";
|
|
2
|
-
import { staticTypeDefinitions } from "./schema-static-types";
|
|
3
|
-
import { pascalCase, toImportPathFromGeneratedSrc } from "../utils/utils";
|
|
4
|
-
import { getZodObjectShape, renderField } from "../utils/zod-utils";
|
|
5
|
-
|
|
6
|
-
function generateDocParts(
|
|
7
|
-
tableNames: string[],
|
|
8
|
-
schema: any,
|
|
9
|
-
): {
|
|
10
|
-
docInterfaces: string[];
|
|
11
|
-
docMapEntries: string[];
|
|
12
|
-
tableIndexEntries: string[];
|
|
13
|
-
} {
|
|
14
|
-
const docInterfaces: string[] = [];
|
|
15
|
-
const docMapEntries: string[] = [];
|
|
16
|
-
const tableIndexEntries: string[] = [];
|
|
17
|
-
|
|
18
|
-
for (const tableName of tableNames) {
|
|
19
|
-
const validator = (schema as any)[tableName];
|
|
20
|
-
const shape = getZodObjectShape(validator);
|
|
21
|
-
const interfaceName = pascalCase(`${tableName}Doc`);
|
|
22
|
-
|
|
23
|
-
const fields = Object.entries(shape);
|
|
24
|
-
const lines: string[] = [];
|
|
25
|
-
lines.push(`export interface ${interfaceName} {`);
|
|
26
|
-
lines.push(`\t_id: Id<${JSON.stringify(tableName)}>;`);
|
|
27
|
-
lines.push(`\t_creationTime: number;`);
|
|
28
|
-
for (const [fieldName, fieldSchema] of fields) {
|
|
29
|
-
const rendered = renderField(fieldName, fieldSchema);
|
|
30
|
-
lines.push(`\t${rendered}`);
|
|
31
|
-
}
|
|
32
|
-
lines.push(`}`);
|
|
33
|
-
|
|
34
|
-
docInterfaces.push(lines.join("\n"));
|
|
35
|
-
docMapEntries.push(`\t${tableName}: ${interfaceName};`);
|
|
36
|
-
tableIndexEntries.push(`\t${tableName}: [],`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { docInterfaces, docMapEntries, tableIndexEntries };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function getSchemaTableNames(
|
|
43
|
-
schemaPathAbs: string,
|
|
44
|
-
): Promise<string[]> {
|
|
45
|
-
const mod = await import(pathToFileURL(schemaPathAbs).href);
|
|
46
|
-
const schema = mod?.default ?? mod;
|
|
47
|
-
if (!schema || typeof schema !== "object") {
|
|
48
|
-
throw new Error(
|
|
49
|
-
`Invalid schema export in ${schemaPathAbs} (expected default export object)`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
return Object.keys(schema).sort();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function generateSchemaTypes(params: {
|
|
56
|
-
schemaPathAbs: string;
|
|
57
|
-
configPathAbs?: string;
|
|
58
|
-
outDirAbs?: string;
|
|
59
|
-
}): Promise<string> {
|
|
60
|
-
const mod = await import(pathToFileURL(params.schemaPathAbs).href);
|
|
61
|
-
const schema = mod?.default ?? mod;
|
|
62
|
-
if (!schema || typeof schema !== "object") {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`Invalid schema export in ${params.schemaPathAbs} (expected default export object)`,
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const tableNames = Object.keys(schema).sort();
|
|
69
|
-
if (tableNames.length === 0) {
|
|
70
|
-
throw new Error(`Schema has no tables in ${params.schemaPathAbs}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const { docInterfaces, docMapEntries, tableIndexEntries } = generateDocParts(
|
|
74
|
-
tableNames,
|
|
75
|
-
schema,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const configImportPath =
|
|
79
|
-
params.configPathAbs && params.outDirAbs
|
|
80
|
-
? toImportPathFromGeneratedSrc(params.outDirAbs, params.configPathAbs)
|
|
81
|
-
: null;
|
|
82
|
-
|
|
83
|
-
const authImports = configImportPath
|
|
84
|
-
? `import type appflareConfig from ${JSON.stringify(configImportPath)};
|
|
85
|
-
import type { Auth, BetterAuthOptions,User,Session } from "better-auth";
|
|
86
|
-
`
|
|
87
|
-
: "";
|
|
88
|
-
|
|
89
|
-
const authTypeDefs = configImportPath
|
|
90
|
-
? `type __AppflareAuthOptions = NonNullable<
|
|
91
|
-
typeof appflareConfig["auth"]
|
|
92
|
-
>["options"];
|
|
93
|
-
|
|
94
|
-
type __AppflareAuthInstance = __AppflareAuthOptions extends BetterAuthOptions
|
|
95
|
-
? Auth<__AppflareAuthOptions>
|
|
96
|
-
: Auth<BetterAuthOptions>;
|
|
97
|
-
|
|
98
|
-
type __AppflareAuthSessionResult = __AppflareAuthOptions extends BetterAuthOptions
|
|
99
|
-
? Awaited<
|
|
100
|
-
ReturnType<__AppflareAuthInstance["api"]["getSession"]>
|
|
101
|
-
>
|
|
102
|
-
: null;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
export type AppflareAuthSession = Session | null
|
|
106
|
-
export type AppflareAuthUser = User | null
|
|
107
|
-
|
|
108
|
-
export type AppflareAuthContext = {
|
|
109
|
-
session: AppflareAuthSession
|
|
110
|
-
user: AppflareAuthUser
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
`
|
|
114
|
-
: `export type AppflareAuthSession = null;
|
|
115
|
-
export type AppflareAuthUser = null;
|
|
116
|
-
export type AppflareAuthContext = { session: null; user: null };
|
|
117
|
-
`;
|
|
118
|
-
|
|
119
|
-
return `/* eslint-disable */
|
|
120
|
-
/**
|
|
121
|
-
* This file is auto-generated by appflare/db-build.ts.
|
|
122
|
-
* Do not edit directly.
|
|
123
|
-
*/
|
|
124
|
-
${authImports}
|
|
125
|
-
type SchemaValidator<TValue> = {
|
|
126
|
-
parse: (value: unknown) => TValue;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
export type AnyValidator = SchemaValidator<unknown>;
|
|
130
|
-
|
|
131
|
-
export type TableNames = ${tableNames.map((t) => JSON.stringify(t)).join(" | ")};
|
|
132
|
-
|
|
133
|
-
export type Id<TableName extends TableNames> = string & { __table: TableName };
|
|
134
|
-
|
|
135
|
-
${docInterfaces.join("\n\n")}
|
|
136
|
-
|
|
137
|
-
export interface TableDocMap {
|
|
138
|
-
${docMapEntries.join("\n")}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export type Doc<TableName extends TableNames> = TableDocMap[TableName];
|
|
142
|
-
|
|
143
|
-
${authTypeDefs}
|
|
144
|
-
|
|
145
|
-
${staticTypeDefinitions}
|
|
146
|
-
|
|
147
|
-
export const tableIndexes = {
|
|
148
|
-
${tableIndexEntries.join("\n")}
|
|
149
|
-
} as const;
|
|
150
|
-
`;
|
|
151
|
-
}
|
package/cli/utils/tsc.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { promises as fs } from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
export async function runTscEmit(tsconfigPathAbs: string): Promise<void> {
|
|
8
|
-
await new Promise<void>((resolve, reject) => {
|
|
9
|
-
const child = spawn("bunx", ["tsc", "-p", tsconfigPathAbs], {
|
|
10
|
-
stdio: "inherit",
|
|
11
|
-
});
|
|
12
|
-
child.on("error", reject);
|
|
13
|
-
child.on("exit", (code) => {
|
|
14
|
-
if (code === 0) resolve();
|
|
15
|
-
reject(new Error(`tsc exited with code ${code}`));
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function writeEmitTsconfig(params: {
|
|
21
|
-
configDirAbs: string;
|
|
22
|
-
outDirAbs: string;
|
|
23
|
-
}): Promise<string> {
|
|
24
|
-
const outDirRel =
|
|
25
|
-
path.relative(params.configDirAbs, params.outDirAbs).replace(/\\/g, "/") ||
|
|
26
|
-
"./_generated";
|
|
27
|
-
const tsconfigPathAbs = path.join(
|
|
28
|
-
params.configDirAbs,
|
|
29
|
-
".appflare.tsconfig.emit.json"
|
|
30
|
-
);
|
|
31
|
-
const content = {
|
|
32
|
-
compilerOptions: {
|
|
33
|
-
noEmit: false,
|
|
34
|
-
declaration: true,
|
|
35
|
-
emitDeclarationOnly: false,
|
|
36
|
-
outDir: `./${outDirRel}/dist`,
|
|
37
|
-
rootDir: `.`,
|
|
38
|
-
sourceMap: false,
|
|
39
|
-
declarationMap: false,
|
|
40
|
-
skipLibCheck: true,
|
|
41
|
-
target: "ES2022",
|
|
42
|
-
module: "ES2022",
|
|
43
|
-
moduleResolution: "Bundler",
|
|
44
|
-
types: ["node"],
|
|
45
|
-
},
|
|
46
|
-
include: [
|
|
47
|
-
`./${outDirRel}/src/schema-types.ts`,
|
|
48
|
-
`./${outDirRel}/src/schema.ts`,
|
|
49
|
-
`./${outDirRel}/src/handlers/**/*`,
|
|
50
|
-
],
|
|
51
|
-
};
|
|
52
|
-
await fs.writeFile(tsconfigPathAbs, JSON.stringify(content, null, 2));
|
|
53
|
-
return tsconfigPathAbs;
|
|
54
|
-
}
|
package/cli/utils/utils.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type { BetterAuthOptions } from "better-auth";
|
|
4
|
-
import type { BetterAuthClientOptions } from "better-auth/client";
|
|
5
|
-
import type { StorageManagerOptions } from "../../server/storage/types";
|
|
6
|
-
|
|
7
|
-
export type AppflareAuthConfig<
|
|
8
|
-
Options extends BetterAuthOptions,
|
|
9
|
-
ClientOptions extends BetterAuthClientOptions,
|
|
10
|
-
> = {
|
|
11
|
-
enabled?: boolean;
|
|
12
|
-
basePath?: string;
|
|
13
|
-
/** Server-side Better Auth options. */
|
|
14
|
-
options?: Options;
|
|
15
|
-
/** Client-side Better Auth options used in the generated API client. */
|
|
16
|
-
clientOptions?: ClientOptions;
|
|
17
|
-
};
|
|
18
|
-
export type AppflareStorageConfig<
|
|
19
|
-
Env = unknown,
|
|
20
|
-
Principal = unknown,
|
|
21
|
-
> = StorageManagerOptions<Env, Principal> & {
|
|
22
|
-
/** Optional KV binding name created in the generated worker. */
|
|
23
|
-
kvBinding?: string;
|
|
24
|
-
/** Optional KV namespace ID used in wrangler.json. */
|
|
25
|
-
kvId?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type AppflareSchedulerConfig = {
|
|
29
|
-
/** Set to false to disable scheduler generation. */
|
|
30
|
-
enabled?: boolean;
|
|
31
|
-
/** Optional Cloudflare Queue binding name. Defaults to APPFLARE_SCHEDULER_QUEUE. */
|
|
32
|
-
queueBinding?: string;
|
|
33
|
-
/** Optional Cloudflare Queue name. Defaults to a sanitized worker name with -scheduler suffix. */
|
|
34
|
-
queueName?: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type AppflareConfig<
|
|
38
|
-
Options extends BetterAuthOptions = BetterAuthOptions,
|
|
39
|
-
ClientOptions extends BetterAuthClientOptions = BetterAuthClientOptions,
|
|
40
|
-
> = {
|
|
41
|
-
dir: string;
|
|
42
|
-
schema: string;
|
|
43
|
-
outDir: string;
|
|
44
|
-
/** Optional override for where wrangler.json is written. Resolved relative to the config file. */
|
|
45
|
-
wranglerOutPath?: string;
|
|
46
|
-
/** Optional override for wrangler.json main entrypoint. Defaults to ./server/index.ts in the generated worker. */
|
|
47
|
-
wranglerMain?: string;
|
|
48
|
-
/** Optional override for wrangler.json compatibility_date. Defaults to current date. */
|
|
49
|
-
wranglerCompatibilityDate?: string;
|
|
50
|
-
/** Optional CORS whitelist for generated Worker entrypoint. */
|
|
51
|
-
corsOrigin?: string | string[];
|
|
52
|
-
/** Optional wrangler.json overrides. */
|
|
53
|
-
wrangler?: {
|
|
54
|
-
name?: string;
|
|
55
|
-
main?: string;
|
|
56
|
-
compatibilityDate?: string;
|
|
57
|
-
[key: string]: unknown;
|
|
58
|
-
};
|
|
59
|
-
auth?: AppflareAuthConfig<Options, ClientOptions>;
|
|
60
|
-
storage?: AppflareStorageConfig;
|
|
61
|
-
scheduler?: AppflareSchedulerConfig;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export type HandlerKind =
|
|
65
|
-
| "query"
|
|
66
|
-
| "mutation"
|
|
67
|
-
| "internalQuery"
|
|
68
|
-
| "internalMutation"
|
|
69
|
-
| "scheduler"
|
|
70
|
-
| "cron"
|
|
71
|
-
| "http";
|
|
72
|
-
|
|
73
|
-
export type DiscoveredHandler = {
|
|
74
|
-
fileName: string;
|
|
75
|
-
/** Path relative to project root without .ts extension, using forward slashes. */
|
|
76
|
-
routePath: string;
|
|
77
|
-
name: string;
|
|
78
|
-
kind: HandlerKind;
|
|
79
|
-
sourceFileAbs: string;
|
|
80
|
-
cronTriggers?: string[];
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export function pascalCase(value: string): string {
|
|
84
|
-
return value
|
|
85
|
-
.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, __, c: string) =>
|
|
86
|
-
c.toUpperCase(),
|
|
87
|
-
)
|
|
88
|
-
.replace(/[^A-Za-z0-9]/g, "");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function isValidIdentifier(value: string): boolean {
|
|
92
|
-
return /^[A-Za-z_$][\w$]*$/.test(value);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function groupBy<T, TKey>(
|
|
96
|
-
items: T[],
|
|
97
|
-
keyFn: (item: T) => TKey,
|
|
98
|
-
): Map<TKey, T[]> {
|
|
99
|
-
const map = new Map<TKey, T[]>();
|
|
100
|
-
for (const item of items) {
|
|
101
|
-
const key = keyFn(item);
|
|
102
|
-
if (map.has(key)) {
|
|
103
|
-
map.get(key)!.push(item);
|
|
104
|
-
} else {
|
|
105
|
-
map.set(key, [item]);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return map;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function toImportPathFromGeneratedSrc(
|
|
112
|
-
outDirAbs: string,
|
|
113
|
-
fileAbs: string,
|
|
114
|
-
): string {
|
|
115
|
-
const fromDir = path.join(outDirAbs, "src");
|
|
116
|
-
let rel = path.relative(fromDir, fileAbs);
|
|
117
|
-
rel = rel.replace(/\\/g, "/");
|
|
118
|
-
rel = rel.replace(/\.ts$/, "");
|
|
119
|
-
if (!rel.startsWith(".")) {
|
|
120
|
-
rel = `./${rel}`;
|
|
121
|
-
}
|
|
122
|
-
return rel;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function toImportPathFromGeneratedServer(
|
|
126
|
-
outDirAbs: string,
|
|
127
|
-
fileAbs: string,
|
|
128
|
-
): string {
|
|
129
|
-
const fromDir = path.join(outDirAbs, "server");
|
|
130
|
-
let rel = path.relative(fromDir, fileAbs);
|
|
131
|
-
rel = rel.replace(/\\/g, "/");
|
|
132
|
-
rel = rel.replace(/\.ts$/, "");
|
|
133
|
-
if (!rel.startsWith(".")) {
|
|
134
|
-
rel = `./${rel}`;
|
|
135
|
-
}
|
|
136
|
-
return rel;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export async function assertFileExists(
|
|
140
|
-
fileAbs: string,
|
|
141
|
-
message: string,
|
|
142
|
-
): Promise<void> {
|
|
143
|
-
const ok = await fileExists(fileAbs);
|
|
144
|
-
if (!ok) throw new Error(message);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export async function assertDirExists(
|
|
148
|
-
dirAbs: string,
|
|
149
|
-
message: string,
|
|
150
|
-
): Promise<void> {
|
|
151
|
-
try {
|
|
152
|
-
const stat = await fs.stat(dirAbs);
|
|
153
|
-
if (!stat.isDirectory()) throw new Error(message);
|
|
154
|
-
} catch {
|
|
155
|
-
throw new Error(message);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function fileExists(fileAbs: string): Promise<boolean> {
|
|
160
|
-
try {
|
|
161
|
-
const stat = await fs.stat(fileAbs);
|
|
162
|
-
return stat.isFile();
|
|
163
|
-
} catch {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export async function walkTsFiles(
|
|
169
|
-
rootAbs: string,
|
|
170
|
-
ignoreDirs: Set<string>,
|
|
171
|
-
): Promise<string[]> {
|
|
172
|
-
const out: string[] = [];
|
|
173
|
-
const entries = await fs.readdir(rootAbs, { withFileTypes: true });
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
const abs = path.join(rootAbs, entry.name);
|
|
176
|
-
if (entry.isDirectory()) {
|
|
177
|
-
if (ignoreDirs.has(entry.name)) continue;
|
|
178
|
-
out.push(...(await walkTsFiles(abs, ignoreDirs)));
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
if (
|
|
182
|
-
entry.isFile() &&
|
|
183
|
-
entry.name.endsWith(".ts") &&
|
|
184
|
-
!entry.name.endsWith(".d.ts")
|
|
185
|
-
) {
|
|
186
|
-
out.push(abs);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return out;
|
|
190
|
-
}
|
package/cli/utils/zod-utils.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { isValidIdentifier } from "./utils";
|
|
2
|
-
|
|
3
|
-
function getZodObjectShape(schema: any): Record<string, any> {
|
|
4
|
-
if (!schema || typeof schema !== "object") {
|
|
5
|
-
throw new Error(`Schema table is not an object`);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const def = (schema as any)._def;
|
|
9
|
-
if (def?.typeName === "ZodObject" || def?.type === "object") {
|
|
10
|
-
const shape = def.shape;
|
|
11
|
-
if (typeof shape === "function") {
|
|
12
|
-
return shape();
|
|
13
|
-
}
|
|
14
|
-
if (shape && typeof shape === "object") {
|
|
15
|
-
return shape;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (typeof (schema as any).shape === "function") {
|
|
20
|
-
return (schema as any).shape;
|
|
21
|
-
}
|
|
22
|
-
if ((schema as any).shape && typeof (schema as any).shape === "object") {
|
|
23
|
-
return (schema as any).shape;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
throw new Error(`Table schema is not a Zod object`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function renderField(fieldName: string, schema: any): string {
|
|
30
|
-
const { tsType, optional } = renderType(schema);
|
|
31
|
-
const safeKey = isValidIdentifier(fieldName)
|
|
32
|
-
? fieldName
|
|
33
|
-
: JSON.stringify(fieldName);
|
|
34
|
-
return `${safeKey}${optional ? "?" : ""}: ${tsType};`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function renderType(schema: any): { tsType: string; optional: boolean } {
|
|
38
|
-
const def = schema?._def;
|
|
39
|
-
const typeName: string | undefined = def?.typeName ?? def?.type;
|
|
40
|
-
const description: string | undefined =
|
|
41
|
-
schema?.description ?? def?.description;
|
|
42
|
-
|
|
43
|
-
if (description === "geo:point") {
|
|
44
|
-
return { tsType: "GeoPoint", optional: false };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (typeName === "ZodOptional" || typeName === "optional") {
|
|
48
|
-
const inner = def?.innerType ?? def?.schema ?? schema?._def?.innerType;
|
|
49
|
-
const rendered = renderType(inner);
|
|
50
|
-
return { tsType: rendered.tsType, optional: true };
|
|
51
|
-
}
|
|
52
|
-
if (typeName === "ZodNullable" || typeName === "nullable") {
|
|
53
|
-
const inner = def?.innerType ?? def?.schema;
|
|
54
|
-
const rendered = renderType(inner);
|
|
55
|
-
return { tsType: `${rendered.tsType} | null`, optional: false };
|
|
56
|
-
}
|
|
57
|
-
if (typeName === "ZodDefault" || typeName === "default") {
|
|
58
|
-
const inner = def?.innerType ?? def?.schema;
|
|
59
|
-
return renderType(inner);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (typeName === "ZodString" || typeName === "string") {
|
|
63
|
-
const description: string | undefined =
|
|
64
|
-
schema?.description ?? def?.description;
|
|
65
|
-
if (typeof description === "string" && description.startsWith("ref:")) {
|
|
66
|
-
const refTableName = description.slice(4);
|
|
67
|
-
return { tsType: `Id<${JSON.stringify(refTableName)}>`, optional: false };
|
|
68
|
-
}
|
|
69
|
-
return { tsType: "string", optional: false };
|
|
70
|
-
}
|
|
71
|
-
if (typeName === "ZodNumber" || typeName === "number") {
|
|
72
|
-
return { tsType: "number", optional: false };
|
|
73
|
-
}
|
|
74
|
-
if (typeName === "ZodBoolean" || typeName === "boolean") {
|
|
75
|
-
return { tsType: "boolean", optional: false };
|
|
76
|
-
}
|
|
77
|
-
if (typeName === "ZodDate" || typeName === "date") {
|
|
78
|
-
return { tsType: "Date", optional: false };
|
|
79
|
-
}
|
|
80
|
-
if (typeName === "ZodArray" || typeName === "array") {
|
|
81
|
-
const inner = def?.element ?? def?.innerType ?? def?.type;
|
|
82
|
-
const rendered = renderType(inner);
|
|
83
|
-
return { tsType: `Array<${rendered.tsType}>`, optional: false };
|
|
84
|
-
}
|
|
85
|
-
if (typeName === "ZodObject" || typeName === "object") {
|
|
86
|
-
const shape = getZodObjectShape(schema);
|
|
87
|
-
const entries = Object.entries(shape);
|
|
88
|
-
if (entries.length === 0) {
|
|
89
|
-
return { tsType: "Record<string, unknown>", optional: false };
|
|
90
|
-
}
|
|
91
|
-
const props = entries
|
|
92
|
-
.map(([key, value]) => {
|
|
93
|
-
const { tsType, optional } = renderType(value);
|
|
94
|
-
const safeKey = isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
95
|
-
return `\t${safeKey}${optional ? "?" : ""}: ${tsType};`;
|
|
96
|
-
})
|
|
97
|
-
.join("\n");
|
|
98
|
-
return { tsType: `{\n${props}\n}`, optional: false };
|
|
99
|
-
}
|
|
100
|
-
if (typeName === "ZodUnion" || typeName === "union") {
|
|
101
|
-
const options: any[] = def?.options ?? def?.optionsMap ?? [];
|
|
102
|
-
const parts = Array.isArray(options)
|
|
103
|
-
? options.map((o) => renderType(o).tsType)
|
|
104
|
-
: ["unknown"];
|
|
105
|
-
return { tsType: parts.join(" | ") || "unknown", optional: false };
|
|
106
|
-
}
|
|
107
|
-
if (typeName === "ZodLiteral" || typeName === "literal") {
|
|
108
|
-
const value = def?.value;
|
|
109
|
-
return { tsType: JSON.stringify(value), optional: false };
|
|
110
|
-
}
|
|
111
|
-
if (typeName === "ZodAny" || typeName === "any") {
|
|
112
|
-
return { tsType: "any", optional: false };
|
|
113
|
-
}
|
|
114
|
-
if (typeName === "ZodUnknown" || typeName === "unknown") {
|
|
115
|
-
return { tsType: "unknown", optional: false };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return { tsType: "unknown", optional: false };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export { getZodObjectShape, renderField, renderType };
|
package/lib/README.md
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Appflare Library
|
|
2
|
-
|
|
3
|
-
Docs for the shared library helpers in `lib/`
|
|
4
|
-
|
|
5
|
-
## What’s here
|
|
6
|
-
|
|
7
|
-
- Schema helpers for defining tables and schemas with Zod
|
|
8
|
-
- Value builders (`v.*`) for common field types and relations
|
|
9
|
-
- React Query hooks with realtime-friendly subscriptions
|
|
10
|
-
|
|
11
|
-
## Library (`lib/`)
|
|
12
|
-
|
|
13
|
-
**Schema builders** ([packages/appflare/lib/db.ts](packages/appflare/lib/db.ts))
|
|
14
|
-
|
|
15
|
-
- `defineTable(shape)`: wrap a Zod shape in `z.object` to define a table.
|
|
16
|
-
- `defineSchema(tables)`: collect a map of tables into a schema object.
|
|
17
|
-
|
|
18
|
-
**Value helpers** ([packages/appflare/lib/values.ts](packages/appflare/lib/values.ts))
|
|
19
|
-
|
|
20
|
-
- Primitives: `v.string()`, `v.number()`, `v.boolean()`, `v.date()`.
|
|
21
|
-
- Relations/ids: `v.id(table)` creates a string with an ObjectId regex and a `ref:<table>` description for downstream typing.
|
|
22
|
-
- Location: `v.point()`/`v.location()` yields a GeoJSON Point schema for geospatial queries.
|
|
23
|
-
- Collections/objects: `v.array(item)`, `v.object(shape)`.
|
|
24
|
-
- Modifiers: `v.optional(schema)`, `v.nullable(schema)`, `v.union(...schemas)`, `v.literal(value)`.
|
|
25
|
-
- Misc: `v.buffer()` (string placeholder), `v.any()`, `v.unknown()`.
|
|
26
|
-
|
|
27
|
-
**Location helpers** ([packages/appflare/lib/location.ts](packages/appflare/lib/location.ts))
|
|
28
|
-
|
|
29
|
-
- Builders: `point(lng, lat)` returns a GeoJSON Point; `geo.point` is the same alias.
|
|
30
|
-
- Queries: `geo.near("location", pointLngLat, { maxDistanceMeters })`, `geo.withinRadius("location", center, radiusMeters)`, `geo.withinBox(...)`, `geo.withinPolygon(...)`, `geo.intersects(...)` produce Mongo-ready filters you can pass to `where`.
|
|
31
|
-
|
|
32
|
-
**Example: define a schema**
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import { defineSchema, defineTable } from "appflare/lib/db";
|
|
36
|
-
import { v } from "appflare/lib/values";
|
|
37
|
-
|
|
38
|
-
export default defineSchema({
|
|
39
|
-
users: defineTable({
|
|
40
|
-
name: v.string(),
|
|
41
|
-
email: v.string(),
|
|
42
|
-
age: v.number().optional(),
|
|
43
|
-
orgId: v.id("orgs"),
|
|
44
|
-
location: v.point(),
|
|
45
|
-
}),
|
|
46
|
-
orgs: defineTable({
|
|
47
|
-
name: v.string(),
|
|
48
|
-
}),
|
|
49
|
-
});
|
|
50
|
-
```
|
package/lib/db.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export type AppflareTable<TShape extends Record<string, z.ZodTypeAny>> =
|
|
4
|
-
z.ZodObject<TShape>;
|
|
5
|
-
|
|
6
|
-
export type AppflareSchema<TTables extends Record<string, AppflareTable<any>>> =
|
|
7
|
-
TTables;
|
|
8
|
-
|
|
9
|
-
export function defineTable<TShape extends Record<string, z.ZodTypeAny>>(
|
|
10
|
-
shape: TShape
|
|
11
|
-
): AppflareTable<TShape> {
|
|
12
|
-
return z.object(shape);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function defineSchema<
|
|
16
|
-
TTables extends Record<string, AppflareTable<any>>,
|
|
17
|
-
>(tables: TTables): AppflareSchema<TTables> {
|
|
18
|
-
return tables;
|
|
19
|
-
}
|
package/lib/location.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export type GeoCoordinates = readonly [number, number];
|
|
4
|
-
|
|
5
|
-
export type GeoPoint = {
|
|
6
|
-
type: "Point";
|
|
7
|
-
coordinates: GeoCoordinates;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const geoPointSchema = z
|
|
11
|
-
.object({
|
|
12
|
-
type: z.literal("Point"),
|
|
13
|
-
coordinates: z.tuple([z.number(), z.number()]),
|
|
14
|
-
})
|
|
15
|
-
.describe("geo:point");
|
|
16
|
-
|
|
17
|
-
export const EARTH_RADIUS_METERS = 6_378_100;
|
|
18
|
-
|
|
19
|
-
const normalizePoint = (point: GeoPoint | GeoCoordinates): GeoPoint =>
|
|
20
|
-
Array.isArray(point)
|
|
21
|
-
? { type: "Point", coordinates: [point[0], point[1]] }
|
|
22
|
-
: (point as GeoPoint);
|
|
23
|
-
|
|
24
|
-
export const point = (lng: number, lat: number): GeoPoint => ({
|
|
25
|
-
type: "Point",
|
|
26
|
-
coordinates: [lng, lat],
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export type NearQueryOptions = {
|
|
30
|
-
maxDistanceMeters?: number;
|
|
31
|
-
minDistanceMeters?: number;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const near = (
|
|
35
|
-
field: string,
|
|
36
|
-
geometry: GeoPoint | GeoCoordinates,
|
|
37
|
-
options: NearQueryOptions = {}
|
|
38
|
-
): Record<string, unknown> => {
|
|
39
|
-
const $near: Record<string, unknown> = {
|
|
40
|
-
$geometry: normalizePoint(geometry),
|
|
41
|
-
};
|
|
42
|
-
if (options.maxDistanceMeters !== undefined) {
|
|
43
|
-
$near.$maxDistance = options.maxDistanceMeters;
|
|
44
|
-
}
|
|
45
|
-
if (options.minDistanceMeters !== undefined) {
|
|
46
|
-
$near.$minDistance = options.minDistanceMeters;
|
|
47
|
-
}
|
|
48
|
-
return { [field]: { $near } };
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export const withinRadius = (
|
|
52
|
-
field: string,
|
|
53
|
-
center: GeoPoint | GeoCoordinates,
|
|
54
|
-
radiusMeters: number
|
|
55
|
-
): Record<string, unknown> => ({
|
|
56
|
-
[field]: {
|
|
57
|
-
$geoWithin: {
|
|
58
|
-
$centerSphere: [
|
|
59
|
-
normalizePoint(center).coordinates,
|
|
60
|
-
radiusMeters / EARTH_RADIUS_METERS,
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export const withinBox = (
|
|
67
|
-
field: string,
|
|
68
|
-
southwest: GeoPoint | GeoCoordinates,
|
|
69
|
-
northeast: GeoPoint | GeoCoordinates
|
|
70
|
-
): Record<string, unknown> => ({
|
|
71
|
-
[field]: {
|
|
72
|
-
$geoWithin: {
|
|
73
|
-
$box: [
|
|
74
|
-
normalizePoint(southwest).coordinates,
|
|
75
|
-
normalizePoint(northeast).coordinates,
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export const withinPolygon = (
|
|
82
|
-
field: string,
|
|
83
|
-
polygon: ReadonlyArray<GeoPoint | GeoCoordinates>
|
|
84
|
-
): Record<string, unknown> => ({
|
|
85
|
-
[field]: {
|
|
86
|
-
$geoWithin: {
|
|
87
|
-
$polygon: polygon.map((p) => normalizePoint(p).coordinates),
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
export const intersects = (
|
|
93
|
-
field: string,
|
|
94
|
-
geometry: { type: string; coordinates: unknown }
|
|
95
|
-
): Record<string, unknown> => ({
|
|
96
|
-
[field]: {
|
|
97
|
-
$geoIntersects: {
|
|
98
|
-
$geometry: geometry,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
export const geo = {
|
|
104
|
-
point,
|
|
105
|
-
near,
|
|
106
|
-
withinRadius,
|
|
107
|
-
withinBox,
|
|
108
|
-
withinPolygon,
|
|
109
|
-
intersects,
|
|
110
|
-
};
|