@getstrata/core 0.5.10 → 0.5.13

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/dist/index.js CHANGED
@@ -149,6 +149,56 @@ function resolveApplicationLogger() {
149
149
  function resolveApplicationDependencies() {
150
150
  return requireActiveApplicationContext().dependencies;
151
151
  }
152
+ // ../../src/core/admin/formatValue.ts
153
+ function formatAdminValue(value, type = "text") {
154
+ if (value === null || value === undefined) {
155
+ return "";
156
+ }
157
+ if (type === "boolean") {
158
+ return value ? "yes" : "no";
159
+ }
160
+ if (type === "number") {
161
+ return String(value);
162
+ }
163
+ if (type === "datetime") {
164
+ if (value instanceof Date) {
165
+ return value.toISOString();
166
+ }
167
+ return String(value);
168
+ }
169
+ if (type === "code") {
170
+ if (typeof value === "string") {
171
+ return value;
172
+ }
173
+ return JSON.stringify(value, null, 2);
174
+ }
175
+ if (typeof value === "object") {
176
+ return JSON.stringify(value);
177
+ }
178
+ return String(value);
179
+ }
180
+ // ../../src/core/admin/registry.ts
181
+ class AdminResourceRegistry {
182
+ resources = new Map;
183
+ register(resource) {
184
+ if (this.resources.has(resource.name)) {
185
+ throw new Error(`Admin resource "${resource.name}" is already registered.`);
186
+ }
187
+ this.resources.set(resource.name, resource);
188
+ }
189
+ get(name) {
190
+ return this.resources.get(name);
191
+ }
192
+ list() {
193
+ return [...this.resources.values()].map(({ handlers: _handlers, ...definition }) => definition);
194
+ }
195
+ all() {
196
+ return [...this.resources.values()];
197
+ }
198
+ clear() {
199
+ this.resources.clear();
200
+ }
201
+ }
152
202
  // ../../src/core/auth/authContext.ts
153
203
  import { AsyncLocalStorage } from "async_hooks";
154
204
  var authContext = new AsyncLocalStorage;
@@ -942,6 +992,66 @@ function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
942
992
  }
943
993
  return groups;
944
994
  }
995
+ function morphMany(definition) {
996
+ return {
997
+ type: "morphMany",
998
+ ...definition
999
+ };
1000
+ }
1001
+ function morphOne(definition) {
1002
+ return {
1003
+ type: "morphOne",
1004
+ ...definition
1005
+ };
1006
+ }
1007
+ function morphTo(definition) {
1008
+ return {
1009
+ type: "morphTo",
1010
+ ...definition
1011
+ };
1012
+ }
1013
+ function indexMorphManyRelation(parents, children, relation) {
1014
+ const groups = new Map;
1015
+ for (const parent of parents) {
1016
+ groups.set(parent[relation.localKey], []);
1017
+ }
1018
+ for (const child of children) {
1019
+ if (child[relation.morphTypeKey] !== relation.morphType) {
1020
+ continue;
1021
+ }
1022
+ const key = child[relation.morphIdKey];
1023
+ const group = groups.get(key);
1024
+ if (!group) {
1025
+ continue;
1026
+ }
1027
+ group.push(child);
1028
+ }
1029
+ return groups;
1030
+ }
1031
+ function indexMorphOneRelation(parents, children, relation) {
1032
+ const grouped = indexMorphManyRelation(parents, children, relation);
1033
+ const result = new Map;
1034
+ for (const parent of parents) {
1035
+ const matches = grouped.get(parent[relation.localKey]) ?? [];
1036
+ result.set(parent[relation.localKey], matches[0]);
1037
+ }
1038
+ return result;
1039
+ }
1040
+ function indexMorphToRelation(children, parentsByType, relation) {
1041
+ const result = new Map;
1042
+ for (const child of children) {
1043
+ const morphType = String(child[relation.morphTypeKey]);
1044
+ const parents = parentsByType.get(morphType);
1045
+ if (!parents) {
1046
+ continue;
1047
+ }
1048
+ const parent = parents.get(child[relation.morphIdKey]);
1049
+ if (parent) {
1050
+ result.set(child[relation.morphIdKey], parent);
1051
+ }
1052
+ }
1053
+ return result;
1054
+ }
945
1055
 
