{"id":16697000,"url":"https://github.com/seratch/deno-slack-data-mapper","last_synced_at":"2025-03-21T19:31:34.868Z","repository":{"id":65495954,"uuid":"593203815","full_name":"seratch/deno-slack-data-mapper","owner":"seratch","description":"A handy way to manage data in Slack's next-generation platform datastores","archived":false,"fork":false,"pushed_at":"2024-07-08T01:22:07.000Z","size":469,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-13T17:45:46.628Z","etag":null,"topics":["datastore","deno","dynamodb","slack","slack-api","slack-plaform"],"latest_commit_sha":null,"homepage":"https://deno.land/x/deno_slack_data_mapper","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seratch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-25T13:35:10.000Z","updated_at":"2024-07-08T01:22:05.000Z","dependencies_parsed_at":"2023-10-28T15:24:37.255Z","dependency_job_id":"cb61240c-b496-4efa-9217-0efccafe1601","html_url":"https://github.com/seratch/deno-slack-data-mapper","commit_stats":{"total_commits":42,"total_committers":2,"mean_commits":21.0,"dds":0.0714285714285714,"last_synced_commit":"c46031a87baf6ac7e349c9f4f682a53f7aa65c00"},"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seratch%2Fdeno-slack-data-mapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seratch%2Fdeno-slack-data-mapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seratch%2Fdeno-slack-data-mapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seratch%2Fdeno-slack-data-mapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seratch","download_url":"https://codeload.github.com/seratch/deno-slack-data-mapper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221817811,"owners_count":16885634,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["datastore","deno","dynamodb","slack","slack-api","slack-plaform"],"created_at":"2024-10-12T17:45:43.334Z","updated_at":"2024-10-28T10:39:07.158Z","avatar_url":"https://github.com/seratch.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# deno-slack-data-mapper\n\n[![deno module](https://shield.deno.dev/x/deno_slack_data_mapper)](https://deno.land/x/deno_slack_data_mapper)\n\nThe deno-slack-data-mapper is a Deno library, which provides a greatly handy way\nto manage data using\n[Slack's next-generation hosting platform datastores](https://api.slack.com/future/datastores).\n\nWhile the underlying datastore APIs are easy enough to use, building a\nDynamoDB-syntax query in your code can sometimes be bothersome (especially when\nhaving many arguments).\n\nThis library brings the following benefits to developers:\n\n- Intuitive Expression Builder\n- Type-safety for Queries\n- Type-safe Response Data Access\n\n### Intuitive Expression Builder\n\nNo need to learn the DynamoDB syntax anymore! With this library, you can build a\ncomplex query with and/or parts intuitively.\n\n\u003cimg src=\"https://user-images.githubusercontent.com/19658/215002015-43ce3087-27c4-4697-ac3b-c90ba3802891.gif\" width=500\u003e\n\nFor the simple equal questions such `id = ?` or `title = ?`, just passing\n`{ where: { id: \"123\" }}` works as you expect.\n\nFor other operators such as `\u003c`, `\u003e=`, `begins_with()`, `contains`, and\n`between A and B`, you can pass something like\n`{ where: { maxParticipants: { value: 100, operator: Operator.GreaterThan } } }`.\n\nAlso, even combining a few expressions in `and`/`or` arrays is feasible like you\ncan see in the above video.\n\n### Type-safety for Queries\n\nThe TypeScript compiler will validate your put operations and queries based on\nyour `DefineDatastore`'s metadata.\n\n\u003cimg src=\"https://user-images.githubusercontent.com/19658/215000937-acad5f1f-ce83-4bd0-bff7-cbeceaffaadc.gif\" width=500\u003e\n\nAs of the latest version, `string`, `number`, `boolean`, `array[string]`,\n`array[number]`, and `array[boolean]` types are supported. Others can be used as\n`any`-typed values.\n\n### Type-safe Response Data Access\n\nThe `item` / `items` in datastore operation responses provide type-safe access\nto their attributes by leveraging your `DefineDatastore`'s metadata.\n\n\u003cimg src=\"https://user-images.githubusercontent.com/19658/215002279-d0d1df01-eba4-4de4-9b40-361bf2dc44c2.gif\" width=500\u003e\n\nAs of the latest version, `string`, `number`, `boolean`, `array[string]`,\n`array[number]`, and `array[boolean]` types are properly supported. Others can\nbe used as `any`-typed values. In addition, when an attribute has the\n`required: true` constraint in the datastore definition, the attribute in item\ndata cannot be undefined.\n\n## Getting Started\n\nOnce you define a datastore table and its list of properties, your code is ready\nto use the data mapper.\n\nThe complete project is available under at\nhttps://github.com/seratch/deno-slack-data-mapper-starter\n\nWith the Slack CLI, you can start a new project using the template:\n\n```bash\nslack create data-mapper-app -t seratch/deno-slack-data-mapper-starter\ncd ./data-mapper-app\nslack run\n```\n\nRefer to\n[the template's README](https://github.com/seratch/deno-slack-data-mapper-starter)\nfor details.\n\n### datastores/surveys.ts\n\nHere is a simple datastore definition:\n\n```typescript\nimport { DefineDatastore, Schema } from \"deno-slack-sdk/mod.ts\";\n\nexport const Surveys = DefineDatastore(\n  {\n    name: \"surveys\",\n    // The primary key's type must be a string\n    primary_key: \"id\",\n    attributes: {\n      id: { type: Schema.types.string, required: true },\n      title: { type: Schema.types.string, required: true },\n      questions: {\n        type: Schema.types.array,\n        items: { type: Schema.types.string },\n        required: true,\n      },\n      tags: {\n        type: Schema.types.array,\n        items: { type: Schema.types.string },\n        required: false,\n      }, // optional\n      maxParticipants: { type: Schema.types.number }, // optional\n      closed: { type: Schema.types.boolean, required: true },\n    },\n  } as const, // `as const` here is necessary to pass `required` values to DataMapper\n);\n```\n\n### functions/survey_demo.ts\n\nIn your custom function, you can instantiate `DataMapper` with the above\ndatastore table definition this way:\n`new DataMapper\u003ctypeof Surveys.definition\u003e(...)`.\n\n```typescript\nimport { DefineFunction, SlackFunction } from \"deno-slack-sdk/mod.ts\";\n\n// Add the following to import_map.json\n// \"deno-slack-data-mapper/\": \"https://deno.land/x/deno_slack_data_mapper@2.6.2/\",\nimport { DataMapper, Operator } from \"deno-slack-data-mapper/mod.ts\";\n\nimport { Surveys } from \"../datastores/surveys.ts\";\n\nexport const def = DefineFunction({\n  callback_id: \"datastore-demo\",\n  title: \"Datastore demo\",\n  source_file: \"functions/survey_demo.ts\",\n  input_parameters: { properties: {}, required: [] },\n  output_parameters: { properties: {}, required: [] },\n});\n\nexport default SlackFunction(def, async ({ client }) =\u003e {\n  const mapper = new DataMapper\u003ctypeof Surveys.definition\u003e({\n    datastore: Surveys.definition,\n    client,\n    logLevel: \"DEBUG\",\n  });\n\n  const cultureSurvey = await mapper.save({\n    attributes: {\n      \"id\": \"1\",\n      \"title\": \"Good things in our company\",\n      \"questions\": [\n        \"Can you share the things you love about our corporate culture?\",\n        \"Do you remember other members' behaviors representing our culture?\",\n      ],\n      \"tags\": [\"culture\"],\n      \"maxParticipants\": 10,\n      \"closed\": false,\n    },\n  });\n  console.log(`culture survey: ${JSON.stringify(cultureSurvey, null, 2)}`);\n  if (cultureSurvey.error) {\n    return { error: `Failed to create a record - ${cultureSurvey.error}` };\n  }\n\n  const productSurvey = await mapper.save({\n    attributes: {\n      \"id\": \"2\",\n      \"title\": \"Project ideas\",\n      \"questions\": [\n        \"Can you share interesting ideas for our future growth? Any crazy ideas are welcomed!\",\n      ],\n      \"tags\": [\"product\", \"future\"],\n      \"maxParticipants\": 150,\n      \"closed\": false,\n    },\n  });\n  console.log(`product survey: ${JSON.stringify(productSurvey, null, 2)}`);\n\n  const findById = await mapper.findById({ id: \"1\" });\n  console.log(`findById: ${JSON.stringify(findById, null, 2)}`);\n  if (findById.error) {\n    return { error: `Failed to find a record by ID - ${findById.error}` };\n  }\n\n  // Type-safe access to the item properties\n  const id: string = findById.item.id;\n  const title: string = findById.item.title;\n  const questions: string[] = findById.item.questions;\n  const tags: string[] | undefined = findById.item.tags;\n  const maxParticipants: number | undefined = findById.item.maxParticipants;\n  const closed: boolean = findById.item.closed;\n  console.log(\n    `id: ${id}, title: ${title}, questions: ${questions}, tags: ${tags}, maxParticipants: ${maxParticipants}, closed: ${closed}`,\n  );\n\n  const simpleQuery = await mapper.findAllBy({\n    where: { title: \"Project ideas\" },\n  });\n  // {\n  //   \"expression\": \"#tt0k11 = :tt0k11\",\n  //   \"attributes\": {\n  //     \"#tt0k11\": \"title\"\n  //   },\n  //   \"values\": {\n  //     \":tt0k11\": \"Project ideas\"\n  //   }\n  // }\n  console.log(\n    `findAllBy + simple '=' query: ${JSON.stringify(simpleQuery, null, 2)}`,\n  );\n  if (simpleQuery.error) {\n    return { error: `Failed to find records - ${simpleQuery.error}` };\n  }\n\n  const greaterThanQuery = await mapper.findAllBy({\n    where: {\n      maxParticipants: {\n        value: 100,\n        operator: Operator.GreaterThan,\n      },\n    },\n  });\n  // {\n  //   \"expression\": \"#e3oad1 \u003e :e3oad1\",\n  //   \"attributes\": {\n  //     \"#e3oad1\": \"maxParticipants\"\n  //   },\n  //   \"values\": {\n  //     \":e3oad1\": 100\n  //   }\n  // }\n  console.log(\n    `findAllBy + '\u003e' query: ${JSON.stringify(greaterThanQuery, null, 2)}`,\n  );\n  if (greaterThanQuery.error) {\n    return { error: `Failed to find records - ${greaterThanQuery.error}` };\n  }\n\n  const betweenQuery = await mapper.findAllBy({\n    where: {\n      maxParticipants: {\n        value: [100, 300],\n        operator: Operator.Between,\n      },\n    },\n  });\n  // {\n  //   \"expression\": \"#z5i0h1 between :z5i0h10 and :z5i0h11\",\n  //   \"attributes\": {\n  //     \"#z5i0h1\": \"maxParticipants\"\n  //   },\n  //   \"values\": {\n  //     \":z5i0h10\": 100,\n  //     \":z5i0h11\": 300\n  //   }\n  // }\n  console.log(\n    `findAllBy + 'between ? and ?' query: ${\n      JSON.stringify(betweenQuery, null, 2)\n    }`,\n  );\n  if (betweenQuery.error) {\n    return { error: `Failed to find records - ${betweenQuery.error}` };\n  }\n\n  const complexQuery = await mapper.findAllBy({\n    where: {\n      or: [\n        { maxParticipants: { value: [100, 300], operator: Operator.Between } },\n        {\n          and: [\n            { id: \"1\" },\n            { title: { value: \"Good things\", operator: Operator.BeginsWith } },\n          ],\n        },\n      ],\n    },\n  });\n  // {\n  //   \"expression\": \"(#nrdak1 between :nrdak10 and :nrdak11) or ((#v1ec82 = :v1ec82) and (begins_with(#xu2ie3, :xu2ie3)))\",\n  //   \"attributes\": {\n  //     \"#nrdak1\": \"maxParticipants\",\n  //     \"#v1ec82\": \"id\",\n  //     \"#xu2ie3\": \"title\"\n  //   },\n  //   \"values\": {\n  //     \":nrdak10\": 100,\n  //     \":nrdak11\": 300,\n  //     \":v1ec82\": \"1\",\n  //     \":xu2ie3\": \"Good things\"\n  //   }\n  // }\n  console.log(\n    `findAllBy + '(between ? and ?) or (id = ?)' query: ${\n      JSON.stringify(complexQuery, null, 2)\n    }`,\n  );\n  if (complexQuery.error) {\n    return { error: `Failed to find records - ${complexQuery.error}` };\n  }\n\n  const modification = await mapper.save({\n    attributes: {\n      \"id\": \"1\",\n      \"title\": \"Good things in our company\",\n      \"maxParticipants\": 20,\n    },\n  });\n  console.log(`modification: ${JSON.stringify(modification, null, 2)}`);\n  if (modification.error) {\n    return { error: `Failed to update a record - ${modification.error}` };\n  }\n\n  const countAllResult = await mapper.countAll();\n  console.log(countAllResult);\n\n  const countResult = await mapper.countBy({\n    where: {\n      title: {\n        operator: Operator.BeginsWith,\n        value: \"Good things\",\n      },\n    },\n  });\n  console.log(countResult);\n\n  const findByIdsResult = await mapper.findAllByIds({\n    ids: [\"1\", \"2\", \"3\"],\n  });\n  console.log(findByIdsResult);\n\n  const deletion1 = await mapper.deleteById({ id: \"1\" });\n  console.log(`deletion 1: ${JSON.stringify(deletion1, null, 2)}`);\n  if (deletion1.error) {\n    return { error: `Failed to delete a record - ${deletion1.error}` };\n  }\n  const deletion2 = await mapper.deleteById({ id: \"2\" });\n  console.log(`deletion 2: ${JSON.stringify(deletion1, null, 2)}`);\n  if (deletion2.error) {\n    return { error: `Failed to delete a record - ${deletion2.error}` };\n  }\n\n  const deleteAllByIdsResult = await mapper.deleteAllByIds({\n    ids: [\"1\", \"2\", \"3\"],\n  });\n  console.log(deleteAllByIdsResult);\n\n  const alreadyInserted = (await mapper.findById({ id: \"100\" })).item;\n  if (!alreadyInserted) {\n    for (let i = 0; i \u003c 100; i += 1) {\n      await mapper.save({\n        attributes: {\n          \"id\": `${i}`,\n          \"title\": `Good ${i} things in our company`,\n          \"questions\": [\n            \"Can you share the things you love about our corporate culture?\",\n          ],\n          \"tags\": [\"culture\"],\n          \"maxParticipants\": i * 10,\n        },\n      });\n    }\n  }\n  const findFirstResults = await mapper.findFirstBy({\n    where: {\n      title: {\n        operator: Operator.BeginsWith,\n        value: \"Good 1\",\n      },\n    },\n    limit: 5,\n  });\n  console.log(findFirstResults);\n\n  const findAllResults = await mapper.findAllBy({\n    where: {\n      title: {\n        operator: Operator.BeginsWith,\n        value: \"Good 1\",\n      },\n    },\n    limit: 5,\n  });\n  console.log(findAllResults);\n\n  return { outputs: {} };\n});\n```\n\n### workfllows/survey_demo.ts\n\nThis file is very straightforward. There is nothing specific to this data-mapper\nlibrary:\n\n```typescript\nimport { DefineWorkflow } from \"deno-slack-sdk/mod.ts\";\nimport { def as Demo } from \"../functions/survey_demo.ts\";\n\nexport const workflow = DefineWorkflow({\n  callback_id: \"data-mapper-demo-workflow\",\n  title: \"Data Mapper Demo Workflow\",\n  input_parameters: { properties: {}, required: [] },\n});\n\nworkflow.addStep(Demo, {});\n```\n\n### manifest.ts\n\nThe same as above, there is nothing specific to this data-mapper library:\n\n```typescript\nimport { Manifest } from \"deno-slack-sdk/mod.ts\";\nimport { Surveys } from \"./datastores/surveys.ts\";\nimport { workflow as SurveyDemo } from \"./workflows/survey_demo.ts\";\n\nexport default Manifest({\n  name: \"data-mapper-examples\",\n  description: \"Data Mapper Example App\",\n  icon: \"assets/default_new_app_icon.png\",\n  datastores: [Surveys],\n  workflows: [SurveyDemo],\n  outgoingDomains: [],\n  botScopes: [\n    \"commands\",\n    \"datastore:read\",\n    \"datastore:write\",\n  ],\n});\n```\n\n## License\n\nThe MIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseratch%2Fdeno-slack-data-mapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseratch%2Fdeno-slack-data-mapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseratch%2Fdeno-slack-data-mapper/lists"}