duto 0.0.0 → 0.260906.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -12
  3. package/package.json +91 -2
  4. package/src/cli.ts +42 -0
  5. package/src/enum.ts +2 -0
  6. package/src/index.d.ts +61 -0
  7. package/src/index.ts +19 -0
  8. package/src/middleware.ts +2 -0
  9. package/src/pages/client/app.tsx +123 -0
  10. package/src/pages/client/hmr.ts +13 -0
  11. package/src/pages/client/island.ts +136 -0
  12. package/src/pages/client/link.ts +70 -0
  13. package/src/pages/client/router.ts +329 -0
  14. package/src/pages/client/storage.ts +95 -0
  15. package/src/pages/commands/build.ts +74 -0
  16. package/src/pages/commands/dev-old.ts +213 -0
  17. package/src/pages/commands/dev.ts +87 -0
  18. package/src/pages/commands/preview.ts +33 -0
  19. package/src/pages/compiler/aliases.ts +36 -0
  20. package/src/pages/compiler/assets.ts +43 -0
  21. package/src/pages/compiler/ast.ts +764 -0
  22. package/src/pages/compiler/constants.ts +24 -0
  23. package/src/pages/compiler/file.ts +60 -0
  24. package/src/pages/compiler/index.ts +307 -0
  25. package/src/pages/compiler/island.ts +108 -0
  26. package/src/pages/compiler/meta.ts +601 -0
  27. package/src/pages/compiler/routes.ts +29 -0
  28. package/src/pages/compiler/types.ts +209 -0
  29. package/src/pages/compiler/utils.ts +5 -0
  30. package/src/pages/compiler/vite-plugin.ts +324 -0
  31. package/src/pages/components/link.tsx +38 -0
  32. package/src/pages/i18n/client.ts +403 -0
  33. package/src/pages/i18n/element.ts +44 -0
  34. package/src/pages/i18n/server.ts +56 -0
  35. package/src/pages/index.ts +28 -0
  36. package/src/pages/preact/client-dev.ts +5 -0
  37. package/src/pages/preact/client.ts +87 -0
  38. package/src/pages/preact/context.ts +41 -0
  39. package/src/pages/preact/server.ts +186 -0
  40. package/src/pages/preact/signals.ts +89 -0
  41. package/src/pages/preact/static-html.ts +45 -0
  42. package/src/pages/preact/types.ts +26 -0
  43. package/src/server/adapter/aws.ts +4 -0
  44. package/src/server/adapter/workerd.ts +4 -0
  45. package/src/server/app.ts +96 -0
  46. package/src/server/auth/ability.ts +72 -0
  47. package/src/server/auth/authnz.ts +86 -0
  48. package/src/server/auth/index.ts +5 -0
  49. package/src/server/auth/token.ts +147 -0
  50. package/src/server/auth/types.ts +2 -0
  51. package/src/server/cli/commands/build.ts +41 -0
  52. package/src/server/cli/commands/deploy.ts +82 -0
  53. package/src/server/cli/commands/dev.ts +277 -0
  54. package/src/server/cli/commands/make.ts +98 -0
  55. package/src/server/cli/commands/migrate.ts +93 -0
  56. package/src/server/cli/commands/routes.ts +79 -0
  57. package/src/server/cli/index.ts +27 -0
  58. package/src/server/cli/stubs.ts +39 -0
  59. package/src/server/cli/types.ts +3 -0
  60. package/src/server/cli/utils.ts +443 -0
  61. package/src/server/config.ts +4 -0
  62. package/src/server/db/d1.d.ts +1 -0
  63. package/src/server/db/d1.ts +1 -0
  64. package/src/server/db/dynamodb.d.ts +1 -0
  65. package/src/server/db/dynamodb.ts +1 -0
  66. package/src/server/db/index.ts +1 -0
  67. package/src/server/entry.ts +19 -0
  68. package/src/server/handle.ts +30 -0
  69. package/src/server/http.ts +23 -0
  70. package/src/server/middlewares/auth.ts +40 -0
  71. package/src/server/middlewares/logger.ts +30 -0
  72. package/src/server/open-api/register.ts +29 -0
  73. package/src/server/open-api/spec.ts +107 -0
  74. package/src/server/request.ts +233 -0
  75. package/src/server/response.ts +142 -0
  76. package/src/server/routes.ts +474 -0
  77. package/src/server/types.ts +142 -0
  78. package/src/server/utils/environment.ts +17 -0
  79. package/src/server/utils.ts +46 -0
  80. package/src/server/validator.ts +70 -0
  81. package/src/utils.ts +5 -0