946
1056
  // ../../src/config/database.ts
947
1057
  function readInteger(name, fallback) {
@@ -1145,6 +1255,37 @@ class RepositoryQuery {
1145
1255
  });
1146
1256
  return this;
1147
1257
  }
1258
+ withMorphMany(as, relation, childRepository, options = {}) {
1259
+ this.eagerLoads.push({
1260
+ kind: "morphMany",
1261
+ as,
1262
+ relation,
1263
+ repository: childRepository,
1264
+ options
1265
+ });
1266
+ return this;
1267
+ }
1268
+ withMorphOne(as, relation, childRepository, options = {}) {
1269
+ this.eagerLoads.push({
1270
+ kind: "morphOne",
1271
+ as,
1272
+ relation,
1273
+ repository: childRepository,
1274
+ options
1275
+ });
1276
+ return this;
1277
+ }
1278
+ withMorphTo(as, relation, repositoriesByType, options = {}) {
1279
+ this.eagerLoads.push({
1280
+ kind: "morphTo",
1281
+ as,
1282
+ relation,
1283
+ repository: this.repository,
1284
+ morphRepositories: repositoriesByType,
1285
+ options
1286
+ });
1287
+ return this;
1288
+ }
1148
1289
  async get() {
1149
1290
  const rows = await this.repository.findAll(this.buildOptions());
1150
1291
  return await this.attach(rows);
@@ -1205,6 +1346,33 @@ class RepositoryQuery {
1205
1346
  }));
1206
1347
  continue;
1207
1348
  }
1349
+ if (load.kind === "morphMany") {
1350
+ const relation2 = load.relation;
1351
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
1352
+ result = result.map((row) => ({
1353
+ ...row,
1354
+ [load.as]: grouped2.get(row[relation2.localKey]) ?? []
1355
+ }));
1356
+ continue;
1357
+ }
1358
+ if (load.kind === "morphOne") {
1359
+ const relation2 = load.relation;
1360
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
1361
+ result = result.map((row) => ({
1362
+ ...row,
1363
+ [load.as]: grouped2.get(row[relation2.localKey])
1364
+ }));
1365
+ continue;
1366
+ }
1367
+ if (load.kind === "morphTo") {
1368
+ const relation2 = load.relation;
1369
+ const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
1370
+ result = result.map((row) => ({
1371
+ ...row,
1372
+ [load.as]: grouped2.get(row[relation2.morphIdKey])
1373
+ }));
1374
+ continue;
1375
+ }
1208
1376
  const relation = load.relation;
1209
1377
  const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
1210
1378
  result = result.map((row) => ({
@@ -1481,6 +1649,56 @@ class BaseRepository {
1481
1649
  }, options);
1482
1650
  return indexBelongsToRelation(children, parents, relation);
1483
1651
  }
1652
+ async loadMorphManyForParents(parents, relation, options = {}) {
1653
+ if (parents.length === 0) {
1654
+ return indexMorphManyRelation(parents, [], relation);
1655
+ }
1656
+ const parentIds = [...new Set(parents.map((parent) => parent[relation.localKey]))];
1657
+ const children = await this.findWhere({
1658
+ [relation.morphTypeKey]: relation.morphType,
1659
+ [relation.morphIdKey]: parentIds
1660
+ }, options);
1661
+ return indexMorphManyRelation(parents, children, relation);
1662
+ }
1663
+ async loadMorphOneForParents(parents, relation, options = {}) {
1664
+ const grouped = await this.loadMorphManyForParents(parents, relation, options);
1665
+ const result = new Map;
1666
+ for (const parent of parents) {
1667
+ const matches = grouped.get(parent[relation.localKey]) ?? [];
1668
+ result.set(parent[relation.localKey], matches[0]);
1669
+ }
1670
+ return result;
1671
+ }
1672
+ async loadMorphToForChildren(children, relation, repositoriesByType, options = {}) {
1673
+ if (children.length === 0) {
1674
+ return new Map;
1675
+ }
1676
+ const idsByType = new Map;
1677
+ for (const child of children) {
1678
+ const morphType = String(child[relation.morphTypeKey]);
1679
+ const morphId = child[relation.morphIdKey];
1680
+ const ids = idsByType.get(morphType) ?? new Set;
1681
+ ids.add(morphId);
1682
+ idsByType.set(morphType, ids);
1683
+ }
1684
+ const parentsByType = new Map;
1685
+ for (const [morphType, ids] of idsByType) {
1686
+ const repository = repositoriesByType.get(morphType);
1687
+ if (!repository) {
1688
+ continue;
1689
+ }
1690
+ const ownerKey = repository.getTable().primaryKey;
1691
+ const parents = await repository.withConnection(this.connection).findWhere({
1692
+ [ownerKey]: [...ids]
1693
+ }, options);
1694
+ const indexed = new Map;
1695
+ for (const parent of parents) {
1696
+ indexed.set(parent[ownerKey], parent);
1697
+ }
1698
+ parentsByType.set(morphType, indexed);
1699
+ }
1700
+ return indexMorphToRelation(children, parentsByType, relation);
1701
+ }
1484
1702
  }
