@supatype/plugin-sdk 0.1.0-alpha.9

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/src/define.ts ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Builder functions for defining Supatype plugins.
3
+ *
4
+ * These are the primary API surface for plugin authors:
5
+ * defineFieldType() — custom field types (e.g., phone, currency)
6
+ * defineComposite() — field bundles (e.g., SEO, address)
7
+ * defineProvider() — service providers (e.g., Stripe, PostHog)
8
+ * defineWidget() — admin panel widgets (e.g., color picker)
9
+ */
10
+
11
+ import {
12
+ PLUGIN_API_VERSION,
13
+ type FieldTypeDefinition,
14
+ type CompositeDefinition,
15
+ type ProviderDefinition,
16
+ type WidgetDefinition,
17
+ type PluginMeta,
18
+ } from "./types.js"
19
+
20
+ // ─── defineFieldType ─────────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Define a custom field type plugin.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { defineFieldType } from '@supatype/plugin-sdk'
28
+ *
29
+ * export default defineFieldType({
30
+ * name: 'phone',
31
+ * pgType: 'TEXT',
32
+ * tsType: 'string',
33
+ * validate(value) {
34
+ * if (typeof value !== 'string') return 'Must be a string'
35
+ * if (!/^\+\d{7,15}$/.test(value)) return 'Invalid phone number (E.164 format required)'
36
+ * return null
37
+ * },
38
+ * widgetPath: './src/PhoneWidget.tsx',
39
+ * filterOperators: ['eq', 'neq', 'in', 'like'],
40
+ * })
41
+ * ```
42
+ */
43
+ export function defineFieldType<TValue = unknown>(
44
+ definition: FieldTypeDefinition<TValue>,
45
+ ): FieldTypeDefinition<TValue> & { __supatype: "field" } {
46
+ return {
47
+ ...definition,
48
+ meta: {
49
+ name: definition.name,
50
+ description: `Custom field type: ${definition.name}`,
51
+ types: ["field"],
52
+ pluginApi: PLUGIN_API_VERSION,
53
+ ...definition.meta,
54
+ },
55
+ __supatype: "field" as const,
56
+ }
57
+ }
58
+
59
+ // ─── defineComposite ─────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Define a composite plugin (field bundle).
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { defineComposite } from '@supatype/plugin-sdk'
67
+ *
68
+ * export default defineComposite({
69
+ * name: 'seo',
70
+ * label: 'SEO Meta',
71
+ * fields: [
72
+ * { name: 'meta_title', type: 'text', options: { maxLength: 60 } },
73
+ * { name: 'meta_description', type: 'text', options: { maxLength: 160 } },
74
+ * { name: 'og_image', type: 'text' },
75
+ * { name: 'canonical_url', type: 'text' },
76
+ * { name: 'no_index', type: 'boolean', defaultValue: false },
77
+ * ],
78
+ * adminGroup: { collapsible: true, defaultCollapsed: true },
79
+ * })
80
+ * ```
81
+ */
82
+ export function defineComposite(
83
+ definition: CompositeDefinition,
84
+ ): CompositeDefinition & { __supatype: "composite" } {
85
+ return {
86
+ ...definition,
87
+ meta: {
88
+ name: definition.name,
89
+ description: `Composite: ${definition.label}`,
90
+ types: ["composite"],
91
+ pluginApi: PLUGIN_API_VERSION,
92
+ ...definition.meta,
93
+ },
94
+ __supatype: "composite" as const,
95
+ }
96
+ }
97
+
98
+ // ─── defineProvider ──────────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Define a service provider plugin.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { defineProvider, type CommerceProvider } from '@supatype/plugin-sdk'
106
+ *
107
+ * export default defineProvider<StripeConfig>({
108
+ * name: 'stripe',
109
+ * category: 'commerce',
110
+ * label: 'Stripe',
111
+ * configSchema: {
112
+ * secretKey: { type: 'string', label: 'Secret Key', required: true, secret: true },
113
+ * webhookSecret: { type: 'string', label: 'Webhook Secret', required: true, secret: true },
114
+ * },
115
+ * create(config): CommerceProvider {
116
+ * return new StripeCommerceProvider(config)
117
+ * },
118
+ * })
119
+ * ```
120
+ */
121
+ export function defineProvider<TConfig = Record<string, unknown>>(
122
+ definition: ProviderDefinition<TConfig>,
123
+ ): ProviderDefinition<TConfig> & { __supatype: "provider" } {
124
+ return {
125
+ ...definition,
126
+ meta: {
127
+ name: definition.name,
128
+ description: `${definition.category} provider: ${definition.label}`,
129
+ types: ["provider"],
130
+ pluginApi: PLUGIN_API_VERSION,
131
+ ...definition.meta,
132
+ },
133
+ __supatype: "provider" as const,
134
+ }
135
+ }
136
+
137
+ // ─── defineWidget ────────────────────────────────────────────────────────────
138
+
139
+ /**
140
+ * Define a standalone widget plugin.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * import { defineWidget } from '@supatype/plugin-sdk'
145
+ *
146
+ * export default defineWidget({
147
+ * name: 'color-picker',
148
+ * label: 'Colour Picker',
149
+ * compatibleTypes: ['text', 'varchar'],
150
+ * componentPath: './src/ColorPickerWidget.tsx',
151
+ * })
152
+ * ```
153
+ */
154
+ export function defineWidget(
155
+ definition: WidgetDefinition,
156
+ ): WidgetDefinition & { __supatype: "widget" } {
157
+ return {
158
+ ...definition,
159
+ meta: {
160
+ name: definition.name,
161
+ description: `Widget: ${definition.label}`,
162
+ types: ["widget"],
163
+ pluginApi: PLUGIN_API_VERSION,
164
+ ...definition.meta,
165
+ },
166
+ __supatype: "widget" as const,
167
+ }
168
+ }
169
+
170
+ // ─── Plugin definition union ─────────────────────────────────────────────────
171
+
172
+ export type AnyPluginDefinition =
173
+ | (FieldTypeDefinition & { __supatype: "field" })
174
+ | (CompositeDefinition & { __supatype: "composite" })
175
+ | (ProviderDefinition & { __supatype: "provider" })
176
+ | (WidgetDefinition & { __supatype: "widget" })
177
+
178
+ /**
179
+ * Check if a value is a Supatype plugin definition.
180
+ */
181
+ export function isPluginDefinition(value: unknown): value is AnyPluginDefinition {
182
+ return typeof value === "object" && value !== null && "__supatype" in value
183
+ }
184
+
185
+ /**
186
+ * Validate plugin API version compatibility.
187
+ */
188
+ export function checkPluginApiVersion(meta: PluginMeta | undefined): {
189
+ compatible: boolean
190
+ message?: string | undefined
191
+ } {
192
+ if (!meta) return { compatible: true }
193
+
194
+ if (meta.pluginApi !== PLUGIN_API_VERSION) {
195
+ return {
196
+ compatible: false,
197
+ message: `Plugin "${meta.name}" targets plugin API v${meta.pluginApi}, but the current version is v${PLUGIN_API_VERSION}. The plugin may not work correctly.`,
198
+ }
199
+ }
200
+
201
+ return { compatible: true }
202
+ }
package/src/docgen.ts ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Plugin documentation generator.
3
+ *
4
+ * Generates Markdown documentation from a plugin's type definitions.
5
+ */
6
+
7
+ import type {
8
+ FieldTypeDefinition,
9
+ CompositeDefinition,
10
+ ProviderDefinition,
11
+ WidgetDefinition,
12
+ } from "./types.js"
13
+ import type { AnyPluginDefinition } from "./define.js"
14
+
15
+ function escapeMarkdown(text: string): string {
16
+ return text.replace(/[|\\`*_{}[\]()#+\-.!]/g, "\\$&")
17
+ }
18
+
19
+ function generateFieldDocs(def: FieldTypeDefinition & { __supatype: "field" }): string {
20
+ const lines: string[] = []
21
+
22
+ lines.push("## Field Type")
23
+ lines.push("")
24
+ lines.push(`| Property | Value |`)
25
+ lines.push(`| --- | --- |`)
26
+ lines.push(`| Name | \`${def.name}\` |`)
27
+ lines.push(`| Postgres Type | \`${def.pgType}\` |`)
28
+ lines.push(`| TypeScript Type | \`${def.tsType}\` |`)
29
+
30
+ if (def.validate !== undefined) {
31
+ lines.push(`| Validate | \`${def.validate.toString().slice(0, 80)}\` |`)
32
+ }
33
+
34
+ if (def.filterOperators !== undefined) {
35
+ lines.push(`| Filter Operators | ${def.filterOperators.map(o => `\`${o}\``).join(", ")} |`)
36
+ }
37
+
38
+ if (def.widgetPath !== undefined) {
39
+ lines.push(`| Widget Path | \`${def.widgetPath}\` |`)
40
+ }
41
+
42
+ lines.push("")
43
+
44
+ if (def.constraints !== undefined && def.constraints.length > 0) {
45
+ lines.push("### Constraints")
46
+ lines.push("")
47
+ for (const c of def.constraints) {
48
+ lines.push(`- \`${c}\``)
49
+ }
50
+ lines.push("")
51
+ }
52
+
53
+ return lines.join("\n")
54
+ }
55
+
56
+ function generateCompositeDocs(def: CompositeDefinition & { __supatype: "composite" }): string {
57
+ const lines: string[] = []
58
+
59
+ lines.push("## Composite")
60
+ lines.push("")
61
+ lines.push(`**Label:** ${escapeMarkdown(def.label)}`)
62
+ lines.push("")
63
+ lines.push("### Fields")
64
+ lines.push("")
65
+ lines.push(`| Name | Type | Required | Default |`)
66
+ lines.push(`| --- | --- | --- | --- |`)
67
+
68
+ for (const field of def.fields) {
69
+ const req = field.required === true ? "Yes" : "No"
70
+ const defaultVal = field.defaultValue !== undefined ? `\`${String(field.defaultValue)}\`` : "-"
71
+ lines.push(`| ${field.name} | \`${field.type}\` | ${req} | ${defaultVal} |`)
72
+ }
73
+
74
+ lines.push("")
75
+
76
+ if (def.adminGroup !== undefined) {
77
+ lines.push("### Admin Group")
78
+ lines.push("")
79
+ if (def.adminGroup.collapsible !== undefined) {
80
+ lines.push(`- Collapsible: ${def.adminGroup.collapsible}`)
81
+ }
82
+ if (def.adminGroup.defaultCollapsed !== undefined) {
83
+ lines.push(`- Default Collapsed: ${def.adminGroup.defaultCollapsed}`)
84
+ }
85
+ lines.push("")
86
+ }
87
+
88
+ return lines.join("\n")
89
+ }
90
+
91
+ function generateProviderDocs(def: ProviderDefinition & { __supatype: "provider" }): string {
92
+ const lines: string[] = []
93
+
94
+ lines.push("## Provider")
95
+ lines.push("")
96
+ lines.push(`**Category:** ${def.category}`)
97
+ lines.push("")
98
+
99
+ if (def.configSchema && Object.keys(def.configSchema).length > 0) {
100
+ lines.push("### Config Schema")
101
+ lines.push("")
102
+ lines.push(`| Field | Type | Label | Required | Secret |`)
103
+ lines.push(`| --- | --- | --- | --- | --- |`)
104
+
105
+ for (const [key, schema] of Object.entries(def.configSchema)) {
106
+ const req = schema.required === true ? "Yes" : "No"
107
+ const secret = schema.secret === true ? "Yes" : "No"
108
+ lines.push(`| ${key} | \`${schema.type}\` | ${escapeMarkdown(schema.label)} | ${req} | ${secret} |`)
109
+ }
110
+
111
+ lines.push("")
112
+ }
113
+
114
+ return lines.join("\n")
115
+ }
116
+
117
+ function generateWidgetDocs(def: WidgetDefinition & { __supatype: "widget" }): string {
118
+ const lines: string[] = []
119
+
120
+ lines.push("## Widget")
121
+ lines.push("")
122
+ lines.push(`| Property | Value |`)
123
+ lines.push(`| --- | --- |`)
124
+ lines.push(`| Name | \`${def.name}\` |`)
125
+ lines.push(`| Label | ${escapeMarkdown(def.label)} |`)
126
+ lines.push(`| Compatible Types | ${def.compatibleTypes.map(t => `\`${t}\``).join(", ")} |`)
127
+ lines.push(`| Component Path | \`${def.componentPath}\` |`)
128
+ lines.push("")
129
+
130
+ return lines.join("\n")
131
+ }
132
+
133
+ /**
134
+ * Generate Markdown documentation from a plugin definition.
135
+ *
136
+ * The output includes YAML frontmatter with the plugin name, type, and version,
137
+ * followed by type-specific documentation sections.
138
+ */
139
+ export function generatePluginDocs(definition: AnyPluginDefinition): string {
140
+ const name = (definition as { name: string }).name
141
+ const type = definition.__supatype
142
+ const version = definition.meta?.pluginApi ?? 1
143
+
144
+ const frontmatter = [
145
+ "---",
146
+ `name: "${name}"`,
147
+ `type: "${type}"`,
148
+ `version: ${version}`,
149
+ "---",
150
+ ].join("\n")
151
+
152
+ const header = `# ${name}\n`
153
+
154
+ let body: string
155
+
156
+ switch (definition.__supatype) {
157
+ case "field":
158
+ body = generateFieldDocs(definition)
159
+ break
160
+ case "composite":
161
+ body = generateCompositeDocs(definition)
162
+ break
163
+ case "provider":
164
+ body = generateProviderDocs(definition)
165
+ break
166
+ case "widget":
167
+ body = generateWidgetDocs(definition)
168
+ break
169
+ }
170
+
171
+ return `${frontmatter}\n\n${header}\n${body}`
172
+ }
package/src/index.ts ADDED
@@ -0,0 +1,58 @@
1
+ // @supatype/plugin-sdk — Plugin development kit for Supatype extensions
2
+
3
+ // Core types
4
+ export {
5
+ PLUGIN_API_VERSION,
6
+ type PluginType,
7
+ type PluginMeta,
8
+ type FieldTypeDefinition,
9
+ type CompositeDefinition,
10
+ type CompositeFieldDef,
11
+ type ProviderDefinition,
12
+ type ProviderCategory,
13
+ type WidgetDefinition,
14
+ type WidgetProps,
15
+ type CommerceProvider,
16
+ type TrackingProvider,
17
+ type EmailProvider,
18
+ type StorageProvider,
19
+ type AuthProvider,
20
+ type SSLProvider,
21
+ } from "./types.js"
22
+
23
+ // Builder functions
24
+ export {
25
+ defineFieldType,
26
+ defineComposite,
27
+ defineProvider,
28
+ defineWidget,
29
+ isPluginDefinition,
30
+ checkPluginApiVersion,
31
+ type AnyPluginDefinition,
32
+ } from "./define.js"
33
+
34
+ // Documentation generator
35
+ export { generatePluginDocs } from "./docgen.js"
36
+
37
+ // Plugin loading and registry
38
+ export {
39
+ registerPlugin,
40
+ getRegisteredPlugins,
41
+ getPluginsByType,
42
+ getFieldTypePlugin,
43
+ getProviderPlugin,
44
+ clearPluginRegistry,
45
+ isSupatypePlugin,
46
+ sortByLoadOrder,
47
+ detectConflicts,
48
+ validateProviders,
49
+ getPluginFieldTypeMap,
50
+ getPluginFieldPgTypeMap,
51
+ expandPluginComposites,
52
+ type RegisteredPlugin,
53
+ type PluginPackageInfo,
54
+ type PluginConflict,
55
+ type ProviderValidationResult,
56
+ type FieldPgTypeInfo,
57
+ type CompositeExpansion,
58
+ } from "./loader.js"