@@ -0,0 +1,93 @@
1
+ import { command } from 't0n/cli'
2
+ import { spawn } from 'node:child_process'
3
+ import { join } from 'pathe'
4
+ import { Migrator } from 'forj'
5
+ import { gray } from 't0n/color'
6
+ import { getRuntime } from 't0n/cli'
7
+ import { wait, info, event, rn, error, log } from 't0n/log'
8
+
9
+ import { cleanDB, cleanDir } from '../utils'
10
+ import { _root } from '@/utils'
11
+
12
+ export default command({
13
+ meta: {
14
+ name: 'migrate',
15
+ description: '🗃️ Migration performs\n',
16
+ },
17
+ args: {
18
+ remote: {
19
+ description: 'Execute against remote environment',
20
+ type: 'boolean',
21
+ },
22
+ },
23
+ async run({ args }) {
24
+ if (args._.length !== 2) {
25
+ error('Invalid args: '+ gray('[ACTION] [DATABASE]'))
26
+ return rn()
27
+ }
28
+
29
+ const [action, database] = args._
30
+ const isBun = getRuntime() === 'bun'
31
+ const isRemote = !!args?.remote
32
+
33
+ try {
34
+ const migrations = await Migrator.dir(database).queue()
35
+ const pending = migrations.pending
36
+ const migrated = migrations.migrated
37
+
38
+ switch (action) {
39
+ case 'migrate':
40
+ case 'apply':
41
+ case 'fresh':
42
+ case 'refresh':
43
+ if (!pending?.length)
44
+ log('Nothing to compile')
45
+ wait('Running migrations')
46
+
47
+ if (action.includes('fresh')) {
48
+ cleanDir(join(_root, 'migrations', database, 'sql'))
49
+ cleanDB(database)
50
+ }
51
+
52
+ await Migrator.compile([...migrated, ...pending])
53
+
54
+ const child = spawn(
55
+ isBun ? 'bunx' : 'npx',
56
+ ['wrangler', 'd1', 'migrations', 'apply', database, isRemote ? '--remove' : '--local'],
57
+ {
58
+ stdio: 'inherit',
59
+ cwd: _root,
60
+ env: {
61
+ ...process.env,
62
+ CI: 'true', // bypass wrangler confirmation msg
63
+ },
64
+ }
65
+ )
66
+
67
+ child.on('exit', code => process.exit(code ?? 0))
68
+ .on('message', msg => process.send && process.send(msg))
69
+ .on('disconnect', () => process.disconnect && process.disconnect())
70
+
71
+ pending.forEach(item => event(item.name))
72
+ break
73
+ case 'status':
74
+ if (migrated?.length) {
75
+ event('Migrated')
76
+ migrated.forEach(item => info(item.name))
77
+ }
78
+
79
+ if (pending?.length) {
80
+ wait('Pending')
81
+ pending.forEach(item => info(item.name))
82
+ }
83
+
84
+ break
85
+ default:
86
+ return error('Invalid action')
87
+ }
88
+
89
+ } finally {
90
+ rn()
91
+ }
92
+ },
93
+ })
@@ -0,0 +1,79 @@
1
+ import { command } from 't0n/cli'
2
+ import { rn } from 't0n/log'
3
+ import { _duto } from '@/utils'
4
+ import { highlightedURI, highlightedMethod } from '../utils'
5
+ import { getRoutes } from '$/routes'
6
+
7
+ export default command({
8
+ meta: {
9
+ name: 'routes',
10
+ description: '📒 Displays all registered routes\n',
11
+ },
12
+ args: {
13
+ path: {
14
+ description: 'Filter the routes by path',
15
+ type: 'string',
16
+ },
17
+ method: {
18
+ description: 'Filter the routes by method',
19
+ type: 'string',
20
+ },
21
+ reverse: {
22
+ description: 'Reverse the ordering of the routes',
23
+ type: 'boolean',
24
+ },
25
+ },
26
+ async run({ args }) {
27
+ const opts = {
28
+ path: args?.path || '',
29
+ method: args?.method?.toUpperCase() || '',
30
+ reverse: !!args?.reverse,
31
+ }
32
+
33
+ const keys: Set<string> = new Set()
34
+ let maxMethodLength = 0
35
+ let maxPathLength = 0
36
+
37
+ let routes = (await getRoutes())
38
+ .filter(({ method, path }) => {
39
+ method = method.toUpperCase()
40
+ const key = method + '-' + path
41
+ if (keys.has(key)) return false
42
+ keys.add(key)
43
+
44
+ let mLength = method.length
45
+ if (method === 'GET') mLength += 5
46
+
47
+ maxMethodLength = Math.max(maxMethodLength, mLength)
48
+ maxPathLength = Math.max(maxPathLength, path.length)
49
+
50
+ return [
51
+ opts.path ? path.startsWith(opts.path) : true,
52
+ opts.method ? method === opts.method : true,
53
+ ].every(Boolean)
54
+ })
55
+
56
+ if (!routes.length) {
57
+ console.log(' No route found.')
58
+ return rn()
59
+ }
60
+
61
+ if (opts.reverse)
62
+ routes = routes.reverse()
63
+
64
+ routes.forEach(route => {
65
+ if (!route) return
66
+ const method = route.method.toUpperCase()
67
+
68
+ let mLength = method.length
69
+ let str = highlightedMethod(method, null, true)
70
+
71
+ if (method === 'GET')
72
+ mLength += 5
73
+
74
+ console.log(str + ' '.repeat(maxMethodLength - mLength) +' '+ highlightedURI(route.path, method))
75
+ })
76
+
77
+ rn()
78
+ },
79
+ })
@@ -0,0 +1,27 @@
1
+ import { join } from 'pathe'
2
+
3
+ const cmd = (c: string) => join(import.meta.dirname, 'commands', c)
4
+ const make = cmd('make')
5
+ const routes = cmd('routes')
6
+
7
+ export default {
8
+ dev: cmd('dev'),
9
+ build: cmd('build'),
10
+ deploy: cmd('deploy'),
11
+ routes,
12
+ endpoints: routes,
13
+ migrate: cmd('migrate'),
14
+ make,
15
+ 'make:config': make,
16
+ 'make:enum': make,
17
+ 'make:route': make,
18
+ 'make:action': make,
19
+ 'make:endpoint': make,
20
+ 'make:migrate': make,
21
+ 'make:migration': make,
22
+ 'make:model': make,
23
+ 'make:job': make,
24
+ 'make:seed': make,
25
+ 'make:seeder': make,
26
+ 'make:test': make,
27
+ }
@@ -0,0 +1,39 @@
1
+ export const replace = (str: string, map: Record<string, string>) => {
2
+ if (!str || typeof str !== 'string' || !map || typeof map !== 'object') return str
3
+ const entries = Object.entries(map)
4
+ const length = entries?.length || 0
5
+ if (!length) return str
6
+
7
+ str = str.trimStart()
8
+ if (length === 1)
9
+ return str.replaceAll(entries[0][0], entries[0][1])
10
+
11
+ return entries.reduce(
12
+ (acc, entry) => acc.replace(new RegExp(entry[0].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), entry[1]),
13
+ str
14
+ )
15
+ }
16
+
17
+ export const route = `
18
+ import type { Req, Res } from 'duto'
19
+
20
+ export async function handle(req: Req, res: Res) {
21
+ return res.ok('R_NAME')
22
+ }
23
+ `
24
+
25
+ export const migration = `
26
+ import { Migration, Schema, Blueprint } from 'duto/db'
27
+
28
+ export default class M_NAME extends Migration {
29
+ static async run() {
30
+ Schema.S_NAME('T_NAME', (table: Blueprint) => {
31
+ M_CONTENT
32
+ })
33
+ }
34
+ }
35
+ `
36
+ export const migrationCreate = `
37
+ table.id()
38
+ table.timestamps()
39
+ `.trim()
@@ -0,0 +1,3 @@
1
+ import { platforms } from './utils'
2
+
3
+ export type Platform = typeof platforms[number]
@@ -0,0 +1,443 @@
1
+ import ts from 'typescript'
2
+ import { Miniflare } from 'miniflare'
3
+ import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync } from 'node:fs'
4
+ import { stat } from 'node:fs/promises'
5
+ import { basename, dirname, join } from 'pathe'
6
+ import { createHash, createHmac } from 'node:crypto'
7
+ import { filesize } from 'filesize'
8
+
9
+ import { findWranglerConfig, parseWranglerConfig, WRANGLER_CONFIG_FILES } from 'localflare-core'
10
+ import type { WranglerConfig, LocalflareManifest } from 'localflare-core'
11
+
12
+ import { gray, bold, italic, purple, yellow, red } from 't0n/color'
13
+ import { substep, event, error, warn } from 't0n/log'
14
+
15
+ import { cacheRoutes } from '$/routes'
16
+ import { _duto, _root } from '@/utils'
17
+
18
+ import type { Platform } from './types'
19
+
20
+ export const platforms = [
21
+ 'aws', 'lambda',
22
+ 'cf', 'cloudflare', 'worker', 'workers', 'workerd',
23
+ 'node', 'bun',
24
+ 'vercel',
25
+ ] as const
26
+
27
+ export function normalizePlatform(platform: Platform) {
28
+ platform = platform?.toLowerCase() as Platform
29
+ if (!platforms?.includes(platform)) return null
30
+
31
+ switch (platform) {
32
+ case 'lambda':
33
+ return 'aws'
34
+
35
+ case 'cf':
36
+ case 'cloudflare':
37
+ case 'worker':
38
+ case 'workers':
39
+ return 'workerd'
40
+
41
+ case 'bun':
42
+ return 'node'
43
+
44
+ default:
45
+ return platform
46
+ }
47
+ }
48
+
49
+ const formatArgs = (...args: any[]) => args.flat().map(a => gray(italic(bold(a)))).join(', ')
50
+ export const platformError = () => error(`Provide a valid platform: ${formatArgs(platforms)}.\n`)
51
+
52
+ const nodeModules = [
53
+ 'crypto', 'buffer', 'http', 'fs', 'path', 'events', 'stream', 'util',
54
+ 'url', 'querystring', 'os', 'child_process', 'cluster', 'dns', 'net',
55
+ 'tls', 'https', 'zlib', 'readline', 'repl', 'vm', 'module', 'assert',
56
+ 'timers', 'string_decoder', 'punycode', 'perf_hooks', 'dgram', 'tty',
57
+ 'worker_threads', 'wasi', 'process', 'diagnostics_channel', 'sqlite',
58
+ 'async_hooks', 'console', 'fsevents',
59
+ ].flatMap(lib => ['node:'+ lib, lib])
60
+
61
+ const printer = ts.createPrinter()
62
+ function stripDecorators(source: string) {
63
+ const sourceFile = ts.createSourceFile(
64
+ 'tmp.ts', source, ts.ScriptTarget.ESNext, false
65
+ )
66
+
67
+ const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
68
+ const visit: ts.Visitor = (node) => {
69
+ if (ts.isClassDeclaration(node) && node.modifiers?.length) {
70
+ let hasDecorator = false
71
+ const modifiers = []
72
+
73
+ for (const m of node.modifiers) {
74
+ if (m.kind === ts.SyntaxKind.Decorator) {
75
+ hasDecorator = true
76
+ continue
77
+ }
78
+ modifiers.push(m)
79
+ }
80
+
81
+ if (hasDecorator) {
82
+ return ts.factory.updateClassDeclaration(
83
+ node,
84
+ modifiers.length ? modifiers : undefined,
85
+ node.name,
86
+ node.typeParameters,
87
+ node.heritageClauses,
88
+ node.members
89
+ )
90
+ }
91
+ }
92
+
93
+ return ts.visitEachChild(node, visit, context)
94
+ }
95
+
96
+ return (node) => ts.visitNode(node, visit) as ts.SourceFile
97
+ }
98
+
99
+ const result = ts.transform(sourceFile, [transformer])
100
+ const code = printer.printFile(result.transformed[0])
101
+
102
+ result.dispose()
103
+ return code
104
+ }
105
+
106
+ const dist = '.duto'
107
+ export const build = async ({
108
+ env = 'prd',
109
+ platform,
110
+ silent = false,
111
+ minify = true,
112
+ }: {
113
+ env: string,
114
+ platform: Platform,
115
+ silent: boolean,
116
+ minify: boolean,
117
+ }) => {
118
+ const startTime = performance.now()
119
+ const isWorkerd = platform === 'workerd'
120
+ cleanDir(join(_root, dist))
121
+
122
+ if (isWorkerd) {
123
+ for (let file of WRANGLER_CONFIG_FILES) {
124
+ file = join(_root, file)
125
+ if (existsSync(file))
126
+ copyFileSync(file, join(_root, dist, basename(file)))
127
+ }
128
+ }
129
+
130
+ if (['bun', 'vercel'].includes(platform)) platform = 'workerd'
131
+
132
+ const outdir = join(_root, dist)
133
+
134
+ const USE_STRICT_RE = /(["'`])\s*use strict\s*\1;?/g
135
+
136
+ await cacheRoutes()
137
+ if (!silent) event('Routes cached')
138
+
139
+ const result = await Bun.build({
140
+ entrypoints: [join(_duto, 'server', 'adapter', `${platform}.ts`)],
141
+ outdir,
142
+ format: 'esm',
143
+ target: 'bun',
144
+ conditions: isWorkerd ? ['worker', 'browser'] : [],
145
+ minify: !!minify,
146
+ // minify: {
147
+ // whitespace: true,
148
+ // syntax: true,
149
+ // identifiers: true,
150
+ // },
151
+
152
+ treeShaking: true,
153
+ legalComments: 'none',
154
+ metafile: false,
155
+ external: [
156
+ '@aws-sdk', '@smithy',
157
+ ...(isWorkerd ? [
158
+ 'cloudflare:workers',
159
+ ...nodeModules,
160
+ ] : []),
161
+ ],
162
+
163
+ define: {
164
+ 'process.env.DUTO_ENV': JSON.stringify(env),
165
+ },
166
+
167
+ plugins: [
168
+ {
169
+ name: 'duto',
170
+ setup(build) {
171
+ build.onResolve({ filter: /\.duto[\/\\]/ }, args => ({ path: join(_root, args.path) }))
172
+ build.onLoad({ filter: /\.ts$/ }, async (args) => {
173
+ return { contents: stripDecorators(await Bun.file(args.path).text()), loader: 'ts' }
174
+ })
175
+ },
176
+ },
177
+ ],
178
+ })
179
+
180
+ if (!result.success || !result?.outputs?.length)
181
+ throw new Error('build fail')
182
+
183
+ const output = result.outputs.find(file => file.kind === 'entry-point' && file.path.endsWith('.js'))
184
+ if (!output)
185
+ throw new Error('Build output not found')
186
+
187
+ let code = (await output.text()).replace(/\/\/\s*@bun\s*\n/g, '')
188
+
189
+ if (USE_STRICT_RE.test(code)) {
190
+ code = code.replace(USE_STRICT_RE, '')
191
+ await Bun.write(output.path, code)
192
+ }
193
+
194
+ const outfile = join(outdir, 'index.js')
195
+ if (output.path !== outfile) {
196
+ await Bun.write(outfile, code)
197
+
198
+ if (existsSync(output.path))
199
+ await Bun.file(output.path).delete()
200
+ }
201
+
202
+ if (!silent) {
203
+ event(`Build done in ${Math.ceil(performance.now() - startTime)}ms`)
204
+ substep(
205
+ // `${relative(join(_root, 'node_modules/duto/src'), opts.entryPoints[0])} → ${relative(_root, opts.outfile)}`,
206
+ `Size: ${filesize((await stat(outfile)).size)}`,
207
+ `Files: ${Object.keys(result.outputs).length}`
208
+ )
209
+ }
210
+ }
211
+
212
+ export function cleanDir(path: string) {
213
+ try {
214
+ if (!existsSync(path))
215
+ return mkdirSync(path, {recursive: true})
216
+
217
+ const files = readdirSync(path)
218
+
219
+ for (const file of files) {
220
+ const filePath = join(path, file)
221
+ const stat = statSync(filePath)
222
+
223
+ stat.isDirectory()
224
+ ? rmSync(filePath, {recursive: true, force: true})
225
+ : unlinkSync(filePath)
226
+ }
227
+ } catch {}
228
+ }
229
+
230
+ function durableObjectNamespace(name: string, uniqueKey: string) {
231
+ const key = createHash('sha256').update(uniqueKey).digest()
232
+ const nameHmac = createHmac('sha256', key).update(name).digest().subarray(0, 16)
233
+ return Buffer.concat([
234
+ nameHmac,
235
+ createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16)
236
+ ]).toString('hex')
237
+ }
238
+
239
+ export function cleanDB(key: string, config?: WranglerConfig) {
240
+ config ??= wranglerConfig()
241
+ if (!config?.d1_databases) return
242
+ for (const db of config.d1_databases) {
243
+ if (key === db.database_name || key === db.binding) {
244
+ const ns = 'miniflare-D1DatabaseObject'
245
+ const fileName = join(ns, durableObjectNamespace(db.preview_database_id || db.database_id || db.binding, ns) +'.sqlite')
246
+
247
+ for (const file of [
248
+ fileName,
249
+ fileName +'-shm',
250
+ fileName +'-wal',
251
+ ]) {
252
+ const filePath = join(d1Path, file)
253
+ if (existsSync(filePath))
254
+ unlinkSync(filePath)
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ export function wranglerConfig(file: string | null = null) {
261
+ file ??= findWranglerConfig(_root)
262
+ if (!file) {
263
+ error('Could not find wrangler config file')
264
+ error('Looking for: '+ formatArgs(WRANGLER_CONFIG_FILES))
265
+ process.exit(1)
266
+ }
267
+
268
+ try {
269
+ return parseWranglerConfig(file)
270
+ } catch (e) {
271
+ warn(`Could not parse '${file}', using defaults`)
272
+ return {}
273
+ }
274
+ }
275
+
276
+ export function localflareManifest(opts: WranglerConfig): LocalflareManifest {
277
+ return {
278
+ name: opts.name || 'worker',
279
+ d1: (opts.d1_databases || []).map((db) => ({
280
+ binding: db.binding,
281
+ database_name: db.database_name,
282
+ })),
283
+ kv: (opts.kv_namespaces || []).map((kv) => ({
284
+ binding: kv.binding,
285
+ })),
286
+ r2: (opts.r2_buckets || []).map((r2) => ({
287
+ binding: r2.binding,
288
+ bucket_name: r2.bucket_name,
289
+ })),
290
+ queues: {
291
+ producers: (opts.queues?.producers || []).map((p) => ({
292
+ binding: p.binding,
293
+ queue: p.queue,
294
+ })),
295
+ consumers: (opts.queues?.consumers || []).map((c) => ({
296
+ queue: c.queue,
297
+ max_batch_size: c.max_batch_size,
298
+ max_batch_timeout: c.max_batch_timeout,
299
+ max_retries: c.max_retries,
300
+ dead_letter_queue: c.dead_letter_queue,
301
+ })),
302
+ },
303
+ do: (opts.durable_objects?.bindings || []).map((d) => ({
304
+ binding: d.name,
305
+ className: d.class_name,
306
+ })),
307
+ vars: Object.entries(opts.vars || {}).map(([key, value]) => ({
308
+ key,
309
+ value,
310
+ isSecret: false,
311
+ // isSecret: looksLikeSecret(key, value),
312
+ })),
313
+ }
314
+ }
315
+
316
+ const wranglerPath = '.wrangler/state/v3'
317
+ export const kvPath = join(_root, wranglerPath, 'kv')
318
+ export const d1Path = join(_root, wranglerPath, 'd1')
319
+ export const r2Path = join(_root, wranglerPath, 'r2')
320
+ export const doPath = join(_root, wranglerPath, 'durable_objects')
321
+
322
+ export function createMiniflare(opts = {}) {
323
+ const entry = join(_root, opts?.main || opts?.script)
324
+ return new Miniflare({
325
+ host: opts.host || 'localhost',
326
+ port: opts.port || 8787,
327
+ https: opts.https || false,
328
+ httpsKey: opts.httpsKey,
329
+ httpsCert: opts.httpsCert,
330
+ liveReload: opts.liveReload !== false,
331
+ updateCheck: false,
332
+ scriptPath: entry,
333
+ modules: [
334
+ {type: 'ESModule', path: entry},
335
+ ],
336
+ compatibilityDate: opts.compatibility_date || '2024-11-01',
337
+ compatibilityFlags: opts.compatibility_flags || [
338
+ 'nodejs_compat',
339
+ ],
340
+ bindings: {
341
+ ...opts.vars,
342
+ },
343
+
344
+ d1Databases: Array.isArray(opts.d1_databases) ? Object.fromEntries(opts.d1_databases.map(db => [db.binding, db.database_id])) : {},
345
+ kvNamespaces: Object.fromEntries((opts.kv_namespaces || []).map(ns => [ns.binding, ns.id])),
346
+ // r2Buckets: Object.fromEntries((opts.r2_buckets || []).map(r2 => [r2.binding, r2.bucket_name])),
347
+ // durableObjects: Object.fromEntries((opts.durable_objects?.bindings || []).map(do => [do.name, do.class_name])),
348
+
349
+ kvPersist: kvPath,
350
+ cachePersist: join(_root, wranglerPath, 'cache'),
351
+ d1Persist: d1Path,
352
+ r2Persist: r2Path,
353
+ durablesPersist: doPath,
354
+
355
+ verbose: false,
356
+
357
+ // Logging
358
+ // log: new console.Console({
359
+ // stdout: process.stdout,
360
+ // stderr: process.stderr,
361
+ // inspectOptions: { depth: 3 }
362
+ // }),
363
+
364
+ cfFetch: false, // disable cf requests
365
+ upstream: opts.upstream || 'https://example.com',
366
+
367
+ sitePath: opts.site?.bucket ?
368
+ join(_root, opts.site.bucket) : undefined,
369
+ siteInclude: opts.site?.include || ['**/*'],
370
+ siteExclude: opts.site?.exclude || [],
371
+
372
+ globalAsyncIO: true,
373
+ globalTimers: true,
374
+ globalRandom: true,
375
+
376
+ inspectorPort: opts.inspectorPort || 9229,
377
+
378
+ cache: true,
379
+ cacheWarnUsage: true,
380
+ })
381
+ }
382
+
383
+ export async function wait(ms: number) {
384
+ return new Promise(r => setTimeout(r, ms))
385
+ }
386
+
387
+ export function getDockerHost() {
388
+ const platform = process.platform
389
+
390
+ if (platform === 'darwin') {
391
+ for (const socket of [
392
+ '/Users/'+ process.env.USER +'/.docker/run/docker.sock',
393
+ '/var/run/docker.sock',
394
+ process.env.DOCKER_HOST
395
+ ]) {
396
+ if (socket && existsSync(socket.replace(/^unix:\/\//, '')))
397
+ return socket.includes('://') ? socket : `unix://${socket}`
398
+ }
399
+
400
+ return 'tcp://localhost:2375'
401
+ }
402
+
403
+ return process.env.DOCKER_HOST || (platform === 'win32' ? 'tcp://localhost:2375' : 'unix:///var/run/docker.sock')
404
+ }
405
+
406
+ export function makeFile(path: string, content: string) {
407
+ const dir = dirname(path)
408
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
409
+
410
+ writeFileSync(path, content)
411
+ }
412
+
413
+ export function hasExt(path: string) {
414
+ const index = path.lastIndexOf('.')
415
+ return index > 0 && index < path.length - 1
416
+ }
417
+
418
+ export const highlightedMethod = (method: string, str?: string | null, siblings: boolean = false): string => {
419
+ const val = str || method
420
+
421
+ switch (method) {
422
+ case 'HEAD':
423
+ case 'OPTIONS':
424
+ case 'CONNECT':
425
+ case 'TRACE':
426
+ return gray(val)
427
+ case 'GET':
428
+ return purple(val) + (siblings ? gray('|') + highlightedMethod('HEAD') : '')
429
+ case 'POST':
430
+ case 'PUT':
431
+ case 'PATCH':
432
+ return yellow(val)
433
+ case 'DELETE':
434
+ return red(val)
435
+ }
436
+
437
+ return val
438
+ }
439
+
440
+ export const highlightedURI = (uri: string, method: string) => uri.replace(
441
+ /(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
442
+ _ => highlightedMethod(method, _)
443
+ )
@@ -0,0 +1,4 @@
1
+ import { DataBag } from 't0n'
2
+
3
+ const Config = new DataBag()
4
+ export default Config
@@ -0,0 +1 @@
1
+ export * from 'forj/d1/types'
@@ -0,0 +1 @@
1
+ export * from 'forj/d1'
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb/types'
@@ -0,0 +1 @@
1
+ export * from 'forj/dynamodb'
@@ -0,0 +1 @@
1
+ export { Migration, Schema, Blueprint } from 'forj'