1485
1703
  var baseRepository_default = BaseRepository;
1486
1704
  // ../../src/core/database/bindConnection.ts
@@ -2698,18 +2916,7 @@ async function defaultSmtpTransport(config, message) {
2698
2916
  await socket.write(`DATA\r
2699
2917
  `);
2700
2918
  await waitForSmtpResponse(readResponse, ["354"]);
2701
- const payload = [
2702
- `From: ${config.from}`,
2703
- `To: ${message.to}`,
2704
- `Subject: ${message.subject}`,
2705
- "MIME-Version: 1.0",
2706
- "Content-Type: text/plain; charset=utf-8",
2707
- "",
2708
- message.body,
2709
- ".",
2710
- ""
2711
- ].join(`\r
2712
- `);
2919
+ const payload = buildSmtpPayload(config.from, message);
2713
2920
  await socket.write(payload);
2714
2921
  await waitForSmtpResponse(readResponse, ["250"]);
2715
2922
  await socket.write(`QUIT\r
@@ -2719,6 +2926,35 @@ async function defaultSmtpTransport(config, message) {
2719
2926
  socket.end();
2720
2927
  }
2721
2928
  }
2929
+ function buildSmtpPayload(from, message) {
2930
+ const headers = [
2931
+ `From: ${from}`,
2932
+ `To: ${message.to}`,
2933
+ `Subject: ${message.subject}`,
2934
+ "MIME-Version: 1.0"
2935
+ ];
2936
+ if (message.html) {
2937
+ const boundary = `strata-${Date.now().toString(36)}`;
2938
+ headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
2939
+ const parts = [
2940
+ `--${boundary}`,
2941
+ "Content-Type: text/plain; charset=utf-8",
2942
+ "",
2943
+ message.body,
2944
+ `--${boundary}`,
2945
+ "Content-Type: text/html; charset=utf-8",
2946
+ "",
2947
+ message.html,
2948
+ `--${boundary}--`,
2949
+ ""
2950
+ ];
2951
+ return [...headers, "", ...parts, ".", ""].join(`\r
2952
+ `);
2953
+ }
2954
+ headers.push("Content-Type: text/plain; charset=utf-8");
2955
+ return [...headers, "", message.body, ".", ""].join(`\r
2956
+ `);
2957
+ }
2722
2958
 
2723
2959
  class LogMailDriver {
2724
2960
  async send(message) {
@@ -2727,7 +2963,8 @@ class LogMailDriver {
2727
2963
  channel: "mail",
2728
2964
  to: message.to,
2729
2965
  subject: message.subject,
2730
- body: message.body
2966
+ body: message.body,
2967
+ ...message.html ? { html: message.html } : {}
2731
2968
  }));
2732
2969
  }
2733
2970
  }
@@ -4867,6 +5104,150 @@ function resetGracefulShutdownForTests() {
4867
5104
  shutdownInstalled = false;
4868
5105
  shuttingDown = false;
4869
5106
  }
5107
+ // ../../src/core/mail/markdownMail.ts
5108
+ function escapeHtml(value) {
5109
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5110
+ }
5111
+ function stripMarkdown(markdown) {
5112
+ return markdown.replace(/^#{1,6}\s+/gm, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/^[-*]\s+/gm, "\u2022 ").replace(/```[\s\S]*?```/g, "").replace(/\n{3,}/g, `
5113
+
5114
+ `).trim();
5115
+ }
5116
+ function markdownToHtml(markdown) {
5117
+ const escaped = markdown.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5118
+ return escaped.replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>').replace(/^[-*]\s+(.+)$/gm, "<li>$1</li>").replace(/(<li>[\s\S]*?<\/li>\n?)+/g, (block) => `<ul>${block}</ul>`).replace(/```(\w+)?\n([\s\S]*?)```/g, "<pre><code>$2</code></pre>").split(/\n\n+/).map((block) => {
5119
+ if (block.startsWith("<")) {
5120
+ return block;
5121
+ }
5122
+ return `<p>${block.replace(/\n/g, " ")}</p>`;
5123
+ }).join(`
5124
+ `);
5125
+ }
5126
+ function wrapMarkdownMailLayout(bodyHtml, options = {}) {
5127
+ const title = escapeHtml(options.title ?? "GetStrata");
5128
+ const preview = escapeHtml(options.preview ?? "");
5129
+ const footer = escapeHtml(options.footer ?? "Sent by GetStrata");
5130
+ return `<!DOCTYPE html>
5131
+ <html lang="en">
5132
+ <head>
5133
+ <meta charset="utf-8">
5134
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5135
+ <title>${title}</title>
5136
+ <style>
5137
+ body { font-family: system-ui, sans-serif; line-height: 1.5; color: #111827; background: #f9fafb; margin: 0; padding: 24px; }
5138
+ .container { max-width: 640px; margin: 0 auto; background: #ffffff; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; }
5139
+ .header { padding: 20px 24px; border-bottom: 1px solid #e5e7eb; font-weight: 600; }
5140
+ .content { padding: 24px; }
5141
+ .footer { padding: 16px 24px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 14px; }
5142
+ a { color: #2563eb; }
5143
+ code { background: #f3f4f6; padding: 2px 4px; border-radius: 4px; }
5144
+ pre { background: #111827; color: #f9fafb; padding: 12px; border-radius: 6px; overflow-x: auto; }
5145
+ </style>
5146
+ </head>
5147
+ <body>
5148
+ ${preview ? `<span style="display:none;max-height:0;overflow:hidden;">${preview}</span>` : ""}
5149
+ <div class="container">
5150
+ <div class="header">${title}</div>
5151
+ <div class="content">${bodyHtml}</div>
5152
+ <div class="footer">${footer}</div>
5153
+ </div>
5154
+ </body>
5155
+ </html>`;
5156
+ }
5157
+ function renderMarkdownMail(markdown, options = {}) {
5158
+ const bodyHtml = markdownToHtml(markdown.trim());
5159
+ const html = wrapMarkdownMailLayout(bodyHtml, options);
5160
+ const text = stripMarkdown(markdown);
5161
+ return { html, text };
5162
+ }
5163
+ // ../../src/core/mail/markdownMailable.ts
5164
+ function buildMarkdownMailMessage(input) {
5165
+ const rendered = renderMarkdownMail(input.markdown, {
5166
+ title: input.layout?.title ?? input.subject,
5167
+ ...input.layout
5168
+ });
5169
+ return {
5170
+ to: input.to,
5171
+ subject: input.subject,
5172
+ body: rendered.text,
5173
+ html: rendered.html
5174
+ };
5175
+ }
5176
+ async function sendMarkdownMail(mailer2, input) {
5177
+ await mailer2.send(buildMarkdownMailMessage(input));
5178
+ }
5179
+ // ../../src/core/notifications/dispatcher.ts
5180
+ class NotificationDispatcher {
5181
+ mailer;
5182
+ databaseStore;
5183
+ constructor(mailer2, databaseStore = null) {
5184
+ this.mailer = mailer2;
5185
+ this.databaseStore = databaseStore;
5186
+ }
5187
+ async send(notifiable, notification) {
5188
+ for (const channel of notification.via(notifiable)) {
5189
+ if (channel === "mail") {
5190
+ await this.sendMail(notifiable, notification);
5191
+ continue;
5192
+ }
5193
+ if (channel === "database") {
5194
+ await this.sendDatabase(notifiable, notification);
5195
+ }
5196
+ }
5197
+ }
5198
+ async sendMail(notifiable, notification) {
5199
+ const routed = notifiable.routeNotificationFor("mail");
5200
+ if (routed === null) {
5201
+ return;
5202
+ }
5203
+ const message = notification.toMail(notifiable);
5204
+ if (!message) {
5205
+ return;
5206
+ }
5207
+ if (message.markdown) {
5208
+ await this.mailer.send(buildMarkdownMailMessage({
5209
+ to: String(routed),
5210
+ subject: message.subject,
5211
+ markdown: message.markdown
5212
+ }));
5213
+ return;
5214
+ }
5215
+ await this.mailer.send({
5216
+ to: String(routed),
5217
+ subject: message.subject,
5218
+ body: message.body ?? "",
5219
+ ...message.html ? { html: message.html } : {}
5220
+ });
5221
+ }
5222
+ async sendDatabase(notifiable, notification) {
5223
+ if (!this.databaseStore) {
5224
+ return;
5225
+ }
5226
+ const payload = notification.toDatabase(notifiable);
5227
+ if (!payload) {
5228
+ return;
5229
+ }
5230
+ await this.databaseStore.create({
5231
+ userId: Number(notifiable.getNotificationKey()),
5232
+ ...payload
5233
+ });
5234
+ }
5235
+ }
5236
+ function createNotificationDispatcher(mailer2, databaseStore) {
5237
+ return new NotificationDispatcher(mailer2, databaseStore ?? null);
5238
+ }
5239
+ // ../../src/core/notifications/notification.ts
5240
+ class Notification {
5241
+ via(_notifiable) {
5242
+ throw new Error("Notification subclasses must implement via().");
5243
+ }
5244
+ toMail(_notifiable) {
5245
+ return null;
5246
+ }
5247
+ toDatabase(_notifiable) {
5248
+ return null;
5249
+ }
5250
+ }
4870
5251
  // ../../src/core/queue/index.ts
4871
5252
  class Job {
4872
5253
  maxAttempts;
@@ -4933,6 +5314,12 @@ class FailedJobService {
4933
5314
  await this.repository.deleteById(id);
4934
5315
  return failedJob;
4935
5316
  }
5317
+ async delete(id) {
5318
+ const deleted = await this.repository.deleteById(id);
5319
+ if (!deleted) {
5320
+ throw new Error(`Failed job ${id} not found.`);
5321
+ }
5322
+ }
4936
5323
  async flush() {
4937
5324
  const jobs = await this.repository.findAll();
4938
5325
  let deleted = 0;
@@ -4974,9 +5361,6 @@ class JobRegistry {
4974
5361
  }
4975
5362
  var jobRegistry = new JobRegistry;
4976
5363
 
4977
- // ../../src/core/queue/redisQueue.ts
4978
- var {RedisClient: RedisClient3 } = globalThis.Bun;
4979
-
4980
5364
  // ../../src/config/queue.ts
4981
5365
  var queueConfig = {
4982
5366
  driver: process.env.QUEUE_DRIVER === "redis" || process.env.QUEUE_DRIVER === "async" || process.env.QUEUE_DRIVER === "sync" ? process.env.QUEUE_DRIVER : DEFAULT_QUEUE_DRIVER,
@@ -5015,6 +5399,7 @@ async function runQueueJob(envelope, failedJobs) {
5015
5399
  }
5016
5400
 
5017
5401
  // ../../src/core/queue/redisQueue.ts
5402
+ var {RedisClient: RedisClient3 } = globalThis.Bun;
5018
5403
  var QUEUE_LIST_KEY = "workhub:queue:default";
5019
5404
  var QUEUE_HIGH_KEY = "workhub:queue:high";
5020
5405
  var QUEUE_LOW_KEY = "workhub:queue:low";
@@ -5370,6 +5755,7 @@ function validateObject(payload, schema) {
5370
5755
  return output;
5371
5756
  }
5372
5757
  export {
5758
+ wrapMarkdownMailLayout,
5373
5759
  withMigrationLock,
5374
5760
  withMiddleware,
5375
5761
  withErrorHandling,
@@ -5377,15 +5763,18 @@ export {
5377
5763
  validateObject,
5378
5764
  toResourceCollection,
5379
5765
  toPaginatedResourceCollection,
5766
+ stripMarkdown,
5380
5767
  stringRule,
5381
5768
  storageFacade as storage,
5382
5769
  setActiveApplicationContext,
5383
5770
  serializeDate,
5771
+ sendMarkdownMail,
5384
5772
  securedBindRouteModelByKey,
5385
5773
  securedBindRouteModel,
5386
5774
  runWithDatabaseConnection,
5387
5775
  runWithAuthUser,
5388
5776
  runSeedersFromDirectory,
5777
+ runQueueJob,
5389
5778
  runInTransaction,
5390
5779
  runGracefulShutdown,
5391
5780
  runDueScheduledTasks,
@@ -5403,6 +5792,7 @@ export {
5403
5792
  resolveApplicationCache,
5404
5793
  resolveApplicationAuth,
5405
5794
  required,
5795
+ renderMarkdownMail,
5406
5796
  registerShutdownHandler,
5407
5797
  registerModelRepository,
5408
5798
  readSubmittedCsrfTokenFromBody,
@@ -5416,9 +5806,13 @@ export {
5416
5806
  paginatedResponse,
5417
5807
  normalizeMetricPath,
5418
5808
  noContentResponse,
5809
+ morphTo,
5810
+ morphOne,
5811
+ morphMany,
5419
5812
  minLength,
5420
5813
  migrateDatabase,
5421
5814
  maxLength,
5815
+ markdownToHtml,
5422
5816
  mailer,
5423
5817
  mail,
5424
5818
  log,
@@ -5430,6 +5824,9 @@ export {
5430
5824
  isEtagEnabled,
5431
5825
  installGracefulShutdownSignals,
5432
5826
  inferReferencedTable,
5827
+ indexMorphToRelation,
5828
+ indexMorphOneRelation,
5829
+ indexMorphManyRelation,
5433
5830
  indexHasOneRelation,
5434
5831
  indexHasManyRelation,
5435
5832
  indexBelongsToRelation,
@@ -5442,6 +5839,7 @@ export {
5442
5839
  getMigrationStatus,
5443
5840
  getActiveDatabaseConnection,
5444
5841
  freshDatabase,
5842
+ formatAdminValue,
5445
5843
  filterMassAssignable,
5446
5844
  events,
5447
5845
  etagFromResource,
@@ -5459,6 +5857,7 @@ export {
5459
5857
  createQueueWorker,
5460
5858
  createQueue,
5461
5859
  createProductionQueue,
5860
+ createNotificationDispatcher,
5462
5861
  createMetricsMiddleware,
5463
5862
  createMemoryThrottleMiddleware,
5464
5863
  createLoginThrottleMiddleware,
@@ -5474,6 +5873,8 @@ export {
5474
5873
  composeMiddleware,
5475
5874
  compileBlueprint,
5476
5875
  cache,
5876
+ buildSmtpPayload,
5877
+ buildMarkdownMailMessage,
5477
5878
  bindDatabaseConnection2 as bindDatabaseConnection,
5478
5879
  belongsToMany,
5479
5880
  belongsTo,
@@ -5503,6 +5904,8 @@ export {
5503
5904
  PostgresGrammar,
5504
5905
  PolicyGate,
5505
5906
  Policy,
5907
+ NotificationDispatcher,
5908
+ Notification,
5506
5909
  NotFoundError,
5507
5910
  MySqlGrammar,
5508
5911
  Model,
@@ -5526,5 +5929,6 @@ export {
5526
5929
  Blueprint,
5527
5930
  baseRepository_default as BaseRepository,
5528
5931
  BadRequestError,
5529
- AsyncQueue
5932
+ AsyncQueue,
5933
+ AdminResourceRegistry
5530
5934
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.10",
3
+ "version": "0.5.13",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",