bunderstack 0.25.2 → 1.0.0-beta.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.
Files changed (164) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/auth-password.d.ts +27 -0
  3. package/dist/auth-password.d.ts.map +1 -0
  4. package/dist/auth-password.js +60 -0
  5. package/dist/auth-password.js.map +1 -0
  6. package/dist/auth.d.ts +1 -1
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +1 -0
  9. package/dist/auth.js.map +1 -1
  10. package/dist/backend.d.ts +3 -0
  11. package/dist/backend.d.ts.map +1 -1
  12. package/dist/backend.js +3 -1
  13. package/dist/backend.js.map +1 -1
  14. package/dist/cli.d.ts +11 -1
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +97 -2
  17. package/dist/cli.js.map +1 -1
  18. package/dist/config.d.ts +0 -8
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +0 -18
  21. package/dist/config.js.map +1 -1
  22. package/dist/crud-operations.d.ts.map +1 -1
  23. package/dist/crud-operations.js +19 -3
  24. package/dist/crud-operations.js.map +1 -1
  25. package/dist/dev/binaries.d.ts +22 -0
  26. package/dist/dev/binaries.d.ts.map +1 -0
  27. package/dist/dev/binaries.js +119 -0
  28. package/dist/dev/binaries.js.map +1 -0
  29. package/dist/dev/env.d.ts +11 -0
  30. package/dist/dev/env.d.ts.map +1 -0
  31. package/dist/dev/env.js +71 -0
  32. package/dist/dev/env.js.map +1 -0
  33. package/dist/dev/index.d.ts +45 -0
  34. package/dist/dev/index.d.ts.map +1 -0
  35. package/dist/dev/index.js +287 -0
  36. package/dist/dev/index.js.map +1 -0
  37. package/dist/dev/processes.d.ts +37 -0
  38. package/dist/dev/processes.d.ts.map +1 -0
  39. package/dist/dev/processes.js +104 -0
  40. package/dist/dev/processes.js.map +1 -0
  41. package/dist/dev/push.d.ts +2 -0
  42. package/dist/dev/push.d.ts.map +1 -0
  43. package/dist/dev/push.js +25 -0
  44. package/dist/dev/push.js.map +1 -0
  45. package/dist/env-probe.d.ts.map +1 -1
  46. package/dist/env-probe.js +0 -1
  47. package/dist/env-probe.js.map +1 -1
  48. package/dist/env.d.ts +0 -3
  49. package/dist/env.d.ts.map +1 -1
  50. package/dist/env.js +0 -7
  51. package/dist/env.js.map +1 -1
  52. package/dist/handler.d.ts +2 -0
  53. package/dist/handler.d.ts.map +1 -1
  54. package/dist/handler.js +1 -2
  55. package/dist/handler.js.map +1 -1
  56. package/dist/hosted-contract.d.ts.map +1 -1
  57. package/dist/hosted-contract.js +7 -3
  58. package/dist/hosted-contract.js.map +1 -1
  59. package/dist/index.d.ts +4 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/jobs/define.d.ts +5 -0
  64. package/dist/jobs/define.d.ts.map +1 -1
  65. package/dist/jobs/define.js.map +1 -1
  66. package/dist/jobs/index.d.ts +2 -4
  67. package/dist/jobs/index.d.ts.map +1 -1
  68. package/dist/jobs/index.js +2 -3
  69. package/dist/jobs/index.js.map +1 -1
  70. package/dist/jobs/queue.d.ts +1 -0
  71. package/dist/jobs/queue.d.ts.map +1 -1
  72. package/dist/jobs/queue.js +6 -3
  73. package/dist/jobs/queue.js.map +1 -1
  74. package/dist/jobs/slots.d.ts +5 -0
  75. package/dist/jobs/slots.d.ts.map +1 -1
  76. package/dist/jobs/slots.js +12 -0
  77. package/dist/jobs/slots.js.map +1 -1
  78. package/dist/jobs/worker.d.ts +6 -0
  79. package/dist/jobs/worker.d.ts.map +1 -1
  80. package/dist/jobs/worker.js +32 -1
  81. package/dist/jobs/worker.js.map +1 -1
  82. package/dist/platform.d.ts +25 -0
  83. package/dist/platform.d.ts.map +1 -0
  84. package/dist/platform.js +24 -0
  85. package/dist/platform.js.map +1 -0
  86. package/dist/rate-limit.d.ts +2 -1
  87. package/dist/rate-limit.d.ts.map +1 -1
  88. package/dist/rate-limit.js +19 -22
  89. package/dist/rate-limit.js.map +1 -1
  90. package/dist/realtime/facade.d.ts +1 -1
  91. package/dist/realtime/facade.d.ts.map +1 -1
  92. package/dist/realtime/facade.js.map +1 -1
  93. package/dist/realtime/publisher.d.ts +0 -4
  94. package/dist/realtime/publisher.d.ts.map +1 -1
  95. package/dist/realtime/publisher.js +0 -9
  96. package/dist/realtime/publisher.js.map +1 -1
  97. package/dist/runtime.d.ts +5 -19
  98. package/dist/runtime.d.ts.map +1 -1
  99. package/dist/runtime.js +28 -102
  100. package/dist/runtime.js.map +1 -1
  101. package/dist/storage/registry.d.ts +2 -1
  102. package/dist/storage/registry.d.ts.map +1 -1
  103. package/dist/storage/registry.js +2 -2
  104. package/dist/storage/registry.js.map +1 -1
  105. package/dist/storage/s3.d.ts +11 -4
  106. package/dist/storage/s3.d.ts.map +1 -1
  107. package/dist/storage/s3.js +88 -34
  108. package/dist/storage/s3.js.map +1 -1
  109. package/dist/storage/sigv4.d.ts +24 -0
  110. package/dist/storage/sigv4.d.ts.map +1 -0
  111. package/dist/storage/sigv4.js +107 -0
  112. package/dist/storage/sigv4.js.map +1 -0
  113. package/dist/testing/fixture.d.ts.map +1 -1
  114. package/dist/testing/fixture.js +0 -3
  115. package/dist/testing/fixture.js.map +1 -1
  116. package/dist/testing/workers-fakes.d.ts +17 -0
  117. package/dist/testing/workers-fakes.d.ts.map +1 -0
  118. package/dist/testing/workers-fakes.js +98 -0
  119. package/dist/testing/workers-fakes.js.map +1 -0
  120. package/dist/typeid.d.ts +1 -0
  121. package/dist/typeid.d.ts.map +1 -1
  122. package/dist/typeid.js +34 -4
  123. package/dist/typeid.js.map +1 -1
  124. package/dist/vite.d.ts +30 -0
  125. package/dist/vite.d.ts.map +1 -0
  126. package/dist/vite.js +37 -0
  127. package/dist/vite.js.map +1 -0
  128. package/dist/workers/app.d.ts +19 -0
  129. package/dist/workers/app.d.ts.map +1 -0
  130. package/dist/workers/app.js +56 -0
  131. package/dist/workers/app.js.map +1 -0
  132. package/dist/workers/index.d.ts +21 -0
  133. package/dist/workers/index.d.ts.map +1 -0
  134. package/dist/workers/index.js +33 -0
  135. package/dist/workers/index.js.map +1 -0
  136. package/dist/workers/r2.d.ts +23 -0
  137. package/dist/workers/r2.d.ts.map +1 -0
  138. package/dist/workers/r2.js +77 -0
  139. package/dist/workers/r2.js.map +1 -0
  140. package/dist/workers/rate-limiter.d.ts +9 -0
  141. package/dist/workers/rate-limiter.d.ts.map +1 -0
  142. package/dist/workers/rate-limiter.js +22 -0
  143. package/dist/workers/rate-limiter.js.map +1 -0
  144. package/dist/workers/realtime-hub.d.ts +20 -0
  145. package/dist/workers/realtime-hub.d.ts.map +1 -0
  146. package/dist/workers/realtime-hub.js +117 -0
  147. package/dist/workers/realtime-hub.js.map +1 -0
  148. package/dist/workers/scheduler.d.ts +13 -0
  149. package/dist/workers/scheduler.d.ts.map +1 -0
  150. package/dist/workers/scheduler.js +71 -0
  151. package/dist/workers/scheduler.js.map +1 -0
  152. package/dist/workers/types.d.ts +55 -0
  153. package/dist/workers/types.d.ts.map +1 -0
  154. package/dist/workers/types.js +4 -0
  155. package/dist/workers/types.js.map +1 -0
  156. package/dist/workers/wrangler.d.ts +64 -0
  157. package/dist/workers/wrangler.d.ts.map +1 -0
  158. package/dist/workers/wrangler.js +115 -0
  159. package/dist/workers/wrangler.js.map +1 -0
  160. package/package.json +11 -3
  161. package/dist/jobs/runtime.d.ts +0 -17
  162. package/dist/jobs/runtime.d.ts.map +0 -1
  163. package/dist/jobs/runtime.js +0 -56
  164. package/dist/jobs/runtime.js.map +0 -1
