diff --git a/docs/writeback-spec-coverage.md b/docs/writeback-spec-coverage.md index 6f699bd3..0245c03f 100644 --- a/docs/writeback-spec-coverage.md +++ b/docs/writeback-spec-coverage.md @@ -22,6 +22,7 @@ Contract-backed means the endpoint uses `contractEndpoint(...)`, loads its reque | gmail | None | 0 | 3 | Inline JS schemas. | | google-calendar | None | 0 | 1 | Inline JS schemas. | | google-drive | None | 0 | 2 | Inline JS schemas. | +| granola | None | 0 | 2 | Inline JS schemas. | | hubspot | None | 0 | 4 | Inline JS schemas. | | intercom | None | 0 | 3 | Inline JS schemas. | | jira | None | 0 | 4 | Inline JS schemas. | @@ -31,6 +32,7 @@ Contract-backed means the endpoint uses `contractEndpoint(...)`, loads its reque | pipedrive | None | 0 | 4 | Inline JS schemas. | | postgres | None | 0 | 2 | Inline JS schemas; database/table shape is runtime-native rather than provider OpenAPI. | | redis | None | 0 | 2 | Inline JS schemas; key/value shape is runtime-native rather than provider OpenAPI. | +| reddit | None | 0 | 2 | Inline JS schemas. | | s3 | None | 0 | 2 | Inline JS schemas. | | salesforce | None | 0 | 5 | Inline JS schemas. | | sharepoint | None | 0 | 2 | Inline JS schemas. | diff --git a/packages/granola/discovery/granola/.adapter.md b/packages/granola/discovery/granola/.adapter.md new file mode 100644 index 00000000..1231d26f --- /dev/null +++ b/packages/granola/discovery/granola/.adapter.md @@ -0,0 +1,63 @@ +# Granola adapter + +The Granola adapter exposes meeting notes and folders under `/granola`, with writeback routes for creating notes and folders. + +Read-only mounts: +- `/granola/notes/.json` - Note records. +- `/granola/notes/by-id/.json` - Note lookup aliases. +- `/granola/folders/.json` - Folder records. +- `/granola/folders/by-id/.json` - Folder lookup aliases. + +Resources: + +| Resource | Schema | Create example | ID pattern | What it does | +|---|---|---|---|---| +| `/granola/notes/.json` | `/granola/notes/.schema.json` | `/granola/notes/.create.example.json` | `^not_[A-Za-z0-9]{14}$` | Creates a Granola note. | +| `/granola/folders/.json` | `/granola/folders/.schema.json` | `/granola/folders/.create.example.json` | `^fol_[A-Za-z0-9]{14}$` | Creates a Granola folder. | + +## Operations + +| To... | Do... | +|---|---| +| Read | `cat ` after listing the resource directory or following an alias when one is available. Use the resource table and ID patterns below to determine whether a resource uses a bare id, an adapter-specific slug/id filename, or an exact sidecar path such as `content.md`. | +| Edit | Write the resource update payload to the canonical resource path. For JSON resources, included mutable fields PATCH; fields marked `readOnly` in `.schema.json` are rejected. | +| Create | Write JSON to any non-canonical filename such as `create request.json`. The adapter creates the record at its canonical resource path and rewrites the draft as `{ "created": "", "path": "", "url": "" }`. | +| Ignore | Editor scratch files named `partial.json`, `.tmp.json`, `.partial.json`, `*.tmp.json`, or `*.partial.json` are ignored and never treated as create drafts. | +| Delete | `rm ` for canonical records. | + +## ID Patterns +- `/granola/notes/.json`: `^not_[A-Za-z0-9]{14}$`. Filenames that do not match this pattern are treated as create drafts. +- `/granola/folders/.json`: `^fol_[A-Za-z0-9]{14}$`. Filenames that do not match this pattern are treated as create drafts. + +## Write field contracts + +### Create Granola note + +Resource: `/granola/notes/.json` +Schema: `/granola/notes/.schema.json` +Create example: `/granola/notes/.create.example.json` +Required fields: `title`. +Optional fields: `summary_markdown`, `summary_text`, `folder_membership`. + +Fields: + +- `title` (required, string) - Note title. +- `summary_markdown` (optional, string) - Markdown note body. +- `summary_text` (optional, string) - Plain-text note summary. +- `folder_membership` (optional, array) - Folders the note belongs to. + +### Create Granola folder + +Resource: `/granola/folders/.json` +Schema: `/granola/folders/.schema.json` +Create example: `/granola/folders/.create.example.json` +Required fields: `name`. +Optional fields: `parent_folder_id`. + +Fields: + +- `name` (required, string) - Folder name. +- `parent_folder_id` (optional, string) - Parent Granola folder id (`fol_…`). Omit for a top-level folder. + +## Create Examples +Read the resource `.schema.json` first, then use the sibling `.create.example.json` as a minimal create document. The example intentionally omits read-only fields. diff --git a/packages/granola/discovery/granola/folders/.create.example.json b/packages/granola/discovery/granola/folders/.create.example.json new file mode 100644 index 00000000..a23a4b6b --- /dev/null +++ b/packages/granola/discovery/granola/folders/.create.example.json @@ -0,0 +1,3 @@ +{ + "name": "Replace example folder name" +} diff --git a/packages/granola/discovery/granola/folders/.schema.json b/packages/granola/discovery/granola/folders/.schema.json new file mode 100644 index 00000000..2f3b0767 --- /dev/null +++ b/packages/granola/discovery/granola/folders/.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Granola folder", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Folder name.", + "minLength": 1 + }, + "parent_folder_id": { + "type": "string", + "description": "Parent Granola folder id (`fol_…`). Omit for a top-level folder." + }, + "id": { + "type": "string", + "description": "Provider canonical record id.", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "Provider creation timestamp.", + "readOnly": true + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "Provider last update timestamp.", + "readOnly": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Provider URL for the record.", + "readOnly": true + }, + "identifier": { + "type": "string", + "description": "Provider human-readable identifier or key.", + "readOnly": true + }, + "provider": { + "type": "string", + "description": "Relayfile provider name.", + "readOnly": true + }, + "objectType": { + "type": "string", + "description": "Relayfile object type.", + "readOnly": true + }, + "objectId": { + "type": "string", + "description": "Relayfile object id.", + "readOnly": true + }, + "workspaceId": { + "type": "string", + "description": "Relayfile workspace id.", + "readOnly": true + }, + "connectionId": { + "type": "string", + "description": "Relayfile connection id.", + "readOnly": true + }, + "_webhook": { + "type": "object", + "description": "Provider webhook metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + }, + "_connection": { + "type": "object", + "description": "Relayfile connection metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + } + }, + "additionalProperties": false, + "description": "Full resource record schema. Fields marked readOnly are synced from the provider and cannot be written by agents." +} diff --git a/packages/granola/discovery/granola/notes/.create.example.json b/packages/granola/discovery/granola/notes/.create.example.json new file mode 100644 index 00000000..85bffc0a --- /dev/null +++ b/packages/granola/discovery/granola/notes/.create.example.json @@ -0,0 +1,4 @@ +{ + "title": "Replace example note title", + "summary_markdown": "# Notes\n\nReplace example note body." +} diff --git a/packages/granola/discovery/granola/notes/.schema.json b/packages/granola/discovery/granola/notes/.schema.json new file mode 100644 index 00000000..274c1df5 --- /dev/null +++ b/packages/granola/discovery/granola/notes/.schema.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Granola note", + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "description": "Note title.", + "minLength": 1 + }, + "summary_markdown": { + "type": "string", + "description": "Markdown note body." + }, + "summary_text": { + "type": "string", + "description": "Plain-text note summary." + }, + "folder_membership": { + "type": "array", + "description": "Folders the note belongs to.", + "items": { + "type": "object", + "description": "Folder membership entry. Provide the Granola folder `id` (`fol_…`). List `/granola/folders/` to find available folders.", + "additionalProperties": true + } + }, + "id": { + "type": "string", + "description": "Provider canonical record id.", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "Provider creation timestamp.", + "readOnly": true + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "Provider last update timestamp.", + "readOnly": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Provider URL for the record.", + "readOnly": true + }, + "identifier": { + "type": "string", + "description": "Provider human-readable identifier or key.", + "readOnly": true + }, + "provider": { + "type": "string", + "description": "Relayfile provider name.", + "readOnly": true + }, + "objectType": { + "type": "string", + "description": "Relayfile object type.", + "readOnly": true + }, + "objectId": { + "type": "string", + "description": "Relayfile object id.", + "readOnly": true + }, + "workspaceId": { + "type": "string", + "description": "Relayfile workspace id.", + "readOnly": true + }, + "connectionId": { + "type": "string", + "description": "Relayfile connection id.", + "readOnly": true + }, + "_webhook": { + "type": "object", + "description": "Provider webhook metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + }, + "_connection": { + "type": "object", + "description": "Relayfile connection metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + } + }, + "additionalProperties": false, + "description": "Full resource record schema. Fields marked readOnly are synced from the provider and cannot be written by agents." +} diff --git a/packages/reddit/discovery/reddit/.adapter.md b/packages/reddit/discovery/reddit/.adapter.md new file mode 100644 index 00000000..aa09e5b9 --- /dev/null +++ b/packages/reddit/discovery/reddit/.adapter.md @@ -0,0 +1,65 @@ +# Reddit adapter + +The Reddit adapter exposes tracked subreddits and posts under `/reddit`, with writeback routes for tracking subreddits and creating posts. + +Read-only mounts: +- `/reddit/subreddits/.json` - Tracked subreddit records. +- `/reddit/subreddits//posts/__<postId>.json` - Post records scoped by subreddit. +- `/reddit/posts/by-id/<subreddit>__<postId>.json` - Post lookup aliases. +- `/reddit/posts/by-status/<status>/<subreddit>__<postId>.json` - Post status aliases. + +Resources: + +| Resource | Schema | Create example | ID pattern | What it does | +|---|---|---|---|---| +| `/reddit/subreddits/<id>.json` | `/reddit/subreddits/.schema.json` | `/reddit/subreddits/.create.example.json` | `^[A-Za-z0-9_][A-Za-z0-9_-]{1,63}$` | Starts tracking a subreddit so its posts sync under `/reddit`. | +| `/reddit/subreddits/{subreddit}/posts/<id>.json` | `/reddit/subreddits/{subreddit}/posts/.schema.json` | `/reddit/subreddits/{subreddit}/posts/.create.example.json` | `^[A-Za-z0-9_/-]+$` | Submits a post to the subreddit named by the path. | + +## Operations + +| To... | Do... | +|---|---| +| Read | `cat <canonical-resource-path>` after listing the resource directory or following an alias when one is available. Use the resource table and ID patterns below to determine whether a resource uses a bare id, an adapter-specific slug/id filename, or an exact sidecar path such as `content.md`. | +| Edit | Write the resource update payload to the canonical resource path. For JSON resources, included mutable fields PATCH; fields marked `readOnly` in `.schema.json` are rejected. | +| Create | Write JSON to any non-canonical filename such as `create request.json`. The adapter creates the record at its canonical resource path and rewrites the draft as `{ "created": "<real-id>", "path": "<canonical-resource-path>", "url": "<provider-url>" }`. | +| Ignore | Editor scratch files named `partial.json`, `.tmp.json`, `.partial.json`, `*.tmp.json`, or `*.partial.json` are ignored and never treated as create drafts. | +| Delete | `rm <canonical-resource-path>` for canonical records. | + +## ID Patterns +- `/reddit/subreddits/<id>.json`: `^[A-Za-z0-9_][A-Za-z0-9_-]{1,63}$`. Filenames that do not match this pattern are treated as create drafts. +- `/reddit/subreddits/{subreddit}/posts/<id>.json`: `^[A-Za-z0-9_/-]+$`. Filenames that do not match this pattern are treated as create drafts. + +## Write field contracts + +### Track Reddit subreddit + +Resource: `/reddit/subreddits/<id>.json` +Schema: `/reddit/subreddits/.schema.json` +Create example: `/reddit/subreddits/.create.example.json` +Required fields: `name`. +Optional fields: none. + +Fields: + +- `name` (required, string) - Subreddit name without the `r/` prefix. + +### Create Reddit post + +Resource: `/reddit/subreddits/{subreddit}/posts/<id>.json` +Schema: `/reddit/subreddits/{subreddit}/posts/.schema.json` +Create example: `/reddit/subreddits/{subreddit}/posts/.create.example.json` +Required fields: `title`. +Optional fields: `text`, `link_url`, `kind`, `flair_id`, `nsfw`, `spoiler`. + +Fields: + +- `title` (required, string) - Post title. +- `text` (optional, string) - Self-post body. Used when `kind` is `self`. +- `link_url` (optional, string, uri) - External URL for link posts. Used when `kind` is `link`. +- `kind` (optional, enum) - Post kind. Defaults to `link` when `link_url` is provided, otherwise `self`. Allowed values: `self`, `link`. +- `flair_id` (optional, string) - Subreddit flair template id. +- `nsfw` (optional, boolean) - Whether the post is marked NSFW. +- `spoiler` (optional, boolean) - Whether the post is marked as a spoiler. + +## Create Examples +Read the resource `.schema.json` first, then use the sibling `.create.example.json` as a minimal create document. The example intentionally omits read-only fields. diff --git a/packages/reddit/discovery/reddit/subreddits/.create.example.json b/packages/reddit/discovery/reddit/subreddits/.create.example.json new file mode 100644 index 00000000..d0c505b8 --- /dev/null +++ b/packages/reddit/discovery/reddit/subreddits/.create.example.json @@ -0,0 +1,3 @@ +{ + "name": "selfhosted" +} diff --git a/packages/reddit/discovery/reddit/subreddits/.schema.json b/packages/reddit/discovery/reddit/subreddits/.schema.json new file mode 100644 index 00000000..59ecb5ed --- /dev/null +++ b/packages/reddit/discovery/reddit/subreddits/.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Track Reddit subreddit", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Subreddit name without the `r/` prefix.", + "minLength": 1 + }, + "id": { + "type": "string", + "description": "Provider canonical record id.", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "Provider creation timestamp.", + "readOnly": true + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "Provider last update timestamp.", + "readOnly": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Provider URL for the record.", + "readOnly": true + }, + "identifier": { + "type": "string", + "description": "Provider human-readable identifier or key.", + "readOnly": true + }, + "provider": { + "type": "string", + "description": "Relayfile provider name.", + "readOnly": true + }, + "objectType": { + "type": "string", + "description": "Relayfile object type.", + "readOnly": true + }, + "objectId": { + "type": "string", + "description": "Relayfile object id.", + "readOnly": true + }, + "workspaceId": { + "type": "string", + "description": "Relayfile workspace id.", + "readOnly": true + }, + "connectionId": { + "type": "string", + "description": "Relayfile connection id.", + "readOnly": true + }, + "_webhook": { + "type": "object", + "description": "Provider webhook metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + }, + "_connection": { + "type": "object", + "description": "Relayfile connection metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + } + }, + "additionalProperties": false, + "description": "Full resource record schema. Fields marked readOnly are synced from the provider and cannot be written by agents." +} diff --git a/packages/reddit/discovery/reddit/subreddits/{subreddit}/posts/.create.example.json b/packages/reddit/discovery/reddit/subreddits/{subreddit}/posts/.create.example.json new file mode 100644 index 00000000..514a1b13 --- /dev/null +++ b/packages/reddit/discovery/reddit/subreddits/{subreddit}/posts/.create.example.json @@ -0,0 +1,5 @@ +{ + "title": "Replace example post title", + "text": "Replace example post body.", + "kind": "self" +} diff --git a/packages/reddit/discovery/reddit/subreddits/{subreddit}/posts/.schema.json b/packages/reddit/discovery/reddit/subreddits/{subreddit}/posts/.schema.json new file mode 100644 index 00000000..03f2c33d --- /dev/null +++ b/packages/reddit/discovery/reddit/subreddits/{subreddit}/posts/.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Reddit post", + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "description": "Post title.", + "minLength": 1 + }, + "text": { + "type": "string", + "description": "Self-post body. Used when `kind` is `self`." + }, + "link_url": { + "type": "string", + "format": "uri", + "description": "External URL for link posts. Used when `kind` is `link`." + }, + "kind": { + "enum": [ + "self", + "link" + ], + "description": "Post kind. Defaults to `link` when `link_url` is provided, otherwise `self`." + }, + "flair_id": { + "type": "string", + "description": "Subreddit flair template id." + }, + "nsfw": { + "type": "boolean", + "description": "Whether the post is marked NSFW." + }, + "spoiler": { + "type": "boolean", + "description": "Whether the post is marked as a spoiler." + }, + "id": { + "type": "string", + "description": "Provider canonical record id.", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "Provider creation timestamp.", + "readOnly": true + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "Provider last update timestamp.", + "readOnly": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Provider URL for the record.", + "readOnly": true + }, + "identifier": { + "type": "string", + "description": "Provider human-readable identifier or key.", + "readOnly": true + }, + "provider": { + "type": "string", + "description": "Relayfile provider name.", + "readOnly": true + }, + "objectType": { + "type": "string", + "description": "Relayfile object type.", + "readOnly": true + }, + "objectId": { + "type": "string", + "description": "Relayfile object id.", + "readOnly": true + }, + "workspaceId": { + "type": "string", + "description": "Relayfile workspace id.", + "readOnly": true + }, + "connectionId": { + "type": "string", + "description": "Relayfile connection id.", + "readOnly": true + }, + "_webhook": { + "type": "object", + "description": "Provider webhook metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + }, + "_connection": { + "type": "object", + "description": "Relayfile connection metadata captured during sync.", + "readOnly": true, + "additionalProperties": true + } + }, + "additionalProperties": false, + "description": "Full resource record schema. Fields marked readOnly are synced from the provider and cannot be written by agents." +} diff --git a/packages/reddit/package.json b/packages/reddit/package.json index fa2c285b..32d41f20 100644 --- a/packages/reddit/package.json +++ b/packages/reddit/package.json @@ -28,7 +28,8 @@ } }, "files": [ - "dist" + "dist", + "discovery" ], "scripts": { "typecheck": "tsc --noEmit -p tsconfig.json", diff --git a/scripts/writeback-discovery-data.mjs b/scripts/writeback-discovery-data.mjs index 06134e84..04e58a07 100644 --- a/scripts/writeback-discovery-data.mjs +++ b/scripts/writeback-discovery-data.mjs @@ -180,6 +180,30 @@ export const adapters = [ endpoint('/gitlab/projects/{projectPath}/issues/{issueIid}__{slug}/comments/new.json', 'Create GitLab issue note', 'Creates a note on an issue.', ['body'], gitlabIssueNoteProps(), { body: 'Replace example note body.' }), ], }, + { + slug: 'granola', + title: 'Granola adapter', + overview: + 'The Granola adapter exposes meeting notes and folders under `/granola`, with writeback routes for creating notes and folders.', + readPaths: [ + ['/granola/notes/<noteId>.json', 'Note records.'], + ['/granola/notes/by-id/<noteId>.json', 'Note lookup aliases.'], + ['/granola/folders/<folderId>.json', 'Folder records.'], + ['/granola/folders/by-id/<folderId>.json', 'Folder lookup aliases.'], + ], + endpoints: [ + endpoint('/granola/notes/new.json', 'Create Granola note', 'Creates a Granola note.', ['title'], { + title: str('Note title.', undefined, { minLength: 1 }), + summary_markdown: str('Markdown note body.'), + summary_text: str('Plain-text note summary.'), + folder_membership: arr(obj('Folder membership entry. Provide the Granola folder `id` (`fol_…`). List `/granola/folders/` to find available folders.'), 'Folders the note belongs to.'), + }, { title: 'Replace example note title', summary_markdown: '# Notes\n\nReplace example note body.' }), + endpoint('/granola/folders/new.json', 'Create Granola folder', 'Creates a Granola folder.', ['name'], { + name: str('Folder name.', undefined, { minLength: 1 }), + parent_folder_id: str('Parent Granola folder id (`fol_…`). Omit for a top-level folder.'), + }, { name: 'Replace example folder name' }), + ], + }, { slug: 'hubspot', title: 'HubSpot adapter', @@ -371,6 +395,32 @@ export const adapters = [ endpoint('/pipedrive/activities/new.json', 'Create Pipedrive activity', 'Creates a Pipedrive activity.', ['subject'], pipedriveActivityProps(), { subject: 'Replace example activity subject' }), ], }, + { + slug: 'reddit', + title: 'Reddit adapter', + overview: + 'The Reddit adapter exposes tracked subreddits and posts under `/reddit`, with writeback routes for tracking subreddits and creating posts.', + readPaths: [ + ['/reddit/subreddits/<subreddit>.json', 'Tracked subreddit records.'], + ['/reddit/subreddits/<subreddit>/posts/<title>__<postId>.json', 'Post records scoped by subreddit.'], + ['/reddit/posts/by-id/<subreddit>__<postId>.json', 'Post lookup aliases.'], + ['/reddit/posts/by-status/<status>/<subreddit>__<postId>.json', 'Post status aliases.'], + ], + endpoints: [ + endpoint('/reddit/subreddits/new.json', 'Track Reddit subreddit', 'Starts tracking a subreddit so its posts sync under `/reddit`.', ['name'], { + name: str('Subreddit name without the `r/` prefix.', undefined, { minLength: 1 }), + }, { name: 'selfhosted' }), + endpoint('/reddit/subreddits/{subreddit}/posts/new.json', 'Create Reddit post', 'Submits a post to the subreddit named by the path.', ['title'], { + title: str('Post title.', undefined, { minLength: 1 }), + text: str('Self-post body. Used when `kind` is `self`.'), + link_url: str('External URL for link posts. Used when `kind` is `link`.', 'uri'), + kind: en(['self', 'link'], 'Post kind. Defaults to `link` when `link_url` is provided, otherwise `self`.'), + flair_id: str('Subreddit flair template id.'), + nsfw: bool('Whether the post is marked NSFW.'), + spoiler: bool('Whether the post is marked as a spoiler.'), + }, { title: 'Replace example post title', text: 'Replace example post body.', kind: 'self' }), + ], + }, { slug: 'salesforce', title: 'Salesforce adapter', diff --git a/scripts/writeback-discovery-normalizer.mjs b/scripts/writeback-discovery-normalizer.mjs index b284d858..cea6a022 100644 --- a/scripts/writeback-discovery-normalizer.mjs +++ b/scripts/writeback-discovery-normalizer.mjs @@ -272,6 +272,16 @@ function idPatternFor(adapterSlug, resourcePath) { if (adapterSlug === 'gitlab') { return pattern('^[A-Za-z0-9_.:-]+$'); } + if (adapterSlug === 'granola') { + return resourcePath.endsWith('/folders') + ? pattern('^fol_[A-Za-z0-9]{14}$') + : pattern('^not_[A-Za-z0-9]{14}$'); + } + if (adapterSlug === 'reddit') { + return resourcePath.endsWith('/posts') + ? pattern('^[A-Za-z0-9_/-]+$') + : pattern('^[A-Za-z0-9_][A-Za-z0-9_-]{1,63}$'); + } if (adapterSlug === 'github') { if (resourcePath === '/github/repos/{owner}/{repo}/pulls/{pullNumber}/merge.json') { return pattern('^[1-9]\\d*(?:__.*)?$');