rimecms 0.32.0 → 0.33.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.
@@ -39,7 +39,7 @@ const RESOLVE_DEFAULT = {
39
39
  checkbox: (f) => f.get.defaultValue ?? false,
40
40
  toggle: (f) => ensureBoolean(f.get.defaultValue) ?? false,
41
41
  number: (f) => ensureNumber(f.get.defaultValue) ?? 0,
42
- date: (f) => ensureDate(f.get.defaultValue)?.getTime() ?? 0,
42
+ date: (f) => ensureDate(f.get.defaultValue) ?? new Date(0),
43
43
  link: (f) => ensureObject(f.get.defaultValue) ?? {},
44
44
  richText: (f) => ensureObject(f.get.defaultValue) ?? {},
45
45
  relation: () => ({}),
@@ -129,15 +129,17 @@ export declare const templateRelations: (tree: Record<string, string[]>, referen
129
129
  }[]) => string;
130
130
  /** Templates Field Relations */
131
131
  /**
132
- * Generates a foreign key column for a relation field
133
- * Creates a reference to another table's primary key with cascade delete
132
+ * The junction column pointing at one target collection. The property is keyed by the slug, which
133
+ * is how the adapter reads it back; the SQL column and the referenced table carry the table name.
134
134
  *
135
- * @example
136
- * ```typescript
135
+ * ```ts
136
+ * // 'medias'
137
137
  * mediasId: text('medias_id').references(() => medias.id, { onDelete: 'cascade' })
138
+ * // 'eventsCategories'
139
+ * eventsCategoriesId: text('events_categories_id').references(() => events_categories.id, { onDelete: 'cascade' })
138
140
  * ```
139
141
  */
140
- export declare const templateFieldRelationColumn: (table: string) => string;
142
+ export declare const templateFieldRelationColumn: (slug: string) => string;
141
143
  /**
142
144
  * Generates a junction table for many-to-many relationships
143
145
  * Creates a table with references to the owner table and related tables
@@ -147,9 +147,19 @@ export const ${declaredTableProperty(table.slug)} = sqliteTable('${baseTableName
147
147
  */
148
148
  export const templateUniqueRequired = (field, defaultValue) => {
149
149
  const { unique, required } = field;
150
- const defaultStr = required && defaultValue !== undefined ? `.default(${JSON.stringify(defaultValue)})` : '';
150
+ const defaultStr = required && defaultValue !== undefined ? `.default(${templateDefault(defaultValue)})` : '';
151
151
  return `${unique ? '.unique()' : ''}${required ? `.notNull()${defaultStr}` : ''}`;
152
152
  };
153
+ /**
154
+ * A default value as drizzle wants it written. A timestamp column takes a `Date`, which
155
+ * drizzle-kit stores as the same integer a number would have been.
156
+ *
157
+ * ```ts
158
+ * templateDefault('') // ""
159
+ * templateDefault(new Date(0)) // new Date(0)
160
+ * ```
161
+ */
162
+ const templateDefault = (value) => value instanceof Date ? `new Date(${value.getTime()})` : JSON.stringify(value);
153
163
  /** Template rows Relation */
154
164
  /**
155
165
  * The whole schema's relations, in one declaration.
@@ -208,16 +218,19 @@ ${entries.join(',\n')}
208
218
  };
209
219
  /** Templates Field Relations */
210
220
  /**
211
- * Generates a foreign key column for a relation field
212
- * Creates a reference to another table's primary key with cascade delete
221
+ * The junction column pointing at one target collection. The property is keyed by the slug, which
222
+ * is how the adapter reads it back; the SQL column and the referenced table carry the table name.
213
223
  *
214
- * @example
215
- * ```typescript
224
+ * ```ts
225
+ * // 'medias'
216
226
  * mediasId: text('medias_id').references(() => medias.id, { onDelete: 'cascade' })
227
+ * // 'eventsCategories'
228
+ * eventsCategoriesId: text('events_categories_id').references(() => events_categories.id, { onDelete: 'cascade' })
217
229
  * ```
218
230
  */