@@ -1,71 +1,125 @@
1
+ import { presignUrl, signRequest } from './sigv4.js';
2
+ function decodeXml(value) {
3
+ return value
4
+ .replace(/&lt;/g, '<')
5
+ .replace(/&gt;/g, '>')
6
+ .replace(/&quot;/g, '"')
7
+ .replace(/&apos;/g, "'")
8
+ .replace(/&amp;/g, '&');
9
+ }
1
10
  export class S3StorageAdapter {
2
- client;
11
+ base;
12
+ credentials;
3
13
  publicUrl;
14
+ fetchFn;
4
15
  constructor(cfg) {
5
- this.client = new Bun.S3Client({
6
- bucket: cfg.bucket,
7
- region: cfg.region,
16
+ const endpoint = (cfg.endpoint ?? `https://s3.${cfg.region}.amazonaws.com`).replace(/\/$/, '');
17
+ this.base = `${endpoint}/${cfg.bucket}`;
18
+ this.credentials = {
8
19
  accessKeyId: cfg.accessKeyId,
9
20
  secretAccessKey: cfg.secretAccessKey,
10
- ...(cfg.endpoint && { endpoint: cfg.endpoint }),
11
- });
21
+ region: cfg.region,
22
+ };
12
23
  this.publicUrl = cfg.publicUrl;
24
+ this.fetchFn = cfg.fetch ?? ((input, init) => globalThis.fetch(input, init));
25
+ }
26
+ objectUrl(key) {
27
+ return `${this.base}/${key.split('/').map(encodeURIComponent).join('/')}`;
28
+ }
29
+ async send(method, url, body, headers = {}) {
30
+ const signed = await signRequest({
31
+ method,
32
+ url,
33
+ headers,
34
+ body: body ?? null,
35
+ credentials: this.credentials,
36
+ });
37
+ return this.fetchFn(url, { method, headers: signed, body });
13
38
  }
14
39
  async upload(fileId, data, contentType) {
15
40
  const bytes = data instanceof Blob ? await data.arrayBuffer() : data;
16
- await this.client.write(fileId, bytes, { type: contentType });
41
+ const res = await this.send('PUT', this.objectUrl(fileId), bytes, {
42
+ 'content-type': contentType,
43
+ });
44
+ if (!res.ok) {
45
+ throw new Error(`[bunderstack] S3 upload failed (${res.status}): ${await res.text()}`);
46
+ }
17
47
  }
18
48
  async get(fileId) {
19
- // `stat` (a HEAD, same cost as `exists`) is the only way to learn the
20
- // stored Content-Type: a lazily-constructed `client.file(key)` reports
21
- // `type: ""` — it never stats, and unlike `Bun.file` it does not guess
22
- // from the extension either.
23
- const info = await this.stat(fileId);
24
- if (!info)
49
+ const res = await this.send('GET', this.objectUrl(fileId));
50
+ if (res.status === 404)
25
51
  return new Response('Not found', { status: 404 });
26
- const file = this.client.file(fileId);
27
- return new Response(file.stream(), {
52
+ if (!res.ok) {
53
+ throw new Error(`[bunderstack] S3 get failed (${res.status})`);
54
+ }
55
+ return new Response(res.body, {
28
56
  headers: {
29
- 'Content-Type': info.contentType || 'application/octet-stream',
57
+ 'Content-Type': res.headers.get('content-type') || 'application/octet-stream',
30
58
  },
31
59
  });
32
60
  }
33
61
  async delete(fileId) {
34
- await this.client.delete(fileId);
62
+ const res = await this.send('DELETE', this.objectUrl(fileId));
63
+ if (!res.ok && res.status !== 404) {
64
+ throw new Error(`[bunderstack] S3 delete failed (${res.status})`);
65
+ }
35
66
  }
36
67
  async exists(fileId) {
37
- return this.client.exists(fileId);
68
+ return (await this.stat(fileId)) !== null;
38
69
  }
70
+ async stat(key) {
71
+ const res = await this.send('HEAD', this.objectUrl(key));
72
+ if (!res.ok)
73
+ return null;
74
+ return {
75
+ size: Number(res.headers.get('content-length') ?? 0),
76
+ contentType: res.headers.get('content-type') ?? '',
77
+ };
78
+ }
79
+ async list(prefix) {
80
+ const keys = [];
81
+ let token;
82
+ do {
83
+ const url = new URL(this.base);
84
+ url.searchParams.set('list-type', '2');
85
+ url.searchParams.set('prefix', prefix);
86
+ if (token)
87
+ url.searchParams.set('continuation-token', token);
88
+ const res = await this.send('GET', url.toString());
89
+ if (!res.ok) {
90
+ throw new Error(`[bunderstack] S3 list failed (${res.status})`);
91
+ }
92
+ const xml = await res.text();
93
+ for (const match of xml.matchAll(/<Key>([\s\S]*?)<\/Key>/g)) {
94
+ keys.push(decodeXml(match[1]));
95
+ }
96
+ token = /<IsTruncated>true<\/IsTruncated>/.test(xml)
97
+ ? decodeXml(/<NextContinuationToken>([\s\S]*?)<\/NextContinuationToken>/.exec(xml)?.[1] ?? '')
98
+ : undefined;
99
+ } while (token);
100
+ return keys;
101
+ }
102
+ // The content type is not signed: confirmUpload checks the stored type.
39
103
  async presignPut(key, opts) {
40
- return this.client.presign(key, {
104
+ return presignUrl({
41
105
  method: 'PUT',
106
+ url: this.objectUrl(key),
107
+ credentials: this.credentials,
42
108
  expiresIn: opts.expiresIn,
43
- ...(opts.contentType && { type: opts.contentType }),
44
109
  });
45
110
  }
46
111
  async presignGet(key, opts) {
47
- return this.client.presign(key, {
112
+ return presignUrl({
48
113
  method: 'GET',
114
+ url: this.objectUrl(key),
115
+ credentials: this.credentials,
49
116
  expiresIn: opts.expiresIn,
50
117
  });
51
118
  }
52
- async stat(key) {
53
- try {
54
- const s = await this.client.stat(key);
55
- return { size: s.size, contentType: s.type };
56
- }
57
- catch {
58
- return null;
59
- }
60
- }
61
119
  publicUrlFor(key) {
62
120
  if (!this.publicUrl)
63
121
  return undefined;
64
122
  return `${this.publicUrl.replace(/\/$/, '')}/${key}`;
65
123
  }
66
- async list(prefix) {
67
- const res = await this.client.list({ prefix });
68
- return res.contents?.map((c) => c.key) ?? [];
69
- }
70
124
  }
71
125
  //# sourceMappingURL=s3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"s3.js","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"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","sourcesContent":["// src/storage/s3.ts\nimport type {\n PresignGetOptions,\n PresignPutOptions,\n StorageAdapter,\n} from './index'\n\ninterface S3Config {\n bucket: string\n region: string\n accessKeyId: string\n secretAccessKey: string\n endpoint?: string\n publicUrl?: string\n}\n\nexport class S3StorageAdapter implements StorageAdapter {\n private client: InstanceType<typeof Bun.S3Client>\n private readonly publicUrl?: string\n\n constructor(cfg: S3Config) {\n this.client = new Bun.S3Client({\n bucket: cfg.bucket,\n region: cfg.region,\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n ...(cfg.endpoint && { endpoint: cfg.endpoint }),\n })\n this.publicUrl = cfg.publicUrl\n }\n\n async upload(\n fileId: string,\n data: Blob | ArrayBuffer,\n contentType: string,\n ): Promise<void> {\n const bytes = data instanceof Blob ? await data.arrayBuffer() : data\n await this.client.write(fileId, bytes, { type: contentType })\n }\n\n async get(fileId: string): Promise<Response> {\n // `stat` (a HEAD, same cost as `exists`) is the only way to learn the\n // stored Content-Type: a lazily-constructed `client.file(key)` reports\n // `type: \"\"` — it never stats, and unlike `Bun.file` it does not guess\n // from the extension either.\n const info = await this.stat(fileId)\n if (!info) return new Response('Not found', { status: 404 })\n const file = this.client.file(fileId)\n return new Response(file.stream(), {\n headers: {\n 'Content-Type': info.contentType || 'application/octet-stream',\n },\n })\n }\n\n async delete(fileId: string): Promise<void> {\n await this.client.delete(fileId)\n }\n\n async exists(fileId: string): Promise<boolean> {\n return this.client.exists(fileId)\n }\n\n async presignPut(key: string, opts: PresignPutOptions): Promise<string> {\n return this.client.presign(key, {\n method: 'PUT',\n expiresIn: opts.expiresIn,\n ...(opts.contentType && { type: opts.contentType }),\n })\n }\n\n async presignGet(key: string, opts: PresignGetOptions): Promise<string> {\n return this.client.presign(key, {\n method: 'GET',\n expiresIn: opts.expiresIn,\n })\n }\n\n async stat(\n key: string,\n ): Promise<{ size: number; contentType: string } | null> {\n try {\n const s = await this.client.stat(key)\n return { size: s.size, contentType: s.type }\n } catch {\n return null\n }\n }\n\n publicUrlFor(key: string): string | undefined {\n if (!this.publicUrl) return undefined\n return `${this.publicUrl.replace(/\\/$/, '')}/${key}`\n }\n\n async list(prefix: string): Promise<string[]> {\n const res = await this.client.list({ prefix })\n return res.contents?.map((c) => c.key) ?? []\n }\n}\n"]}
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"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","sourcesContent":["// src/storage/s3.ts — S3 on fetch + SigV4, so the adapter runs outside Bun.\nimport type {\n PresignGetOptions,\n PresignPutOptions,\n StorageAdapter,\n} from './index'\n\nimport { presignUrl, signRequest, type SigV4Credentials } from './sigv4'\n\ntype Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n\ninterface S3Config {\n bucket: string\n region: string\n accessKeyId: string\n secretAccessKey: string\n endpoint?: string\n publicUrl?: string\n /** Injected in tests; defaults to the global fetch. */\n fetch?: Fetch\n}\n\nfunction decodeXml(value: string): string {\n return value\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, '&')\n}\n\nexport class S3StorageAdapter implements StorageAdapter {\n private readonly base: string\n private readonly credentials: SigV4Credentials\n private readonly publicUrl?: string\n private readonly fetchFn: Fetch\n\n constructor(cfg: S3Config) {\n const endpoint = (\n cfg.endpoint ?? `https://s3.${cfg.region}.amazonaws.com`\n ).replace(/\\/$/, '')\n this.base = `${endpoint}/${cfg.bucket}`\n this.credentials = {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n region: cfg.region,\n }\n this.publicUrl = cfg.publicUrl\n this.fetchFn = cfg.fetch ?? ((input, init) => globalThis.fetch(input, init))\n }\n\n private objectUrl(key: string): string {\n return `${this.base}/${key.split('/').map(encodeURIComponent).join('/')}`\n }\n\n private async send(\n method: string,\n url: string,\n body?: ArrayBuffer,\n headers: Record<string, string> = {},\n ): Promise<Response> {\n const signed = await signRequest({\n method,\n url,\n headers,\n body: body ?? null,\n credentials: this.credentials,\n })\n return this.fetchFn(url, { method, headers: signed, body })\n }\n\n async upload(\n fileId: string,\n data: Blob | ArrayBuffer,\n contentType: string,\n ): Promise<void> {\n const bytes = data instanceof Blob ? await data.arrayBuffer() : data\n const res = await this.send('PUT', this.objectUrl(fileId), bytes, {\n 'content-type': contentType,\n })\n if (!res.ok) {\n throw new Error(\n `[bunderstack] S3 upload failed (${res.status}): ${await res.text()}`,\n )\n }\n }\n\n async get(fileId: string): Promise<Response> {\n const res = await this.send('GET', this.objectUrl(fileId))\n if (res.status === 404) return new Response('Not found', { status: 404 })\n if (!res.ok) {\n throw new Error(`[bunderstack] S3 get failed (${res.status})`)\n }\n return new Response(res.body, {\n headers: {\n 'Content-Type':\n res.headers.get('content-type') || 'application/octet-stream',\n },\n })\n }\n\n async delete(fileId: string): Promise<void> {\n const res = await this.send('DELETE', this.objectUrl(fileId))\n if (!res.ok && res.status !== 404) {\n throw new Error(`[bunderstack] S3 delete failed (${res.status})`)\n }\n }\n\n async exists(fileId: string): Promise<boolean> {\n return (await this.stat(fileId)) !== null\n }\n\n async stat(\n key: string,\n ): Promise<{ size: number; contentType: string } | null> {\n const res = await this.send('HEAD', this.objectUrl(key))\n if (!res.ok) return null\n return {\n size: Number(res.headers.get('content-length') ?? 0),\n contentType: res.headers.get('content-type') ?? '',\n }\n }\n\n async list(prefix: string): Promise<string[]> {\n const keys: string[] = []\n let token: string | undefined\n do {\n const url = new URL(this.base)\n url.searchParams.set('list-type', '2')\n url.searchParams.set('prefix', prefix)\n if (token) url.searchParams.set('continuation-token', token)\n const res = await this.send('GET', url.toString())\n if (!res.ok) {\n throw new Error(`[bunderstack] S3 list failed (${res.status})`)\n }\n const xml = await res.text()\n for (const match of xml.matchAll(/<Key>([\\s\\S]*?)<\\/Key>/g)) {\n keys.push(decodeXml(match[1]!))\n }\n token = /<IsTruncated>true<\\/IsTruncated>/.test(xml)\n ? decodeXml(\n /<NextContinuationToken>([\\s\\S]*?)<\\/NextContinuationToken>/.exec(\n xml,\n )?.[1] ?? '',\n )\n : undefined\n } while (token)\n return keys\n }\n\n // The content type is not signed: confirmUpload checks the stored type.\n async presignPut(key: string, opts: PresignPutOptions): Promise<string> {\n return presignUrl({\n method: 'PUT',\n url: this.objectUrl(key),\n credentials: this.credentials,\n expiresIn: opts.expiresIn,\n })\n }\n\n async presignGet(key: string, opts: PresignGetOptions): Promise<string> {\n return presignUrl({\n method: 'GET',\n url: this.objectUrl(key),\n credentials: this.credentials,\n expiresIn: opts.expiresIn,\n })\n }\n\n publicUrlFor(key: string): string | undefined {\n if (!this.publicUrl) return undefined\n return `${this.publicUrl.replace(/\\/$/, '')}/${key}`\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ export type SigV4Credentials = {
2
+ accessKeyId: string;
3
+ secretAccessKey: string;
4
+ region: string;
5
+ service?: string;
6
+ };
7
+ export declare function signRequest(input: {
8
+ method: string;
9
+ url: string;
10
+ headers?: Record<string, string>;
11
+ body?: ArrayBuffer | Uint8Array<ArrayBuffer> | string | null;
12
+ credentials: SigV4Credentials;
13
+ date?: Date;
14
+ unsignedPayload?: boolean;
15
+ }): Promise<Record<string, string>>;
16
+ export declare function presignUrl(input: {
17
+ method: string;
18
+ url: string;
19
+ credentials: SigV4Credentials;
20
+ expiresIn: number;
21
+ date?: Date;
22
+ signedHeaders?: Record<string, string>;
23
+ }): Promise<string>;
24
+ //# sourceMappingURL=sigv4.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sigv4.d.ts","sourceRoot":"","sources":["../../src/storage/sigv4.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAiGD,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,MAAM,GAAG,IAAI,CAAA;IAC5D,WAAW,EAAE,gBAAgB,CAAA;IAC7B,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgClC;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,gBAAgB,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiClB"}
@@ -0,0 +1,107 @@
1
+ const encoder = new TextEncoder();
2
+ function hex(bytes) {
3
+ return Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, '0')).join('');
4
+ }
5
+ async function sha256Hex(data) {
6
+ const bytes = typeof data === 'string' ? encoder.encode(data) : data;
7
+ return hex(await crypto.subtle.digest('SHA-256', bytes));
8
+ }
9
+ async function hmac(key, data) {
10
+ const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
11
+ return crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(data));
12
+ }
13
+ async function signingKey(secret, day, region, service) {
14
+ const kDate = await hmac(encoder.encode(`AWS4${secret}`), day);
15
+ const kRegion = await hmac(kDate, region);
16
+ const kService = await hmac(kRegion, service);
17
+ return hmac(kService, 'aws4_request');
18
+ }
19
+ /** RFC 3986 encoding, as SigV4 requires: only A-Z a-z 0-9 - _ . ~ stay. */
20
+ function uriEncode(value) {
21
+ return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
22
+ }
23
+ /** S3 does not normalize paths: encode each segment once, keep the slashes. */
24
+ function canonicalPath(pathname) {
25
+ return pathname
26
+ .split('/')
27
+ .map((segment) => uriEncode(decodeURIComponent(segment)))
28
+ .join('/');
29
+ }
30
+ function canonicalQuery(params) {
31
+ return [...params.entries()]
32
+ .map(([k, v]) => [uriEncode(k), uriEncode(v)])
33
+ .sort(([a, av], [b, bv]) => a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0)
34
+ .map(([k, v]) => `${k}=${v}`)
35
+ .join('&');
36
+ }
37
+ function amzDates(date) {
38
+ const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '');
39
+ return { amzDate, day: amzDate.slice(0, 8) };
40
+ }
41
+ async function signature(credentials, day, amzDate, canonicalRequest) {
42
+ const service = credentials.service ?? 's3';
43
+ const scope = `${day}/${credentials.region}/${service}/aws4_request`;
44
+ const stringToSign = [
45
+ 'AWS4-HMAC-SHA256',
46
+ amzDate,
47
+ scope,
48
+ await sha256Hex(canonicalRequest),
49
+ ].join('\n');
50
+ const key = await signingKey(credentials.secretAccessKey, day, credentials.region, service);
51
+ return { scope, signature: hex(await hmac(key, stringToSign)) };
52
+ }
53
+ export async function signRequest(input) {
54
+ const url = new URL(input.url);
55
+ const { amzDate, day } = amzDates(input.date ?? new Date());
56
+ const payloadHash = input.unsignedPayload
57
+ ? 'UNSIGNED-PAYLOAD'
58
+ : await sha256Hex(input.body ?? '');
59
+ const headers = {};
60
+ for (const [k, v] of Object.entries(input.headers ?? {})) {
61
+ headers[k.toLowerCase()] = v.trim();
62
+ }
63
+ headers['host'] = url.host;
64
+ headers['x-amz-date'] = amzDate;
65
+ headers['x-amz-content-sha256'] = payloadHash;
66
+ const names = Object.keys(headers).sort();
67
+ const canonicalRequest = [
68
+ input.method.toUpperCase(),
69
+ canonicalPath(url.pathname),
70
+ canonicalQuery(url.searchParams),
71
+ names.map((n) => `${n}:${headers[n]}\n`).join(''),
72
+ names.join(';'),
73
+ payloadHash,
74
+ ].join('\n');
75
+ const signed = await signature(input.credentials, day, amzDate, canonicalRequest);
76
+ headers['authorization'] =
77
+ `AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${signed.scope}, ` +
78
+ `SignedHeaders=${names.join(';')}, Signature=${signed.signature}`;
79
+ return headers;
80
+ }
81
+ export async function presignUrl(input) {
82
+ const url = new URL(input.url);
83
+ const { amzDate, day } = amzDates(input.date ?? new Date());
84
+ const service = input.credentials.service ?? 's3';
85
+ const headers = { host: url.host };
86
+ for (const [k, v] of Object.entries(input.signedHeaders ?? {})) {
87
+ headers[k.toLowerCase()] = v.trim();
88
+ }
89
+ const names = Object.keys(headers).sort();
90
+ url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
91
+ url.searchParams.set('X-Amz-Credential', `${input.credentials.accessKeyId}/${day}/${input.credentials.region}/${service}/aws4_request`);
92
+ url.searchParams.set('X-Amz-Date', amzDate);
93
+ url.searchParams.set('X-Amz-Expires', String(input.expiresIn));
94
+ url.searchParams.set('X-Amz-SignedHeaders', names.join(';'));
95
+ const canonicalRequest = [
96
+ input.method.toUpperCase(),
97
+ canonicalPath(url.pathname),
98
+ canonicalQuery(url.searchParams),
99
+ names.map((n) => `${n}:${headers[n]}\n`).join(''),
100
+ names.join(';'),
101
+ 'UNSIGNED-PAYLOAD',
102
+ ].join('\n');
103
+ const signed = await signature(input.credentials, day, amzDate, canonicalRequest);
104
+ url.searchParams.set('X-Amz-Signature', signed.signature);
105
+ return url.toString();
106
+ }
107
+ //# sourceMappingURL=sigv4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sigv4.js","sourceRoot":"","sources":["../../src/storage/sigv4.ts"],"names":[],"mappings":"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","sourcesContent":["// src/storage/sigv4.ts — AWS Signature Version 4 on WebCrypto, so S3 access\n// and presigned URLs work in Bun, workerd, and celld without Bun.S3Client.\nexport type SigV4Credentials = {\n accessKeyId: string\n secretAccessKey: string\n region: string\n service?: string\n}\n\nconst encoder = new TextEncoder()\n\nfunction hex(bytes: ArrayBuffer): string {\n return Array.from(new Uint8Array(bytes), (b) =>\n b.toString(16).padStart(2, '0'),\n ).join('')\n}\n\nasync function sha256Hex(\n data: ArrayBuffer | Uint8Array<ArrayBuffer> | string,\n): Promise<string> {\n const bytes = typeof data === 'string' ? encoder.encode(data) : data\n return hex(await crypto.subtle.digest('SHA-256', bytes))\n}\n\nasync function hmac(\n key: ArrayBuffer | Uint8Array<ArrayBuffer>,\n data: string,\n): Promise<ArrayBuffer> {\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n key,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n return crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(data))\n}\n\nasync function signingKey(\n secret: string,\n day: string,\n region: string,\n service: string,\n): Promise<ArrayBuffer> {\n const kDate = await hmac(encoder.encode(`AWS4${secret}`), day)\n const kRegion = await hmac(kDate, region)\n const kService = await hmac(kRegion, service)\n return hmac(kService, 'aws4_request')\n}\n\n/** RFC 3986 encoding, as SigV4 requires: only A-Z a-z 0-9 - _ . ~ stay. */\nfunction uriEncode(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n )\n}\n\n/** S3 does not normalize paths: encode each segment once, keep the slashes. */\nfunction canonicalPath(pathname: string): string {\n return pathname\n .split('/')\n .map((segment) => uriEncode(decodeURIComponent(segment)))\n .join('/')\n}\n\nfunction canonicalQuery(params: URLSearchParams): string {\n return [...params.entries()]\n .map(([k, v]) => [uriEncode(k), uriEncode(v)] as const)\n .sort(([a, av], [b, bv]) =>\n a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0,\n )\n .map(([k, v]) => `${k}=${v}`)\n .join('&')\n}\n\nfunction amzDates(date: Date): { amzDate: string; day: string } {\n const amzDate = date.toISOString().replace(/[:-]|\\.\\d{3}/g, '')\n return { amzDate, day: amzDate.slice(0, 8) }\n}\n\nasync function signature(\n credentials: SigV4Credentials,\n day: string,\n amzDate: string,\n canonicalRequest: string,\n): Promise<{ scope: string; signature: string }> {\n const service = credentials.service ?? 's3'\n const scope = `${day}/${credentials.region}/${service}/aws4_request`\n const stringToSign = [\n 'AWS4-HMAC-SHA256',\n amzDate,\n scope,\n await sha256Hex(canonicalRequest),\n ].join('\\n')\n const key = await signingKey(\n credentials.secretAccessKey,\n day,\n credentials.region,\n service,\n )\n return { scope, signature: hex(await hmac(key, stringToSign)) }\n}\n\nexport async function signRequest(input: {\n method: string\n url: string\n headers?: Record<string, string>\n body?: ArrayBuffer | Uint8Array<ArrayBuffer> | string | null\n credentials: SigV4Credentials\n date?: Date\n unsignedPayload?: boolean\n}): Promise<Record<string, string>> {\n const url = new URL(input.url)\n const { amzDate, day } = amzDates(input.date ?? new Date())\n const payloadHash = input.unsignedPayload\n ? 'UNSIGNED-PAYLOAD'\n : await sha256Hex(input.body ?? '')\n const headers: Record<string, string> = {}\n for (const [k, v] of Object.entries(input.headers ?? {})) {\n headers[k.toLowerCase()] = v.trim()\n }\n headers['host'] = url.host\n headers['x-amz-date'] = amzDate\n headers['x-amz-content-sha256'] = payloadHash\n const names = Object.keys(headers).sort()\n const canonicalRequest = [\n input.method.toUpperCase(),\n canonicalPath(url.pathname),\n canonicalQuery(url.searchParams),\n names.map((n) => `${n}:${headers[n]}\\n`).join(''),\n names.join(';'),\n payloadHash,\n ].join('\\n')\n const signed = await signature(\n input.credentials,\n day,\n amzDate,\n canonicalRequest,\n )\n headers['authorization'] =\n `AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${signed.scope}, ` +\n `SignedHeaders=${names.join(';')}, Signature=${signed.signature}`\n return headers\n}\n\nexport async function presignUrl(input: {\n method: string\n url: string\n credentials: SigV4Credentials\n expiresIn: number\n date?: Date\n signedHeaders?: Record<string, string>\n}): Promise<string> {\n const url = new URL(input.url)\n const { amzDate, day } = amzDates(input.date ?? new Date())\n const service = input.credentials.service ?? 's3'\n const headers: Record<string, string> = { host: url.host }\n for (const [k, v] of Object.entries(input.signedHeaders ?? {})) {\n headers[k.toLowerCase()] = v.trim()\n }\n const names = Object.keys(headers).sort()\n url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256')\n url.searchParams.set(\n 'X-Amz-Credential',\n `${input.credentials.accessKeyId}/${day}/${input.credentials.region}/${service}/aws4_request`,\n )\n url.searchParams.set('X-Amz-Date', amzDate)\n url.searchParams.set('X-Amz-Expires', String(input.expiresIn))\n url.searchParams.set('X-Amz-SignedHeaders', names.join(';'))\n const canonicalRequest = [\n input.method.toUpperCase(),\n canonicalPath(url.pathname),\n canonicalQuery(url.searchParams),\n names.map((n) => `${n}:${headers[n]}\\n`).join(''),\n names.join(';'),\n 'UNSIGNED-PAYLOAD',\n ].join('\\n')\n const signed = await signature(\n input.credentials,\n day,\n amzDate,\n canonicalRequest,\n )\n url.searchParams.set('X-Amz-Signature', signed.signature)\n return url.toString()\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,6 @@ import { createTestStorage, resolveTestBuckets } from './storage.js';
13
13
  const defaultTestEnv = {
14
14
  NODE_ENV: 'test',
15
15
  AUTH_SECRET: 'bunderstack-test-secret',
16
- BUNDERSTACK_ROLE: 'web',
17
16
  };
18
17
  function mergeTestOptions(defaults, overrides) {
19
18
  return {
@@ -76,8 +75,6 @@ export async function createTestApp(backend, options = {}) {
76
75
  messagingAdapters: testMessaging.adapters,
77
76
  authResolver: sessions.resolver,
78
77
  logger,
79
- forceMemoryRealtime: true,
80
- backgroundAutoStart: false,
81
78
  captureTestingHandle: (handle) => {
82
79
  testingHandle = handle;
83
80
  },
@@ -1 +1 @@
1
- {"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtemp, rm } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport type { BunderstackBackend } from '../backend'\nimport type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client'\nimport type { TestDatabaseStrategy } from '../database/adapter'\nimport type { TestSchemaMode } from '../provision-schema'\nimport type { TestAuth, TestIdentity } from './auth'\nimport type { TestLogs, TestLogMode } from './logs'\nimport type { TestMessagingForApp } from './messaging'\nimport type { TestStorage } from './storage'\n\nimport {\n BACKEND_INTERNALS,\n type RuntimeTestingHandle,\n} from '../backend-internals'\nimport { provisionForTest } from '../provision-schema'\nimport { createTestAuth, createTestSessionRegistry } from './auth'\nimport { testClient } from './client'\nimport { createTestDatabaseTarget } from './database'\nimport { createTestJobs, type TestJobs } from './jobs'\nimport { createTestLogs } from './logs'\nimport { createTestMessaging } from './messaging'\nimport { createTestStorage, resolveTestBuckets } from './storage'\n\nexport type TestOptions = {\n env?: Record<string, string | undefined>\n database?: {\n mode?: 'memory' | 'temporary'\n schema?: TestSchemaMode\n strategy?: TestDatabaseStrategy\n }\n logs?: TestLogMode\n}\n\nexport type TestCleanup = () => unknown | Promise<unknown>\n\nexport type TestSetup<TApp, TContext> = (\n fixture: TestFixture<TApp>,\n) => TContext | Promise<TContext>\n\nexport type TestConfigureOptions<TApp, TContext> = TestOptions & {\n setup?: TestSetup<TApp, TContext>\n}\n\nexport type TestFixture<TApp> = AsyncDisposable & {\n readonly app: TApp\n readonly auth: TestAuth\n readonly messaging: TestMessagingForApp<TApp>\n readonly jobs: TestJobs\n readonly logs: TestLogs\n readonly storage: TestStorage\n defer(cleanup: TestCleanup): void\n client(\n identity?: TestIdentity,\n ): TApp extends AnyBunderstackApp ? BunderstackClient<TApp> : never\n close(): Promise<void>\n}\n\nexport type ConfiguredTestFixture<TApp, TContext> = TestFixture<TApp> & {\n readonly context: TContext\n}\n\nexport type TestFactory<TApp, TContext> = (\n options?: TestOptions,\n) => Promise<ConfiguredTestFixture<TApp, TContext>>\n\nexport type TestMethod<TApp> = {\n (options?: TestOptions): Promise<TestFixture<TApp>>\n configure<TContext = undefined>(\n options: TestConfigureOptions<TApp, TContext>,\n ): TestFactory<TApp, TContext>\n}\n\ntype TestableApp = {\n auth: unknown\n handler(request: Request): Promise<Response>\n close(): Promise<void>\n}\n\nconst defaultTestEnv = {\n NODE_ENV: 'test',\n AUTH_SECRET: 'bunderstack-test-secret',\n BUNDERSTACK_ROLE: 'web',\n} satisfies Record<string, string | undefined>\n\nfunction mergeTestOptions(\n defaults: TestOptions,\n overrides: TestOptions,\n): TestOptions {\n return {\n ...defaults,\n ...overrides,\n env:\n defaults.env || overrides.env\n ? { ...defaults.env, ...overrides.env }\n : undefined,\n database:\n defaults.database || overrides.database\n ? { ...defaults.database, ...overrides.database }\n : undefined,\n }\n}\n\nexport function configureTestApp<\n TApp extends TestableApp,\n TContext = undefined,\n>(\n backend: BunderstackBackend<TApp>,\n options: TestConfigureOptions<TApp, TContext>,\n): TestFactory<TApp, TContext> {\n const { setup, ...defaults } = options\n return async (overrides = {}) => {\n const fixture = await createTestApp(\n backend,\n mergeTestOptions(defaults, overrides),\n )\n try {\n const context = setup ? await setup(fixture) : (undefined as TContext)\n return Object.assign(fixture, { context })\n } catch (cause) {\n try {\n await fixture.close()\n } catch (cleanupCause) {\n throw new AggregateError(\n [cause, cleanupCause],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n }\n}\n\nexport async function createTestApp<TApp extends TestableApp>(\n backend: BunderstackBackend<TApp>,\n options: TestOptions = {},\n): Promise<TestFixture<TApp>> {\n const internals = backend[BACKEND_INTERNALS]\n const source = { ...defaultTestEnv, ...options.env }\n const inspected = internals.inspect(source)\n const storageRoot = await mkdtemp(join(tmpdir(), 'bunderstack-storage-'))\n const resolvedStorage = resolveTestBuckets(\n inspected.config.storage,\n storageRoot,\n )\n const testMessaging = createTestMessaging(inspected.config.messaging)\n const storage = createTestStorage(resolvedStorage)\n const sessions = createTestSessionRegistry()\n const { logger, logs } = createTestLogs(options.logs)\n\n let target\n try {\n target = await createTestDatabaseTarget(inspected.config.database.adapter, {\n mode: options.database?.mode ?? 'memory',\n strategy: options.database?.strategy,\n })\n } catch (cause) {\n await rm(storageRoot, { recursive: true, force: true })\n throw cause\n }\n\n let app: TApp | undefined\n let testingHandle: RuntimeTestingHandle | undefined\n try {\n app = await internals.start(\n source,\n {\n database: target.connection,\n resolvedStorage,\n messagingAdapters: testMessaging.adapters,\n authResolver: sessions.resolver,\n logger,\n forceMemoryRealtime: true,\n backgroundAutoStart: false,\n captureTestingHandle: (handle) => {\n testingHandle = handle\n },\n },\n inspected,\n )\n await provisionForTest(app as object, options.database?.schema ?? 'auto')\n } catch (cause) {\n const cleanupErrors: unknown[] = []\n if (app) {\n try {\n await app.close()\n } catch (error) {\n cleanupErrors.push(error)\n }\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n cleanupErrors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n cleanupErrors.push(error)\n }\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n [cause, ...cleanupErrors],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n\n if (!testingHandle) {\n await app.close()\n await target[Symbol.asyncDispose]()\n await rm(storageRoot, { recursive: true, force: true })\n throw new Error('[bunderstack] runtime did not provide test controls')\n }\n\n const auth = createTestAuth(app, testMessaging.authEmail, sessions)\n const jobs = createTestJobs(testingHandle)\n const deferred: TestCleanup[] = []\n let closePromise: Promise<void> | undefined\n const defer = (cleanup: TestCleanup) => {\n if (closePromise) {\n throw new Error('[bunderstack] cannot defer cleanup after fixture close')\n }\n deferred.push(cleanup)\n }\n const close = () => {\n closePromise ??= (async () => {\n const errors: unknown[] = []\n for (const cleanup of deferred.reverse()) {\n try {\n await cleanup()\n } catch (error) {\n errors.push(error)\n }\n }\n try {\n await app.close()\n } catch (error) {\n errors.push(error)\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n errors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n errors.push(error)\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) {\n throw new AggregateError(errors, '[bunderstack] fixture cleanup failed')\n }\n })()\n return closePromise\n }\n\n return {\n app,\n auth,\n messaging: testMessaging.messaging as TestMessagingForApp<TApp>,\n jobs,\n logs,\n storage,\n defer,\n client: (identity) => testClient(app as never, identity) as never,\n close,\n [Symbol.asyncDispose]: close,\n }\n}\n"]}
1
+ {"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtemp, rm } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport type { BunderstackBackend } from '../backend'\nimport type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client'\nimport type { TestDatabaseStrategy } from '../database/adapter'\nimport type { TestSchemaMode } from '../provision-schema'\nimport type { TestAuth, TestIdentity } from './auth'\nimport type { TestLogs, TestLogMode } from './logs'\nimport type { TestMessagingForApp } from './messaging'\nimport type { TestStorage } from './storage'\n\nimport {\n BACKEND_INTERNALS,\n type RuntimeTestingHandle,\n} from '../backend-internals'\nimport { provisionForTest } from '../provision-schema'\nimport { createTestAuth, createTestSessionRegistry } from './auth'\nimport { testClient } from './client'\nimport { createTestDatabaseTarget } from './database'\nimport { createTestJobs, type TestJobs } from './jobs'\nimport { createTestLogs } from './logs'\nimport { createTestMessaging } from './messaging'\nimport { createTestStorage, resolveTestBuckets } from './storage'\n\nexport type TestOptions = {\n env?: Record<string, string | undefined>\n database?: {\n mode?: 'memory' | 'temporary'\n schema?: TestSchemaMode\n strategy?: TestDatabaseStrategy\n }\n logs?: TestLogMode\n}\n\nexport type TestCleanup = () => unknown | Promise<unknown>\n\nexport type TestSetup<TApp, TContext> = (\n fixture: TestFixture<TApp>,\n) => TContext | Promise<TContext>\n\nexport type TestConfigureOptions<TApp, TContext> = TestOptions & {\n setup?: TestSetup<TApp, TContext>\n}\n\nexport type TestFixture<TApp> = AsyncDisposable & {\n readonly app: TApp\n readonly auth: TestAuth\n readonly messaging: TestMessagingForApp<TApp>\n readonly jobs: TestJobs\n readonly logs: TestLogs\n readonly storage: TestStorage\n defer(cleanup: TestCleanup): void\n client(\n identity?: TestIdentity,\n ): TApp extends AnyBunderstackApp ? BunderstackClient<TApp> : never\n close(): Promise<void>\n}\n\nexport type ConfiguredTestFixture<TApp, TContext> = TestFixture<TApp> & {\n readonly context: TContext\n}\n\nexport type TestFactory<TApp, TContext> = (\n options?: TestOptions,\n) => Promise<ConfiguredTestFixture<TApp, TContext>>\n\nexport type TestMethod<TApp> = {\n (options?: TestOptions): Promise<TestFixture<TApp>>\n configure<TContext = undefined>(\n options: TestConfigureOptions<TApp, TContext>,\n ): TestFactory<TApp, TContext>\n}\n\ntype TestableApp = {\n auth: unknown\n handler(request: Request): Promise<Response>\n close(): Promise<void>\n}\n\nconst defaultTestEnv = {\n NODE_ENV: 'test',\n AUTH_SECRET: 'bunderstack-test-secret',\n} satisfies Record<string, string | undefined>\n\nfunction mergeTestOptions(\n defaults: TestOptions,\n overrides: TestOptions,\n): TestOptions {\n return {\n ...defaults,\n ...overrides,\n env:\n defaults.env || overrides.env\n ? { ...defaults.env, ...overrides.env }\n : undefined,\n database:\n defaults.database || overrides.database\n ? { ...defaults.database, ...overrides.database }\n : undefined,\n }\n}\n\nexport function configureTestApp<\n TApp extends TestableApp,\n TContext = undefined,\n>(\n backend: BunderstackBackend<TApp>,\n options: TestConfigureOptions<TApp, TContext>,\n): TestFactory<TApp, TContext> {\n const { setup, ...defaults } = options\n return async (overrides = {}) => {\n const fixture = await createTestApp(\n backend,\n mergeTestOptions(defaults, overrides),\n )\n try {\n const context = setup ? await setup(fixture) : (undefined as TContext)\n return Object.assign(fixture, { context })\n } catch (cause) {\n try {\n await fixture.close()\n } catch (cleanupCause) {\n throw new AggregateError(\n [cause, cleanupCause],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n }\n}\n\nexport async function createTestApp<TApp extends TestableApp>(\n backend: BunderstackBackend<TApp>,\n options: TestOptions = {},\n): Promise<TestFixture<TApp>> {\n const internals = backend[BACKEND_INTERNALS]\n const source = { ...defaultTestEnv, ...options.env }\n const inspected = internals.inspect(source)\n const storageRoot = await mkdtemp(join(tmpdir(), 'bunderstack-storage-'))\n const resolvedStorage = resolveTestBuckets(\n inspected.config.storage,\n storageRoot,\n )\n const testMessaging = createTestMessaging(inspected.config.messaging)\n const storage = createTestStorage(resolvedStorage)\n const sessions = createTestSessionRegistry()\n const { logger, logs } = createTestLogs(options.logs)\n\n let target\n try {\n target = await createTestDatabaseTarget(inspected.config.database.adapter, {\n mode: options.database?.mode ?? 'memory',\n strategy: options.database?.strategy,\n })\n } catch (cause) {\n await rm(storageRoot, { recursive: true, force: true })\n throw cause\n }\n\n let app: TApp | undefined\n let testingHandle: RuntimeTestingHandle | undefined\n try {\n app = await internals.start(\n source,\n {\n database: target.connection,\n resolvedStorage,\n messagingAdapters: testMessaging.adapters,\n authResolver: sessions.resolver,\n logger,\n captureTestingHandle: (handle) => {\n testingHandle = handle\n },\n },\n inspected,\n )\n await provisionForTest(app as object, options.database?.schema ?? 'auto')\n } catch (cause) {\n const cleanupErrors: unknown[] = []\n if (app) {\n try {\n await app.close()\n } catch (error) {\n cleanupErrors.push(error)\n }\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n cleanupErrors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n cleanupErrors.push(error)\n }\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n [cause, ...cleanupErrors],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n\n if (!testingHandle) {\n await app.close()\n await target[Symbol.asyncDispose]()\n await rm(storageRoot, { recursive: true, force: true })\n throw new Error('[bunderstack] runtime did not provide test controls')\n }\n\n const auth = createTestAuth(app, testMessaging.authEmail, sessions)\n const jobs = createTestJobs(testingHandle)\n const deferred: TestCleanup[] = []\n let closePromise: Promise<void> | undefined\n const defer = (cleanup: TestCleanup) => {\n if (closePromise) {\n throw new Error('[bunderstack] cannot defer cleanup after fixture close')\n }\n deferred.push(cleanup)\n }\n const close = () => {\n closePromise ??= (async () => {\n const errors: unknown[] = []\n for (const cleanup of deferred.reverse()) {\n try {\n await cleanup()\n } catch (error) {\n errors.push(error)\n }\n }\n try {\n await app.close()\n } catch (error) {\n errors.push(error)\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n errors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n errors.push(error)\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) {\n throw new AggregateError(errors, '[bunderstack] fixture cleanup failed')\n }\n })()\n return closePromise\n }\n\n return {\n app,\n auth,\n messaging: testMessaging.messaging as TestMessagingForApp<TApp>,\n jobs,\n logs,\n storage,\n defer,\n client: (identity) => testClient(app as never, identity) as never,\n close,\n [Symbol.asyncDispose]: close,\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { DurableObjectNamespaceLike, DurableObjectStateLike, R2BucketLike } from '../workers/types.js';
2
+ export declare function createFakeState(): DurableObjectStateLike & {
3
+ alarmAt(): number | null;
4
+ };
5
+ export declare function createFakeNamespace<T extends {
6
+ fetch(request: Request): Promise<Response>;
7
+ }>(make: (state: DurableObjectStateLike, name: string) => T): DurableObjectNamespaceLike & {
8
+ instance(name: string): T;
9
+ state(name: string): ReturnType<typeof createFakeState>;
10
+ };
11
+ export declare function createFakeR2(): R2BucketLike & {
12
+ objects: Map<string, {
13
+ bytes: Uint8Array;
14
+ contentType?: string;
15
+ }>;
16
+ };
17
+ //# sourceMappingURL=workers-fakes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers-fakes.d.ts","sourceRoot":"","sources":["../../src/testing/workers-fakes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,YAAY,EAEb,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,eAAe;eAEsB,MAAM,GAAG,IAAI;EAejE;AAED,wBAAgB,mBAAmB,CACjC,CAAC,SAAS;IAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,EACxD,IAAI,EAAE,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;mBAevC,MAAM,GAAG,CAAC;gBACb,MAAM,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC;EAW1D;AAED,wBAAgB,YAAY;;eACe,UAAU;sBAAgB,MAAM;;EAwD1E"}
@@ -0,0 +1,98 @@
1
+ export function createFakeState() {
2
+ let alarm = null;
3
+ const state = {
4
+ storage: {
5
+ async getAlarm() {
6
+ return alarm;
7
+ },
8
+ async setAlarm(time) {
9
+ alarm = typeof time === 'number' ? time : time.getTime();
10
+ },
11
+ async deleteAlarm() {
12
+ alarm = null;
13
+ },
14
+ },
15
+ alarmAt: () => alarm,
16
+ };
17
+ return state;
18
+ }
19
+ export function createFakeNamespace(make) {
20
+ const instances = new Map();
21
+ const entry = (name) => {
22
+ let found = instances.get(name);
23
+ if (!found) {
24
+ const state = createFakeState();
25
+ found = { object: make(state, name), state };
26
+ instances.set(name, found);
27
+ }
28
+ return found;
29
+ };
30
+ const namespace = {
31
+ idFromName: (name) => name,
32
+ get: (id) => ({
33
+ fetch: (input, init) => entry(String(id)).object.fetch(new Request(input, init)),
34
+ }),
35
+ instance: (name) => entry(name).object,
36
+ state: (name) => entry(name).state,
37
+ };
38
+ return namespace;
39
+ }
40
+ export function createFakeR2() {
41
+ const objects = new Map();
42
+ const meta = (key) => {
43
+ const found = objects.get(key);
44
+ return found
45
+ ? {
46
+ key,
47
+ size: found.bytes.byteLength,
48
+ httpMetadata: { contentType: found.contentType },
49
+ }
50
+ : null;
51
+ };
52
+ // Pages of two keys, so a cursor loop is exercised.
53
+ const PAGE = 2;
54
+ const bucket = {
55
+ objects,
56
+ async put(key, value, options) {
57
+ const bytes = typeof value === 'string'
58
+ ? new TextEncoder().encode(value)
59
+ : value instanceof ArrayBuffer
60
+ ? new Uint8Array(value)
61
+ : new Uint8Array(await new Response(value).arrayBuffer());
62
+ objects.set(key, {
63
+ bytes,
64
+ contentType: options?.httpMetadata?.contentType,
65
+ });
66
+ return meta(key);
67
+ },
68
+ async get(key) {
69
+ const found = objects.get(key);
70
+ if (!found)
71
+ return null;
72
+ return {
73
+ ...meta(key),
74
+ body: new Response(found.bytes).body,
75
+ };
76
+ },
77
+ async head(key) {
78
+ return meta(key);
79
+ },
80
+ async delete(key) {
81
+ objects.delete(key);
82
+ },
83
+ async list(options) {
84
+ const keys = [...objects.keys()]
85
+ .filter((key) => key.startsWith(options?.prefix ?? ''))
86
+ .sort();
87
+ const start = options?.cursor ? Number(options.cursor) : 0;
88
+ const truncated = start + PAGE < keys.length;
89
+ return {
90
+ objects: keys.slice(start, start + PAGE).map((key) => meta(key)),
91
+ truncated,
92
+ cursor: truncated ? String(start + PAGE) : undefined,
93
+ };
94
+ },
95
+ };
96
+ return bucket;
97
+ }
98
+ //# sourceMappingURL=workers-fakes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers-fakes.js","sourceRoot":"","sources":["../../src/testing/workers-fakes.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,eAAe;IAC7B,IAAI,KAAK,GAAkB,IAAI,CAAA;IAC/B,MAAM,KAAK,GAA0D;QACnE,OAAO,EAAE;YACP,KAAK,CAAC,QAAQ;gBACZ,OAAO,KAAK,CAAA;YACd,CAAC;YACD,KAAK,CAAC,QAAQ,CAAC,IAAI;gBACjB,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAA;YAC1D,CAAC;YACD,KAAK,CAAC,WAAW;gBACf,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;SACF;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK;KACrB,CAAA;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,mBAAmB,CAEjC,IAAwD;IACxD,MAAM,SAAS,GAAG,IAAI,GAAG,EAGtB,CAAA;IACH,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE;QAC7B,IAAI,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,eAAe,EAAE,CAAA;YAC/B,KAAK,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA;YAC5C,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC5B,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;IACD,MAAM,SAAS,GAGX;QACF,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;QAC1B,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACZ,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;SAC3D,CAAC;QACF,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;QACtC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK;KACnC,CAAA;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuD,CAAA;IAC9E,MAAM,IAAI,GAAG,CAAC,GAAW,EAAuB,EAAE;QAChD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC9B,OAAO,KAAK;YACV,CAAC,CAAC;gBACE,GAAG;gBACH,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU;gBAC5B,YAAY,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE;aACjD;YACH,CAAC,CAAC,IAAI,CAAA;IACV,CAAC,CAAA;IACD,oDAAoD;IACpD,MAAM,IAAI,GAAG,CAAC,CAAA;IACd,MAAM,MAAM,GAA+C;QACzD,OAAO;QACP,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO;YAC3B,MAAM,KAAK,GACT,OAAO,KAAK,KAAK,QAAQ;gBACvB,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;gBACjC,CAAC,CAAC,KAAK,YAAY,WAAW;oBAC5B,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC;oBACvB,CAAC,CAAC,IAAI,UAAU,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;YAC/D,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;gBACf,KAAK;gBACL,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW;aAChD,CAAC,CAAA;YACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAA;QAClB,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,GAAG;YACX,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YACvB,OAAO;gBACL,GAAG,IAAI,CAAC,GAAG,CAAE;gBACb,IAAI,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,KAA4B,CAAC,CAAC,IAAK;aAC7D,CAAA;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG;YACZ,OAAO,IAAI,CAAC,GAAG,CAAC,CAAA;QAClB,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,GAAG;YACd,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACrB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO;YAChB,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;iBAC7B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;iBACtD,IAAI,EAAE,CAAA;YACT,MAAM,KAAK,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC1D,MAAM,SAAS,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAA;YAC5C,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAE,CAAC;gBACjE,SAAS;gBACT,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;aACrD,CAAA;QACH,CAAC;KACF,CAAA;IACD,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["// In-memory stand-ins for Worker bindings, so Durable Object classes and the\n// Worker entry run under `bun test`. Not a full emulation: alarms fire only\n// when a test calls `alarm()`.\nimport type {\n DurableObjectNamespaceLike,\n DurableObjectStateLike,\n R2BucketLike,\n R2ObjectLike,\n} from '../workers/types'\n\nexport function createFakeState() {\n let alarm: number | null = null\n const state: DurableObjectStateLike & { alarmAt(): number | null } = {\n storage: {\n async getAlarm() {\n return alarm\n },\n async setAlarm(time) {\n alarm = typeof time === 'number' ? time : time.getTime()\n },\n async deleteAlarm() {\n alarm = null\n },\n },\n alarmAt: () => alarm,\n }\n return state\n}\n\nexport function createFakeNamespace<\n T extends { fetch(request: Request): Promise<Response> },\n>(make: (state: DurableObjectStateLike, name: string) => T) {\n const instances = new Map<\n string,\n { object: T; state: ReturnType<typeof createFakeState> }\n >()\n const entry = (name: string) => {\n let found = instances.get(name)\n if (!found) {\n const state = createFakeState()\n found = { object: make(state, name), state }\n instances.set(name, found)\n }\n return found\n }\n const namespace: DurableObjectNamespaceLike & {\n instance(name: string): T\n state(name: string): ReturnType<typeof createFakeState>\n } = {\n idFromName: (name) => name,\n get: (id) => ({\n fetch: (input, init) =>\n entry(String(id)).object.fetch(new Request(input, init)),\n }),\n instance: (name) => entry(name).object,\n state: (name) => entry(name).state,\n }\n return namespace\n}\n\nexport function createFakeR2() {\n const objects = new Map<string, { bytes: Uint8Array; contentType?: string }>()\n const meta = (key: string): R2ObjectLike | null => {\n const found = objects.get(key)\n return found\n ? {\n key,\n size: found.bytes.byteLength,\n httpMetadata: { contentType: found.contentType },\n }\n : null\n }\n // Pages of two keys, so a cursor loop is exercised.\n const PAGE = 2\n const bucket: R2BucketLike & { objects: typeof objects } = {\n objects,\n async put(key, value, options) {\n const bytes =\n typeof value === 'string'\n ? new TextEncoder().encode(value)\n : value instanceof ArrayBuffer\n ? new Uint8Array(value)\n : new Uint8Array(await new Response(value).arrayBuffer())\n objects.set(key, {\n bytes,\n contentType: options?.httpMetadata?.contentType,\n })\n return meta(key)\n },\n async get(key) {\n const found = objects.get(key)\n if (!found) return null\n return {\n ...meta(key)!,\n body: new Response(found.bytes as unknown as BodyInit).body!,\n }\n },\n async head(key) {\n return meta(key)\n },\n async delete(key) {\n objects.delete(key)\n },\n async list(options) {\n const keys = [...objects.keys()]\n .filter((key) => key.startsWith(options?.prefix ?? ''))\n .sort()\n const start = options?.cursor ? Number(options.cursor) : 0\n const truncated = start + PAGE < keys.length\n return {\n objects: keys.slice(start, start + PAGE).map((key) => meta(key)!),\n truncated,\n cursor: truncated ? String(start + PAGE) : undefined,\n }\n },\n }\n return bucket\n}\n"]}
package/dist/typeid.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare function encode(bytes: Uint8Array): string;
9
9
  export declare function decode(suffix: string): Uint8Array;
10
10
  /** Whether a string is a valid, non-empty TypeID prefix. */
11
11
  export declare function isValidPrefix(prefix: string): boolean;
12
+ export declare function uuidv7Bytes(now?: number): Uint8Array;
12
13
  /** Generate a new, branded TypeID for the given prefix. */
13
14
  export declare function generate<P extends string>(prefix: P): TypeId<P>;
14
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"typeid.d.ts","sourceRoot":"","sources":["../src/typeid.ts"],"names":[],"mappings":"AAkBA,OAAO,CAAC,MAAM,WAAW,EAAE,OAAO,MAAM,CAAA;AAExC,uFAAuF;AACvF,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAA;CAAE,CAAA;AAE9E,iEAAiE;AACjE,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAWhD;AAED,gFAAgF;AAChF,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAUjD;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAErD;AAOD,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAK/D;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,EACpC,EAAE,EAAE,MAAM,EACV,cAAc,CAAC,EAAE,CAAC,GACjB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAelD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAG5E;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC;;;;;;;GAMjD"}
1
+ {"version":3,"file":"typeid.d.ts","sourceRoot":"","sources":["../src/typeid.ts"],"names":[],"mappings":"AAkBA,OAAO,CAAC,MAAM,WAAW,EAAE,OAAO,MAAM,CAAA;AAExC,uFAAuF;AACvF,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAA;CAAE,CAAA;AAE9E,iEAAiE;AACjE,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAWhD;AAED,gFAAgF;AAChF,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAUjD;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAErD;AAYD,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAAmB,GAAG,UAAU,CAwBhE;AAED,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAK/D;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,EACpC,EAAE,EAAE,MAAM,EACV,cAAc,CAAC,EAAE,CAAC,GACjB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAelD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAG5E;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC;;;;;;;GAMjD"}