219
- export const templateFieldRelationColumn = (table) => {
220
- return `${table}Id: text('${s(table)}_id').references(() => ${table}.id, { onDelete: 'cascade' })`;
231
+ export const templateFieldRelationColumn = (slug) => {
232
+ const table = baseTableName(slug);
233
+ return `${slug}Id: text('${table}_id').references(() => ${table}.id, { onDelete: 'cascade' })`;
221
234
  };
222
235
  /**
223
236
  * Generates a junction table for many-to-many relationships
@@ -10,6 +10,7 @@ import type { Adapter } from '../adapter.js';
10
10
  import type { Hook, HookBeforeOperation } from '../pipeline/types.js';
11
11
  import type { Plugin } from '../plugins/index.js';
12
12
  import type { SMTPConfig } from '../plugins/mailer/index.server.js';
13
+ import type { SSEConfig } from '../plugins/sse/types.js';
13
14
  import type { Field } from '../../fields/types.js';
14
15
  import type { RegisterArea, RegisterCollection } from '../../index.js';
15
16
  import type { DashboardEntry } from '../../panel/pages/dashboard/types.js';
@@ -85,6 +86,15 @@ export interface Config {
85
86
  $cache?: CacheConfig;
86
87
  /** SMTP config */
87
88
  $smtp?: SMTPConfig;
89
+ /**
90
+ * The event stream at `/api/sse`: which keys a caller may listen to.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * $sse: { access: (key, user) => key.startsWith('public:') || (key === `user:${user?.id}`) }
95
+ * ```
96
+ */
97
+ $sse?: SSEConfig;
88
98
  /** Custom API routes
89
99
  * @example
90
100
  * routes: {
@@ -23,7 +23,12 @@ export function regenerateModulesDeclaration() {
23
23
  })
24
24
  .filter((block) => block !== null)
25
25
  .join('\n\n');
26
- writeFileSync(path.resolve(process.cwd(), 'src/rime.modules.generated.d.ts'), `${blocks}\n`);
26
+ // Nothing to declare, no file.
27
+ const file = path.resolve(process.cwd(), 'src/rime.modules.generated.d.ts');
28
+ if (blocks)
29
+ writeFileSync(file, `${blocks}\n`);
30
+ else
31
+ rmSync(file, { force: true });
27
32
  }
28
33
  export function rime() {
29
34
  /** Two reserved names, always resolved to *the currently running app's* generated
@@ -5,8 +5,8 @@ import { mailer } from '../mailer/index.server.js';
5
5
  import { sse } from '../sse/index.server.js';
6
6
  /** The plugins rime adds to every config, server half. Two of the four are conditional. */
7
7
  export const defaultPlugins = (config) => [
8
- // Server Sent Events
9
- sse(),
8
+ // The event stream, `$sse` says which keys an app opens
9
+ sse(config.$sse),
10
10
  // Cache plugin, with default isEnabled: event => !event.locals.user
11
11
  cache(config.$cache || {}),
12
12
  // Init endpoint, dev only
@@ -1,18 +1,20 @@
1
1
  import type { RouteConfig } from '../../config/types.js';
2
2
  import type { CacheConfig } from '../cache/types.js';
3
3
  import type { SMTPConfig } from '../mailer/index.server.js';
4
+ import type { SSEConfig } from '../sse/types.js';
4
5
  import type { Plugin } from '../index.js';
5
6
  /**
6
7
  * What the plugin step reads off a config.
7
8
  *
8
9
  * Both halves of `defaultPlugins` take this rather than `Config` or `SanitizedConfigClient`: the
9
- * step runs on both sides, and naming either one there would pin it to a side. `$cache` and
10
- * `$smtp` are what the server half branches on; a client build reads neither, and still takes the
11
- * argument so the two halves are the same function.
10
+ * step runs on both sides, and naming either one there would pin it to a side. `$cache`, `$smtp`
11
+ * and `$sse` are what the server half reads; a client build reads none of them, and still takes
12
+ * the argument so the two halves are the same function.
12
13
  */
13
14
  export type PluginHost = {
14
15
  plugins?: Plugin[];
15
16
  $routes?: Record<string, RouteConfig>;
16
17
  $cache?: CacheConfig;
17
18
  $smtp?: SMTPConfig;
19
+ $sse?: SSEConfig;
18
20
  };
@@ -7,4 +7,4 @@
7
7
  * - The client's handler is called with that event.
8
8
  */
9
9
  export { openSse } from './client.js';
10
- export type { SSEAccess, SSEEvent, SSEHandler } from './types.js';
10
+ export type { SSEAccess, SSEConfig, SSEEvent, SSEHandler } from './types.js';
@@ -1,7 +1,7 @@
1
1
  import { emit } from './bus.server.js';
2
- import type { SSEAccess } from './types.js';
2
+ import type { SSEConfig } from './types.js';
3
3
  /**
4
- * The event stream, at `/api/sse`.
4
+ * The event stream, at `/api/sse`. Added to every config, configured by its `$sse` key.
5
5
  *
6
6
  * Send with `rime.sse.emit(key, event, payload)`, listen with `openSse(keys, handler)`.
7
7
  *
@@ -9,11 +9,10 @@ import type { SSEAccess } from './types.js';
9
9
  * otherwise, so an app opens its own deliberately rather than by leaving a default alone.
10
10
  *
11
11
  * @example
12
- * sse({ access: (key) => key.startsWith('public:') })
12
+ * // rime.config.server.ts
13
+ * $sse: { access: (key) => key.startsWith('public:') }
13
14
  */
14
- export declare const sse: (options?: {
15
- access?: SSEAccess;
16
- }) => {
15
+ export declare const sse: (options?: SSEConfig) => {
17
16
  readonly name: "sse";
18
17
  readonly actions: {
19
18
  readonly emit: (key: string, event: string, payload?: Record<string, any>) => void;
@@ -1,7 +1,7 @@
1
1
  import { definePlugin } from '../index.js';
2
2
  import { connect, emit } from './bus.server.js';
3
3
  /**
4
- * The event stream, at `/api/sse`.
4
+ * The event stream, at `/api/sse`. Added to every config, configured by its `$sse` key.
5
5
  *
6
6
  * Send with `rime.sse.emit(key, event, payload)`, listen with `openSse(keys, handler)`.
7
7
  *
@@ -9,7 +9,8 @@ import { connect, emit } from './bus.server.js';
9
9
  * otherwise, so an app opens its own deliberately rather than by leaving a default alone.
10
10
  *
11
11
  * @example
12
- * sse({ access: (key) => key.startsWith('public:') })
12
+ * // rime.config.server.ts
13
+ * $sse: { access: (key) => key.startsWith('public:') }
13
14
  */
14
15
  export const sse = definePlugin((options) => ({
15
16
  name: 'sse',
@@ -14,3 +14,8 @@ export type SSEHandler = (event: SSEEvent) => void | Promise<void>;
14
14
  * point of naming keys up front rather than filtering in the browser.
15
15
  */
16
16
  export type SSEAccess = (key: string, user?: User) => boolean;
17
+ /** The `$sse` key of the config. */
18
+ export type SSEConfig = {
19
+ /** Which keys, besides the panel's own, a caller may listen to. None without it. */
20
+ access?: SSEAccess;
21
+ };
package/dist/types.d.ts CHANGED
@@ -13,6 +13,7 @@ export type { Link } from './fields/link/types.js';
13
13
  export type { WithRelationPopulated } from './core/fields/types.js';
14
14
  export type { RouteHandlers } from './core/routes/handler.server.js';
15
15
  export type { Plugin } from './core/plugins/index.js';
16
+ export type { SSEAccess, SSEConfig } from './core/plugins/sse/types.js';
16
17
  export type { Rime, RimeContext } from './core/rime.server.js';
17
18
  export type { BlocksFieldBlockRenderTitle } from './fields/blocks/index.js';
18
19
  export type { RichTextFeature, RichTextFeatureMark, RichTextFeatureNode } from './fields/rich-text/core/types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimecms",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "homepage": "https://github.com/bienbiendev/rime",
5
5
  "packageManager": "bun@1.3.0",
6
6
  "scripts": {